chiark / gitweb /
activate: clean up inherited descriptors
[elogind.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 }