]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/activate/activate.c
tree-wide: beautify remaining copyright statements
[thirdparty/systemd.git] / src / activate / activate.c
index 558d16824a80ed928651cb0ed97fbe6645323df7..18a8e932b3d7d5dc8642f93d8b335384a2378e9e 100644 (file)
@@ -1,22 +1,6 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2013 Zbigniew Jędrzejewski-Szmek
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+  Copyright © 2013 Zbigniew Jędrzejewski-Szmek
 ***/
 
 #include <getopt.h>
 #include "sd-daemon.h"
 
 #include "alloc-util.h"
+#include "escape.h"
 #include "fd-util.h"
 #include "log.h"
 #include "macro.h"
+#include "process-util.h"
 #include "signal-util.h"
 #include "socket-util.h"
 #include "string-util.h"
 
 static char** arg_listen = NULL;
 static bool arg_accept = false;
-static bool arg_datagram = false;
+static int arg_socket_type = SOCK_STREAM;
 static char** arg_args = NULL;
 static char** arg_setenv = NULL;
-static const char *arg_fdname = NULL;
+static char **arg_fdnames = NULL;
+static bool arg_inetd = false;
 
 static int add_epoll(int epoll_fd, int fd) {
         struct epoll_event ev = {
-                .events = EPOLLIN
+                .events = EPOLLIN,
+                .data.fd = fd,
         };
-        int r;
 
         assert(epoll_fd >= 0);
         assert(fd >= 0);
 
-        ev.data.fd = fd;
-        r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
-        if (r < 0)
+        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
                 return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd);
 
         return 0;
@@ -77,7 +62,7 @@ static int open_sockets(int *epoll_fd, bool accept) {
                         if (r < 0)
                                 return r;
 
-                        count ++;
+                        count++;
                 }
         }
 
@@ -98,19 +83,14 @@ static int open_sockets(int *epoll_fd, bool accept) {
          */
 
         STRV_FOREACH(address, arg_listen) {
-
-                if (arg_datagram)
-                        fd = make_socket_fd(LOG_DEBUG, *address, SOCK_DGRAM, SOCK_CLOEXEC);
-                else
-                        fd = make_socket_fd(LOG_DEBUG, *address, SOCK_STREAM, (arg_accept*SOCK_CLOEXEC));
-
+                fd = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept*SOCK_CLOEXEC));
                 if (fd < 0) {
                         log_open();
                         return log_error_errno(fd, "Failed to open '%s': %m", *address);
                 }
 
                 assert(fd == SD_LISTEN_FDS_START + count);
-                count ++;
+                count++;
         }
 
         if (arg_listen)
@@ -134,14 +114,19 @@ static int open_sockets(int *epoll_fd, bool accept) {
         return count;
 }
 
