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