Fix missing #include (needed for compiling with GCC 10)
[oweals/dinit.git] / src / baseproc-service.cc
index e66e1a57e2f3bca214b45c87b00de3b084cafb28..32c49d5969600901f3ae2f59815ea62f1717cc49 100644 (file)
@@ -1,4 +1,5 @@
 #include <cstring>
+#include <cstdlib>
 
 #include <sys/un.h>
 #include <sys/socket.h>
@@ -40,10 +41,13 @@ bool base_process_service::bring_up() noexcept
             return false;
         }
 
-        event_loop.get_time(restart_interval_time, clock_type::MONOTONIC);
         restart_interval_count = 0;
-        if (start_ps_process(exec_arg_parts, onstart_flags.starts_on_console)) {
-            if (start_timeout != time_val(0,0)) {
+        if (start_ps_process(exec_arg_parts,
+                onstart_flags.starts_on_console || onstart_flags.shares_console)) {
+            // start_ps_process updates last_start_time, use it also for restart_interval_time:
+            restart_interval_time = last_start_time;
+            // Note: we don't set a start timeout for PROCESS services.
+            if (start_timeout != time_val(0,0) && get_type() != service_type_t::PROCESS) {
                 restart_timer.arm_timer_rel(event_loop, start_timeout);
                 stop_timer_armed = true;
             }
@@ -53,6 +57,7 @@ bool base_process_service::bring_up() noexcept
             }
             return true;
         }
+        restart_interval_time = last_start_time;
         return false;
     }
 }
@@ -81,6 +86,11 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
     control_conn_t *control_conn = nullptr;
 
     int control_socket[2] = {-1, -1};
+    int notify_pipe[2] = {-1, -1};
+    bool have_notify = !notification_var.empty() || force_notification_fd != -1;
+    ready_notify_watcher * rwatcher = have_notify ? get_ready_watcher() : nullptr;
+    bool ready_watcher_registered = false;
+
     if (onstart_flags.pass_cs_fd) {
         if (dinit_socketpair(AF_UNIX, SOCK_STREAM, /* protocol */ 0, control_socket, SOCK_NONBLOCK)) {
             log(loglevel_t::ERROR, get_name(), ": can't create control socket: ", strerror(errno));
@@ -100,6 +110,27 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
         }
     }
 
+    if (have_notify) {
+        // Create a notification pipe:
+        if (bp_sys::pipe2(notify_pipe, 0) != 0) {
+            log(loglevel_t::ERROR, get_name(), ": can't create notification pipe: ", strerror(errno));
+            goto out_cs_h;
+        }
+
+        // Set the read side as close-on-exec:
+        int fdflags = bp_sys::fcntl(notify_pipe[0], F_GETFD);
+        bp_sys::fcntl(notify_pipe[0], F_SETFD, fdflags | FD_CLOEXEC);
+
+        // add, but don't yet enable, readiness watcher:
+        try {
+            rwatcher->add_watch(event_loop, notify_pipe[0], dasynq::IN_EVENTS, false);
+            ready_watcher_registered = true;
+        }
+        catch (std::exception &exc) {
+            log(loglevel_t::ERROR, get_name(), ": can't add notification watch: ", exc.what());
+        }
+    }
+
     // Set up complete, now fork and exec:
 
     pid_t forkpid;
