]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/socket-proxy/socket-proxyd.c
util: split out escaping code into escape.[ch]
[thirdparty/systemd.git] / src / socket-proxy / socket-proxyd.c
CommitLineData
912b54ad
DS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2013 David Strauss
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
912b54ad 22#include <errno.h>
3f6fd1ba 23#include <fcntl.h>
912b54ad 24#include <getopt.h>
3f6fd1ba 25#include <netdb.h>
912b54ad
DS
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
912b54ad
DS
29#include <sys/socket.h>
30#include <sys/un.h>
31#include <unistd.h>
32
912b54ad
DS
33#include "sd-daemon.h"
34#include "sd-event.h"
fb69d709 35#include "sd-resolve.h"
3f6fd1ba 36
96c374d0 37#include "log.h"
3f6fd1ba
LP
38#include "path-util.h"
39#include "set.h"
912b54ad
DS
40#include "socket-util.h"
41#include "util.h"
8569a776
LP
42
43#define BUFFER_SIZE (256 * 1024)
44#define CONNECTIONS_MAX 256
912b54ad 45
fb69d709
LP
46static const char *arg_remote_host = NULL;
47
8569a776 48typedef struct Context {
fb69d709
LP
49 sd_event *event;
50 sd_resolve *resolve;
51
8569a776
LP
52 Set *listen;
53 Set *connections;
54} Context;
912b54ad 55
8569a776 56typedef struct Connection {
e633ea1c
LP
57 Context *context;
58
8569a776
LP
59 int server_fd, client_fd;
60 int server_to_client_buffer[2]; /* a pipe */
61 int client_to_server_buffer[2]; /* a pipe */
912b54ad 62
8569a776
LP
63 size_t server_to_client_buffer_full, client_to_server_buffer_full;
64 size_t server_to_client_buffer_size, client_to_server_buffer_size;
65
66 sd_event_source *server_event_source, *client_event_source;
912b54ad 67
fb69d709
LP
68 sd_resolve_query *resolve_query;
69} Connection;
912b54ad 70
8569a776
LP
71static void connection_free(Connection *c) {
72 assert(c);
32d3c809 73
e633ea1c
LP
74 if (c->context)
75 set_remove(c->context->connections, c);
76
8569a776
LP
77 sd_event_source_unref(c->server_event_source);
78 sd_event_source_unref(c->client_event_source);
32d3c809 79
03e334a1
LP
80 safe_close(c->server_fd);
81 safe_close(c->client_fd);
32d3c809 82
3d94f76c
LP
83 safe_close_pair(c->server_to_client_buffer);
84 safe_close_pair(c->client_to_server_buffer);
32d3c809 85
fb69d709
LP
86 sd_resolve_query_unref(c->resolve_query);
87
8569a776
LP
88 free(c);
89}
32d3c809 90
8569a776
LP
91static void context_free(Context *context) {
92 sd_event_source *es;
93 Connection *c;
94
95 assert(context);
96
97 while ((es = set_steal_first(context->listen)))
98 sd_event_source_unref(es);
99
e633ea1c 100 while ((c = set_first(context->connections)))
8569a776
LP
101 connection_free(c);
102
103 set_free(context->listen);
104 set_free(context->connections);
32d3c809 105
fb69d709
LP
106 sd_event_unref(context->event);
107 sd_resolve_unref(context->resolve);
8569a776 108}
912b54ad 109
8569a776
LP
110static int connection_create_pipes(Connection *c, int buffer[2], size_t *sz) {
111 int r;
912b54ad 112
8569a776
LP
113 assert(c);
114 assert(buffer);
115 assert(sz);
912b54ad 116
8569a776
LP
117 if (buffer[0] >= 0)
118 return 0;
912b54ad 119
8569a776 120 r = pipe2(buffer, O_CLOEXEC|O_NONBLOCK);
4a62c710
MS
121 if (r < 0)
122 return log_error_errno(errno, "Failed to allocate pipe buffer: %m");
912b54ad 123
25dbe4f5 124 (void) fcntl(buffer[0], F_SETPIPE_SZ, BUFFER_SIZE);
8569a776
LP
125
126 r = fcntl(buffer[0], F_GETPIPE_SZ);
4a62c710
MS
127 if (r < 0)
128 return log_error_errno(errno, "Failed to get pipe buffer size: %m");
912b54ad 129
8569a776
LP
130 assert(r > 0);
131 *sz = r;
132
912b54ad
DS
133 return 0;
134}
135
8569a776
LP
136static int connection_shovel(
137 Connection *c,
138 int *from, int buffer[2], int *to,
139 size_t *full, size_t *sz,
140 sd_event_source **from_source, sd_event_source **to_source) {
141
142 bool shoveled;
143
144 assert(c);
145 assert(from);
146 assert(buffer);
147 assert(buffer[0] >= 0);
148 assert(buffer[1] >= 0);
149 assert(to);
150 assert(full);
151 assert(sz);
152 assert(from_source);
153 assert(to_source);
154
155 do {
156 ssize_t z;
157
158 shoveled = false;
159
160 if (*full < *sz && *from >= 0 && *to >= 0) {
161 z = splice(*from, NULL, buffer[1], NULL, *sz - *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
162 if (z > 0) {
163 *full += z;
164 shoveled = true;
165 } else if (z == 0 || errno == EPIPE || errno == ECONNRESET) {
166 *from_source = sd_event_source_unref(*from_source);
03e334a1 167 *from = safe_close(*from);
4a62c710
MS
168 } else if (errno != EAGAIN && errno != EINTR)
169 return log_error_errno(errno, "Failed to splice: %m");
912b54ad
DS
170 }
171
8569a776
LP
172 if (*full > 0 && *to >= 0) {
173 z = splice(buffer[0], NULL, *to, NULL, *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
174 if (z > 0) {
175 *full -= z;
176 shoveled = true;
177 } else if (z == 0 || errno == EPIPE || errno == ECONNRESET) {
178 *to_source = sd_event_source_unref(*to_source);
03e334a1 179 *to = safe_close(*to);
4a62c710
MS
180 } else if (errno != EAGAIN && errno != EINTR)
181 return log_error_errno(errno, "Failed to splice: %m");
8569a776
LP
182 }
183 } while (shoveled);
912b54ad 184
8569a776 185 return 0;
912b54ad
DS
186}
187
fb69d709 188static int connection_enable_event_sources(Connection *c);
8569a776
LP
189
190static int traffic_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
191 Connection *c = userdata;
912b54ad
DS
192 int r;
193
8569a776
LP
194 assert(s);
195 assert(fd >= 0);
196 assert(c);
912b54ad 197
8569a776
LP
198 r = connection_shovel(c,
199 &c->server_fd, c->server_to_client_buffer, &c->client_fd,
200 &c->server_to_client_buffer_full, &c->server_to_client_buffer_size,
201 &c->server_event_source, &c->client_event_source);
202 if (r < 0)
203 goto quit;
912b54ad 204
8569a776
LP
205 r = connection_shovel(c,
206 &c->client_fd, c->client_to_server_buffer, &c->server_fd,
207 &c->client_to_server_buffer_full, &c->client_to_server_buffer_size,
208 &c->client_event_source, &c->server_event_source);
209 if (r < 0)
210 goto quit;
912b54ad 211
8569a776
LP
212 /* EOF on both sides? */
213 if (c->server_fd == -1 && c->client_fd == -1)
214 goto quit;
912b54ad 215
8569a776
LP
216 /* Server closed, and all data written to client? */
217 if (c->server_fd == -1 && c->server_to_client_buffer_full <= 0)
218 goto quit;
912b54ad 219
8569a776
LP
220 /* Client closed, and all data written to server? */
221 if (c->client_fd == -1 && c->client_to_server_buffer_full <= 0)
222 goto quit;
912b54ad 223
fb69d709 224 r = connection_enable_event_sources(c);
8569a776
LP
225 if (r < 0)
226 goto quit;
912b54ad 227
8569a776 228 return 1;
912b54ad 229
8569a776
LP
230quit:
231 connection_free(c);
232 return 0; /* ignore errors, continue serving */
912b54ad
DS
233}
234
fb69d709 235static int connection_enable_event_sources(Connection *c) {
8569a776
LP
236 uint32_t a = 0, b = 0;
237 int r;
912b54ad 238
8569a776 239 assert(c);
912b54ad 240
8569a776
LP
241 if (c->server_to_client_buffer_full > 0)
242 b |= EPOLLOUT;
243 if (c->server_to_client_buffer_full < c->server_to_client_buffer_size)
244 a |= EPOLLIN;
912b54ad 245
8569a776
LP
246 if (c->client_to_server_buffer_full > 0)
247 a |= EPOLLOUT;
248 if (c->client_to_server_buffer_full < c->client_to_server_buffer_size)
249 b |= EPOLLIN;
912b54ad 250
8569a776
LP
251 if (c->server_event_source)
252 r = sd_event_source_set_io_events(c->server_event_source, a);
253 else if (c->server_fd >= 0)
fb69d709 254 r = sd_event_add_io(c->context->event, &c->server_event_source, c->server_fd, a, traffic_cb, c);
8569a776
LP
255 else
256 r = 0;
257
f647962d
MS
258 if (r < 0)
259 return log_error_errno(r, "Failed to set up server event source: %m");
912b54ad 260
8569a776
LP
261 if (c->client_event_source)
262 r = sd_event_source_set_io_events(c->client_event_source, b);
263 else if (c->client_fd >= 0)
fb69d709 264 r = sd_event_add_io(c->context->event, &c->client_event_source, c->client_fd, b, traffic_cb, c);
8569a776
LP
265 else
266 r = 0;
912b54ad 267
f647962d
MS
268 if (r < 0)
269 return log_error_errno(r, "Failed to set up client event source: %m");
912b54ad 270
8569a776 271 return 0;
912b54ad
DS
272}
273
fb69d709
LP
274static int connection_complete(Connection *c) {
275 int r;
276
277 assert(c);
278
279 r = connection_create_pipes(c, c->server_to_client_buffer, &c->server_to_client_buffer_size);
280 if (r < 0)
281 goto fail;
282
283 r = connection_create_pipes(c, c->client_to_server_buffer, &c->client_to_server_buffer_size);
284 if (r < 0)
285 goto fail;
286
287 r = connection_enable_event_sources(c);
288 if (r < 0)
289 goto fail;
290
291 return 0;
292
293fail:
294 connection_free(c);
295 return 0; /* ignore errors, continue serving */
296}
297
8569a776
LP
298static int connect_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
299 Connection *c = userdata;
300 socklen_t solen;
301 int error, r;
302
303 assert(s);
304 assert(fd >= 0);
305 assert(c);
306
307 solen = sizeof(error);
308 r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &solen);
309 if (r < 0) {
56f64d95 310 log_error_errno(errno, "Failed to issue SO_ERROR: %m");
40976028
DS
311 goto fail;
312 }
912b54ad 313
8569a776 314 if (error != 0) {
279d3c9c 315 log_error_errno(error, "Failed to connect to remote host: %m");
912b54ad
DS
316 goto fail;
317 }
318
8569a776
LP
319 c->client_event_source = sd_event_source_unref(c->client_event_source);
320
fb69d709 321 return connection_complete(c);
912b54ad 322
fb69d709
LP
323fail:
324 connection_free(c);
325 return 0; /* ignore errors, continue serving */
326}
912b54ad 327
fb69d709
LP
328static int connection_start(Connection *c, struct sockaddr *sa, socklen_t salen) {
329 int r;
330
331 assert(c);
332 assert(sa);
333 assert(salen);
334
335 c->client_fd = socket(sa->sa_family, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
336 if (c->client_fd < 0) {
56f64d95 337 log_error_errno(errno, "Failed to get remote socket: %m");
8569a776 338 goto fail;
fb69d709
LP
339 }
340
341 r = connect(c->client_fd, sa, salen);
342 if (r < 0) {
343 if (errno == EINPROGRESS) {
344 r = sd_event_add_io(c->context->event, &c->client_event_source, c->client_fd, EPOLLOUT, connect_cb, c);
345 if (r < 0) {
da927ba9 346 log_error_errno(r, "Failed to add connection socket: %m");
fb69d709
LP
347 goto fail;
348 }
349
350 r = sd_event_source_set_enabled(c->client_event_source, SD_EVENT_ONESHOT);
351 if (r < 0) {
da927ba9 352 log_error_errno(r, "Failed to enable oneshot event source: %m");
fb69d709
LP
353 goto fail;
354 }
355 } else {
56f64d95 356 log_error_errno(errno, "Failed to connect to remote host: %m");
fb69d709
LP
357 goto fail;
358 }
359 } else {
360 r = connection_complete(c);
361 if (r < 0)
362 goto fail;
363 }
912b54ad 364
8569a776 365 return 0;
912b54ad 366
8569a776
LP
367fail:
368 connection_free(c);
369 return 0; /* ignore errors, continue serving */
370}
371
fb69d709
LP
372static int resolve_cb(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) {
373 Connection *c = userdata;
374
375 assert(q);
376 assert(c);
377
378 if (ret != 0) {
379 log_error("Failed to resolve host: %s", gai_strerror(ret));
380 goto fail;
381 }
382
383 c->resolve_query = sd_resolve_query_unref(c->resolve_query);
384
385 return connection_start(c, ai->ai_addr, ai->ai_addrlen);
386
387fail:
388 connection_free(c);
389 return 0; /* ignore errors, continue serving */
390}
391
392static int resolve_remote(Connection *c) {
393
394 static const struct addrinfo hints = {
395 .ai_family = AF_UNSPEC,
396 .ai_socktype = SOCK_STREAM,
397 .ai_flags = AI_ADDRCONFIG
398 };
399
1ec6af16 400 union sockaddr_union sa = {};
fb69d709 401 const char *node, *service;
8569a776 402 socklen_t salen;
fb69d709
LP
403 int r;
404
405 if (path_is_absolute(arg_remote_host)) {
406 sa.un.sun_family = AF_UNIX;
407 strncpy(sa.un.sun_path, arg_remote_host, sizeof(sa.un.sun_path)-1);
408 sa.un.sun_path[sizeof(sa.un.sun_path)-1] = 0;
409
410 salen = offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path);
411
412 return connection_start(c, &sa.sa, salen);
413 }
414
415 if (arg_remote_host[0] == '@') {
416 sa.un.sun_family = AF_UNIX;
417 sa.un.sun_path[0] = 0;
418 strncpy(sa.un.sun_path+1, arg_remote_host+1, sizeof(sa.un.sun_path)-2);
419 sa.un.sun_path[sizeof(sa.un.sun_path)-1] = 0;
420
421 salen = offsetof(union sockaddr_union, un.sun_path) + 1 + strlen(sa.un.sun_path + 1);
422
423 return connection_start(c, &sa.sa, salen);
424 }
425
426 service = strrchr(arg_remote_host, ':');
427 if (service) {
428 node = strndupa(arg_remote_host, service - arg_remote_host);
429 service ++;
430 } else {
431 node = arg_remote_host;
432 service = "80";
433 }
434
435 log_debug("Looking up address info for %s:%s", node, service);
436 r = sd_resolve_getaddrinfo(c->context->resolve, &c->resolve_query, node, service, &hints, resolve_cb, c);
437 if (r < 0) {
da927ba9 438 log_error_errno(r, "Failed to resolve remote host: %m");
fb69d709
LP
439 goto fail;
440 }
441
442 return 0;
443
444fail:
445 connection_free(c);
446 return 0; /* ignore errors, continue serving */
447}
448
449static int add_connection_socket(Context *context, int fd) {
8569a776
LP
450 Connection *c;
451 int r;
452
453 assert(context);
8569a776
LP
454 assert(fd >= 0);
455
456 if (set_size(context->connections) > CONNECTIONS_MAX) {
457 log_warning("Hit connection limit, refusing connection.");
03e334a1 458 safe_close(fd);
8569a776 459 return 0;
912b54ad
DS
460 }
461
d5099efc 462 r = set_ensure_allocated(&context->connections, NULL);
fb69d709
LP
463 if (r < 0) {
464 log_oom();
465 return 0;
466 }
912b54ad 467
8569a776 468 c = new0(Connection, 1);
fb69d709
LP
469 if (!c) {
470 log_oom();
471 return 0;
472 }
8569a776 473
e633ea1c 474 c->context = context;
8569a776
LP
475 c->server_fd = fd;
476 c->client_fd = -1;
477 c->server_to_client_buffer[0] = c->server_to_client_buffer[1] = -1;
478 c->client_to_server_buffer[0] = c->client_to_server_buffer[1] = -1;
479
e633ea1c
LP
480 r = set_put(context->connections, c);
481 if (r < 0) {
482 free(c);
fb69d709
LP
483 log_oom();
484 return 0;
8569a776 485 }
912b54ad 486
fb69d709 487 return resolve_remote(c);
40976028
DS
488}
489
490static int accept_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
7b77ed8c 491 _cleanup_free_ char *peer = NULL;
8569a776
LP
492 Context *context = userdata;
493 int nfd = -1, r;
40976028 494
8569a776
LP
495 assert(s);
496 assert(fd >= 0);
40976028 497 assert(revents & EPOLLIN);
8569a776
LP
498 assert(context);
499
500 nfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
7b77ed8c
LP
501 if (nfd < 0) {
502 if (errno != -EAGAIN)
56f64d95 503 log_warning_errno(errno, "Failed to accept() socket: %m");
7b77ed8c 504 } else {
8569a776
LP
505 getpeername_pretty(nfd, &peer);
506 log_debug("New connection from %s", strna(peer));
40976028 507
fb69d709 508 r = add_connection_socket(context, nfd);
f4bd42aa 509 if (r < 0) {
da927ba9 510 log_error_errno(r, "Failed to accept connection, ignoring: %m");
03e334a1 511 safe_close(fd);
f4bd42aa 512 }
7b77ed8c 513 }
8569a776 514
40976028
DS
515 r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
516 if (r < 0) {
da927ba9 517 log_error_errno(r, "Error while re-enabling listener with ONESHOT: %m");
fb69d709 518 sd_event_exit(context->event, r);
40976028
DS
519 return r;
520 }
521
96c374d0 522 return 1;
912b54ad
DS
523}
524
fb69d709 525static int add_listen_socket(Context *context, int fd) {
8569a776
LP
526 sd_event_source *source;
527 int r;
528
529 assert(context);
8569a776 530 assert(fd >= 0);
912b54ad 531
d5099efc 532 r = set_ensure_allocated(&context->listen, NULL);
96c374d0 533 if (r < 0) {
8569a776
LP
534 log_oom();
535 return r;
536 }
537
538 r = sd_is_socket(fd, 0, SOCK_STREAM, 1);
f647962d
MS
539 if (r < 0)
540 return log_error_errno(r, "Failed to determine socket type: %m");
8569a776
LP
541 if (r == 0) {
542 log_error("Passed in socket is not a stream socket.");
543 return -EINVAL;
544 }
912b54ad 545
8569a776 546 r = fd_nonblock(fd, true);
f647962d
MS
547 if (r < 0)
548 return log_error_errno(r, "Failed to mark file descriptor non-blocking: %m");
912b54ad 549
fb69d709 550 r = sd_event_add_io(context->event, &source, fd, EPOLLIN, accept_cb, context);
f647962d
MS
551 if (r < 0)
552 return log_error_errno(r, "Failed to add event source: %m");
912b54ad 553
8569a776 554 r = set_put(context->listen, source);
96c374d0 555 if (r < 0) {
da927ba9 556 log_error_errno(r, "Failed to add source to set: %m");
8569a776 557 sd_event_source_unref(source);
40976028
DS
558 return r;
559 }
560
561 /* Set the watcher to oneshot in case other processes are also
562 * watching to accept(). */
8569a776 563 r = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
f647962d
MS
564 if (r < 0)
565 return log_error_errno(r, "Failed to enable oneshot mode: %m");
912b54ad 566
8569a776 567 return 0;
912b54ad
DS
568}
569
601185b4
ZJS
570static void help(void) {
571 printf("%1$s [HOST:PORT]\n"
572 "%1$s [SOCKET]\n\n"
8569a776 573 "Bidirectionally proxy local sockets to another (possibly remote) socket.\n\n"
8cf030b3
LP
574 " -h --help Show this help\n"
575 " --version Show package version\n",
912b54ad 576 program_invocation_short_name);
912b54ad
DS
577}
578
8569a776 579static int parse_argv(int argc, char *argv[]) {
912b54ad
DS
580
581 enum {
8cf030b3
LP
582 ARG_VERSION = 0x100,
583 ARG_IGNORE_ENV
912b54ad
DS
584 };
585
586 static const struct option options[] = {
8cf030b3
LP
587 { "help", no_argument, NULL, 'h' },
588 { "version", no_argument, NULL, ARG_VERSION },
eb9da376 589 {}
912b54ad
DS
590 };
591
8cf030b3 592 int c;
912b54ad
DS
593
594 assert(argc >= 0);
595 assert(argv);
596
601185b4 597 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
912b54ad
DS
598
599 switch (c) {
600
601 case 'h':
601185b4
ZJS
602 help();
603 return 0;
912b54ad
DS
604
605 case ARG_VERSION:
3f6fd1ba 606 return version();
912b54ad 607
eb9da376 608 case '?':
912b54ad 609 return -EINVAL;
eb9da376
LP
610
611 default:
612 assert_not_reached("Unhandled option");
912b54ad 613 }
912b54ad 614
8569a776
LP
615 if (optind >= argc) {
616 log_error("Not enough parameters.");
912b54ad
DS
617 return -EINVAL;
618 }
619
8569a776
LP
620 if (argc != optind+1) {
621 log_error("Too many parameters.");
912b54ad
DS
622 return -EINVAL;
623 }
624
8569a776 625 arg_remote_host = argv[optind];
912b54ad
DS
626 return 1;
627}
628
629int main(int argc, char *argv[]) {
8569a776
LP
630 Context context = {};
631 int r, n, fd;
912b54ad
DS
632
633 log_parse_environment();
634 log_open();
635
8569a776 636 r = parse_argv(argc, argv);
912b54ad
DS
637 if (r <= 0)
638 goto finish;
639
fb69d709 640 r = sd_event_default(&context.event);
8569a776 641 if (r < 0) {
da927ba9 642 log_error_errno(r, "Failed to allocate event loop: %m");
8569a776
LP
643 goto finish;
644 }
912b54ad 645
fb69d709
LP
646 r = sd_resolve_default(&context.resolve);
647 if (r < 0) {
da927ba9 648 log_error_errno(r, "Failed to allocate resolver: %m");
fb69d709
LP
649 goto finish;
650 }
651
652 r = sd_resolve_attach_event(context.resolve, context.event, 0);
653 if (r < 0) {
da927ba9 654 log_error_errno(r, "Failed to attach resolver: %m");
fb69d709
LP
655 goto finish;
656 }
657
658 sd_event_set_watchdog(context.event, true);
cde93897 659
8cf030b3
LP
660 n = sd_listen_fds(1);
661 if (n < 0) {
662 log_error("Failed to receive sockets from parent.");
663 r = n;
664 goto finish;
665 } else if (n == 0) {
666 log_error("Didn't get any sockets passed in.");
667 r = -EINVAL;
668 goto finish;
669 }
670
671 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
fb69d709 672 r = add_listen_socket(&context, fd);
8569a776 673 if (r < 0)
96c374d0 674 goto finish;
912b54ad
DS
675 }
676
fb69d709 677 r = sd_event_loop(context.event);
912b54ad 678 if (r < 0) {
da927ba9 679 log_error_errno(r, "Failed to run event loop: %m");
912b54ad
DS
680 goto finish;
681 }
682
912b54ad 683finish:
8569a776
LP
684 context_free(&context);
685
912b54ad
DS
686 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
687}