]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent/tty-ask-password-agent.c
shared: add process-util.[ch]
[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>
0a6f50c0 28#include <poll.h>
ec863ba6
LP
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"
1d749d04 44#include "def.h"
0b452006 45#include "process-util.h"
ec863ba6
LP
46
47static enum {
48 ACTION_LIST,
49 ACTION_QUERY,
50 ACTION_WATCH,
51 ACTION_WALL
52} arg_action = ACTION_QUERY;
53
e5ebf783 54static bool arg_plymouth = false;
0cf84693 55static bool arg_console = false;
e5ebf783 56
21bc923a
LP
57static int ask_password_plymouth(
58 const char *message,
59 usec_t until,
60 const char *flag_file,
61 bool accept_cached,
62 char ***_passphrases) {
63
1d749d04
ZJS
64 _cleanup_close_ int fd = -1, notify = -1;
65 union sockaddr_union sa = PLYMOUTH_SOCKET;
66 _cleanup_free_ char *packet = NULL;
e5ebf783
LP
67 ssize_t k;
68 int r, n;
b92bea5d 69 struct pollfd pollfd[2] = {};
e5ebf783
LP
70 char buffer[LINE_MAX];
71 size_t p = 0;
72 enum {
73 POLL_SOCKET,
74 POLL_INOTIFY
75 };
76
21bc923a
LP
77 assert(_passphrases);
78
e5ebf783 79 if (flag_file) {
1d749d04
ZJS
80 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
81 if (notify < 0)
82 return -errno;
e5ebf783 83
1d749d04
ZJS
84 r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
85 if (r < 0)
86 return -errno;
e5ebf783
LP
87 }
88
1d749d04
ZJS
89 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
90 if (fd < 0)
91 return -errno;
e5ebf783 92
1d749d04 93 r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1));
4a62c710
MS
94 if (r < 0)
95 return log_error_errno(errno, "Failed to connect to Plymouth: %m");
e5ebf783 96
21bc923a
LP
97 if (accept_cached) {
98 packet = strdup("c");
99 n = 1;
7de80bfe
KZ
100 } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1),
101 message, &n) < 0)
102 packet = NULL;
21bc923a 103
1d749d04
ZJS
104 if (!packet)
105 return log_oom();
e5ebf783 106
553acb7b
ZJS
107 r = loop_write(fd, packet, n + 1, true);
108 if (r < 0)
109 return r;
e5ebf783 110
e5ebf783
LP
111 pollfd[POLL_SOCKET].fd = fd;
112 pollfd[POLL_SOCKET].events = POLLIN;
113 pollfd[POLL_INOTIFY].fd = notify;
114 pollfd[POLL_INOTIFY].events = POLLIN;
115
116 for (;;) {
117 int sleep_for = -1, j;
118
119 if (until > 0) {
120 usec_t y;
121
122 y = now(CLOCK_MONOTONIC);
123
1d749d04
ZJS
124 if (y > until)
125 return -ETIME;
e5ebf783
LP
126
127 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
128 }
129
1d749d04
ZJS
130 if (flag_file && access(flag_file, F_OK) < 0)
131 return -errno;
e5ebf783 132
1d749d04
ZJS
133 j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
134 if (j < 0) {
e5ebf783
LP
135 if (errno == EINTR)
136 continue;
137
1d749d04
ZJS
138 return -errno;
139 } else if (j == 0)
140 return -ETIME;
e5ebf783
LP
141
142 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
143 flush_fd(notify);
144
145 if (pollfd[POLL_SOCKET].revents == 0)
146 continue;
147
1d749d04
ZJS
148 k = read(fd, buffer + p, sizeof(buffer) - p);
149 if (k <= 0)
150 return r = k < 0 ? -errno : -EIO;
e5ebf783
LP
151
152 p += k;
153
154 if (p < 1)
155 continue;
156
157 if (buffer[0] == 5) {
21bc923a
LP
158
159 if (accept_cached) {
160 /* Hmm, first try with cached
161 * passwords failed, so let's retry
162 * with a normal password request */
163 free(packet);
164 packet = NULL;
165
1d749d04
ZJS
166 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
167 return -ENOMEM;
21bc923a 168
553acb7b
ZJS
169 r = loop_write(fd, packet, n+1, true);
170 if (r < 0)
171 return r;
21bc923a
LP
172
173 accept_cached = false;
174 p = 0;
175 continue;
176 }
177
e5ebf783 178 /* No password, because UI not shown */
1d749d04 179 return -ENOENT;
e5ebf783 180
21bc923a 181 } else if (buffer[0] == 2 || buffer[0] == 9) {
e5ebf783 182 uint32_t size;
21bc923a 183 char **l;
e5ebf783 184
4cf07da2 185 /* One or more answers */
e5ebf783
LP
186 if (p < 5)
187 continue;
188
189 memcpy(&size, buffer+1, sizeof(size));
bb53abeb 190 size = le32toh(size);
1d749d04
ZJS
191 if (size + 5 > sizeof(buffer))
192 return -EIO;
e5ebf783
LP
193
194 if (p-5 < size)
195 continue;
196
1d749d04
ZJS
197 l = strv_parse_nulstr(buffer + 5, size);
198 if (!l)
199 return -ENOMEM;
e5ebf783 200
21bc923a 201 *_passphrases = l;
e5ebf783 202 break;
21bc923a 203
1d749d04 204 } else
e5ebf783 205 /* Unknown packet */
1d749d04 206 return -EIO;
e5ebf783
LP
207 }
208
1d749d04 209 return 0;
e5ebf783
LP
210}
211
0ddf1d3a 212static int parse_password(const char *filename, char **wall) {
e46eab86 213 _cleanup_free_ char *socket_name = NULL, *message = NULL, *packet = NULL;
ec863ba6
LP
214 uint64_t not_after = 0;
215 unsigned pid = 0;
64845bdc 216 bool accept_cached = false, echo = false;
ec863ba6 217
f975e971
LP
218 const ConfigTableItem items[] = {
219 { "Ask", "Socket", config_parse_string, 0, &socket_name },
220 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
221 { "Ask", "Message", config_parse_string, 0, &message },
222 { "Ask", "PID", config_parse_unsigned, 0, &pid },
223 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
64845bdc 224 { "Ask", "Echo", config_parse_bool, 0, &echo },
1d749d04 225 {}
ec863ba6
LP
226 };
227
ec863ba6 228 int r;
ec863ba6
LP
229
230 assert(filename);
231
36f822c4
ZJS
232 r = config_parse(NULL, filename, NULL,
233 NULL,
234 config_item_table_lookup, items,
235 true, false, true, NULL);
236 if (r < 0)
237 return r;
ec863ba6 238
7dcda352 239 if (!socket_name) {
ec863ba6 240 log_error("Invalid password file %s", filename);
e46eab86 241 return -EBADMSG;
ec863ba6
LP
242 }
243
e46eab86
ZJS
244 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
245 return 0;
ec863ba6 246
e46eab86
ZJS
247 if (pid > 0 && !pid_is_alive(pid))
248 return 0;
ded80335 249
ec863ba6
LP
250 if (arg_action == ACTION_LIST)
251 printf("'%s' (PID %u)\n", message, pid);
e46eab86 252
ec863ba6 253 else if (arg_action == ACTION_WALL) {
0ddf1d3a 254 char *_wall;
ec863ba6 255
0ddf1d3a
LP
256 if (asprintf(&_wall,
257 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
9d3e691e 258 "Please enter password with the systemd-tty-ask-password-agent tool!",
0ddf1d3a
LP
259 *wall ? *wall : "",
260 *wall ? "\r\n\r\n" : "",
ec863ba6 261 message,
e46eab86
ZJS
262 pid) < 0)
263 return log_oom();
ec863ba6 264
0ddf1d3a
LP
265 free(*wall);
266 *wall = _wall;
e46eab86 267
ec863ba6 268 } else {
e46eab86 269 union sockaddr_union sa = {};
3414abee 270 size_t packet_length = 0;
e46eab86 271 _cleanup_close_ int socket_fd = -1;
ec863ba6
LP
272
273 assert(arg_action == ACTION_QUERY ||
274 arg_action == ACTION_WATCH);
275
276 if (access(socket_name, W_OK) < 0) {
ec863ba6
LP
277 if (arg_action == ACTION_QUERY)
278 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
279
e46eab86 280 return 0;
ec863ba6
LP
281 }
282
21bc923a 283 if (arg_plymouth) {
cb90460e 284 _cleanup_strv_free_ char **passwords = NULL;
21bc923a 285
e46eab86
ZJS
286 r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords);
287 if (r >= 0) {
21bc923a
LP
288 char **p;
289
290 packet_length = 1;
291 STRV_FOREACH(p, passwords)
292 packet_length += strlen(*p) + 1;
293
e46eab86
ZJS
294 packet = new(char, packet_length);
295 if (!packet)
21bc923a
LP
296 r = -ENOMEM;
297 else {
1d749d04 298 char *d = packet + 1;
21bc923a
LP
299
300 STRV_FOREACH(p, passwords)
301 d = stpcpy(d, *p) + 1;
1d749d04
ZJS
302
303 packet[0] = '+';
21bc923a
LP
304 }
305 }
306
307 } else {
0cf84693 308 int tty_fd = -1;
e46eab86 309 _cleanup_free_ char *password = NULL;
0cf84693 310
e46eab86 311 if (arg_console) {
3a43da28 312 tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY);
e46eab86
ZJS
313 if (tty_fd < 0)
314 return tty_fd;
315 }
0cf84693 316
64845bdc 317 r = ask_password_tty(message, not_after, echo, filename, &password);
e5ebf783 318
0cf84693 319 if (arg_console) {
03e334a1 320 safe_close(tty_fd);
0cf84693
LP
321 release_terminal();
322 }
21bc923a 323
9726f9ff 324 if (r >= 0) {
e46eab86
ZJS
325 packet_length = 1 + strlen(password) + 1;
326 packet = new(char, packet_length);
327 if (!packet)
9726f9ff
LP
328 r = -ENOMEM;
329 else {
330 packet[0] = '+';
e46eab86 331 strcpy(packet + 1, password);
9726f9ff 332 }
5ba7b871 333 }
0cf84693
LP
334 }
335
1d749d04 336 if (IN_SET(r, -ETIME, -ENOENT))
446f0046 337 /* If the query went away, that's OK */
e46eab86 338 return 0;
446f0046 339
f647962d
MS
340 if (r < 0)
341 return log_error_errno(r, "Failed to query password: %m");
ec863ba6 342
e46eab86 343 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
4a62c710
MS
344 if (socket_fd < 0)
345 return log_error_errno(errno, "socket(): %m");
ec863ba6 346
ec863ba6
LP
347 sa.un.sun_family = AF_UNIX;
348 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
349
e46eab86
ZJS
350 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
351 offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
352 if (r < 0) {
56f64d95 353 log_error_errno(errno, "Failed to send: %m");
e46eab86 354 return r;
ec863ba6
LP
355 }
356 }
357
e46eab86 358 return 0;
ec863ba6
LP
359}
360
0cf84693 361static int wall_tty_block(void) {
1d749d04 362 _cleanup_free_ char *p = NULL;
fc116c6a
LP
363 int fd, r;
364 dev_t devnr;
7af53310 365
4d6d6518
LP
366 r = get_ctty_devnr(0, &devnr);
367 if (r < 0)
ee0e4cca 368 return r;
7af53310 369
2b583ce6 370 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
7af53310
LP
371 return -ENOMEM;
372
d2e54fae 373 mkdir_parents_label(p, 0700);
7af53310
LP
374 mkfifo(p, 0600);
375
376 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
7af53310
LP
377 if (fd < 0)
378 return -errno;
379
380 return fd;
381}
382
0cf84693 383static bool wall_tty_match(const char *path) {
1d749d04 384 int fd, r;
fc116c6a 385 struct stat st;
1d749d04 386 _cleanup_free_ char *p = NULL;
fc116c6a 387
1d749d04 388 if (!path_is_absolute(path))
63c372cb 389 path = strjoina("/dev/", path);
fc116c6a 390
1d749d04
ZJS
391 r = lstat(path, &st);
392 if (r < 0)
fc116c6a
LP
393 return true;
394
395 if (!S_ISCHR(st.st_mode))
396 return true;
7af53310
LP
397
398 /* We use named pipes to ensure that wall messages suggesting
399 * password entry are not printed over password prompts
400 * already shown. We use the fact here that opening a pipe in
401 * non-blocking mode for write-only will succeed only if
402 * there's some writer behind it. Using pipes has the
403 * advantage that the block will automatically go away if the
404 * process dies. */
405
2b583ce6 406 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
7af53310
LP
407 return true;
408
409 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
7af53310
LP
410 if (fd < 0)
411 return true;
412
413 /* What, we managed to open the pipe? Then this tty is filtered. */
03e334a1 414 safe_close(fd);
7af53310
LP
415 return false;
416}
417
ec863ba6 418static int show_passwords(void) {
1d749d04 419 _cleanup_closedir_ DIR *d;
ec863ba6
LP
420 struct dirent *de;
421 int r = 0;
422
1d749d04
ZJS
423 d = opendir("/run/systemd/ask-password");
424 if (!d) {
ec863ba6
LP
425 if (errno == ENOENT)
426 return 0;
427
56f64d95 428 log_error_errno(errno, "opendir(/run/systemd/ask-password): %m");
ec863ba6
LP
429 return -errno;
430 }
431
432 while ((de = readdir(d))) {
1d749d04 433 _cleanup_free_ char *p = NULL, *wall = NULL;
ec863ba6
LP
434 int q;
435
1a6f4df6
LP
436 /* We only support /dev on tmpfs, hence we can rely on
437 * d_type to be reliable */
438
ec863ba6
LP
439 if (de->d_type != DT_REG)
440 continue;
441
a34bf9db 442 if (hidden_file(de->d_name))
ec863ba6
LP
443 continue;
444
445 if (!startswith(de->d_name, "ask."))
446 continue;
447
1d749d04
ZJS
448 p = strappend("/run/systemd/ask-password/", de->d_name);
449 if (!p)
450 return log_oom();
ec863ba6 451
1d749d04
ZJS
452 q = parse_password(p, &wall);
453 if (q < 0 && r == 0)
ec863ba6
LP
454 r = q;
455
1d749d04 456 if (wall)
9003d9b0 457 utmp_wall(wall, NULL, wall_tty_match);
ec863ba6
LP
458 }
459
ec863ba6
LP
460 return r;
461}
462
463static int watch_passwords(void) {
b9ba604e
LP
464 enum {
465 FD_INOTIFY,
466 FD_SIGNAL,
467 _FD_MAX
468 };
469
1d749d04 470 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
b92bea5d 471 struct pollfd pollfd[_FD_MAX] = {};
b9ba604e 472 sigset_t mask;
ec863ba6
LP
473 int r;
474
0cf84693 475 tty_block_fd = wall_tty_block();
7af53310 476
d2e54fae 477 mkdir_p_label("/run/systemd/ask-password", 0755);
ec863ba6 478
1d749d04
ZJS
479 notify = inotify_init1(IN_CLOEXEC);
480 if (notify < 0)
481 return -errno;
ec863ba6 482
1d749d04
ZJS
483 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
484 return -errno;
ec863ba6 485
b9ba604e
LP
486 assert_se(sigemptyset(&mask) == 0);
487 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
488 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
489
1d749d04
ZJS
490 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
491 if (signal_fd < 0)
492 return -errno;
b9ba604e 493
b9ba604e
LP
494 pollfd[FD_INOTIFY].fd = notify;
495 pollfd[FD_INOTIFY].events = POLLIN;
496 pollfd[FD_SIGNAL].fd = signal_fd;
497 pollfd[FD_SIGNAL].events = POLLIN;
ec863ba6
LP
498
499 for (;;) {
1d749d04
ZJS
500 r = show_passwords();
501 if (r < 0)
da927ba9 502 log_error_errno(r, "Failed to show password: %m");
ec863ba6 503
b9ba604e 504 if (poll(pollfd, _FD_MAX, -1) < 0) {
ec863ba6
LP
505 if (errno == EINTR)
506 continue;
507
1d749d04 508 return -errno;
ec863ba6
LP
509 }
510
b9ba604e 511 if (pollfd[FD_INOTIFY].revents != 0)
ec863ba6 512 flush_fd(notify);
b9ba604e
LP
513
514 if (pollfd[FD_SIGNAL].revents != 0)
515 break;
ec863ba6
LP
516 }
517
1d749d04 518 return 0;
ec863ba6
LP
519}
520
601185b4 521static void help(void) {
ec863ba6
LP
522 printf("%s [OPTIONS...]\n\n"
523 "Process system password requests.\n\n"
e5ebf783 524 " -h --help Show this help\n"
c52f663b 525 " --version Show package version\n"
e5ebf783
LP
526 " --list Show pending password requests\n"
527 " --query Process pending password requests\n"
35b8ca3a
HH
528 " --watch Continuously process password requests\n"
529 " --wall Continuously forward password requests to wall\n"
0cf84693
LP
530 " --plymouth Ask question with Plymouth instead of on TTY\n"
531 " --console Ask question on /dev/console instead of current TTY\n",
ec863ba6 532 program_invocation_short_name);
ec863ba6
LP
533}
534
535static int parse_argv(int argc, char *argv[]) {
536
537 enum {
538 ARG_LIST = 0x100,
539 ARG_QUERY,
540 ARG_WATCH,
541 ARG_WALL,
0cf84693 542 ARG_PLYMOUTH,
c52f663b
LP
543 ARG_CONSOLE,
544 ARG_VERSION
ec863ba6
LP
545 };
546
547 static const struct option options[] = {
e5ebf783 548 { "help", no_argument, NULL, 'h' },
c52f663b 549 { "version", no_argument, NULL, ARG_VERSION },
e5ebf783
LP
550 { "list", no_argument, NULL, ARG_LIST },
551 { "query", no_argument, NULL, ARG_QUERY },
552 { "watch", no_argument, NULL, ARG_WATCH },
553 { "wall", no_argument, NULL, ARG_WALL },
554 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
0cf84693 555 { "console", no_argument, NULL, ARG_CONSOLE },
eb9da376 556 {}
ec863ba6
LP
557 };
558
559 int c;
560
561 assert(argc >= 0);
562 assert(argv);
563
601185b4 564 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
ec863ba6
LP
565
566 switch (c) {
567
568 case 'h':
601185b4
ZJS
569 help();
570 return 0;
ec863ba6 571
c52f663b
LP
572 case ARG_VERSION:
573 puts(PACKAGE_STRING);
c52f663b
LP
574 puts(SYSTEMD_FEATURES);
575 return 0;
576
ec863ba6
LP
577 case ARG_LIST:
578 arg_action = ACTION_LIST;
579 break;
580
581 case ARG_QUERY:
582 arg_action = ACTION_QUERY;
583 break;
584
585 case ARG_WATCH:
586 arg_action = ACTION_WATCH;
587 break;
588
589 case ARG_WALL:
590 arg_action = ACTION_WALL;
591 break;
592
e5ebf783
LP
593 case ARG_PLYMOUTH:
594 arg_plymouth = true;
595 break;
596
0cf84693
LP
597 case ARG_CONSOLE:
598 arg_console = true;
599 break;
600
ec863ba6
LP
601 case '?':
602 return -EINVAL;
603
604 default:
eb9da376 605 assert_not_reached("Unhandled option");
ec863ba6 606 }
ec863ba6
LP
607
608 if (optind != argc) {
601185b4 609 log_error("%s takes no arguments.", program_invocation_short_name);
ec863ba6
LP
610 return -EINVAL;
611 }
612
613 return 1;
614}
615
616int main(int argc, char *argv[]) {
617 int r;
618
4b261568 619 log_set_target(LOG_TARGET_AUTO);
ec863ba6
LP
620 log_parse_environment();
621 log_open();
622
4c12626c
LP
623 umask(0022);
624
1d749d04
ZJS
625 r = parse_argv(argc, argv);
626 if (r <= 0)
ec863ba6
LP
627 goto finish;
628
0cf84693
LP
629 if (arg_console) {
630 setsid();
631 release_terminal();
632 }
633
1d749d04 634 if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
ec863ba6
LP
635 r = watch_passwords();
636 else
637 r = show_passwords();
638
96707269 639 if (r < 0)
da927ba9 640 log_error_errno(r, "Error: %m");
96707269 641
ec863ba6
LP
642finish:
643 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
644}