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