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