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