66f51994f45663d0633e5479f9c6e64bc6806deb
[oweals/dinit.git] / src / run-child-proc.cc
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <sys/ioctl.h>
4 #include <sys/un.h>
5 #include <sys/socket.h>
6 #include <fcntl.h>
7 #include <unistd.h>
8 #include <termios.h>
9
10 #include "service.h"
11
12 void service_record::run_child_proc(const char * const *args, const char *logfile, bool on_console,
13         int wpipefd, int csfd, int socket_fd) noexcept
14 {
15     // Child process. Must not allocate memory (or otherwise risk throwing any exception)
16     // from here until exit().
17
18     // If the console already has a session leader, presumably it is us. On the other hand
19     // if it has no session leader, and we don't create one, then control inputs such as
20     // ^C will have no effect.
21     bool do_set_ctty = (tcgetsid(0) == -1);
22
23     // Copy signal mask, but unmask signals that we masked on startup. For the moment, we'll
24     // also block all signals, since apparently dup() can be interrupted (!!! really, POSIX??).
25     sigset_t sigwait_set;
26     sigset_t sigall_set;
27     sigfillset(&sigall_set);
28     sigprocmask(SIG_SETMASK, &sigall_set, &sigwait_set);
29     sigdelset(&sigwait_set, SIGCHLD);
30     sigdelset(&sigwait_set, SIGINT);
31     sigdelset(&sigwait_set, SIGTERM);
32     sigdelset(&sigwait_set, SIGQUIT);
33
34     constexpr int bufsz = ((CHAR_BIT * sizeof(pid_t)) / 3 + 2) + 11;
35     // "LISTEN_PID=" - 11 characters; the expression above gives a conservative estimate
36     // on the maxiumum number of bytes required for LISTEN=nnn, including nul terminator,
37     // where nnn is a pid_t in decimal (i.e. one decimal digit is worth just over 3 bits).
38     char nbuf[bufsz];
39
40     // "DINIT_CS_FD=" - 12 bytes. (we -1 from sizeof(int) in account of sign bit).
41     constexpr int csenvbufsz = ((CHAR_BIT * sizeof(int) - 1) / 3 + 2) + 12;
42     char csenvbuf[csenvbufsz];
43
44     int minfd = (socket_fd == -1) ? 3 : 4;
45
46     // Move wpipefd/csfd to another fd if necessary
47     if (wpipefd < minfd) {
48         wpipefd = fcntl(wpipefd, F_DUPFD_CLOEXEC, minfd);
49         if (wpipefd == -1) goto failure_out;
50     }
51
52     if (csfd != -1 && csfd < minfd) {
53         csfd = fcntl(csfd, F_DUPFD, minfd);
54         if (csfd == -1) goto failure_out;
55     }
56
57     if (socket_fd != -1) {
58
59         if (dup2(socket_fd, 3) == -1) goto failure_out;
60         if (socket_fd != 3) {
61             close(socket_fd);
62         }
63
64         if (putenv(const_cast<char *>("LISTEN_FDS=1"))) goto failure_out;
65         snprintf(nbuf, bufsz, "LISTEN_PID=%jd", static_cast<intmax_t>(getpid()));
66         if (putenv(nbuf)) goto failure_out;
67     }
68
69     if (csfd != -1) {
70         snprintf(csenvbuf, csenvbufsz, "DINIT_CS_FD=%d", csfd);
71         if (putenv(csenvbuf)) goto failure_out;
72     }
73
74     if (! on_console) {
75         // Re-set stdin, stdout, stderr
76         close(0); close(1); close(2);
77
78         if (open("/dev/null", O_RDONLY) == 0) {
79             // stdin = 0. That's what we should have; proceed with opening
80             // stdout and stderr.
81             if (open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR) != 1) {
82                 goto failure_out;
83             }
84             if (dup2(1, 2) != 2) {
85                 goto failure_out;
86             }
87         }
88         else goto failure_out;
89
90         // We have the option of creating a session and process group, or just a new process
91         // group. If we just create a new process group, the child process cannot make itself
92         // a session leader if it wants to do that (eg getty/login will generally want this).
93         // If we do neither, and we are running with a controlling terminal, a ^C or similar
94         // will also affect the child process (which probably isn't so bad, though since we
95         // will handle the shutdown ourselves it's not necessary). Creating a new session
96         // (and a new process group as part of that) seems like a safe bet, and has the
97         // advantage of letting us signal the process as part of a process group.
98         setsid();
99     }
100     else {
101         // "run on console" - run as a foreground job on the terminal/console device
102
103         // if do_set_ctty is false, we are the session leader; we are probably running
104         // as a user process. Don't create a new session leader in that case, and run
105         // as part of the parent session. Otherwise, the new session cannot claim the
106         // terminal as a controlling terminal (it is already claimed), meaning that it
107         // will not see control signals from ^C etc.
108
109         if (do_set_ctty) {
110             // Disable suspend (^Z) (and on some systems, delayed suspend / ^Y)
111             signal(SIGTSTP, SIG_IGN);
112
113             // Become session leader
114             setsid();
115             ioctl(0, TIOCSCTTY, 0);
116         }
117         setpgid(0,0);
118         tcsetpgrp(0, getpgrp());
119     }
120
121     sigprocmask(SIG_SETMASK, &sigwait_set, nullptr);
122
123     execvp(args[0], const_cast<char **>(args));
124
125     // If we got here, the exec failed:
126     failure_out:
127     int exec_status = errno;
128     write(wpipefd, &exec_status, sizeof(int));
129     _exit(0);
130 }