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