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