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