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