chiark / gitweb /
activate: export make_socket_fd
[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 int 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         return 0;
297 }
298
299 static int parse_argv(int argc, char *argv[]) {
300         enum {
301                 ARG_VERSION = 0x100,
302         };
303
304         static const struct option options[] = {
305                 { "help",        no_argument,       NULL, 'h'           },
306                 { "version",     no_argument,       NULL, ARG_VERSION   },
307                 { "listen",      required_argument, NULL, 'l'           },
308                 { "accept",      no_argument,       NULL, 'a'           },
309                 { "setenv",      required_argument, NULL, 'E'           },
310                 { "environment", required_argument, NULL, 'E'           }, /* alias */
311                 {}
312         };
313
314         int c;
315
316         assert(argc >= 0);
317         assert(argv);
318
319         while ((c = getopt_long(argc, argv, "+hl:aE:", options, NULL)) >= 0)
320                 switch(c) {
321                 case 'h':
322                         return help();
323
324                 case ARG_VERSION:
325                         puts(PACKAGE_STRING);
326                         puts(SYSTEMD_FEATURES);
327                         return 0 /* done */;
328
329                 case 'l': {
330                         int r = strv_extend(&arg_listen, optarg);
331                         if (r < 0)
332                                 return r;
333
334                         break;
335                 }
336
337                 case 'a':
338                         arg_accept = true;
339                         break;
340
341                 case 'E': {
342                         int r = strv_extend(&arg_setenv, optarg);
343                         if (r < 0)
344                                 return r;
345
346                         break;
347                 }
348
349                 case '?':
350                         return -EINVAL;
351
352                 default:
353                         assert_not_reached("Unhandled option");
354                 }
355
356         if (optind == argc) {
357                 log_error("Usage: %s [OPTION...] PROGRAM [OPTION...]",
358                           program_invocation_short_name);
359                 return -EINVAL;
360         }
361
362         arg_args = argv + optind;
363
364         return 1 /* work to do */;
365 }
366
367 int main(int argc, char **argv, char **envp) {
368         int r, n;
369         int epoll_fd = -1;
370
371         log_parse_environment();
372         log_open();
373
374         r = parse_argv(argc, argv);
375         if (r <= 0)
376                 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
377
378         r = install_chld_handler();
379         if (r < 0)
380                 return EXIT_FAILURE;
381
382         n = open_sockets(&epoll_fd, arg_accept);
383         if (n < 0)
384                 return EXIT_FAILURE;
385         if (n == 0) {
386                 log_error("No sockets to listen on specified or passed in.");
387                 return EXIT_FAILURE;
388         }
389
390         for (;;) {
391                 struct epoll_event event;
392
393                 r = epoll_wait(epoll_fd, &event, 1, -1);
394                 if (r < 0) {
395                         if (errno == EINTR)
396                                 continue;
397
398                         log_error("epoll_wait() failed: %m");
399                         return EXIT_FAILURE;
400                 }
401
402                 log_info("Communication attempt on fd %i.", event.data.fd);
403                 if (arg_accept) {
404                         r = do_accept(argv[optind], argv + optind, envp,
405                                       event.data.fd);
406                         if (r < 0)
407                                 return EXIT_FAILURE;
408                 } else
409                         break;
410         }
411
412         launch(argv[optind], argv + optind, envp, n);
413
414         return EXIT_SUCCESS;
415 }