From f70495a48646e54272783b4b709aca0396cb85f8 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Fri, 26 Nov 2021 08:19:58 +0100 Subject: Create daemon module and use it from server Need to run setup() after forking, otherwise each TaskRunner created in setup() will dead-lock at exit as there are now two copies of each of them but not of the threads causing the destructors to lock. This made setup a little bit more complicated as it has to forward the log and status to parent process but I turned out quite nice. --- src/daemon.cc | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/daemon.cc (limited to 'src/daemon.cc') diff --git a/src/daemon.cc b/src/daemon.cc new file mode 100644 index 0000000..2cebf1c --- /dev/null +++ b/src/daemon.cc @@ -0,0 +1,162 @@ +#include "common.hh" + +#include "daemon.hh" +#include "io.hh" +#include "logger.hh" +#include "logger_base.hh" +#include "unique_pipe.hh" + +#include +#include +#include + +namespace { + +enum class Type { + EXIT_GOOD, + EXIT_BAD, + LOG_ERR, + LOG_WARN, + LOG_INFO, + LOG_DBG, +}; + +struct MsgHeader { + Type type_; + size_t len_; + + MsgHeader() = default; + MsgHeader(Type type, size_t len) + : type_(type), len_(len) {} +}; + +class DaemonWriter : public LoggerBase { +public: + explicit DaemonWriter(unique_fd fd) + : fd_(std::move(fd)) {} + + void exit(bool success) { + MsgHeader header(success ? Type::EXIT_GOOD : Type::EXIT_BAD, 0); + if (!io::write_all(fd_.get(), &header, sizeof(header))) { + abort(); + } + } + +protected: + void msg(Level level, std::string_view str) override { + Type type; + switch (level) { + case Level::ERR: + type = Type::LOG_ERR; + break; + case Level::WARN: + type = Type::LOG_WARN; + break; + case Level::INFO: + type = Type::LOG_INFO; + break; + case Level::DBG: + type = Type::LOG_DBG; + break; + } + MsgHeader header(type, str.size()); + if (!io::write_all(fd_.get(), &header, sizeof(header)) || + !io::write_all(fd_.get(), str.data(), str.size())) { + abort(); + } + } + +private: + unique_fd fd_; +}; + +class DaemonReader { +public: + explicit DaemonReader(unique_fd fd) + : fd_(std::move(fd)) {} + + bool wait(Logger* logger) { + MsgHeader header; + std::string msg; + while (true) { + if (!io::read_all(fd_.get(), &header, sizeof(header))) { + logger->err("Error reading pipe: %s", strerror(errno)); + return false; + } + msg.resize(header.len_); + if (!io::read_all(fd_.get(), msg.data(), header.len_)) { + logger->err("Error reading pipe: %s", strerror(errno)); + return false; + } + switch (header.type_) { + case Type::EXIT_GOOD: + return true; + case Type::EXIT_BAD: + return false; + case Type::LOG_ERR: + logger->err("%.*s", static_cast(msg.size()), msg.data()); + break; + case Type::LOG_WARN: + logger->warn("%.*s", static_cast(msg.size()), msg.data()); + break; + case Type::LOG_INFO: + logger->info("%.*s", static_cast(msg.size()), msg.data()); + break; + case Type::LOG_DBG: + logger->dbg("%.*s", static_cast(msg.size()), msg.data()); + break; + } + } + } + +private: + unique_fd fd_; +}; + +} // namespace + +bool Daemon::run_in_foreground(Logger* logger, + std::unique_ptr daemon) { + return daemon->setup(logger) && daemon->run(); +} + +bool Daemon::fork_in_background(Logger* logger, + std::unique_ptr daemon) { + unique_pipe pipe; + if (!pipe) { + logger->err("Unable to create pipe(): %s", strerror(errno)); + return false; + } + auto pid = fork(); + if (pid == -1) { + logger->err("Failed to fork(): %s", strerror(errno)); + return false; + } + if (pid == 0) { + // Daemon process + setpgrp(); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + { + DaemonWriter writer(pipe.release_writer()); + pipe.reset(); + + if (!daemon->setup(&writer)) { + writer.exit(false); + exit(EXIT_FAILURE); + } + + writer.exit(true); + } + + chdir("/"); + exit(daemon->run() ? EXIT_SUCCESS : EXIT_FAILURE); + } else { + // Calling process + DaemonReader reader(pipe.release_reader()); + pipe.reset(); + return reader.wait(logger); + } +} -- cgit v1.2.3-70-g09d2