]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent/tty-ask-password-agent.c
update TODO
[thirdparty/systemd.git] / src / tty-ask-password-agent / tty-ask-password-agent.c
CommitLineData
ec863ba6
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
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
ec863ba6
LP
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
5430f7f2 16 Lesser General Public License for more details.
ec863ba6 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
ec863ba6
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <stdbool.h>
23#include <errno.h>
24#include <string.h>
25#include <sys/socket.h>
26#include <sys/un.h>
27#include <stddef.h>
28#include <sys/poll.h>
29#include <sys/inotify.h>
30#include <unistd.h>
31#include <getopt.h>
b9ba604e 32#include <sys/signalfd.h>
7af53310 33#include <fcntl.h>
ec863ba6
LP
34
35#include "util.h"
49e942b2 36#include "mkdir.h"
9eb977db 37#include "path-util.h"
ec863ba6
LP
38#include "conf-parser.h"
39#include "utmp-wtmp.h"
e5ebf783 40#include "socket-util.h"
7f4e0805 41#include "ask-password-api.h"
21bc923a 42#include "strv.h"
c52f663b 43#include "build.h"
ec863ba6
LP
44
45static enum {
46 ACTION_LIST,
47 ACTION_QUERY,
48 ACTION_WATCH,
49 ACTION_WALL
50} arg_action = ACTION_QUERY;
51
e5ebf783 52static bool arg_plymouth = false;
0cf84693 53static bool arg_console = false;
e5ebf783 54
21bc923a
LP
55static int ask_password_plymouth(
56 const char *message,
57 usec_t until,
58 const char *flag_file,
59 bool accept_cached,
60 char ***_passphrases) {
61
e5ebf783
LP
62 int fd = -1, notify = -1;
63 union sockaddr_union sa;
64 char *packet = NULL;
65 ssize_t k;
66 int r, n;
67 struct pollfd pollfd[2];
68 char buffer[LINE_MAX];
69 size_t p = 0;
70 enum {
71 POLL_SOCKET,
72 POLL_INOTIFY
73 };
74
21bc923a
LP
75 assert(_passphrases);
76
e5ebf783
LP
77 if (flag_file) {
78 if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
79 r = -errno;
80 goto finish;
81 }
82
83 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
84 r = -errno;
85 goto finish;
86 }
87 }
88
89 if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
90 r = -errno;
91 goto finish;
92 }
93
94 zero(sa);
95 sa.sa.sa_family = AF_UNIX;
96707269
LP
96 strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1);
97 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
c0f9c7da 98 log_error("Failed to connect to Plymouth: %m");
e5ebf783
LP
99 r = -errno;
100 goto finish;
101 }
102
21bc923a
LP
103 if (accept_cached) {
104 packet = strdup("c");
105 n = 1;
106 } else
107 asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n);
108
109 if (!packet) {
e5ebf783
LP
110 r = -ENOMEM;
111 goto finish;
112 }
113
114 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
115 r = k < 0 ? (int) k : -EIO;
116 goto finish;
117 }
118
119 zero(pollfd);
120 pollfd[POLL_SOCKET].fd = fd;
121 pollfd[POLL_SOCKET].events = POLLIN;
122 pollfd[POLL_INOTIFY].fd = notify;
123 pollfd[POLL_INOTIFY].events = POLLIN;
124
125 for (;;) {
126 int sleep_for = -1, j;
127
128 if (until > 0) {
129 usec_t y;
130
131 y = now(CLOCK_MONOTONIC);
132
133 if (y > until) {
ccc80078 134 r = -ETIME;
e5ebf783
LP
135 goto finish;
136 }
137
138 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
139 }
140
141 if (flag_file)
142 if (access(flag_file, F_OK) < 0) {
143 r = -errno;
144 goto finish;
145 }
146
147 if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
148
149 if (errno == EINTR)
150 continue;
151
152 r = -errno;
153 goto finish;
154 } else if (j == 0) {
ccc80078 155 r = -ETIME;
e5ebf783
LP
156 goto finish;
157 }
158
159 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
160 flush_fd(notify);
161
162 if (pollfd[POLL_SOCKET].revents == 0)
163 continue;
164
165 if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) {
166 r = k < 0 ? -errno : -EIO;
167 goto finish;
168 }
169
170 p += k;
171
172 if (p < 1)
173 continue;
174
175 if (buffer[0] == 5) {
21bc923a
LP
176
177 if (accept_cached) {
178 /* Hmm, first try with cached
179 * passwords failed, so let's retry
180 * with a normal password request */
181 free(packet);
182 packet = NULL;
183
184 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
185 r = -ENOMEM;
186 goto finish;
187 }
188
189 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
190 r = k < 0 ? (int) k : -EIO;
191 goto finish;
192 }
193
194 accept_cached = false;
195 p = 0;
196 continue;
197 }
198
e5ebf783
LP
199 /* No password, because UI not shown */
200 r = -ENOENT;
201 goto finish;
202
21bc923a 203 } else if (buffer[0] == 2 || buffer[0] == 9) {
e5ebf783 204 uint32_t size;
21bc923a 205 char **l;
e5ebf783 206
21bc923a 207 /* One ore more answers */
e5ebf783
LP
208 if (p < 5)
209 continue;
210
211 memcpy(&size, buffer+1, sizeof(size));
bb53abeb 212 size = le32toh(size);
e5ebf783
LP
213 if (size+5 > sizeof(buffer)) {
214 r = -EIO;
215 goto finish;
216 }
217
218 if (p-5 < size)
219 continue;
220
21bc923a 221 if (!(l = strv_parse_nulstr(buffer + 5, size))) {
e5ebf783
LP
222 r = -ENOMEM;
223 goto finish;
224 }
225
21bc923a 226 *_passphrases = l;
e5ebf783 227 break;
21bc923a 228
e5ebf783
LP
229 } else {
230 /* Unknown packet */
231 r = -EIO;
232 goto finish;
233 }
234 }
235
236 r = 0;
237
238finish:
239 if (notify >= 0)
240 close_nointr_nofail(notify);
241
242 if (fd >= 0)
243 close_nointr_nofail(fd);
244
245 free(packet);
246
247 return r;
248}
249
0ddf1d3a 250static int parse_password(const char *filename, char **wall) {
ec863ba6
LP
251 char *socket_name = NULL, *message = NULL, *packet = NULL;
252 uint64_t not_after = 0;
253 unsigned pid = 0;
254 int socket_fd = -1;
21bc923a 255 bool accept_cached = false;
ec863ba6 256
f975e971
LP
257 const ConfigTableItem items[] = {
258 { "Ask", "Socket", config_parse_string, 0, &socket_name },
259 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
260 { "Ask", "Message", config_parse_string, 0, &message },
261 { "Ask", "PID", config_parse_unsigned, 0, &pid },
262 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
263 { NULL, NULL, NULL, 0, NULL }
ec863ba6
LP
264 };
265
266 FILE *f;
267 int r;
ec863ba6
LP
268
269 assert(filename);
270
f975e971
LP
271 f = fopen(filename, "re");
272 if (!f) {
ec863ba6
LP
273 if (errno == ENOENT)
274 return 0;
275
276 log_error("open(%s): %m", filename);
277 return -errno;
278 }
279
f975e971
LP
280 r = config_parse(filename, f, NULL, config_item_table_lookup, (void*) items, true, NULL);
281 if (r < 0) {
ec863ba6
LP
282 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
283 goto finish;
284 }
285
7dcda352 286 if (!socket_name) {
ec863ba6
LP
287 log_error("Invalid password file %s", filename);
288 r = -EBADMSG;
289 goto finish;
290 }
291
7dcda352
LP
292 if (not_after > 0) {
293 if (now(CLOCK_MONOTONIC) > not_after) {
294 r = 0;
295 goto finish;
296 }
ec863ba6
LP
297 }
298
ded80335
LP
299 if (pid > 0 &&
300 kill(pid, 0) < 0 &&
301 errno == ESRCH) {
302 r = 0;
303 goto finish;
304 }
305
ec863ba6
LP
306 if (arg_action == ACTION_LIST)
307 printf("'%s' (PID %u)\n", message, pid);
308 else if (arg_action == ACTION_WALL) {
0ddf1d3a 309 char *_wall;
ec863ba6 310
0ddf1d3a
LP
311 if (asprintf(&_wall,
312 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
9d3e691e 313 "Please enter password with the systemd-tty-ask-password-agent tool!",
0ddf1d3a
LP
314 *wall ? *wall : "",
315 *wall ? "\r\n\r\n" : "",
ec863ba6
LP
316 message,
317 pid) < 0) {
0d0f0c50 318 r = log_oom();
ec863ba6
LP
319 goto finish;
320 }
321
0ddf1d3a
LP
322 free(*wall);
323 *wall = _wall;
ec863ba6
LP
324 } else {
325 union {
326 struct sockaddr sa;
327 struct sockaddr_un un;
328 } sa;
3414abee 329 size_t packet_length = 0;
ec863ba6
LP
330
331 assert(arg_action == ACTION_QUERY ||
332 arg_action == ACTION_WATCH);
333
334 if (access(socket_name, W_OK) < 0) {
335
336 if (arg_action == ACTION_QUERY)
337 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
338
339 r = 0;
340 goto finish;
341 }
342
21bc923a 343 if (arg_plymouth) {
3414abee 344 char **passwords = NULL;
21bc923a
LP
345
346 if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
347 char **p;
348
349 packet_length = 1;
350 STRV_FOREACH(p, passwords)
351 packet_length += strlen(*p) + 1;
352
353 if (!(packet = new(char, packet_length)))
354 r = -ENOMEM;
355 else {
356 char *d;
357
358 packet[0] = '+';
359 d = packet+1;
360
361 STRV_FOREACH(p, passwords)
362 d = stpcpy(d, *p) + 1;
363 }
364 }
365
366 } else {
0cf84693 367 int tty_fd = -1;
21bc923a 368 char *password;
0cf84693
LP
369
370 if (arg_console)
af6da548 371 if ((tty_fd = acquire_terminal("/dev/console", false, false, false, (usec_t) -1)) < 0) {
0cf84693
LP
372 r = tty_fd;
373 goto finish;
374 }
375
e5ebf783
LP
376 r = ask_password_tty(message, not_after, filename, &password);
377
0cf84693
LP
378 if (arg_console) {
379 close_nointr_nofail(tty_fd);
380 release_terminal();
381 }
21bc923a 382
9726f9ff
LP
383 if (r >= 0) {
384 packet_length = 1+strlen(password)+1;
385 if (!(packet = new(char, packet_length)))
386 r = -ENOMEM;
387 else {
388 packet[0] = '+';
389 strcpy(packet+1, password);
390 }
21bc923a 391
5ba7b871
LP
392 free(password);
393 }
0cf84693
LP
394 }
395
446f0046
LP
396 if (r == -ETIME || r == -ENOENT) {
397 /* If the query went away, that's OK */
398 r = 0;
399 goto finish;
400 }
401
e5ebf783
LP
402 if (r < 0) {
403 log_error("Failed to query password: %s", strerror(-r));
ec863ba6
LP
404 goto finish;
405 }
406
ec863ba6
LP
407 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
408 log_error("socket(): %m");
409 r = -errno;
410 goto finish;
411 }
412
413 zero(sa);
414 sa.un.sun_family = AF_UNIX;
415 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
416
21bc923a 417 if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
ec863ba6
LP
418 log_error("Failed to send: %m");
419 r = -errno;
420 goto finish;
421 }
422 }
423
424finish:
425 fclose(f);
426
427 if (socket_fd >= 0)
428 close_nointr_nofail(socket_fd);
429
430 free(packet);
431 free(socket_name);
432 free(message);
433
434 return r;
435}
436
0cf84693 437static int wall_tty_block(void) {
7af53310 438 char *p;
fc116c6a
LP
439 int fd, r;
440 dev_t devnr;
7af53310 441
4d6d6518
LP
442 r = get_ctty_devnr(0, &devnr);
443 if (r < 0)
fc116c6a 444 return -r;
7af53310 445
2b583ce6 446 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
7af53310
LP
447 return -ENOMEM;
448
d2e54fae 449 mkdir_parents_label(p, 0700);
7af53310
LP
450 mkfifo(p, 0600);
451
452 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
453 free(p);
454
455 if (fd < 0)
456 return -errno;
457
458 return fd;
459}
460
0cf84693 461static bool wall_tty_match(const char *path) {
fc116c6a 462 int fd, k;
7af53310 463 char *p;
fc116c6a
LP
464 struct stat st;
465
466 if (path_is_absolute(path))
467 k = lstat(path, &st);
468 else {
469 if (asprintf(&p, "/dev/%s", path) < 0)
470 return true;
471
472 k = lstat(p, &st);
473 free(p);
474 }
475
476 if (k < 0)
477 return true;
478
479 if (!S_ISCHR(st.st_mode))
480 return true;
7af53310
LP
481
482 /* We use named pipes to ensure that wall messages suggesting
483 * password entry are not printed over password prompts
484 * already shown. We use the fact here that opening a pipe in
485 * non-blocking mode for write-only will succeed only if
486 * there's some writer behind it. Using pipes has the
487 * advantage that the block will automatically go away if the
488 * process dies. */
489
2b583ce6 490 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
7af53310
LP
491 return true;
492
493 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
494 free(p);
495
496 if (fd < 0)
497 return true;
498
499 /* What, we managed to open the pipe? Then this tty is filtered. */
500 close_nointr_nofail(fd);
501 return false;
502}
503
ec863ba6
LP
504static int show_passwords(void) {
505 DIR *d;
506 struct dirent *de;
507 int r = 0;
508
2b583ce6 509 if (!(d = opendir("/run/systemd/ask-password"))) {
ec863ba6
LP
510 if (errno == ENOENT)
511 return 0;
512
513 log_error("opendir(): %m");
514 return -errno;
515 }
516
517 while ((de = readdir(d))) {
518 char *p;
519 int q;
0ddf1d3a 520 char *wall;
ec863ba6 521
1a6f4df6
LP
522 /* We only support /dev on tmpfs, hence we can rely on
523 * d_type to be reliable */
524
ec863ba6
LP
525 if (de->d_type != DT_REG)
526 continue;
527
528 if (ignore_file(de->d_name))
529 continue;
530
531 if (!startswith(de->d_name, "ask."))
532 continue;
533
2b583ce6 534 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
0d0f0c50 535 r = log_oom();
ec863ba6
LP
536 goto finish;
537 }
538
0ddf1d3a
LP
539 wall = NULL;
540 if ((q = parse_password(p, &wall)) < 0)
ec863ba6
LP
541 r = q;
542
543 free(p);
0ddf1d3a
LP
544
545 if (wall) {
0cf84693 546 utmp_wall(wall, wall_tty_match);
0ddf1d3a
LP
547 free(wall);
548 }
ec863ba6
LP
549 }
550
551finish:
552 if (d)
553 closedir(d);
554
555 return r;
556}
557
558static int watch_passwords(void) {
b9ba604e
LP
559 enum {
560 FD_INOTIFY,
561 FD_SIGNAL,
562 _FD_MAX
563 };
564
7af53310 565 int notify = -1, signal_fd = -1, tty_block_fd = -1;
b9ba604e
LP
566 struct pollfd pollfd[_FD_MAX];
567 sigset_t mask;
ec863ba6
LP
568 int r;
569
0cf84693 570 tty_block_fd = wall_tty_block();
7af53310 571
d2e54fae 572 mkdir_p_label("/run/systemd/ask-password", 0755);
ec863ba6
LP
573
574 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
575 r = -errno;
576 goto finish;
577 }
578
2b583ce6 579 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
ec863ba6
LP
580 r = -errno;
581 goto finish;
582 }
583
b9ba604e
LP
584 assert_se(sigemptyset(&mask) == 0);
585 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
586 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
587
588 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
589 log_error("signalfd(): %m");
590 r = -errno;
591 goto finish;
592 }
593
ec863ba6 594 zero(pollfd);
b9ba604e
LP
595 pollfd[FD_INOTIFY].fd = notify;
596 pollfd[FD_INOTIFY].events = POLLIN;
597 pollfd[FD_SIGNAL].fd = signal_fd;
598 pollfd[FD_SIGNAL].events = POLLIN;
ec863ba6
LP
599
600 for (;;) {
601 if ((r = show_passwords()) < 0)
0cf84693 602 log_error("Failed to show password: %s", strerror(-r));
ec863ba6 603
b9ba604e 604 if (poll(pollfd, _FD_MAX, -1) < 0) {
ec863ba6
LP
605
606 if (errno == EINTR)
607 continue;
608
609 r = -errno;
610 goto finish;
611 }
612
b9ba604e 613 if (pollfd[FD_INOTIFY].revents != 0)
ec863ba6 614 flush_fd(notify);
b9ba604e
LP
615
616 if (pollfd[FD_SIGNAL].revents != 0)
617 break;
ec863ba6
LP
618 }
619
620 r = 0;
621
622finish:
623 if (notify >= 0)
624 close_nointr_nofail(notify);
625
b9ba604e
LP
626 if (signal_fd >= 0)
627 close_nointr_nofail(signal_fd);
628
7af53310
LP
629 if (tty_block_fd >= 0)
630 close_nointr_nofail(tty_block_fd);
631
ec863ba6
LP
632 return r;
633}
634
635static int help(void) {
636
637 printf("%s [OPTIONS...]\n\n"
638 "Process system password requests.\n\n"
e5ebf783 639 " -h --help Show this help\n"
c52f663b 640 " --version Show package version\n"
e5ebf783
LP
641 " --list Show pending password requests\n"
642 " --query Process pending password requests\n"
35b8ca3a
HH
643 " --watch Continuously process password requests\n"
644 " --wall Continuously forward password requests to wall\n"
0cf84693
LP
645 " --plymouth Ask question with Plymouth instead of on TTY\n"
646 " --console Ask question on /dev/console instead of current TTY\n",
ec863ba6
LP
647 program_invocation_short_name);
648
649 return 0;
650}
651
652static int parse_argv(int argc, char *argv[]) {
653
654 enum {
655 ARG_LIST = 0x100,
656 ARG_QUERY,
657 ARG_WATCH,
658 ARG_WALL,
0cf84693 659 ARG_PLYMOUTH,
c52f663b
LP
660 ARG_CONSOLE,
661 ARG_VERSION
ec863ba6
LP
662 };
663
664 static const struct option options[] = {
e5ebf783 665 { "help", no_argument, NULL, 'h' },
c52f663b 666 { "version", no_argument, NULL, ARG_VERSION },
e5ebf783
LP
667 { "list", no_argument, NULL, ARG_LIST },
668 { "query", no_argument, NULL, ARG_QUERY },
669 { "watch", no_argument, NULL, ARG_WATCH },
670 { "wall", no_argument, NULL, ARG_WALL },
671 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
0cf84693 672 { "console", no_argument, NULL, ARG_CONSOLE },
e5ebf783 673 { NULL, 0, NULL, 0 }
ec863ba6
LP
674 };
675
676 int c;
677
678 assert(argc >= 0);
679 assert(argv);
680
681 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
682
683 switch (c) {
684
685 case 'h':
686 help();
687 return 0;
688
c52f663b
LP
689 case ARG_VERSION:
690 puts(PACKAGE_STRING);
691 puts(DISTRIBUTION);
692 puts(SYSTEMD_FEATURES);
693 return 0;
694
ec863ba6
LP
695 case ARG_LIST:
696 arg_action = ACTION_LIST;
697 break;
698
699 case ARG_QUERY:
700 arg_action = ACTION_QUERY;
701 break;
702
703 case ARG_WATCH:
704 arg_action = ACTION_WATCH;
705 break;
706
707 case ARG_WALL:
708 arg_action = ACTION_WALL;
709 break;
710
e5ebf783
LP
711 case ARG_PLYMOUTH:
712 arg_plymouth = true;
713 break;
714
0cf84693
LP
715 case ARG_CONSOLE:
716 arg_console = true;
717 break;
718
ec863ba6
LP
719 case '?':
720 return -EINVAL;
721
722 default:
723 log_error("Unknown option code %c", c);
724 return -EINVAL;
725 }
726 }
727
728 if (optind != argc) {
729 help();
730 return -EINVAL;
731 }
732
733 return 1;
734}
735
736int main(int argc, char *argv[]) {
737 int r;
738
4b261568 739 log_set_target(LOG_TARGET_AUTO);
ec863ba6
LP
740 log_parse_environment();
741 log_open();
742
4c12626c
LP
743 umask(0022);
744
ec863ba6
LP
745 if ((r = parse_argv(argc, argv)) <= 0)
746 goto finish;
747
0cf84693
LP
748 if (arg_console) {
749 setsid();
750 release_terminal();
751 }
752
ec863ba6
LP
753 if (arg_action == ACTION_WATCH ||
754 arg_action == ACTION_WALL)
755 r = watch_passwords();
756 else
757 r = show_passwords();
758
96707269
LP
759 if (r < 0)
760 log_error("Error: %s", strerror(-r));
761
ec863ba6
LP
762finish:
763 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
764}