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