chiark / gitweb /
a6e72485749783c1a95ec9ec5692b92dad1a63bf
[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         STRV_FOREACH(address, arg_listen) {
141                 log_info("Opening address %s", *address);
142
143                 fd = make_socket_fd(*address, SOCK_STREAM | (arg_accept*SOCK_CLOEXEC));
144                 if (fd < 0) {
145                         log_error("Failed to open '%s': %s", *address, strerror(-fd));
146                         return fd;
147                 }
148
149                 count ++;
150         }
151
152         *epoll_fd = epoll_create1(EPOLL_CLOEXEC);
153         if (*epoll_fd < 0) {
154                 log_error("Failed to create epoll object: %m");
155                 return -errno;
156         }
157
158         for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) {
159                 int r = add_epoll(*epoll_fd, fd);
160                 if (r < 0)
161                         return r;
162         }
163
164         return count;
165 }
166
167 static int launch(char* name, char **argv, char **env, int fds) {
168         unsigned n_env = 0, length;
169         char **envp = NULL, **s;
170         static const char* tocopy[] = {"TERM=", "PATH=", "USER=", "HOME="};
171         char _cleanup_free_ *tmp = NULL;
172         unsigned i;
173
174         length = strv_length(arg_environ);
175         /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, NULL */
176         envp = new(char *, length + 7);
177
178         STRV_FOREACH(s, arg_environ) {
179                 if (strchr(*s, '='))
180                         envp[n_env++] = *s;
181                 else {
182                         char _cleanup_free_ *p = strappend(*s, "=");
183                         if (!p)
184                                 return log_oom();
185                         envp[n_env] = strv_find_prefix(env, p);
186                         if (envp[n_env])
187                                 n_env ++;
188                 }
189         }
190
191         for (i = 0; i < ELEMENTSOF(tocopy); i++) {
192                 envp[n_env] = strv_find_prefix(env, tocopy[i]);
193                 if (envp[n_env])
194                         n_env ++;
195         }
196
197         if ((asprintf((char**)(envp + n_env++), "LISTEN_FDS=%d", fds) < 0) ||
198             (asprintf((char**)(envp + n_env++), "LISTEN_PID=%d", getpid()) < 0))
199                 return log_oom();
200
201         tmp = strv_join(argv, " ");
202         if (!tmp)
203                 return log_oom();
204
205         log_info("Execing %s (%s)", name, tmp);
206         execvpe(name, argv, envp);
207         log_error("Failed to execp %s (%s): %m", name, tmp);
208         return -errno;
209 }
210
211 static int launch1(const char* child, char** argv, char **env, int fd) {
212         pid_t parent_pid, child_pid;
213         int r;
214
215         char _cleanup_free_ *tmp = NULL;
216         tmp = strv_join(argv, " ");
217         if (!tmp)
218                 return log_oom();
219
220         parent_pid = getpid();
221
222         child_pid = fork();
223         if (child_pid < 0) {
224                 log_error("Failed to fork: %m");
225                 return -errno;
226         }
227
228         /* In the child */
229         if (child_pid == 0) {
230                 r = dup2(fd, STDIN_FILENO);
231                 if (r < 0) {
232                         log_error("Failed to dup connection to stdin: %m");
233                         _exit(EXIT_FAILURE);
234                 }
235
236                 r = dup2(fd, STDOUT_FILENO);
237                 if (r < 0) {
238                         log_error("Failed to dup connection to stdout: %m");
239                         _exit(EXIT_FAILURE);
240                 }
241
242                 r = close(fd);
243                 if (r < 0) {
244                         log_error("Failed to close dupped connection: %m");
245                         _exit(EXIT_FAILURE);
246                 }
247
248                 /* Make sure the child goes away when the parent dies */
249                 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
250                         _exit(EXIT_FAILURE);
251
252                 /* Check whether our parent died before we were able
253                  * to set the death signal */
254                 if (getppid() != parent_pid)
255                         _exit(EXIT_SUCCESS);
256
257                 execvp(child, argv);
258                 log_error("Failed to exec child %s: %m", child);
259                 _exit(EXIT_FAILURE);
260         }
261
262         log_info("Spawned %s (%s) as PID %d", child, tmp, child_pid);
263
264         return 0;
265 }
266
267 static int do_accept(const char* name, char **argv, char **envp, int fd) {
268         SocketAddress addr = {
269                 .size = sizeof(union sockaddr_union),
270                 .type = SOCK_STREAM,
271         };
272         int fd2, r;
273
274         fd2 = accept(fd, &addr.sockaddr.sa, &addr.size);
275         if (fd2 < 0) {
276                 log_error("Failed to accept connection on fd:%d: %m", fd);
277                 return fd2;
278         }
279
280         print_socket("Connection from", fd2);
281
282         r = launch1(name, argv, envp, fd2);
283         return r;
284 }
285
286 /* SIGCHLD handler. */
287 static void sigchld_hdl(int sig, siginfo_t *t, void *data)
288 {
289         log_info("Child %d died with code %d", t->si_pid, t->si_status);
290         /* Wait for a dead child. */
291         waitpid(t->si_pid, NULL, 0);
292 }
293
294 static int install_chld_handler(void) {
295         int r;
296         struct sigaction act;
297         zero(act);
298         act.sa_flags = SA_SIGINFO;
299         act.sa_sigaction = sigchld_hdl;
300
301         r = sigaction(SIGCHLD, &act, 0);
302         if (r < 0)
303                 log_error("Failed to install SIGCHLD handler: %m");
304         return r;
305 }
306
307 static int help(void) {
308         printf("%s [OPTIONS...]\n\n"
309                "Listen on sockets and launch child on connection.\n\n"
310                "Options:\n"
311                "  -l --listen=ADDR     Listen for raw connections at ADDR\n"
312                "  -a --accept          Spawn separate child for each connection\n"
313                "  -h --help            Show this help and exit\n"
314                "  --version            Print version string and exit\n"
315                "\n"
316                "Note: file descriptors from sd_listen_fds() will be passed through.\n"
317                , program_invocation_short_name
318                );
319
320         return 0;
321 }
322
323 static int parse_argv(int argc, char *argv[]) {
324         enum {
325                 ARG_VERSION = 0x100,
326         };
327
328         static const struct option options[] = {
329                 { "help",         no_argument,       NULL, 'h'           },
330                 { "version",      no_argument,       NULL, ARG_VERSION   },
331                 { "listen",       required_argument, NULL, 'l'           },
332                 { "accept",       no_argument,       NULL, 'a'           },
333                 { "environment",  required_argument, NULL, 'E'           },
334                 { NULL,           0,                 NULL, 0             }
335         };
336
337         int c;
338
339         assert(argc >= 0);
340         assert(argv);
341
342         while ((c = getopt_long(argc, argv, "+hl:saE:", options, NULL)) >= 0)
343                 switch(c) {
344                 case 'h':
345                         help();
346                         return 0 /* done */;
347
348                 case ARG_VERSION:
349                         puts(PACKAGE_STRING);
350                         puts(SYSTEMD_FEATURES);
351                         return 0 /* done */;
352
353                 case 'l': {
354                         int r = strv_extend(&arg_listen, optarg);
355                         if (r < 0)
356                                 return r;
357
358                         break;
359                 }
360
361                 case 'a':
362                         arg_accept = true;
363                         break;
364
365                 case 'E': {
366                         int r = strv_extend(&arg_environ, optarg);
367                         if (r < 0)
368                                 return r;
369
370                         break;
371                 }
372
373                 case '?':
374                         return -EINVAL;
375
376                 default:
377                         log_error("Unknown option code %c", c);
378                         return -EINVAL;
379                 }
380
381         if (optind == argc) {
382                 log_error("Usage: %s [OPTION...] PROGRAM [OPTION...]",
383                           program_invocation_short_name);
384                 return -EINVAL;
385         }
386
387         arg_args = argv + optind;
388
389         return 1 /* work to do */;
390 }
391
392 int main(int argc, char **argv, char **envp) {
393         int r, n;
394         int epoll_fd = -1;
395
396         log_set_max_level(LOG_DEBUG);
397         log_show_color(true);
398         log_parse_environment();
399
400         r = parse_argv(argc, argv);
401         if (r <= 0)
402                 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
403
404         r = install_chld_handler();
405         if (r < 0)
406                 return EXIT_FAILURE;
407
408         n = open_sockets(&epoll_fd, arg_accept);
409         if (n < 0)
410                 return EXIT_FAILURE;
411
412         while (true) {
413                 struct epoll_event event;
414
415                 r = epoll_wait(epoll_fd, &event, 1, -1);
416                 if (r < 0) {
417                         if (errno == EINTR)
418                                 continue;
419
420                         log_error("epoll_wait() failed: %m");
421                         return EXIT_FAILURE;
422                 }
423
424                 log_info("Communication attempt on fd:%d", event.data.fd);
425                 if (arg_accept) {
426                         r = do_accept(argv[optind], argv + optind, envp,
427                                       event.data.fd);
428                         if (r < 0)
429                                 return EXIT_FAILURE;
430                 } else
431                         break;
432         }
433
434         launch(argv[optind], argv + optind, envp, n);
435
436         return EXIT_SUCCESS;
437 }