LCOV - code coverage report
Current view: top level - src/daemonize - daemonize.cpp (source / functions) Hit Total Coverage
Test: pid-daemonize_coverage Lines: 89 149 59.7 %
Date: 2026-06-01 18:54:45 Functions: 13 16 81.2 %

          Line data    Source code
       1             : #include <pid/daemonize.h>
       2             : 
       3             : // If the local compiler check passes then we have std::string_view available
       4             : // and so can use the C++17 guarded overloads for pid::hashed_string
       5             : #define PID_UTILS_CXX17_AVAILABLE 1
       6             : #include <pid/hashed_string.h>
       7             : #undef PID_UTILS_CXX17_AVAILABLE
       8             : 
       9             : #include <string>
      10             : #include <thread>
      11             : #include <chrono>
      12             : #include <iostream>
      13             : #include <fstream>
      14             : #include <cstdlib>
      15             : 
      16             : #include <unistd.h>
      17             : #include <csignal>
      18             : #include <cstring>
      19             : #include <sys/types.h>
      20             : #include <sys/stat.h>
      21             : 
      22             : namespace {
      23             : 
      24          56 : std::string get_temp_file_path_for(std::string_view daemon) {
      25         112 :     return "/tmp/pid_daemonize_" + std::to_string(pid::hashed_string(daemon));
      26             : }
      27             : 
      28          14 : void delete_temp_file_path_for(std::string_view daemon) {
      29          14 :     remove(get_temp_file_path_for(daemon).c_str());
      30          14 : }
      31             : 
      32          41 : long get_pid_of(std::string_view daemon) {
      33          41 :     const auto temp_file_path = get_temp_file_path_for(daemon);
      34             : 
      35          41 :     auto pid_file = std::ifstream{temp_file_path};
      36          41 :     if (pid_file.is_open()) {
      37          30 :         int pid{};
      38          30 :         pid_file >> pid;
      39          30 :         return pid;
      40             :     } else {
      41          11 :         return -1;
      42             :     }
      43          41 : }
      44             : 
      45          25 : bool is_daemon_alive(int pid) {
      46          25 :     return kill(pid, 0) == 0;
      47             : }
      48             : 
      49             : } // namespace
      50             : 
      51             : namespace pid::daemonize {
      52             : 
      53          10 : void start(const std::string& executable_path,
      54             :            const std::vector<std::string>& arguments) {
      55          10 :     start(executable_path, executable_path, arguments);
      56           5 : }
      57             : 
      58           0 : void start(const std::string& executable_path,
      59             :            const std::vector<std::string>& arguments,
      60             :            const std::vector<std::pair<std::string, std::string>>& environment,
      61             :            bool keep_fd) {
      62           0 :     start(executable_path, executable_path, arguments, environment, keep_fd);
      63           0 : }
      64             : 
      65          10 : void start(std::string_view name, const std::string& executable_path,
      66             :            const std::vector<std::string>& arguments) {
      67          10 :     start(name, [&] {
      68             :         // Prepare arguments list
      69           0 :         std::vector<const char*> argv;
      70           0 :         argv.reserve(arguments.size() + 2);
      71           0 :         argv.push_back(executable_path.c_str());
      72           0 :         for (const auto& arg : arguments) {
      73           0 :             argv.push_back(arg.c_str());
      74             :         }
      75           0 :         argv.push_back(nullptr);
      76             : 
      77             :         // Replace current process
      78           0 :         if (execv(executable_path.c_str(), const_cast<char**>(argv.data())) ==
      79             :             -1) {
      80           0 :             throw std::runtime_error("unable to launch " + executable_path);
      81             :         }
      82           0 :     });
      83           5 : }
      84             : 
      85           2 : void start(std::string_view name, const std::string& executable_path,
      86             :            const std::vector<std::string>& arguments,
      87             :            const std::vector<std::pair<std::string, std::string>>& environment,
      88             :            bool keep_fd) {
      89           2 :     start(
      90             :         name,
      91           0 :         [&] {
      92             :             // Prepare arguments list
      93           0 :             std::vector<const char*> argv;
      94           0 :             argv.reserve(arguments.size() + 2);
      95           0 :             argv.push_back(executable_path.c_str());
      96           0 :             for (const auto& arg : arguments) {
      97           0 :                 argv.push_back(arg.c_str());
      98             :             }
      99           0 :             argv.push_back(nullptr);
     100             : 
     101             :             // building the environment variables to give to the exec function
     102           0 :             std::vector<std::string> list_of_env;
     103           0 :             list_of_env.reserve(environment.size());
     104           0 :             for (const auto& var : environment) {
     105           0 :                 list_of_env.push_back(std::string(var.first) + "=" +
     106           0 :                                       std::string(var.second));
     107             :             }
     108           0 :             std::vector<const char*> env;
     109           0 :             env.reserve(list_of_env.size() + 1);
     110           0 :             for (const auto& arg : list_of_env) {
     111           0 :                 env.push_back(arg.c_str());
     112             :             }
     113           0 :             env.push_back(nullptr);
     114             : 
     115             :             // Replace current process
     116           0 :             if (execve(executable_path.c_str(), const_cast<char**>(argv.data()),
     117           0 :                        const_cast<char**>(env.data())) == -1) {
     118           0 :                 throw std::runtime_error("unable to launch " + executable_path);
     119             :             }
     120           0 :         },
     121             :         {}, keep_fd);
     122           1 : }
     123          13 : void start(std::string_view name, std::function<void()> function) {
     124          13 :     start(name, std::move(function), {});
     125           6 : }
     126             : 
     127          15 : void start(std::string_view name, std::function<void()> function,
     128             :            const std::vector<std::pair<std::string, std::string>>& env,
     129             :            bool keep_fd) {
     130             :     std::array<int, 2> descriptors;
     131          15 :     if (pipe(descriptors.data()) < 0) {
     132           0 :         throw std::runtime_error("pid::daemonize::start: failed to create a "
     133           0 :                                  "new pipe for the daemon");
     134             :         return;
     135             :     }
     136          15 :     pid_t pid = fork();
     137          15 :     if (pid > 0) {
     138             :         // Calling process
     139             :         // close the write descriptor, reading only
     140           7 :         close(descriptors[1]);
     141             :         int val;
     142             :         // blocking read
     143           7 :         if (read(descriptors[0], &val, sizeof(val)) < 0) {
     144             :             std::cerr << "pid::daemonize::start: failed to read pipe written "
     145           0 :                          "by the daemon"
     146           0 :                       << std::endl;
     147             :         }
     148           7 :         close(descriptors[0]);
     149           7 :         return;
     150           8 :     } else if (pid == 0) {
     151             :         // Child process
     152             : 
     153             :         // Create a new session
     154           8 :         if (setsid() < 0) {
     155             :             std::cerr << "pid::daemonize::start: failed to create a new "
     156           0 :                          "session for the daemon"
     157           0 :                       << std::endl;
     158           0 :             std::exit(-1);
     159             :         }
     160             : 
     161           8 :         signal(SIGCHLD, SIG_IGN);
     162           8 :         signal(SIGHUP, SIG_IGN);
     163             : 
     164           8 :         pid = fork();
     165             : 
     166           8 :         if (pid > 0) {
     167             :             // Exit parent process
     168             :             // closing all pipes
     169           7 :             close(descriptors[1]);
     170           7 :             close(descriptors[0]);
     171           7 :             std::exit(0);
     172           1 :         } else if (pid == 0) {
     173             :             // Daemon process
     174           1 :             umask(0);
     175             :             // write the file containing PID of daemon process
     176             :             {
     177           1 :                 const auto current_pid = getpid();
     178           1 :                 const auto temp_file_path = get_temp_file_path_for(name);
     179           1 :                 auto pid_file = std::ofstream(temp_file_path);
     180           1 :                 if (not pid_file.is_open()) {
     181             :                     std::cerr << "pid::daemonize::start: failed to create "
     182             :                                  "temporary file "
     183             :                               << temp_file_path
     184           0 :                               << " to store the daemon's pid. Aborting launch."
     185           0 :                               << std::endl;
     186           0 :                     std::exit(1);
     187             :                 }
     188           1 :                 pid_file << current_pid;
     189           1 :             }
     190             :             // unlock calling process
     191           1 :             close(descriptors[0]);
     192           1 :             int val = 1;
     193           1 :             if (write(descriptors[1], &val, sizeof(val)) < 0) {
     194             :                 std::cerr
     195           0 :                     << "pid::daemonize::start: daemon failed to write pipe"
     196           0 :                     << std::endl;
     197           0 :                 std::exit(-1);
     198             :             }
     199           1 :             close(descriptors[1]);
     200             :             // Close all open file descriptors
     201           1 :             if (not keep_fd) {
     202        1026 :                 for (auto file_descriptor = sysconf(_SC_OPEN_MAX);
     203        1026 :                      file_descriptor >= 0; file_descriptor--) {
     204        1025 :                     close(static_cast<int>(file_descriptor));
     205             :                 }
     206             :             }
     207             : 
     208             :             // setting enviroment variables
     209           1 :             for (const auto& var : env) {
     210           0 :                 if (setenv(var.first.c_str(), var.second.c_str(), 1) < 0) {
     211             :                     std::cerr << "pid::daemonize::start: daemon failed to set "
     212           0 :                                  "environment variables"
     213           0 :                               << std::endl;
     214           0 :                     std::exit(-3);
     215             :                 }
     216             :             }
     217             :             // calling the user defined function
     218           1 :             function();
     219             : 
     220             :             // Make sure the process exits
     221           1 :             std::exit(0);
     222             :         } else {
     223             :             std::cerr << "pid::daemonize::start: failed to create the "
     224           0 :                          "daemon process"
     225           0 :                       << std::endl;
     226           0 :             std::exit(-2);
     227             :         }
     228             :     } else {
     229           0 :         throw std::runtime_error("pid::daemonize::start: failed to create a "
     230           0 :                                  "new process for the daemon");
     231             :     }
     232             : }
     233             : 
     234           5 : void stop(std::string_view daemon, int signal) {
     235           5 :     const auto pid = get_pid_of(daemon);
     236           5 :     if (pid >= 0) {
     237           5 :         const auto ret = kill(static_cast<pid_t>(pid), signal);
     238           5 :         if (ret == 0) {
     239           5 :             ::delete_temp_file_path_for(daemon);
     240             :         } else {
     241           0 :             std::string err = strerror(errno);
     242             :             std::cerr << "Cannot send signal to daemon '" << daemon
     243           0 :                       << "'. kill() returned '" << err << "'\n";
     244           0 :         }
     245             :     } else {
     246           0 :         std::cerr << "Cannot stop unknown daemon '" << daemon << "'\n";
     247             :     }
     248           5 : }
     249             : 
     250          36 : bool is_alive(std::string_view daemon) {
     251          36 :     const auto pid = get_pid_of(daemon);
     252          36 :     if (pid >= 0) {
     253          25 :         const bool is_alive = ::is_daemon_alive(static_cast<pid_t>(pid));
     254          25 :         if (not is_alive) {
     255           2 :             ::delete_temp_file_path_for(daemon);
     256             :         }
     257          25 :         return is_alive;
     258             :     } else {
     259          11 :         return false;
     260             :     }
     261             : }
     262             : 
     263           6 : void wait_started(std::string_view daemon,
     264             :                   std::chrono::duration<double> polling_rate) {
     265             :     while (true) {
     266           6 :         if (is_alive(daemon)) {
     267           6 :             break;
     268             :         } else {
     269           0 :             std::this_thread::sleep_for(polling_rate);
     270             :         }
     271             :     }
     272           6 : }
     273             : 
     274          18 : void wait_stopped(std::string_view daemon,
     275             :                   std::chrono::duration<double> polling_rate) {
     276             :     while (true) {
     277          18 :         if (not is_alive(daemon)) {
     278           7 :             break;
     279             :         } else {
     280          11 :             std::this_thread::sleep_for(polling_rate);
     281             :         }
     282             :     }
     283           7 :     ::delete_temp_file_path_for(daemon);
     284           7 : }
     285             : 
     286             : } // namespace pid::daemonize

Generated by: LCOV version 1.14