@@ -121,16 +152,28 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
     }
 
     if (forkpid == 0) {
-        run_child_proc(cmd.data(), logfile, on_console, pipefd[1], control_socket[1], socket_fd);
+        const char * working_dir_c = nullptr;
+        if (! working_dir.empty()) working_dir_c = working_dir.c_str();
+        after_fork(getpid());
+        run_proc_params run_params{cmd.data(), working_dir_c, logfile, pipefd[1], run_as_uid, run_as_gid, rlimits};
+        run_params.on_console = on_console;
+        run_params.in_foreground = !onstart_flags.shares_console;
+        run_params.csfd = control_socket[1];
+        run_params.socket_fd = socket_fd;
+        run_params.notify_fd = notify_pipe[1];
+        run_params.force_notify_fd = force_notification_fd;
+        run_params.notify_var = notification_var.c_str();
+        run_params.env_file = env_file.c_str();
+        run_child_proc(run_params);
     }
     else {
         // Parent process
-        bp_sys::close(pipefd[1]); // close the 'other end' fd
-        if (control_socket[1] != -1) {
-            bp_sys::close(control_socket[1]);
-        }
         pid = forkpid;
 
+        bp_sys::close(pipefd[1]); // close the 'other end' fd
+        if (control_socket[1] != -1) bp_sys::close(control_socket[1]);
+        if (notify_pipe[1] != -1) bp_sys::close(notify_pipe[1]);
+        notification_fd = notify_pipe[0];
         waiting_for_execstat = true;
         return true;
     }
@@ -142,6 +185,12 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
         child_status_listener.deregister(event_loop);
     }
 
+    if (notify_pipe[0] != -1) bp_sys::close(notify_pipe[0]);
+    if (notify_pipe[1] != -1) bp_sys::close(notify_pipe[1]);
+    if (ready_watcher_registered) {
+        rwatcher->deregister(event_loop);
+    }
+
     if (onstart_flags.pass_cs_fd) {
         delete control_conn;
 
@@ -157,39 +206,9 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
     return false;
 }
 
-void base_process_service::bring_down() noexcept
-{
-    if (pid != -1) {
-        // The process is still kicking on - must actually kill it. We signal the process
-        // group (-pid) rather than just the process as there's less risk then of creating
-        // an orphaned process group:
-        if (! onstart_flags.no_sigterm) {
-            kill_pg(SIGTERM);
-        }
-        if (term_signal != -1) {
-            kill_pg(term_signal);
-        }
-
-        // In most cases, the rest is done in handle_exit_status.
-        // If we are a BGPROCESS and the process is not our immediate child, however, that
-        // won't work - check for this now:
-        if (get_type() == service_type_t::BGPROCESS && ! tracking_child) {
-            stopped();
-        }
-        else if (stop_timeout != time_val(0,0)) {
-            restart_timer.arm_timer_rel(event_loop, stop_timeout);
-            stop_timer_armed = true;
-        }
-    }
-    else {
-        // The process is already dead.
-        stopped();
-    }
-}
-
 base_process_service::base_process_service(service_set *sset, string name,
         service_type_t service_type_p, string &&command,
-        std::list<std::pair<unsigned,unsigned>> &command_offsets,
+        const std::list<std::pair<unsigned,unsigned>> &command_offsets,
         const std::list<prelim_dep> &deplist_p)
      : service_record(sset, name, service_type_p, deplist_p), child_listener(this),
        child_status_listener(this), restart_timer(this)
@@ -211,7 +230,6 @@ base_process_service::base_process_service(service_set *sset, string name,
     reserved_child_watch = false;
     tracking_child = false;
     stop_timer_armed = false;
-    start_is_interruptible = false;
 }
 
 void base_process_service::do_restart() noexcept
@@ -229,7 +247,7 @@ void base_process_service::do_restart() noexcept
         }
     }
 
