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