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