-    if (! start_ps_process(exec_arg_parts, have_console)) {
+    if (! start_ps_process(exec_arg_parts, have_console || onstart_flags.shares_console)) {
         restarting = false;
         if (service_state == service_state_t::STARTING) {
             failed_to_start();
@@ -286,8 +304,10 @@ bool base_process_service::interrupt_start() noexcept
         return service_record::interrupt_start();
     }
     else {
-        log(loglevel_t::WARN, "Interrupting start of service ", get_name(), " with pid ", pid, " (with SIGINT).");
+        log(loglevel_t::WARN, "Interrupting start of service ", get_name(), " with pid ", pid,
+                " (with SIGINT).");
         kill_pg(SIGINT);
+
         if (stop_timeout != time_val(0,0)) {
             restart_timer.arm_timer_rel(event_loop, stop_timeout);
             stop_timer_armed = true;
@@ -296,8 +316,8 @@ bool base_process_service::interrupt_start() noexcept
             restart_timer.stop_timer(event_loop);
             stop_timer_armed = false;
         }
+
         set_state(service_state_t::STOPPING);
-        notify_listeners(service_event_t::STARTCANCELLED);
         return false;
     }
 }
@@ -305,20 +325,27 @@ bool base_process_service::interrupt_start() noexcept
 void base_process_service::kill_with_fire() noexcept
 {
     if (pid != -1) {
-        log(loglevel_t::WARN, "Service ", get_name(), " with pid ", pid, " exceeded allowed stop time; killing.");
+        log(loglevel_t::WARN, "Service ", get_name(), " with pid ", pid,
+                " exceeded allowed stop time; killing.");
         kill_pg(SIGKILL);
     }
 }
 
 void base_process_service::kill_pg(int signo) noexcept
 {
-    pid_t pgid = bp_sys::getpgid(pid);
-    if (pgid == -1) {
-        // only should happen if pid is invalid, which should never happen...
-        log(loglevel_t::ERROR, get_name(), ": can't signal process: ", strerror(errno));
-        return;
+    if (onstart_flags.signal_process_only) {
+        bp_sys::kill(pid, signo);
+    }
+    else {
+        pid_t pgid = bp_sys::getpgid(pid);
+        if (pgid == -1) {
+            // On some OSes (eg OpenBSD) we aren't allowed to get the pgid of a process in a different
+            // session. If the process is in a different session, however, it must be a process group
+            // leader and the pgid must equal the process id.
+            pgid = pid;
+        }
+        bp_sys::kill(-pgid, signo);
     }
-    bp_sys::kill(-pgid, signo);
 }
 
 void base_process_service::timer_expired() noexcept
@@ -334,12 +361,11 @@ void base_process_service::timer_expired() noexcept
     }
     else if (pid != -1) {
         // Starting, start timed out.
-        stop_dependents();
-        if (start_explicit) {
-            start_explicit = false;
-            release();
-        }
+        log(loglevel_t::WARN, "Service ", get_name(), " with pid ", pid,
+                " exceeded allowed start time; cancelling.");
         interrupt_start();
+        stop_reason = stopped_reason_t::TIMEDOUT;
+        failed_to_start(false, false);
     }
     else {
         // STARTING / STARTED, and we have a pid: must be restarting (smooth recovery if STARTED)
@@ -349,13 +375,8 @@ void base_process_service::timer_expired() noexcept
 
 void base_process_service::emergency_stop() noexcept
 {
-    if (! do_auto_restart() && start_explicit) {
-        start_explicit = false;
-        release(false);
-    }
     forced_stop();
     stop_dependents();
-    stopped();
 }
 
 void base_process_service::becoming_inactive() noexcept
@@ -424,13 +445,15 @@ bool base_process_service::open_socket() noexcept
     // POSIX (1003.1, 2013) says that fchown and fchmod don't necessarily work on sockets. We have to
     // use chown and chmod instead.
     if (chown(saddrname, socket_uid, socket_gid)) {
-        log(loglevel_t::ERROR, get_name(), ": Error setting activation socket owner/group: ", strerror(errno));
+        log(loglevel_t::ERROR, get_name(), ": Error setting activation socket owner/group: ",
+                strerror(errno));
         close(sockfd);
         return false;
     }
 
     if (chmod(saddrname, socket_perms) == -1) {
-        log(loglevel_t::ERROR, get_name(), ": Error setting activation socket permissions: ", strerror(errno));
+        log(loglevel_t::ERROR, get_name(), ": Error setting activation socket permissions: ",
+                strerror(errno));
         close(sockfd);
         return false;
     }