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