]>
Commit | Line | Data |
---|---|---|
2ca0435b ZJS |
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; | |
5e65c93a | 41 | static char** arg_environ = NULL; |
2ca0435b ZJS |
42 | |
43 | static int add_epoll(int epoll_fd, int fd) { | |
30374ebe LP |
44 | struct epoll_event ev = { |
45 | .events = EPOLLIN | |
46 | }; | |
2ca0435b | 47 | int r; |
2ca0435b ZJS |
48 | |
49 | assert(epoll_fd >= 0); | |
50 | assert(fd >= 0); | |
51 | ||
30374ebe | 52 | ev.data.fd = fd; |
2ca0435b ZJS |
53 | r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); |
54 | if (r < 0) | |
30374ebe LP |
55 | log_error("Failed to add event on epoll fd:%d for fd:%d: %m", |
56 | epoll_fd, fd); | |
57 | return -errno; | |
2ca0435b ZJS |
58 | } |
59 | ||
175a3d25 LP |
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 | ||
175a3d25 LP |
77 | return fd; |
78 | } | |
79 | ||
2ca0435b | 80 | static int open_sockets(int *epoll_fd, bool accept) { |
30374ebe | 81 | char **address; |
29a5ca9b | 82 | int n, fd, r; |
2ca0435b | 83 | int count = 0; |
2ca0435b ZJS |
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 | } | |
30374ebe LP |
91 | if (n > 0) { |
92 | log_info("Received %i descriptors via the environment.", n); | |
2ca0435b | 93 | |
30374ebe LP |
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; | |
2ca0435b | 98 | |
30374ebe LP |
99 | count ++; |
100 | } | |
2ca0435b ZJS |
101 | } |
102 | ||
fff40a51 ZJS |
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 | ||
2ca0435b | 108 | STRV_FOREACH(address, arg_listen) { |
2ca0435b ZJS |
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 | ||
175a3d25 | 116 | assert(fd == SD_LISTEN_FDS_START + count); |
2ca0435b ZJS |
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++) { | |
30374ebe LP |
127 | _cleanup_free_ char *name = NULL; |
128 | ||
129 | getsockname_pretty(fd, &name); | |
130 | log_info("Listening on %s.", strna(name)); | |
131 | ||
29a5ca9b | 132 | r = add_epoll(*epoll_fd, fd); |
2ca0435b ZJS |
133 | if (r < 0) |
134 | return r; | |
135 | } | |
136 | ||
137 | return count; | |
138 | } | |
139 | ||
23ea3dab | 140 | static int launch(char* name, char **argv, char **env, int fds) { |
30374ebe | 141 | |
2ca0435b | 142 | static const char* tocopy[] = {"TERM=", "PATH=", "USER=", "HOME="}; |
30374ebe | 143 | _cleanup_strv_free_ char **envp = NULL; |
7fd1b19b | 144 | _cleanup_free_ char *tmp = NULL; |
30374ebe LP |
145 | unsigned n_env = 0, length; |
146 | char **s; | |
2ca0435b ZJS |
147 | unsigned i; |
148 | ||
5e65c93a | 149 | length = strv_length(arg_environ); |
30374ebe | 150 | |
5e65c93a | 151 | /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, NULL */ |
5b84559a | 152 | envp = new0(char *, length + 7); |
30374ebe LP |
153 | if (!envp) |
154 | return log_oom(); | |
5e65c93a ZJS |
155 | |
156 | STRV_FOREACH(s, arg_environ) { | |
157 | if (strchr(*s, '=')) | |
158 | envp[n_env++] = *s; | |
159 | else { | |
7fd1b19b | 160 | _cleanup_free_ char *p = strappend(*s, "="); |
5e65c93a ZJS |
161 | if (!p) |
162 | return log_oom(); | |
23ea3dab | 163 | envp[n_env] = strv_find_prefix(env, p); |
5e65c93a ZJS |
164 | if (envp[n_env]) |
165 | n_env ++; | |
166 | } | |
167 | } | |
168 | ||
2ca0435b | 169 | for (i = 0; i < ELEMENTSOF(tocopy); i++) { |
23ea3dab | 170 | envp[n_env] = strv_find_prefix(env, tocopy[i]); |
2ca0435b ZJS |
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); | |
30374ebe | 186 | |
2ca0435b ZJS |
187 | return -errno; |
188 | } | |
189 | ||
23ea3dab | 190 | static int launch1(const char* child, char** argv, char **env, int fd) { |
30374ebe | 191 | _cleanup_free_ char *tmp = NULL; |
2ca0435b ZJS |
192 | pid_t parent_pid, child_pid; |
193 | int r; | |
194 | ||
2ca0435b ZJS |
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) { | |
30374ebe LP |
247 | _cleanup_free_ char *local = NULL, *peer = NULL; |
248 | int fd2; | |
2ca0435b | 249 | |
30374ebe | 250 | fd2 = accept(fd, NULL, NULL); |
2ca0435b ZJS |
251 | if (fd2 < 0) { |
252 | log_error("Failed to accept connection on fd:%d: %m", fd); | |
253 | return fd2; | |
254 | } | |
255 | ||
30374ebe LP |
256 | getsockname_pretty(fd2, &local); |
257 | getpeername_pretty(fd2, &peer); | |
258 | log_info("Connection from %s to %s", strna(peer), strna(local)); | |
2ca0435b | 259 | |
30374ebe | 260 | return launch1(name, argv, envp, fd2); |
2ca0435b ZJS |
261 | } |
262 | ||
263 | /* SIGCHLD handler. */ | |
e9c1ea9d | 264 | static void sigchld_hdl(int sig, siginfo_t *t, void *data) { |
2ca0435b | 265 | log_info("Child %d died with code %d", t->si_pid, t->si_status); |
e9c1ea9d JSJ |
266 | /* Wait for a dead child. */ |
267 | waitpid(t->si_pid, NULL, 0); | |
2ca0435b ZJS |
268 | } |
269 | ||
270 | static int install_chld_handler(void) { | |
271 | int r; | |
e9c1ea9d | 272 | struct sigaction act; |
2ca0435b ZJS |
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' }, | |
5e65c93a | 309 | { "environment", required_argument, NULL, 'E' }, |
eb9da376 | 310 | {} |
2ca0435b ZJS |
311 | }; |
312 | ||
313 | int c; | |
314 | ||
315 | assert(argc >= 0); | |
316 | assert(argv); | |
317 | ||
5e65c93a | 318 | while ((c = getopt_long(argc, argv, "+hl:saE:", options, NULL)) >= 0) |
2ca0435b ZJS |
319 | switch(c) { |
320 | case 'h': | |
eb9da376 | 321 | return help(); |
2ca0435b ZJS |
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 | ||
5e65c93a ZJS |
340 | case 'E': { |
341 | int r = strv_extend(&arg_environ, optarg); | |
342 | if (r < 0) | |
343 | return r; | |
344 | ||
345 | break; | |
346 | } | |
347 | ||
2ca0435b ZJS |
348 | case '?': |
349 | return -EINVAL; | |
350 | ||
351 | default: | |
eb9da376 | 352 | assert_not_reached("Unhandled option"); |
2ca0435b ZJS |
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 | ||
2ca0435b | 370 | log_parse_environment(); |
eceb8483 | 371 | log_open(); |
2ca0435b ZJS |
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 | ||
eceb8483 | 385 | for (;;) { |
2ca0435b ZJS |
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 | } |