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