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