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