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