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