]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/activate/activate.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / activate / activate.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <sys/epoll.h>
5 #include <sys/prctl.h>
6 #include <sys/wait.h>
7 #include <unistd.h>
8
9 #include "sd-daemon.h"
10
11 #include "alloc-util.h"
12 #include "build.h"
13 #include "env-util.h"
14 #include "errno-util.h"
15 #include "escape.h"
16 #include "fd-util.h"
17 #include "log.h"
18 #include "macro.h"
19 #include "pretty-print.h"
20 #include "process-util.h"
21 #include "signal-util.h"
22 #include "socket-netlink.h"
23 #include "socket-util.h"
24 #include "string-util.h"
25 #include "strv.h"
26 #include "terminal-util.h"
27
28 static char **arg_listen = NULL;
29 static bool arg_accept = false;
30 static int arg_socket_type = SOCK_STREAM;
31 static char **arg_args = NULL;
32 static char **arg_setenv = NULL;
33 static char **arg_fdnames = NULL;
34 static bool arg_inetd = false;
35
36 static int add_epoll(int epoll_fd, int fd) {
37 struct epoll_event ev = {
38 .events = EPOLLIN,
39 .data.fd = fd,
40 };
41
42 assert(epoll_fd >= 0);
43 assert(fd >= 0);
44
45 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
46 return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd);
47
48 return 0;
49 }
50
51 static int open_sockets(int *epoll_fd, bool accept) {
52 int n, r, count = 0;
53
54 n = sd_listen_fds(true);
55 if (n < 0)
56 return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
57 if (n > 0) {
58 log_info("Received %i descriptors via the environment.", n);
59
60 for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
61 r = fd_cloexec(fd, arg_accept);
62 if (r < 0)
63 return r;
64
65 count++;
66 }
67 }
68
69 /* Close logging and all other descriptors */
70 if (arg_listen) {
71 _cleanup_free_ int *except = new(int, n);
72 if (!except)
73 return log_oom();
74
75 for (int i = 0; i < n; i++)
76 except[i] = SD_LISTEN_FDS_START + i;
77
78 log_close();
79 r = close_all_fds(except, n);
80 if (r < 0)
81 return log_error_errno(r, "Failed to close all file descriptors: %m");
82 }
83
84 /** Note: we leak some fd's on error here. I doesn't matter
85 * much, since the program will exit immediately anyway, but
86 * would be a pain to fix.
87 */
88
89 STRV_FOREACH(address, arg_listen) {
90 r = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept * SOCK_CLOEXEC));
91 if (r < 0) {
92 log_open();
93 return log_error_errno(r, "Failed to open '%s': %m", *address);
94 }
95
96 assert(r == SD_LISTEN_FDS_START + count);
97 count++;
98 }
99
100 if (arg_listen)
101 log_open();
102
103 *epoll_fd = epoll_create1(EPOLL_CLOEXEC);
104 if (*epoll_fd < 0)
105 return log_error_errno(errno, "Failed to create epoll object: %m");
106
107 for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) {
108 _cleanup_free_ char *name = NULL;
109
110 getsockname_pretty(fd, &name);
111 log_info("Listening on %s as %i.", strna(name), fd);
112
113 r = add_epoll(*epoll_fd, fd);
114 if (r < 0)
115 return r;
116 }
117
118 return count;
119 }
120
121 static int exec_process(const char *name, char **argv, int start_fd, size_t n_fds) {
122 _cleanup_strv_free_ char **envp = NULL;
123 int r;
124
125 if (arg_inetd && n_fds != 1)
126 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
127 "--inetd only supported for single file descriptors.");
128
129 FOREACH_STRING(var, "TERM", "PATH", "USER", "HOME") {
130 const char *n;
131
132 n = strv_find_prefix(environ, var);
133 if (!n)
134 continue;
135
136 r = strv_extend(&envp, n);
137 if (r < 0)
138 return r;
139 }
140
141 if (arg_inetd) {
142 assert(n_fds == 1);
143
144 r = rearrange_stdio(start_fd, start_fd, STDERR_FILENO); /* invalidates start_fd on success + error */
145 if (r < 0)
146 return log_error_errno(r, "Failed to move fd to stdin+stdout: %m");
147
148 } else {
149 if (start_fd != SD_LISTEN_FDS_START) {
150 assert(n_fds == 1);
151
152 if (dup2(start_fd, SD_LISTEN_FDS_START) < 0)
153 return log_error_errno(errno, "Failed to dup connection: %m");
154
155 safe_close(start_fd);
156 }
157
158 r = strv_extendf(&envp, "LISTEN_FDS=%zu", n_fds);
159 if (r < 0)
160 return r;
161
162 r = strv_extendf(&envp, "LISTEN_PID=" PID_FMT, getpid_cached());
163 if (r < 0)
164 return r;
165
166 if (arg_fdnames) {
167 _cleanup_free_ char *names = NULL;
168 size_t len;
169
170 len = strv_length(arg_fdnames);
171 if (len == 1)
172 for (size_t i = 1; i < n_fds; i++) {
173 r = strv_extend(&arg_fdnames, arg_fdnames[0]);
174 if (r < 0)
175 return log_oom();
176 }
177 else if (len != n_fds)
178 log_warning("The number of fd names is different than number of fds: %zu vs %zu", len, n_fds);
179
180 names = strv_join(arg_fdnames, ":");
181 if (!names)
182 return log_oom();
183
184 char *t = strjoin("LISTEN_FDNAMES=", names);
185 if (!t)
186 return log_oom();
187
188 r = strv_consume(&envp, t);
189 if (r < 0)
190 return r;
191 }
192 }
193
194 STRV_FOREACH(s, arg_setenv) {
195 r = strv_env_replace_strdup(&envp, *s);
196 if (r < 0)
197 return r;
198 }
199
200 _cleanup_free_ char *joined = strv_join(argv, " ");
201 if (!joined)
202 return log_oom();
203
204 log_info("Execing %s (%s)", name, joined);
205 execvpe(name, argv, envp);
206
207 return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined);
208 }
209
210 static int fork_and_exec_process(const char *child, char **argv, int fd) {
211 _cleanup_free_ char *joined = NULL;
212 pid_t child_pid;
213 int r;
214
215 joined = strv_join(argv, " ");
216 if (!joined)
217 return log_oom();
218
219 r = safe_fork("(activate)",
220 FORK_RESET_SIGNALS | FORK_DEATHSIG | FORK_RLIMIT_NOFILE_SAFE | FORK_LOG,
221 &child_pid);
222 if (r < 0)
223 return r;
224 if (r == 0) {
225 /* In the child */
226 exec_process(child, argv, fd, 1);
227 _exit(EXIT_FAILURE);
228 }
229
230 log_info("Spawned %s (%s) as PID " PID_FMT ".", child, joined, child_pid);
231 return 0;
232 }
233
234 static int do_accept(const char *name, char **argv, int fd) {
235 _cleanup_free_ char *local = NULL, *peer = NULL;
236 _cleanup_close_ int fd_accepted = -EBADF;
237
238 fd_accepted = accept4(fd, NULL, NULL, 0);
239 if (fd_accepted < 0) {
240 if (ERRNO_IS_ACCEPT_AGAIN(errno))
241 return 0;
242
243 return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
244 }
245
246 (void) getsockname_pretty(fd_accepted, &local);
247 (void) getpeername_pretty(fd_accepted, true, &peer);
248 log_info("Connection from %s to %s", strna(peer), strna(local));
249
250 return fork_and_exec_process(name, argv, fd_accepted);
251 }
252
253 /* SIGCHLD handler. */
254 static void sigchld_hdl(int sig) {
255 PROTECT_ERRNO;
256
257 for (;;) {
258 siginfo_t si;
259 int r;
260
261 si.si_pid = 0;
262 r = waitid(P_ALL, 0, &si, WEXITED | WNOHANG);
263 if (r < 0) {
264 if (errno != ECHILD)
265 log_error_errno(errno, "Failed to reap children: %m");
266 return;
267 }
268 if (si.si_pid == 0)
269 return;
270
271 log_info("Child %d died with code %d", si.si_pid, si.si_status);
272 }
273 }
274
275 static int install_chld_handler(void) {
276 static const struct sigaction act = {
277 .sa_flags = SA_NOCLDSTOP | SA_RESTART,
278 .sa_handler = sigchld_hdl,
279 };
280
281 if (sigaction(SIGCHLD, &act, 0) < 0)
282 return log_error_errno(errno, "Failed to install SIGCHLD handler: %m");
283
284 return 0;
285 }
286
287 static int help(void) {
288 _cleanup_free_ char *link = NULL;
289 int r;
290
291 r = terminal_urlify_man("systemd-socket-activate", "1", &link);
292 if (r < 0)
293 return log_oom();
294
295 printf("%s [OPTIONS...]\n"
296 "\n%sListen on sockets and launch child on connection.%s\n"
297 "\nOptions:\n"
298 " -h --help Show this help and exit\n"
299 " --version Print version string and exit\n"
300 " -l --listen=ADDR Listen for raw connections at ADDR\n"
301 " -d --datagram Listen on datagram instead of stream socket\n"
302 " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n"
303 " -a --accept Spawn separate child for each connection\n"
304 " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n"
305 " --fdname=NAME[:NAME...] Specify names for file descriptors\n"
306 " --inetd Enable inetd file descriptor passing protocol\n"
307 "\nNote: file descriptors from sd_listen_fds() will be passed through.\n"
308 "\nSee the %s for details.\n",
309 program_invocation_short_name,
310 ansi_highlight(),
311 ansi_normal(),
312 link);
313
314 return 0;
315 }
316
317 static int parse_argv(int argc, char *argv[]) {
318 enum {
319 ARG_VERSION = 0x100,
320 ARG_FDNAME,
321 ARG_SEQPACKET,
322 ARG_INETD,
323 };
324
325 static const struct option options[] = {
326 { "help", no_argument, NULL, 'h' },
327 { "version", no_argument, NULL, ARG_VERSION },
328 { "datagram", no_argument, NULL, 'd' },
329 { "seqpacket", no_argument, NULL, ARG_SEQPACKET },
330 { "listen", required_argument, NULL, 'l' },
331 { "accept", no_argument, NULL, 'a' },
332 { "setenv", required_argument, NULL, 'E' },
333 { "environment", required_argument, NULL, 'E' }, /* legacy alias */
334 { "fdname", required_argument, NULL, ARG_FDNAME },
335 { "inetd", no_argument, NULL, ARG_INETD },
336 {}
337 };
338
339 int c, r;
340
341 assert(argc >= 0);
342 assert(argv);
343
344 while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0)
345 switch (c) {
346 case 'h':
347 return help();
348
349 case ARG_VERSION:
350 return version();
351
352 case 'l':
353 r = strv_extend(&arg_listen, optarg);
354 if (r < 0)
355 return log_oom();
356
357 break;
358
359 case 'd':
360 if (arg_socket_type == SOCK_SEQPACKET)
361 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
362 "--datagram may not be combined with --seqpacket.");
363
364 arg_socket_type = SOCK_DGRAM;
365 break;
366
367 case ARG_SEQPACKET:
368 if (arg_socket_type == SOCK_DGRAM)
369 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
370 "--seqpacket may not be combined with --datagram.");
371
372 arg_socket_type = SOCK_SEQPACKET;
373 break;
374
375 case 'a':
376 arg_accept = true;
377 break;
378
379 case 'E':
380 r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg);
381 if (r < 0)
382 return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg);
383 break;
384
385 case ARG_FDNAME: {
386 _cleanup_strv_free_ char **names = NULL;
387
388 names = strv_split(optarg, ":");
389 if (!names)
390 return log_oom();
391
392 STRV_FOREACH(s, names)
393 if (!fdname_is_valid(*s)) {
394 _cleanup_free_ char *esc = NULL;
395
396 esc = cescape(*s);
397 log_warning("File descriptor name \"%s\" is not valid.", esc);
398 }
399
400 /* Empty optargs means one empty name */
401 r = strv_extend_strv(&arg_fdnames,
402 strv_isempty(names) ? STRV_MAKE("") : names,
403 false);
404 if (r < 0)
405 return log_error_errno(r, "strv_extend_strv: %m");
406 break;
407 }
408
409 case ARG_INETD:
410 arg_inetd = true;
411 break;
412
413 case '?':
414 return -EINVAL;
415
416 default:
417 assert_not_reached();
418 }
419
420 if (optind == argc)
421 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
422 "%s: command to execute is missing.",
423 program_invocation_short_name);
424
425 if (arg_socket_type == SOCK_DGRAM && arg_accept)
426 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
427 "Datagram sockets do not accept connections. "
428 "The --datagram and --accept options may not be combined.");
429
430 arg_args = argv + optind;
431
432 return 1 /* work to do */;
433 }
434
435 int main(int argc, char **argv) {
436 int r, n;
437 int epoll_fd = -EBADF;
438
439 log_show_color(true);
440 log_parse_environment();
441 log_open();
442
443 r = parse_argv(argc, argv);
444 if (r <= 0)
445 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
446
447 r = install_chld_handler();
448 if (r < 0)
449 return EXIT_FAILURE;
450
451 n = open_sockets(&epoll_fd, arg_accept);
452 if (n < 0)
453 return EXIT_FAILURE;
454 if (n == 0) {
455 log_error("No sockets to listen on specified or passed in.");
456 return EXIT_FAILURE;
457 }
458
459 for (;;) {
460 struct epoll_event event;
461
462 if (epoll_wait(epoll_fd, &event, 1, -1) < 0) {
463 if (errno == EINTR)
464 continue;
465
466 log_error_errno(errno, "epoll_wait() failed: %m");
467 return EXIT_FAILURE;
468 }
469
470 log_info("Communication attempt on fd %i.", event.data.fd);
471 if (arg_accept) {
472 r = do_accept(argv[optind], argv + optind, event.data.fd);
473 if (r < 0)
474 return EXIT_FAILURE;
475 } else
476 break;
477 }
478
479 exec_process(argv[optind], argv + optind, SD_LISTEN_FDS_START, (size_t) n);
480
481 return EXIT_SUCCESS;
482 }