-static int launch(char* name, char **argv, char **env, int fds) {
+static int exec_process(const char* name, char **argv, char **env, int start_fd, size_t n_fds) {
 
-        static const char* tocopy[] = {"TERM=", "PATH=", "USER=", "HOME="};
         _cleanup_strv_free_ char **envp = NULL;
-        _cleanup_free_ char *tmp = NULL;
-        unsigned n_env = 0, length;
+        _cleanup_free_ char *joined = NULL;
+        size_t n_env = 0, length;
+        const char *tocopy;
         char **s;
-        unsigned i;
+        int r;
+
+        if (arg_inetd && n_fds != 1) {
+                log_error("--inetd only supported for single file descriptors.");
+                return -EINVAL;
+        }
 
         length = strv_length(arg_setenv);
 
@@ -151,6 +136,7 @@ static int launch(char* name, char **argv, char **env, int fds) {
                 return log_oom();
 
         STRV_FOREACH(s, arg_setenv) {
+
                 if (strchr(*s, '=')) {
                         char *k;
 
@@ -174,13 +160,15 @@ static int launch(char* name, char **argv, char **env, int fds) {
                         envp[n_env] = strdup(n);
                         if (!envp[n_env])
                                 return log_oom();
+
+                        n_env++;
                 }
         }
 
-        for (i = 0; i < ELEMENTSOF(tocopy); i++) {
+        FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") {
                 const char *n;
 
-                n = strv_find_prefix(env, tocopy[i]);
+                n = strv_find_prefix(env, tocopy);
                 if (!n)
                         continue;
 
@@ -188,153 +176,156 @@ static int launch(char* name, char **argv, char **env, int fds) {
                 if (!envp[n_env])
                         return log_oom();
 
-                n_env ++;
+                n_env++;
         }
 
-        if ((asprintf((char**)(envp + n_env++), "LISTEN_FDS=%d", fds) < 0) ||
-            (asprintf((char**)(envp + n_env++), "LISTEN_PID=%d", getpid()) < 0))
-                return log_oom();
+        if (arg_inetd) {
+                assert(n_fds == 1);
+
+                r = rearrange_stdio(start_fd, start_fd, STDERR_FILENO); /* invalidates start_fd on success + error */
+                if (r < 0)
+                        return log_error_errno(r, "Failed to move fd to stdin+stdout: %m");
+
+        } else {
+                if (start_fd != SD_LISTEN_FDS_START) {
+                        assert(n_fds == 1);
 
-        if (arg_fdname) {
-                char *e;
+                        if (dup2(start_fd, SD_LISTEN_FDS_START) < 0)
+                                return log_error_errno(errno, "Failed to dup connection: %m");
 
-                e = strappend("LISTEN_FDNAMES=", arg_fdname);
-                if (!e)
+                        safe_close(start_fd);
+                        start_fd = SD_LISTEN_FDS_START;
+                }
+
+                if (asprintf((char**)(envp + n_env++), "LISTEN_FDS=%zu", n_fds) < 0)
                         return log_oom();
 
-                for (i = 1; i < (unsigned) fds; i++) {
-                        char *c;
+                if (asprintf((char**)(envp + n_env++), "LISTEN_PID=" PID_FMT, getpid_cached()) < 0)
+                        return log_oom();
 
-                        c = strjoin(e, ":", arg_fdname, NULL);
-                        if (!c) {
-                                free(e);
+                if (arg_fdnames) {
+                        _cleanup_free_ char *names = NULL;
+                        size_t len;
+                        char *e;
+
+                        len = strv_length(arg_fdnames);
+                        if (len == 1) {
+                                size_t i;
+
+                                for (i = 1; i < n_fds; i++) {
+                                        r = strv_extend(&arg_fdnames, arg_fdnames[0]);
+                                        if (r < 0)
+                                                return log_error_errno(r, "Failed to extend strv: %m");
+                                }
+                        } else if (len != n_fds)
+                                log_warning("The number of fd names is different than number of fds: %zu vs %zu", len, n_fds);
+
+                        names = strv_join(arg_fdnames, ":");
+                        if (!names)
                                 return log_oom();
-                        }
 
-                        free(e);
-                        e = c;
-                }
+                        e = strappend("LISTEN_FDNAMES=", names);
+                        if (!e)
+                                return log_oom();
 
-                envp[n_env++] = e;
+                        envp[n_env++] = e;
+                }
         }
 
-        tmp = strv_join(argv, " ");
-        if (!tmp)
+        joined = strv_join(argv, " ");
+        if (!joined)
                 return log_oom();
 
-        log_info("Execing %s (%s)", name, tmp);
+        log_info("Execing %s (%s)", name, joined);
         execvpe(name, argv, envp);
 
-        return log_error_errno(errno, "Failed to execp %s (%s): %m", name, tmp);
+        return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined);
 }
 
-static int launch1(const char* child, char** argv, char **env, int fd) {
-        _cleanup_free_ char *tmp = NULL;
-        pid_t parent_pid, child_pid;
+static int fork_and_exec_process(const char* child, char** argv, char **env, int fd) {
+        _cleanup_free_ char *joined = NULL;
+        pid_t child_pid;
         int r;
 
-        tmp = strv_join(argv, " ");
-        if (!tmp)
+        joined = strv_join(argv, " ");
+        if (!joined)
                 return log_oom();
 
-        parent_pid = getpid();
-
-        child_pid = fork();
-        if (child_pid < 0)
-                return log_error_errno(errno, "Failed to fork: %m");
-
-        /* In the child */
-        if (child_pid == 0) {
-
-                (void) reset_all_signal_handlers();
-                (void) reset_signal_mask();
-
-                r = dup2(fd, STDIN_FILENO);
-                if (r < 0) {
-                        log_error_errno(errno, "Failed to dup connection to stdin: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                r = dup2(fd, STDOUT_FILENO);
-                if (r < 0) {
-                        log_error_errno(errno, "Failed to dup connection to stdout: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                r = close(fd);
-                if (r < 0) {
-                        log_error_errno(errno, "Failed to close dupped connection: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                /* Make sure the child goes away when the parent dies */
-                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
-                        _exit(EXIT_FAILURE);
-
-                /* Check whether our parent died before we were able
-                 * to set the death signal */
-                if (getppid() != parent_pid)
-                        _exit(EXIT_SUCCESS);
-
-                execvp(child, argv);
-                log_error_errno(errno, "Failed to exec child %s: %m", child);
+        r = safe_fork("(activate)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &child_pid);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                /* In the child */
+                exec_process(child, argv, env, fd, 1);
                 _exit(EXIT_FAILURE);
         }
 
-        log_info("Spawned %s (%s) as PID %d", child, tmp, child_pid);
-
+        log_info("Spawned %s (%s) as PID " PID_FMT ".", child, joined, child_pid);
         return 0;
 }
 
 static int do_accept(const char* name, char **argv, char **envp, int fd) {
         _cleanup_free_ char *local = NULL, *peer = NULL;
-        _cleanup_close_ int fd2 = -1;
+        _cleanup_close_ int fd_accepted = -1;
 
-        fd2 = accept(fd, NULL, NULL);
-        if (fd2 < 0) {
-                log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
-                return fd2;
-        }
+        fd_accepted = accept4(fd, NULL, NULL, 0);
+        if (fd_accepted < 0)
+                return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
 
-        getsockname_pretty(fd2, &local);
-        getpeername_pretty(fd2, true, &peer);
+        getsockname_pretty(fd_accepted, &local);
+        getpeername_pretty(fd_accepted, true, &peer);
         log_info("Connection from %s to %s", strna(peer), strna(local));
 
-        return launch1(name, argv, envp, fd2);
+        return fork_and_exec_process(name, argv, envp, fd_accepted);
 }
 
 /* SIGCHLD handler. */
-static void sigchld_hdl(int sig, siginfo_t *t, void *data) {
+static void sigchld_hdl(int sig) {
         PROTECT_ERRNO;
 
-        log_info("Child %d died with code %d", t->si_pid, t->si_status);
-        /* Wait for a dead child. */
-        waitpid(t->si_pid, NULL, 0);
+        for (;;) {
+                siginfo_t si;
+                int r;
+
+                si.si_pid = 0;
+                r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG);
+                if (r < 0) {
+                        if (errno != ECHILD)
+                                log_error_errno(errno, "Failed to reap children: %m");
+                        return;
+                }
+                if (si.si_pid == 0)
+                        return;
+
+                log_info("Child %d died with code %d", si.si_pid, si.si_status);
+        }
 }
 
 static int install_chld_handler(void) {
-        int r;
-        struct sigaction act = {
-                .sa_flags = SA_SIGINFO,
-                .sa_sigaction = sigchld_hdl,
+        static const struct sigaction act = {
+                .sa_flags = SA_NOCLDSTOP|SA_RESTART,
+                .sa_handler = sigchld_hdl,
         };
 
-        r = sigaction(SIGCHLD, &act, 0);
-        if (r < 0)
-                log_error_errno(errno, "Failed to install SIGCHLD handler: %m");
-        return r;
+        if (sigaction(SIGCHLD, &act, 0) < 0)
+                return log_error_errno(errno, "Failed to install SIGCHLD handler: %m");
+
+        return 0;
 }
 
 static void help(void) {
         printf("%s [OPTIONS...]\n\n"
                "Listen on sockets and launch child on connection.\n\n"
                "Options:\n"
-               "  -h --help                Show this help and exit\n"
-               "     --version             Print version string and exit\n"
-               "  -l --listen=ADDR         Listen for raw connections at ADDR\n"
-               "  -d --datagram            Listen on datagram instead of stream socket\n"
-               "  -a --accept              Spawn separate child for each connection\n"
-               "  -E --setenv=NAME[=VALUE] Pass an environment variable to children\n"
+               "  -h --help                  Show this help and exit\n"
+               "     --version               Print version string and exit\n"
+               "  -l --listen=ADDR           Listen for raw connections at ADDR\n"
+               "  -d --datagram              Listen on datagram instead of stream socket\n"
+               "     --seqpacket             Listen on SOCK_SEQPACKET instead of stream socket\n"
+               "  -a --accept                Spawn separate child for each connection\n"
+               "  -E --setenv=NAME[=VALUE]   Pass an environment variable to children\n"
+               "     --fdname=NAME[:NAME...] Specify names for file descriptors\n"
+               "     --inetd                 Enable inetd file descriptor passing protocol\n"
                "\n"
                "Note: file descriptors from sd_listen_fds() will be passed through.\n"
                , program_invocation_short_name);
@@ -344,17 +335,21 @@ static int parse_argv(int argc, char *argv[]) {
         enum {
                 ARG_VERSION = 0x100,
                 ARG_FDNAME,
+                ARG_SEQPACKET,
+                ARG_INETD,
         };
 
         static const struct option options[] = {
                 { "help",        no_argument,       NULL, 'h'           },
                 { "version",     no_argument,       NULL, ARG_VERSION   },
                 { "datagram",    no_argument,       NULL, 'd'           },
+                { "seqpacket",   no_argument,       NULL, ARG_SEQPACKET },
                 { "listen",      required_argument, NULL, 'l'           },
                 { "accept",      no_argument,       NULL, 'a'           },
                 { "setenv",      required_argument, NULL, 'E'           },
                 { "environment", required_argument, NULL, 'E'           }, /* legacy alias */
                 { "fdname",      required_argument, NULL, ARG_FDNAME    },
+                { "inetd",       no_argument,       NULL, ARG_INETD     },
                 {}
         };
 
@@ -363,7 +358,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "+hl:aEd", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0)
                 switch(c) {
                 case 'h':
                         help();
@@ -380,7 +375,21 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case 'd':
-                        arg_datagram = true;
+                        if (arg_socket_type == SOCK_SEQPACKET) {
+                                log_error("--datagram may not be combined with --seqpacket.");
+                                return -EINVAL;
+                        }
+
+                        arg_socket_type = SOCK_DGRAM;
+                        break;
+
+                case ARG_SEQPACKET:
+                        if (arg_socket_type == SOCK_DGRAM) {
+                                log_error("--seqpacket may not be combined with --datagram.");
+                                return -EINVAL;
+                        }
+
+                        arg_socket_type = SOCK_SEQPACKET;
                         break;
 
                 case 'a':
@@ -394,13 +403,33 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
-                case ARG_FDNAME:
-                        if (!fdname_is_valid(optarg)) {
-                                log_error("File descriptor name %s is not valid, refusing.", optarg);
-                                return -EINVAL;
-                        }
+                case ARG_FDNAME: {
+                        _cleanup_strv_free_ char **names;
+                        char **s;
 
-                        arg_fdname = optarg;
+                        names = strv_split(optarg, ":");
+                        if (!names)
+                                return log_oom();
+
+                        STRV_FOREACH(s, names)
+                                if (!fdname_is_valid(*s)) {
+                                        _cleanup_free_ char *esc;
+
+                                        esc = cescape(*s);
+                                        log_warning("File descriptor name \"%s\" is not valid.", esc);
+                                }
+
+                        /* Empty optargs means one empty name */
+                        r = strv_extend_strv(&arg_fdnames,
+                                             strv_isempty(names) ? STRV_MAKE("") : names,
+                                             false);
+                        if (r < 0)
+                                return log_error_errno(r, "strv_extend_strv: %m");
+                        break;
+                }
+
+                case ARG_INETD:
+                        arg_inetd = true;
                         break;
 
                 case '?':
@@ -416,7 +445,7 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
-        if (arg_datagram && arg_accept) {
+        if (arg_socket_type == SOCK_DGRAM && arg_accept) {
                 log_error("Datagram sockets do not accept connections. "
                           "The --datagram and --accept options may not be combined.");
                 return -EINVAL;
@@ -453,8 +482,7 @@ int main(int argc, char **argv, char **envp) {
         for (;;) {
                 struct epoll_event event;
 
-                r = epoll_wait(epoll_fd, &event, 1, -1);
-                if (r < 0) {
+                if (epoll_wait(epoll_fd, &event, 1, -1) < 0) {
                         if (errno == EINTR)
                                 continue;
 
@@ -464,15 +492,14 @@ int main(int argc, char **argv, char **envp) {
 
                 log_info("Communication attempt on fd %i.", event.data.fd);
                 if (arg_accept) {
-                        r = do_accept(argv[optind], argv + optind, envp,
-                                      event.data.fd);
+                        r = do_accept(argv[optind], argv + optind, envp, event.data.fd);
                         if (r < 0)
                                 return EXIT_FAILURE;
                 } else
                         break;
         }
 
-        launch(argv[optind], argv + optind, envp, n);
+        exec_process(argv[optind], argv + optind, envp, SD_LISTEN_FDS_START, (size_t) n);
 
         return EXIT_SUCCESS;
 }