]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/timedate/timedated.c
Merge pull request #7388 from keszybz/doc-tweak
[thirdparty/systemd.git] / src / timedate / timedated.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2011 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <errno.h>
21 #include <string.h>
22 #include <unistd.h>
23
24 #include "sd-bus.h"
25 #include "sd-event.h"
26 #include "sd-messages.h"
27
28 #include "alloc-util.h"
29 #include "bus-common-errors.h"
30 #include "bus-error.h"
31 #include "bus-util.h"
32 #include "clock-util.h"
33 #include "def.h"
34 #include "fileio-label.h"
35 #include "fs-util.h"
36 #include "path-util.h"
37 #include "selinux-util.h"
38 #include "strv.h"
39 #include "user-util.h"
40 #include "util.h"
41
42 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
43 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
44
45 static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = {
46 SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP),
47 SD_BUS_ERROR_MAP_END
48 };
49
50 typedef struct Context {
51 char *zone;
52 bool local_rtc;
53 bool can_ntp;
54 bool use_ntp;
55 Hashmap *polkit_registry;
56 } Context;
57
58 static void context_free(Context *c) {
59 assert(c);
60
61 free(c->zone);
62 bus_verify_polkit_async_registry_free(c->polkit_registry);
63 }
64
65 static int context_read_data(Context *c) {
66 _cleanup_free_ char *t = NULL;
67 int r;
68
69 assert(c);
70
71 r = get_timezone(&t);
72 if (r == -EINVAL)
73 log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
74 else if (r < 0)
75 log_warning_errno(r, "Failed to get target of /etc/localtime: %m");
76
77 free(c->zone);
78 c->zone = t;
79 t = NULL;
80
81 c->local_rtc = clock_is_localtime(NULL) > 0;
82
83 return 0;
84 }
85
86 static int context_write_data_timezone(Context *c) {
87 _cleanup_free_ char *p = NULL;
88 int r = 0;
89
90 assert(c);
91
92 if (isempty(c->zone)) {
93 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
94 r = -errno;
95
96 return r;
97 }
98
99 p = strappend("../usr/share/zoneinfo/", c->zone);
100 if (!p)
101 return log_oom();
102
103 r = symlink_atomic(p, "/etc/localtime");
104 if (r < 0)
105 return r;
106
107 return 0;
108 }
109
110 static int context_write_data_local_rtc(Context *c) {
111 int r;
112 _cleanup_free_ char *s = NULL, *w = NULL;
113
114 assert(c);
115
116 r = read_full_file("/etc/adjtime", &s, NULL);
117 if (r < 0) {
118 if (r != -ENOENT)
119 return r;
120
121 if (!c->local_rtc)
122 return 0;
123
124 w = strdup(NULL_ADJTIME_LOCAL);
125 if (!w)
126 return -ENOMEM;
127 } else {
128 char *p;
129 const char *e = "\n"; /* default if there is less than 3 lines */
130 const char *prepend = "";
131 size_t a, b;
132
133 p = strchrnul(s, '\n');
134 if (*p == '\0')
135 /* only one line, no \n terminator */
136 prepend = "\n0\n";
137 else if (p[1] == '\0') {
138 /* only one line, with \n terminator */
139 ++p;
140 prepend = "0\n";
141 } else {
142 p = strchr(p+1, '\n');
143 if (!p) {
144 /* only two lines, no \n terminator */
145 prepend = "\n";
146 p = s + strlen(s);
147 } else {
148 char *end;
149 /* third line might have a \n terminator or not */
150 p++;
151 end = strchr(p, '\n');
152 /* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */
153 if (end)
154 e = end;
155 }
156 }
157
158 a = p - s;
159 b = strlen(e);
160
161 w = new(char, a + (c->local_rtc ? 5 : 3) + strlen(prepend) + b + 1);
162 if (!w)
163 return -ENOMEM;
164
165 *(char*) mempcpy(stpcpy(stpcpy(mempcpy(w, s, a), prepend), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
166
167 if (streq(w, NULL_ADJTIME_UTC)) {
168 if (unlink("/etc/adjtime") < 0)
169 if (errno != ENOENT)
170 return -errno;
171
172 return 0;
173 }
174 }
175
176 mac_selinux_init();
177 return write_string_file_atomic_label("/etc/adjtime", w);
178 }
179
180 static int context_read_ntp(Context *c, sd_bus *bus) {
181 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
182 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
183 const char *s;
184 int r;
185
186 assert(c);
187 assert(bus);
188
189 r = sd_bus_call_method(
190 bus,
191 "org.freedesktop.systemd1",
192 "/org/freedesktop/systemd1",
193 "org.freedesktop.systemd1.Manager",
194 "GetUnitFileState",
195 &error,
196 &reply,
197 "s",
198 "systemd-timesyncd.service");
199
200 if (r < 0) {
201 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
202 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
203 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
204 return 0;
205
206 return r;
207 }
208
209 r = sd_bus_message_read(reply, "s", &s);
210 if (r < 0)
211 return r;
212
213 c->can_ntp = true;
214 c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
215
216 return 0;
217 }
218
219 static int context_start_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
220 int r;
221
222 assert(bus);
223 assert(error);
224
225 r = sd_bus_call_method(
226 bus,
227 "org.freedesktop.systemd1",
228 "/org/freedesktop/systemd1",
229 "org.freedesktop.systemd1.Manager",
230 enabled ? "StartUnit" : "StopUnit",
231 error,
232 NULL,
233 "ss",
234 "systemd-timesyncd.service",
235 "replace");
236 if (r < 0) {
237 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
238 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
239 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
240 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
241
242 return r;
243 }
244
245 return 0;
246 }
247
248 static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
249 int r;
250
251 assert(bus);
252 assert(error);
253
254 if (enabled)
255 r = sd_bus_call_method(
256 bus,
257 "org.freedesktop.systemd1",
258 "/org/freedesktop/systemd1",
259 "org.freedesktop.systemd1.Manager",
260 "EnableUnitFiles",
261 error,
262 NULL,
263 "asbb", 1,
264 "systemd-timesyncd.service",
265 false, true);
266 else
267 r = sd_bus_call_method(
268 bus,
269 "org.freedesktop.systemd1",
270 "/org/freedesktop/systemd1",
271 "org.freedesktop.systemd1.Manager",
272 "DisableUnitFiles",
273 error,
274 NULL,
275 "asb", 1,
276 "systemd-timesyncd.service",
277 false);
278
279 if (r < 0) {
280 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
281 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
282
283 return r;
284 }
285
286 r = sd_bus_call_method(
287 bus,
288 "org.freedesktop.systemd1",
289 "/org/freedesktop/systemd1",
290 "org.freedesktop.systemd1.Manager",
291 "Reload",
292 error,
293 NULL,
294 NULL);
295 if (r < 0)
296 return r;
297
298 return 0;
299 }
300
301 static int property_get_rtc_time(
302 sd_bus *bus,
303 const char *path,
304 const char *interface,
305 const char *property,
306 sd_bus_message *reply,
307 void *userdata,
308 sd_bus_error *error) {
309
310 struct tm tm;
311 usec_t t;
312 int r;
313
314 zero(tm);
315 r = clock_get_hwclock(&tm);
316 if (r == -EBUSY) {
317 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
318 t = 0;
319 } else if (r == -ENOENT) {
320 log_debug("/dev/rtc not found.");
321 t = 0; /* no RTC found */
322 } else if (r < 0)
323 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
324 else
325 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
326
327 return sd_bus_message_append(reply, "t", t);
328 }
329
330 static int property_get_time(
331 sd_bus *bus,
332 const char *path,
333 const char *interface,
334 const char *property,
335 sd_bus_message *reply,
336 void *userdata,
337 sd_bus_error *error) {
338
339 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
340 }
341
342 static int property_get_ntp_sync(
343 sd_bus *bus,
344 const char *path,
345 const char *interface,
346 const char *property,
347 sd_bus_message *reply,
348 void *userdata,
349 sd_bus_error *error) {
350
351 return sd_bus_message_append(reply, "b", ntp_synced());
352 }
353
354 static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) {
355 Context *c = userdata;
356 const char *z;
357 int interactive;
358 char *t;
359 int r;
360
361 assert(m);
362 assert(c);
363
364 r = sd_bus_message_read(m, "sb", &z, &interactive);
365 if (r < 0)
366 return r;
367
368 if (!timezone_is_valid(z))
369 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
370
371 if (streq_ptr(z, c->zone))
372 return sd_bus_reply_method_return(m, NULL);
373
374 r = bus_verify_polkit_async(
375 m,
376 CAP_SYS_TIME,
377 "org.freedesktop.timedate1.set-timezone",
378 NULL,
379 interactive,
380 UID_INVALID,
381 &c->polkit_registry,
382 error);
383 if (r < 0)
384 return r;
385 if (r == 0)
386 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
387
388 t = strdup(z);
389 if (!t)
390 return -ENOMEM;
391
392 free(c->zone);
393 c->zone = t;
394
395 /* 1. Write new configuration file */
396 r = context_write_data_timezone(c);
397 if (r < 0) {
398 log_error_errno(r, "Failed to set time zone: %m");
399 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m");
400 }
401
402 /* 2. Tell the kernel our timezone */
403 clock_set_timezone(NULL);
404
405 if (c->local_rtc) {
406 struct timespec ts;
407 struct tm *tm;
408
409 /* 3. Sync RTC from system clock, with the new delta */
410 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
411 assert_se(tm = localtime(&ts.tv_sec));
412 clock_set_hwclock(tm);
413 }
414
415 log_struct(LOG_INFO,
416 "MESSAGE_ID=" SD_MESSAGE_TIMEZONE_CHANGE_STR,
417 "TIMEZONE=%s", c->zone,
418 LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
419 NULL);
420
421 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
422
423 return sd_bus_reply_method_return(m, NULL);
424 }
425
426 static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) {
427 int lrtc, fix_system, interactive;
428 Context *c = userdata;
429 struct timespec ts;
430 int r;
431
432 assert(m);
433 assert(c);
434
435 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
436 if (r < 0)
437 return r;
438
439 if (lrtc == c->local_rtc)
440 return sd_bus_reply_method_return(m, NULL);
441
442 r = bus_verify_polkit_async(
443 m,
444 CAP_SYS_TIME,
445 "org.freedesktop.timedate1.set-local-rtc",
446 NULL,
447 interactive,
448 UID_INVALID,
449 &c->polkit_registry,
450 error);
451 if (r < 0)
452 return r;
453 if (r == 0)
454 return 1;
455
456 c->local_rtc = lrtc;
457
458 /* 1. Write new configuration file */
459 r = context_write_data_local_rtc(c);
460 if (r < 0) {
461 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
462 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m");
463 }
464
465 /* 2. Tell the kernel our timezone */
466 clock_set_timezone(NULL);
467
468 /* 3. Synchronize clocks */
469 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
470
471 if (fix_system) {
472 struct tm tm;
473
474 /* Sync system clock from RTC; first,
475 * initialize the timezone fields of
476 * struct tm. */
477 if (c->local_rtc)
478 tm = *localtime(&ts.tv_sec);
479 else
480 tm = *gmtime(&ts.tv_sec);
481
482 /* Override the main fields of
483 * struct tm, but not the timezone
484 * fields */
485 if (clock_get_hwclock(&tm) >= 0) {
486
487 /* And set the system clock
488 * with this */
489 if (c->local_rtc)
490 ts.tv_sec = mktime(&tm);
491 else
492 ts.tv_sec = timegm(&tm);
493
494 clock_settime(CLOCK_REALTIME, &ts);
495 }
496
497 } else {
498 struct tm *tm;
499
500 /* Sync RTC from system clock */
501 if (c->local_rtc)
502 tm = localtime(&ts.tv_sec);
503 else
504 tm = gmtime(&ts.tv_sec);
505
506 clock_set_hwclock(tm);
507 }
508
509 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
510
511 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
512
513 return sd_bus_reply_method_return(m, NULL);
514 }
515
516 static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) {
517 int relative, interactive;
518 Context *c = userdata;
519 int64_t utc;
520 struct timespec ts;
521 usec_t start;
522 struct tm* tm;
523 int r;
524
525 assert(m);
526 assert(c);
527
528 if (c->use_ntp)
529 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
530
531 /* this only gets used if dbus does not provide a timestamp */
532 start = now(CLOCK_MONOTONIC);
533
534 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
535 if (r < 0)
536 return r;
537
538 if (!relative && utc <= 0)
539 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
540
541 if (relative && utc == 0)
542 return sd_bus_reply_method_return(m, NULL);
543
544 if (relative) {
545 usec_t n, x;
546
547 n = now(CLOCK_REALTIME);
548 x = n + utc;
549
550 if ((utc > 0 && x < n) ||
551 (utc < 0 && x > n))
552 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
553
554 timespec_store(&ts, x);
555 } else
556 timespec_store(&ts, (usec_t) utc);
557
558 r = bus_verify_polkit_async(
559 m,
560 CAP_SYS_TIME,
561 "org.freedesktop.timedate1.set-time",
562 NULL,
563 interactive,
564 UID_INVALID,
565 &c->polkit_registry,
566 error);
567 if (r < 0)
568 return r;
569 if (r == 0)
570 return 1;
571
572 /* adjust ts for time spent in program */
573 r = sd_bus_message_get_monotonic_usec(m, &start);
574 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
575 if (r < 0 && r != -ENODATA)
576 return r;
577
578 timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start));
579
580 /* Set system clock */
581 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
582 log_error_errno(errno, "Failed to set local time: %m");
583 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
584 }
585
586 /* Sync down to RTC */
587 if (c->local_rtc)
588 tm = localtime(&ts.tv_sec);
589 else
590 tm = gmtime(&ts.tv_sec);
591 clock_set_hwclock(tm);
592
593 log_struct(LOG_INFO,
594 "MESSAGE_ID=" SD_MESSAGE_TIME_CHANGE_STR,
595 "REALTIME="USEC_FMT, timespec_load(&ts),
596 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
597 NULL);
598
599 return sd_bus_reply_method_return(m, NULL);
600 }
601
602 static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) {
603 int enabled, interactive;
604 Context *c = userdata;
605 int r;
606
607 assert(m);
608 assert(c);
609
610 r = sd_bus_message_read(m, "bb", &enabled, &interactive);
611 if (r < 0)
612 return r;
613
614 if ((bool)enabled == c->use_ntp)
615 return sd_bus_reply_method_return(m, NULL);
616
617 r = bus_verify_polkit_async(
618 m,
619 CAP_SYS_TIME,
620 "org.freedesktop.timedate1.set-ntp",
621 NULL,
622 interactive,
623 UID_INVALID,
624 &c->polkit_registry,
625 error);
626 if (r < 0)
627 return r;
628 if (r == 0)
629 return 1;
630
631 r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled);
632 if (r < 0)
633 return r;
634
635 r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled);
636 if (r < 0)
637 return r;
638
639 c->use_ntp = enabled;
640 log_info("Set NTP to %sd", enable_disable(enabled));
641
642 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
643
644 return sd_bus_reply_method_return(m, NULL);
645 }
646
647 static const sd_bus_vtable timedate_vtable[] = {
648 SD_BUS_VTABLE_START(0),
649 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
650 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
651 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
652 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
653 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
654 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
655 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
656 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
657 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
658 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
659 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
660 SD_BUS_VTABLE_END,
661 };
662
663 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
664 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
665 int r;
666
667 assert(c);
668 assert(event);
669 assert(_bus);
670
671 r = sd_bus_default_system(&bus);
672 if (r < 0)
673 return log_error_errno(r, "Failed to get system bus connection: %m");
674
675 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
676 if (r < 0)
677 return log_error_errno(r, "Failed to register object: %m");
678
679 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
680 if (r < 0)
681 return log_error_errno(r, "Failed to register name: %m");
682
683 r = sd_bus_attach_event(bus, event, 0);
684 if (r < 0)
685 return log_error_errno(r, "Failed to attach bus to event loop: %m");
686
687 *_bus = bus;
688 bus = NULL;
689
690 return 0;
691 }
692
693 int main(int argc, char *argv[]) {
694 Context context = {};
695 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
696 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
697 int r;
698
699 log_set_target(LOG_TARGET_AUTO);
700 log_parse_environment();
701 log_open();
702
703 umask(0022);
704
705 if (argc != 1) {
706 log_error("This program takes no arguments.");
707 r = -EINVAL;
708 goto finish;
709 }
710
711 r = sd_event_default(&event);
712 if (r < 0) {
713 log_error_errno(r, "Failed to allocate event loop: %m");
714 goto finish;
715 }
716
717 sd_event_set_watchdog(event, true);
718
719 r = connect_bus(&context, event, &bus);
720 if (r < 0)
721 goto finish;
722
723 (void) sd_bus_negotiate_timestamp(bus, true);
724
725 r = context_read_data(&context);
726 if (r < 0) {
727 log_error_errno(r, "Failed to read time zone data: %m");
728 goto finish;
729 }
730
731 r = context_read_ntp(&context, bus);
732 if (r < 0) {
733 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
734 goto finish;
735 }
736
737 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
738 if (r < 0) {
739 log_error_errno(r, "Failed to run event loop: %m");
740 goto finish;
741 }
742
743 finish:
744 context_free(&context);
745
746 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
747 }