8 #include <sys/socket.h>
14 #include "proc-service.h"
16 // Move an fd, if necessary, to another fd. The destination fd must be available (not open).
17 // if fd is specified as -1, returns -1 immediately. Returns 0 on success.
18 static int move_fd(int fd, int dest)
20 if (fd == -1) return -1;
21 if (fd == dest) return 0;
23 if (dup2(fd, dest) == -1) {
31 // Move a file descriptor to another, freeing up the original descriptor so that it can be used
32 // for some reserved purpose.
33 static int move_reserved_fd(int *fd, int min_fd)
35 int new_fd = fcntl(*fd, F_DUPFD_CLOEXEC, min_fd);
43 void base_process_service::run_child_proc(run_proc_params params) noexcept
45 // Child process. Must not risk throwing any uncaught exception from here until exit().
46 const char * const *args = params.args;
47 const char *working_dir = params.working_dir;
48 const char *logfile = params.logfile;
49 bool on_console = params.on_console;
50 int wpipefd = params.wpipefd;
51 int csfd = params.csfd;
52 int notify_fd = params.notify_fd;
53 int force_notify_fd = params.force_notify_fd;
54 const char *notify_var = params.notify_var;
55 uid_t uid = params.uid;
56 gid_t gid = params.gid;
57 const std::vector<service_rlimits> &rlimits = params.rlimits;
59 // If the console already has a session leader, presumably it is us. On the other hand
60 // if it has no session leader, and we don't create one, then control inputs such as
61 // ^C will have no effect.
62 bool do_set_ctty = (tcgetsid(0) == -1);
64 // Copy signal mask, but unmask signals that we masked on startup. For the moment, we'll
65 // also block all signals, since apparently dup() can be interrupted (!!! really, POSIX??).
68 sigfillset(&sigall_set);
69 sigprocmask(SIG_SETMASK, &sigall_set, &sigwait_set);
70 sigdelset(&sigwait_set, SIGCHLD);
71 sigdelset(&sigwait_set, SIGINT);
72 sigdelset(&sigwait_set, SIGTERM);
73 sigdelset(&sigwait_set, SIGQUIT);
75 constexpr int bufsz = 11 + ((CHAR_BIT * sizeof(pid_t) + 2) / 3) + 1;
76 // "LISTEN_PID=" - 11 characters; the expression above gives a conservative estimate
77 // on the maxiumum number of bytes required for LISTEN=nnn, including nul terminator,
78 // where nnn is a pid_t in decimal (i.e. one decimal digit is worth just over 3 bits).
81 // "DINIT_CS_FD=" - 12 bytes. (we -1 from sizeof(int) in account of sign bit).
82 constexpr int csenvbufsz = 12 + ((CHAR_BIT * sizeof(int) - 1 + 2) / 3) + 1;
83 char csenvbuf[csenvbufsz];
86 err.stage = exec_stage::ARRANGE_FDS;
88 int minfd = (socket_fd == -1) ? 3 : 4;
90 if (force_notify_fd != -1) {
91 // Move wpipefd/csfd/socket_fd to another fd if necessary:
92 if (wpipefd == force_notify_fd) {
93 if (move_reserved_fd(&wpipefd, minfd) == -1) {
97 if (csfd == force_notify_fd) {
98 if (move_reserved_fd(&csfd, minfd) == -1) {
102 if (socket_fd == force_notify_fd) {
103 // Note that we might move this again later
104 if (move_reserved_fd(&socket_fd, 0) == -1) {
109 // allocate the forced notification fd:
110 if (notify_fd != force_notify_fd) {
111 if (dup2(notify_fd, force_notify_fd) == -1) {
115 notify_fd = force_notify_fd;
119 // Make sure we have the fds for stdin/out/err (and pre-opened socket) available:
120 if (wpipefd < minfd) {
121 wpipefd = fcntl(wpipefd, F_DUPFD_CLOEXEC, minfd);
122 if (wpipefd == -1) goto failure_out;
125 if (csfd != -1 && csfd < minfd) {
126 csfd = fcntl(csfd, F_DUPFD, minfd);
127 if (csfd == -1) goto failure_out;
130 if (notify_fd < minfd && notify_fd != force_notify_fd) {
131 notify_fd = fcntl(notify_fd, F_DUPFD, minfd);
132 if (notify_fd == -1) goto failure_out;
135 err.stage = exec_stage::READ_ENV_FILE;
137 // Read environment from file
138 if (params.env_file != nullptr) {
140 read_env_file(params.env_file);
142 catch (std::system_error &sys_err) {
143 errno = sys_err.code().value();
145 catch (std::bad_alloc &alloc_err) {
146 errno = ENOMEM; goto failure_out;
150 // Set up notify-fd variable:
151 if (notify_var != nullptr && *notify_var != 0) {
152 err.stage = exec_stage::SET_NOTIFYFD_VAR;
153 // We need to do an allocation: the variable name length, '=', and space for the value,
154 // and nul terminator:
155 int notify_var_len = strlen(notify_var);
156 int req_sz = notify_var_len + ((CHAR_BIT * sizeof(int) - 1 + 2) / 3) + 1;
157 char * var_str = (char *) malloc(req_sz);
158 if (var_str == nullptr) goto failure_out;
159 snprintf(var_str, req_sz, "%s=%d", notify_var, notify_fd);
160 if (putenv(var_str)) goto failure_out;
163 // Set up Systemd-style socket activation:
164 if (socket_fd != -1) {
165 err.stage = exec_stage::SETUP_ACTIVATION_SOCKET;
167 // If we passing a pre-opened socket, it has to be fd number 3. (Thanks, Systemd).
168 if (dup2(socket_fd, 3) == -1) goto failure_out;
169 if (socket_fd != 3) close(socket_fd);
171 if (putenv(const_cast<char *>("LISTEN_FDS=1"))) goto failure_out;
172 snprintf(nbuf, bufsz, "LISTEN_PID=%jd", static_cast<intmax_t>(getpid()));
173 if (putenv(nbuf)) goto failure_out;
177 err.stage = exec_stage::SETUP_CONTROL_SOCKET;
178 snprintf(csenvbuf, csenvbufsz, "DINIT_CS_FD=%d", csfd);
179 if (putenv(csenvbuf)) goto failure_out;
182 if (working_dir != nullptr && *working_dir != 0) {
183 err.stage = exec_stage::CHDIR;
184 if (chdir(working_dir) == -1) {
190 // Re-set stdin, stdout, stderr
191 for (int i = 0; i < 3; i++) {
192 if (i != force_notify_fd) close(i);
195 err.stage = exec_stage::SETUP_STDINOUTERR;
196 if (notify_fd == 0 || move_fd(open("/dev/null", O_RDONLY), 0) == 0) {
197 // stdin = 0. That's what we should have; proceed with opening stdout and stderr. We have to
198 // take care not to clobber the notify_fd.
199 if (notify_fd != 1) {
200 if (move_fd(open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR), 1) != 0) {
203 if (notify_fd != 2 && dup2(1, 2) != 2) {
207 else if (move_fd(open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR), 2) != 0) {
211 else goto failure_out;
213 // We have the option of creating a session and process group, or just a new process
214 // group. If we just create a new process group, the child process cannot make itself
215 // a session leader if it wants to do that (eg getty/login will generally want this).
216 // If we do neither, and we are running with a controlling terminal, a ^C or similar
217 // will also affect the child process (which probably isn't so bad, though since we
218 // will handle the shutdown ourselves it's not necessary). Creating a new session
219 // (and a new process group as part of that) seems like a safe bet, and has the
220 // advantage of letting us signal the process as part of a process group.
224 // "run on console" - run as a foreground job on the terminal/console device
226 // if do_set_ctty is false, we are the session leader; we are probably running
227 // as a user process. Don't create a new session leader in that case, and run
228 // as part of the parent session. Otherwise, the new session cannot claim the
229 // terminal as a controlling terminal (it is already claimed), meaning that it
230 // will not see control signals from ^C etc.
233 // Disable suspend (^Z) (and on some systems, delayed suspend / ^Y)
234 signal(SIGTSTP, SIG_IGN);
236 // Become session leader
238 ioctl(0, TIOCSCTTY, 0);
241 if (params.in_foreground) {
242 tcsetpgrp(0, getpgrp());
247 err.stage = exec_stage::SET_RLIMITS;
248 for (auto &limit : rlimits) {
250 if (!limit.hard_set || !limit.soft_set) {
251 // if either hard or soft limit is not set, use current:
252 if (getrlimit(limit.resource_id, &setlimits) != 0) goto failure_out;
254 if (limit.hard_set) setlimits.rlim_max = limit.limits.rlim_max;
255 if (limit.soft_set) setlimits.rlim_cur = limit.limits.rlim_cur;
256 if (setrlimit(limit.resource_id, &setlimits) != 0) goto failure_out;
259 if (uid != uid_t(-1)) {
260 err.stage = exec_stage::SET_UIDGID;
261 // We must set group first (i.e. before we drop privileges)
262 if (setregid(gid, gid) != 0) goto failure_out;
263 if (setreuid(uid, uid) != 0) goto failure_out;
266 sigprocmask(SIG_SETMASK, &sigwait_set, nullptr);
268 err.stage = exec_stage::DO_EXEC;
269 execvp(args[0], const_cast<char **>(args));
271 // If we got here, the exec failed:
273 err.st_errno = errno;
274 write(wpipefd, &err, sizeof(err));