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