Fix missing #include (needed for compiling with GCC 10)
[oweals/dinit.git] / src / baseproc-service.cc
index a424c2a8a5f17b52d0eff861c8958f4ba8086aba..32c49d5969600901f3ae2f59815ea62f1717cc49 100644 (file)
@@ -1,4 +1,11 @@
 #include <cstring>
+#include <cstdlib>
+
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
 #include "dinit.h"
 #include "dinit-log.h"
@@ -30,10 +37,17 @@ bool base_process_service::bring_up() noexcept
         return true;
     }
     else {
-        event_loop.get_time(restart_interval_time, clock_type::MONOTONIC);
+        if (! open_socket()) {
+            return false;
+        }
+
         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;
             }
@@ -43,6 +57,7 @@ bool base_process_service::bring_up() noexcept
             }
             return true;
         }
+        restart_interval_time = last_start_time;
         return false;
     }
 }
@@ -71,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));
@@ -90,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;
@@ -111,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]);
+        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;
     }
@@ -132,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;
 
@@ -147,40 +206,9 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
     return false;
 }
 
-void base_process_service::bring_down() noexcept
-{
-    waiting_for_deps = false;
-    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)
@@ -202,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
@@ -211,11 +238,6 @@ void base_process_service::do_restart() noexcept
     restart_interval_count++;
     auto service_state = get_state();
 
-    // We may be STARTING (regular restart) or STARTED ("smooth recovery"). This affects whether
-    // the process should be granted access to the console:
-    bool on_console = service_state == service_state_t::STARTING
-            ? onstart_flags.starts_on_console : onstart_flags.runs_on_console;
-
     if (service_state == service_state_t::STARTING) {
         // for a smooth recovery, we want to check dependencies are available before actually
         // starting:
@@ -225,7 +247,7 @@ void base_process_service::do_restart() noexcept
         }
     }
 
-    if (! start_ps_process(exec_arg_parts, on_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();
@@ -260,7 +282,7 @@ bool base_process_service::restart_ps_process() noexcept
         }
     }
 
-    // Check if enough time has lapsed since the prevous restart. If not, start a timer:
+    // Check if enough time has lapsed since the previous restart. If not, start a timer:
     time_val tdiff = current_time - last_start_time;
     if (restart_delay <= tdiff) {
         // > restart delay (normally 200ms)
@@ -282,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;
@@ -292,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;
     }
 }
@@ -301,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 = 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
@@ -330,11 +361,109 @@ void base_process_service::timer_expired() noexcept
     }
     else if (pid != -1) {
         // Starting, start timed out.
-        stop_dependents();
+        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)
         do_restart();
     }
 }
+
+void base_process_service::emergency_stop() noexcept
+{
+    forced_stop();
+    stop_dependents();
+}
+
+void base_process_service::becoming_inactive() noexcept
+{
+    if (socket_fd != -1) {
+        close(socket_fd);
+        socket_fd = -1;
+    }
+}
+
+bool base_process_service::open_socket() noexcept
+{
+    if (socket_path.empty() || socket_fd != -1) {
+        // No socket, or already open
+        return true;
+    }
+
+    const char * saddrname = socket_path.c_str();
+
+    // Check the specified socket path
+    struct stat stat_buf;
+    if (stat(saddrname, &stat_buf) == 0) {
+        if ((stat_buf.st_mode & S_IFSOCK) == 0) {
+            // Not a socket
+            log(loglevel_t::ERROR, get_name(), ": Activation socket file exists (and is not a socket)");
+            return false;
+        }
+    }
+    else if (errno != ENOENT) {
+        // Other error
+        log(loglevel_t::ERROR, get_name(), ": Error checking activation socket: ", strerror(errno));
+        return false;
+    }
+
+    // Remove stale socket file (if it exists).
+    // We won't test the return from unlink - if it fails other than due to ENOENT, we should get an
+    // error when we try to create the socket anyway.
+    unlink(saddrname);
+
+    uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + socket_path.length() + 1;
+    struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
+    if (name == nullptr) {
+        log(loglevel_t::ERROR, get_name(), ": Opening activation socket: out of memory");
+        return false;
+    }
+
+    name->sun_family = AF_UNIX;
+    strcpy(name->sun_path, saddrname);
+
+    int sockfd = dinit_socket(AF_UNIX, SOCK_STREAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
+    if (sockfd == -1) {
+        log(loglevel_t::ERROR, get_name(), ": Error creating activation socket: ", strerror(errno));
+        free(name);
+        return false;
+    }
+
+    if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
+        log(loglevel_t::ERROR, get_name(), ": Error binding activation socket: ", strerror(errno));
+        close(sockfd);
+        free(name);
+        return false;
+    }
+
+    free(name);
+
+    // 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));
+        close(sockfd);
+        return false;
+    }
+
+    if (chmod(saddrname, socket_perms) == -1) {
+        log(loglevel_t::ERROR, get_name(), ": Error setting activation socket permissions: ",
+                strerror(errno));
+        close(sockfd);
+        return false;
+    }
+
+    if (listen(sockfd, 128) == -1) { // 128 "seems reasonable".
+        log(loglevel_t::ERROR, ": Error listening on activation socket: ", strerror(errno));
+        close(sockfd);
+        return false;
+    }
+
+    socket_fd = sockfd;
+    return true;
+}