blob: 4ad6bc564045a988edbfb64ffd31aaef00659ac5 [file] [edit]
#include "watch.hpp"
#include <sys/inotify.h>
#include <unistd.h>
#include <phosphor-logging/lg2.hpp>
#include <cstddef>
#include <cstring>
#include <filesystem>
#include <stdexcept>
#include <string>
PHOSPHOR_LOG2_USING;
namespace pldm
{
namespace fw_update
{
using namespace std::string_literals;
namespace fs = std::filesystem;
Watch::Watch(sd_event* loop, std::function<int(std::string&)> imageCallback) :
imageCallback(imageCallback)
{
fd = inotify_init1(IN_NONBLOCK);
if (fd == -1)
{
// Store a copy of errno, because the string creation below will
// invalidate errno due to one more system calls.
const auto ec = errno;
error("Error {EC} {ERR} when calling inotify_init1.", "EC", ec, "ERR",
std::strerror(ec));
throw std::runtime_error(
"inotify_init1 failed, errno="s + std::strerror(ec));
}
const auto rc = sd_event_add_io(loop, nullptr, fd, EPOLLIN, callback, this);
if (rc < 0)
{
error("Error {EC} calling sd_event_add_io.", "EC", rc);
throw std::runtime_error(
"failed to add to event loop, rc="s + std::strerror(-rc));
}
setupWatch();
}
void Watch::setupWatch()
{
// Check if FIRMWARE_PACKAGE_STAGING_DIR exists and create it if not.
fs::path imgDirPath(FIRMWARE_PACKAGE_STAGING_DIR);
if (!fs::is_directory(imgDirPath))
{
fs::create_directories(imgDirPath);
info("Directory {DIR} wasn't found, so it was created.", "DIR",
std::string(FIRMWARE_PACKAGE_STAGING_DIR));
}
wd = inotify_add_watch(fd, FIRMWARE_PACKAGE_STAGING_DIR,
IN_CLOSE_WRITE | IN_DELETE_SELF);
if (wd == -1)
{
const auto ec = errno;
close(fd);
error("Error {EC} {ERR} calling inotify_add_watch.", "EC", ec, "ERR",
std::strerror(ec));
throw std::runtime_error(
"inotify_add_watch failed, errno="s + std::strerror(ec));
}
info("Set up filesystem watch for {DIR}", "DIR",
std::string(FIRMWARE_PACKAGE_STAGING_DIR));
}
Watch::~Watch()
{
if (-1 != fd)
{
if (-1 != wd)
{
inotify_rm_watch(fd, wd);
}
close(fd);
}
}
int Watch::callback(sd_event_source* /* s */, int fd, uint32_t revents,
void* userdata)
{
if (!(revents & EPOLLIN))
{
return 0;
}
constexpr auto maxBytes = 1024;
uint8_t buffer[maxBytes];
auto bytes = read(fd, buffer, maxBytes);
if (0 > bytes)
{
auto ec = errno;
error("Error {EC} {ERR} reading inotify event.", "EC", ec, "ERR",
std::strerror(ec));
throw std::runtime_error(
"failed to read inotify event, errno="s + std::strerror(ec));
}
auto offset = 0;
while (offset < bytes)
{
auto event = reinterpret_cast<inotify_event*>(&buffer[offset]);
if ((event->mask & IN_CLOSE_WRITE) && !(event->mask & IN_ISDIR))
{
info("Received IN_CLOSE_WRITE event on {DIR} for {FILE}", "DIR",
std::string(FIRMWARE_PACKAGE_STAGING_DIR), "FILE",
event->name);
auto tarballPath =
std::string{FIRMWARE_PACKAGE_STAGING_DIR} + '/' + event->name;
auto rc = static_cast<Watch*>(userdata)->imageCallback(tarballPath);
if (rc < 0)
{
error("Error ({EC}) processing image {IMAGE_PATH}", "EC", rc,
"IMAGE_PATH", tarballPath.c_str());
}
}
else if (event->mask & IN_DELETE_SELF)
{
info("Received IN_DELETE_SELF event on {DIR}", "DIR",
std::string(FIRMWARE_PACKAGE_STAGING_DIR));
auto* const watch = reinterpret_cast<Watch*>(userdata);
watch->wd = -1;
// IN_DELETE_SELF event automatically cleans up the filesystem
// watch, so we can just go ahead and set it up again without
// cleaning.
watch->setupWatch();
}
offset += offsetof(inotify_event, name) + event->len;
}
return 0;
}
} // namespace fw_update
} // namespace pldm