summaryrefslogtreecommitdiff
path: root/src/daemon.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2021-11-26 08:19:58 +0100
committerJoel Klinghed <the_jk@spawned.biz>2021-11-26 08:19:58 +0100
commitf70495a48646e54272783b4b709aca0396cb85f8 (patch)
tree5e63b1f57582b3f025f1008034e4b066db0c32a6 /src/daemon.cc
parent9c26f52e0942e3ddc8fe90fad5da871324c66f08 (diff)
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.
Diffstat (limited to 'src/daemon.cc')
-rw-r--r--src/daemon.cc162
1 files changed, 162 insertions, 0 deletions
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 <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+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<int>(msg.size()), msg.data());
+ break;
+ case Type::LOG_WARN:
+ logger->warn("%.*s", static_cast<int>(msg.size()), msg.data());
+ break;
+ case Type::LOG_INFO:
+ logger->info("%.*s", static_cast<int>(msg.size()), msg.data());
+ break;
+ case Type::LOG_DBG:
+ logger->dbg("%.*s", static_cast<int>(msg.size()), msg.data());
+ break;
+ }
+ }
+ }
+
+private:
+ unique_fd fd_;
+};
+
+} // namespace
+
+bool Daemon::run_in_foreground(Logger* logger,
+ std::unique_ptr<Daemon> daemon) {
+ return daemon->setup(logger) && daemon->run();
+}
+
+bool Daemon::fork_in_background(Logger* logger,
+ std::unique_ptr<Daemon> 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);
+ }
+}