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