]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/activate/activate.c
Merge pull request #2405 from zonque/sysusers
[thirdparty/systemd.git] / src / activate / activate.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2013 Zbigniew Jędrzejewski-Szmek
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <getopt.h>
23 #include <sys/epoll.h>
24 #include <sys/prctl.h>
25 #include <sys/socket.h>
26 #include <sys/wait.h>
27 #include <unistd.h>
28
29 #include "sd-daemon.h"
30
31 #include "alloc-util.h"
32 #include "fd-util.h"
33 #include "log.h"
34 #include "macro.h"
35 #include "signal-util.h"
36 #include "socket-util.h"
37 #include "string-util.h"
38 #include "strv.h"
39
40 static char** arg_listen = NULL;
41 static bool arg_accept = false;
42 static bool arg_datagram = false;
43 static char** arg_args = NULL;
44 static char** arg_setenv = NULL;
45 static const char *arg_fdname = NULL;
46
47 static int add_epoll(int epoll_fd, int fd) {
48 struct epoll_event ev = {
49 .events = EPOLLIN
50 };
51 int r;
52
53 assert(epoll_fd >= 0);
54 assert(fd >= 0);
55
56 ev.data.fd = fd;
57 r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
58 if (r < 0)
59 return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd);
60
61 return 0;
62 }
63
64 static int open_sockets(int *epoll_fd, bool accept) {
65 char **address;
66 int n, fd, r;
67 int count = 0;
68
69 n = sd_listen_fds(true);
70 if (n < 0)
71 return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
72 if (n > 0) {
73 log_info("Received %i descriptors via the environment.", n);
74
75 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
76 r = fd_cloexec(fd, arg_accept);
77 if (r < 0)
78 return r;
79
80 count ++;
81 }
82 }
83
84 /* Close logging and all other descriptors */
85 if (arg_listen) {
86 int except[3 + n];
87
88 for (fd = 0; fd < SD_LISTEN_FDS_START + n; fd++)
89 except[fd] = fd;
90
91 log_close();
92 close_all_fds(except, 3 + n);
93 }
94
95 /** Note: we leak some fd's on error here. I doesn't matter
96 * much, since the program will exit immediately anyway, but
97 * would be a pain to fix.
98 */
99
100 STRV_FOREACH(address, arg_listen) {
101
102 if (arg_datagram)
103 fd = make_socket_fd(LOG_DEBUG, *address, SOCK_DGRAM, SOCK_CLOEXEC);
104 else
105 fd = make_socket_fd(LOG_DEBUG, *address, SOCK_STREAM, (arg_accept*SOCK_CLOEXEC));
106
107 if (fd < 0) {
108 log_open();
109 return log_error_errno(fd, "Failed to open '%s': %m", *address);
110 }
111
112 assert(fd == SD_LISTEN_FDS_START + count);
113 count ++;
114 }
115
116 if (arg_listen)
117 log_open();
118
119 *epoll_fd = epoll_create1(EPOLL_CLOEXEC);
120 if (*epoll_fd < 0)
121 return log_error_errno(errno, "Failed to create epoll object: %m");
122
123 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) {
124 _cleanup_free_ char *name = NULL;
125
126 getsockname_pretty(fd, &name);
127 log_info("Listening on %s as %i.", strna(name), fd);
128
129 r = add_epoll(*epoll_fd, fd);
130 if (r < 0)
131 return r;
132 }
133
134 return count;
135 }
136
137 static int launch(char* name, char **argv, char **env, int fds) {
138
139 static const char* tocopy[] = {"TERM=", "PATH=", "USER=", "HOME="};
140 _cleanup_strv_free_ char **envp = NULL;
141 _cleanup_free_ char *tmp = NULL;
142 unsigned n_env = 0, length;
143 char **s;
144 unsigned i;
145
146 length = strv_length(arg_setenv);
147
148 /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */
149 envp = new0(char *, length + 8);
150 if (!envp)
151 return log_oom();
152
153 STRV_FOREACH(s, arg_setenv) {
154 if (strchr(*s, '='))
155 envp[n_env++] = *s;
156 else {
157 _cleanup_free_ char *p;
158
159 p = strappend(*s, "=");
160 if (!p)
161 return log_oom();
162 envp[n_env] = strv_find_prefix(env, p);
163 if (envp[n_env])
164 n_env ++;
165 }
166 }
167
168 for (i = 0; i < ELEMENTSOF(tocopy); i++) {
169 envp[n_env] = strv_find_prefix(env, tocopy[i]);
170 if (envp[n_env])
171 n_env ++;
172 }
173
174 if ((asprintf((char**)(envp + n_env++), "LISTEN_FDS=%d", fds) < 0) ||
175 (asprintf((char**)(envp + n_env++), "LISTEN_PID=%d", getpid()) < 0))
176 return log_oom();
177
178 if (arg_fdname) {
179 char *e;
180
181 e = strappend("LISTEN_FDNAMES=", arg_fdname);
182 if (!e)
183 return log_oom();
184
185 for (i = 1; i < (unsigned) fds; i++) {
186 char *c;
187
188 c = strjoin(e, ":", arg_fdname, NULL);
189 if (!c) {
190 free(e);
191 return log_oom();
192 }
193
194 free(e);
195 e = c;
196 }
197
198 envp[n_env++] = e;
199 }
200
201 tmp = strv_join(argv, " ");
202 if (!tmp)
203 return log_oom();
204
205 log_info("Execing %s (%s)", name, tmp);
206 execvpe(name, argv, envp);
207
208 return log_error_errno(errno, "Failed to execp %s (%s): %m", name, tmp);
209 }
210
211 static int launch1(const char* child, char** argv, char **env, int fd) {
212 _cleanup_free_ char *tmp = NULL;
213 pid_t parent_pid, child_pid;
214 int r;
215
216 tmp = strv_join(argv, " ");
217 if (!tmp)
218 return log_oom();
219
220 parent_pid = getpid();
221
222 child_pid = fork();
223 if (child_pid < 0)
224 return log_error_errno(errno, "Failed to fork: %m");
225
226 /* In the child */
227 if (child_pid == 0) {
228
229 (void) reset_all_signal_handlers();
230 (void) reset_signal_mask();
231
232 r = dup2(fd, STDIN_FILENO);
233 if (r < 0) {
234 log_error_errno(errno, "Failed to dup connection to stdin: %m");
235 _exit(EXIT_FAILURE);
236 }
237
238 r = dup2(fd, STDOUT_FILENO);
239 if (r < 0) {
240 log_error_errno(errno, "Failed to dup connection to stdout: %m");
241 _exit(EXIT_FAILURE);
242 }
243
244 r = close(fd);
245 if (r < 0) {
246 log_error_errno(errno, "Failed to close dupped connection: %m");
247 _exit(EXIT_FAILURE);
248 }
249
250 /* Make sure the child goes away when the parent dies */
251 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
252 _exit(EXIT_FAILURE);
253
254 /* Check whether our parent died before we were able
255 * to set the death signal */
256 if (getppid() != parent_pid)
257 _exit(EXIT_SUCCESS);
258
259 execvp(child, argv);
260 log_error_errno(errno, "Failed to exec child %s: %m", child);
261 _exit(EXIT_FAILURE);
262 }
263
264 log_info("Spawned %s (%s) as PID %d", child, tmp, child_pid);
265
266 return 0;
267 }
268
269 static int do_accept(const char* name, char **argv, char **envp, int fd) {
270 _cleanup_free_ char *local = NULL, *peer = NULL;
271 _cleanup_close_ int fd2 = -1;
272
273 fd2 = accept(fd, NULL, NULL);
274 if (fd2 < 0) {
275 log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
276 return fd2;
277 }
278
279 getsockname_pretty(fd2, &local);
280 getpeername_pretty(fd2, true, &peer);
281 log_info("Connection from %s to %s", strna(peer), strna(local));
282
283 return launch1(name, argv, envp, fd2);
284 }
285
286 /* SIGCHLD handler. */
287 static void sigchld_hdl(int sig, siginfo_t *t, void *data) {
288 PROTECT_ERRNO;
289
290 log_info("Child %d died with code %d", t->si_pid, t->si_status);
291 /* Wait for a dead child. */
292 waitpid(t->si_pid, NULL, 0);
293 }
294
295 static int install_chld_handler(void) {
296 int r;
297 struct sigaction act = {
298 .sa_flags = SA_SIGINFO,
299 .sa_sigaction = sigchld_hdl,
300 };
301
302 r = sigaction(SIGCHLD, &act, 0);
303 if (r < 0)
304 log_error_errno(errno, "Failed to install SIGCHLD handler: %m");
305 return r;
306 }
307
308 static void help(void) {
309 printf("%s [OPTIONS...]\n\n"
310 "Listen on sockets and launch child on connection.\n\n"
311 "Options:\n"
312 " -d --datagram Datagram sockets\n"
313 " -l --listen=ADDR Listen for raw connections at ADDR\n"
314 " -a --accept Spawn separate child for each connection\n"
315 " -h --help Show this help and exit\n"
316 " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n"
317 " --version Print version string and exit\n"
318 "\n"
319 "Note: file descriptors from sd_listen_fds() will be passed through.\n"
320 , program_invocation_short_name);
321 }
322
323 static int parse_argv(int argc, char *argv[]) {
324 enum {
325 ARG_VERSION = 0x100,
326 ARG_FDNAME,
327 };
328
329 static const struct option options[] = {
330 { "help", no_argument, NULL, 'h' },
331 { "version", no_argument, NULL, ARG_VERSION },
332 { "datagram", no_argument, NULL, 'd' },
333 { "listen", required_argument, NULL, 'l' },
334 { "accept", no_argument, NULL, 'a' },
335 { "setenv", required_argument, NULL, 'E' },
336 { "environment", required_argument, NULL, 'E' }, /* legacy alias */
337 { "fdname", required_argument, NULL, ARG_FDNAME },
338 {}
339 };
340
341 int c, r;
342
343 assert(argc >= 0);
344 assert(argv);
345
346 while ((c = getopt_long(argc, argv, "+hl:aEd", options, NULL)) >= 0)
347 switch(c) {
348 case 'h':
349 help();
350 return 0;
351
352 case ARG_VERSION:
353 return version();
354
355 case 'l':
356 r = strv_extend(&arg_listen, optarg);
357 if (r < 0)
358 return log_oom();
359
360 break;
361
362 case 'd':
363 arg_datagram = true;
364 break;
365
366 case 'a':
367 arg_accept = true;
368 break;
369
370 case 'E':
371 r = strv_extend(&arg_setenv, optarg);
372 if (r < 0)
373 return log_oom();
374
375 break;
376
377 case ARG_FDNAME:
378 if (!fdname_is_valid(optarg)) {
379 log_error("File descriptor name %s is not valid, refusing.", optarg);
380 return -EINVAL;
381 }
382
383 arg_fdname = optarg;
384 break;
385
386 case '?':
387 return -EINVAL;
388
389 default:
390 assert_not_reached("Unhandled option");
391 }
392
393 if (optind == argc) {
394 log_error("%s: command to execute is missing.",
395 program_invocation_short_name);
396 return -EINVAL;
397 }
398
399 if (arg_datagram && arg_accept) {
400 log_error("Datagram sockets do not accept connections. "
401 "The --datagram and --accept options may not be combined.");
402 return -EINVAL;
403 }
404
405 arg_args = argv + optind;
406
407 return 1 /* work to do */;
408 }
409
410 int main(int argc, char **argv, char **envp) {
411 int r, n;
412 int epoll_fd = -1;
413
414 log_parse_environment();
415 log_open();
416
417 r = parse_argv(argc, argv);
418 if (r <= 0)
419 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
420
421 r = install_chld_handler();
422 if (r < 0)
423 return EXIT_FAILURE;
424
425 n = open_sockets(&epoll_fd, arg_accept);
426 if (n < 0)
427 return EXIT_FAILURE;
428 if (n == 0) {
429 log_error("No sockets to listen on specified or passed in.");
430 return EXIT_FAILURE;
431 }
432
433 for (;;) {
434 struct epoll_event event;
435
436 r = epoll_wait(epoll_fd, &event, 1, -1);
437 if (r < 0) {
438 if (errno == EINTR)
439 continue;
440
441 log_error_errno(errno, "epoll_wait() failed: %m");
442 return EXIT_FAILURE;
443 }
444
445 log_info("Communication attempt on fd %i.", event.data.fd);
446 if (arg_accept) {
447 r = do_accept(argv[optind], argv + optind, envp,
448 event.data.fd);
449 if (r < 0)
450 return EXIT_FAILURE;
451 } else
452 break;
453 }
454
455 launch(argv[optind], argv + optind, envp, n);
456
457 return EXIT_SUCCESS;
458 }