chiark / gitweb /
16e24c1b8efd0f332dc29651bf32182036baeaec
[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 "hwclock.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 "event-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 typedef struct Context {
46         char *zone;
47         bool local_rtc;
48         int can_ntp;
49         int use_ntp;
50         Hashmap *polkit_registry;
51 } Context;
52
53 static void context_reset(Context *c) {
54         assert(c);
55
56         free(c->zone);
57         c->zone = NULL;
58
59         c->local_rtc = false;
60         c->can_ntp = c->use_ntp = -1;
61 }
62
63 static void context_free(Context *c, sd_bus *bus) {
64         assert(c);
65
66         free(c->zone);
67         bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
68 }
69
70 static bool valid_timezone(const char *name) {
71         const char *p;
72         char *t;
73         bool slash = false;
74         int r;
75         struct stat st;
76
77         assert(name);
78
79         if (*name == '/' || *name == 0)
80                 return false;
81
82         for (p = name; *p; p++) {
83                 if (!(*p >= '0' && *p <= '9') &&
84                     !(*p >= 'a' && *p <= 'z') &&
85                     !(*p >= 'A' && *p <= 'Z') &&
86                     !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
87                         return false;
88
89                 if (*p == '/') {
90
91                         if (slash)
92                                 return false;
93
94                         slash = true;
95                 } else
96                         slash = false;
97         }
98
99         if (slash)
100                 return false;
101
102         t = strappend("/usr/share/zoneinfo/", name);
103         if (!t)
104                 return false;
105
106         r = stat(t, &st);
107         free(t);
108
109         if (r < 0)
110                 return false;
111
112         if (!S_ISREG(st.st_mode))
113                 return false;
114
115         return true;
116 }
117
118 static int context_read_data(Context *c) {
119         _cleanup_free_ char *t = NULL;
120         int r;
121
122         assert(c);
123
124         context_reset(c);
125
126         r = readlink_malloc("/etc/localtime", &t);
127         if (r < 0) {
128                 if (r == -EINVAL)
129                         log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
130                 else
131                         log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
132         } else {
133                 const char *e;
134
135                 e = path_startswith(t, "/usr/share/zoneinfo/");
136                 if (!e)
137                         e = path_startswith(t, "../usr/share/zoneinfo/");
138
139                 if (!e)
140                         log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
141                 else {
142                         c->zone = strdup(e);
143                         if (!c->zone)
144                                 return log_oom();
145
146                         goto have_timezone;
147                 }
148         }
149
150 have_timezone:
151         if (isempty(c->zone)) {
152                 free(c->zone);
153                 c->zone = NULL;
154         }
155
156         c->local_rtc = hwclock_is_localtime() > 0;
157
158         return 0;
159 }
160
161 static int context_write_data_timezone(Context *c) {
162         _cleanup_free_ char *p = NULL;
163         int r = 0;
164
165         assert(c);
166
167         if (isempty(c->zone)) {
168                 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
169                         r = -errno;
170
171                 return r;
172         }
173
174         p = strappend("../usr/share/zoneinfo/", c->zone);
175         if (!p)
176                 return log_oom();
177
178         r = symlink_atomic(p, "/etc/localtime");
179         if (r < 0)
180                 return r;
181
182         return 0;
183 }
184
185 static int context_write_data_local_rtc(Context *c) {
186         int r;
187         _cleanup_free_ char *s = NULL, *w = NULL;
188
189         assert(c);
190
191         r = read_full_file("/etc/adjtime", &s, NULL);
192         if (r < 0) {
193                 if (r != -ENOENT)
194                         return r;
195
196                 if (!c->local_rtc)
197                         return 0;
198
199                 w = strdup(NULL_ADJTIME_LOCAL);
200                 if (!w)
201                         return -ENOMEM;
202         } else {
203                 char *p, *e;
204                 size_t a, b;
205
206                 p = strchr(s, '\n');
207                 if (!p)
208                         return -EIO;
209
210                 p = strchr(p+1, '\n');
211                 if (!p)
212                         return -EIO;
213
214                 p++;
215                 e = strchr(p, '\n');
216                 if (!e)
217                         return -EIO;
218
219                 a = p - s;
220                 b = strlen(e);
221
222                 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
223                 if (!w)
224                         return -ENOMEM;
225
226                 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
227
228                 if (streq(w, NULL_ADJTIME_UTC)) {
229                         if (unlink("/etc/adjtime") < 0)
230                                 if (errno != ENOENT)
231                                         return -errno;
232
233                         return 0;
234                 }
235         }
236
237         label_init("/etc");
238         return write_string_file_atomic_label("/etc/adjtime", w);
239 }
240
241 static char** get_ntp_services(void) {
242         _cleanup_strv_free_ char **r = NULL, **files = NULL;
243         char **i;
244         int k;
245
246         k = conf_files_list(&files, ".list", NULL,
247                             "/etc/systemd/ntp-units.d",
248                             "/run/systemd/ntp-units.d",
249                             "/usr/local/lib/systemd/ntp-units.d",
250                             "/usr/lib/systemd/ntp-units.d",
251                             NULL);
252         if (k < 0)
253                 return NULL;
254
255         STRV_FOREACH(i, files) {
256                 _cleanup_fclose_ FILE *f;
257
258                 f = fopen(*i, "re");
259                 if (!f)
260                         continue;
261
262                 for (;;) {
263                         char line[PATH_MAX], *l;
264
265                         if (!fgets(line, sizeof(line), f)) {
266
267                                 if (ferror(f))
268                                         log_error("Failed to read NTP units file: %m");
269
270                                 break;
271                         }
272
273                         l = strstrip(line);
274                         if (l[0] == 0 || l[0] == '#')
275                                 continue;
276
277                         if (strv_extend(&r, l) < 0) {
278                                 log_oom();
279                                 return NULL;
280                         }
281                 }
282         }
283
284         i = r;
285         r = NULL; /* avoid cleanup */
286
287         return strv_uniq(i);
288 }
289
290 static int context_read_ntp(Context *c, sd_bus *bus) {
291         _cleanup_strv_free_ char **l;
292         char **i;
293         int r;
294
295         assert(c);
296         assert(bus);
297
298         l = get_ntp_services();
299         STRV_FOREACH(i, l) {
300                 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
301                 sd_bus_message *reply = NULL;
302                 const char *s;
303
304                 r = sd_bus_call_method(
305                                 bus,
306                                 "org.freedesktop.systemd1",
307                                 "/org/freedesktop/systemd1",
308                                 "org.freedesktop.systemd1.Manager",
309                                 "GetUnitFileState",
310                                 &error,
311                                 &reply,
312                                 "s",
313                                 *i);
314
315                 if (r < 0) {
316                         /* This implementation does not exist, try next one */
317                         if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND))
318                                 continue;
319
320                         return r;
321                 }
322
323                 r = sd_bus_message_read(reply, "s", &s);
324                 if (r < 0)
325                         return r;
326
327                 c->can_ntp = 1;
328                 c->use_ntp =
329                         streq(s, "enabled") ||
330                         streq(s, "enabled-runtime");
331
332                 return 0;
333         }
334
335         /* NTP is not installed. */
336         c->can_ntp = 0;
337         c->use_ntp = 0;
338
339         return 0;
340 }
341
342 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
343         _cleanup_strv_free_ char **l = NULL;
344         char **i;
345         int r;
346
347         assert(c);
348         assert(bus);
349         assert(error);
350
351         l = get_ntp_services();
352         STRV_FOREACH(i, l) {
353
354                 if (c->use_ntp)
355                         r = sd_bus_call_method(
356                                         bus,
357                                         "org.freedesktop.systemd1",
358                                         "/org/freedesktop/systemd1",
359                                         "org.freedesktop.systemd1.Manager",
360                                         "StartUnit",
361                                         error,
362                                         NULL,
363                                         "ss", *i, "replace");
364                 else
365                         r = sd_bus_call_method(
366                                         bus,
367                                         "org.freedesktop.systemd1",
368                                         "/org/freedesktop/systemd1",
369                                         "org.freedesktop.systemd1.Manager",
370                                         "StopUnit",
371                                         error,
372                                         NULL,
373                                         "ss", *i, "replace");
374
375                 if (r < 0) {
376                         if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
377                             sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
378                             sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
379                                 /* This implementation does not exist, try next one */
380                                 sd_bus_error_free(error);
381                                 continue;
382                         }
383
384                         return r;
385                 }
386
387                 return 1;
388         }
389
390         sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
391         return -ENOTSUP;
392 }
393
394 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
395         _cleanup_strv_free_ char **l = NULL;
396         char **i;
397         int r;
398
399         assert(c);
400         assert(bus);
401         assert(error);
402
403         l = get_ntp_services();
404         STRV_FOREACH(i, l) {
405                 if (c->use_ntp)
406                         r = sd_bus_call_method(
407                                         bus,
408                                         "org.freedesktop.systemd1",
409                                         "/org/freedesktop/systemd1",
410                                         "org.freedesktop.systemd1.Manager",
411                                         "EnableUnitFiles",
412                                         error,
413                                         NULL,
414                                         "asbb", 1, *i, false, true);
415                 else
416                         r = sd_bus_call_method(
417                                         bus,
418                                         "org.freedesktop.systemd1",
419                                         "/org/freedesktop/systemd1",
420                                         "org.freedesktop.systemd1.Manager",
421                                         "DisableUnitFiles",
422                                         error,
423                                         NULL,
424                                         "asb", 1, *i, false);
425
426                 if (r < 0) {
427                         if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
428                                 /* This implementation does not exist, try next one */
429                                 sd_bus_error_free(error);
430                                 continue;
431                         }
432
433                         return r;
434                 }
435
436                 r = sd_bus_call_method(
437                                 bus,
438                                 "org.freedesktop.systemd1",
439                                 "/org/freedesktop/systemd1",
440                                 "org.freedesktop.systemd1.Manager",
441                                 "Reload",
442                                 error,
443                                 NULL,
444                                 NULL);
445                 if (r < 0)
446                         return r;
447
448                 return 1;
449         }
450
451         sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
452         return -ENOTSUP;
453 }
454
455 static int property_get_rtc_time(
456                 sd_bus *bus,
457                 const char *path,
458                 const char *interface,
459                 const char *property,
460                 sd_bus_message *reply,
461                 sd_bus_error *error,
462                 void *userdata) {
463
464         struct tm tm;
465         usec_t t;
466         int r;
467
468         zero(tm);
469         r = hwclock_get_time(&tm);
470         if (r < 0) {
471                 sd_bus_error_set_errnof(error, -r, "Failed to read RTC: %s", strerror(-r));
472                 return r;
473         }
474
475         t = (usec_t) mktime(&tm) * USEC_PER_SEC;
476
477         r = sd_bus_message_append(reply, "t", t);
478         if (r < 0)
479                 return r;
480
481         return 1;
482 }
483
484 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata) {
485         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
486         Context *c = userdata;
487         const char *z;
488         bool interactive;
489         char *t;
490         int r;
491
492         r = sd_bus_message_read(m, "sb", &z, &interactive);
493         if (r < 0)
494                 return sd_bus_reply_method_errno(bus, m, r, NULL);
495
496         if (!valid_timezone(z))
497                 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
498
499         if (streq_ptr(z, c->zone))
500                 return sd_bus_reply_method_return(bus, m, NULL);
501
502         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, &error, method_set_timezone, c);
503         if (r < 0)
504                 return sd_bus_reply_method_errno(bus, m, r, &error);
505         if (r == 0)
506                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
507
508         t = strdup(z);
509         if (!t)
510                 return log_oom();
511
512         free(c->zone);
513         c->zone = t;
514
515         /* 1. Write new configuration file */
516         r = context_write_data_timezone(c);
517         if (r < 0) {
518                 log_error("Failed to set timezone: %s", strerror(-r));
519                 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set timezone: %s", strerror(-r));
520         }
521
522         /* 2. Tell the kernel our timezone */
523         hwclock_set_timezone(NULL);
524
525         if (c->local_rtc) {
526                 struct timespec ts;
527                 struct tm *tm;
528
529                 /* 3. Sync RTC from system clock, with the new delta */
530                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
531                 assert_se(tm = localtime(&ts.tv_sec));
532                 hwclock_set_time(tm);
533         }
534
535         log_struct(LOG_INFO,
536                    MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
537                    "TIMEZONE=%s", c->zone,
538                    "MESSAGE=Changed timezone to '%s'.", c->zone,
539                    NULL);
540
541         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
542
543         return sd_bus_reply_method_return(bus, m, NULL);
544 }
545
546 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata) {
547         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
548         bool lrtc, fix_system, interactive;
549         Context *c = userdata;
550         struct timespec ts;
551         int r;
552
553         assert(bus);
554         assert(m);
555         assert(c);
556
557         r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
558         if (r < 0)
559                 return sd_bus_reply_method_errno(bus, m, r, NULL);
560
561         if (lrtc == c->local_rtc)
562                 return sd_bus_reply_method_return(bus, m, NULL);
563
564         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, &error, method_set_local_rtc, c);
565         if (r < 0)
566                 return sd_bus_reply_method_errno(bus, m, r, &error);
567         if (r == 0)
568                 return 1;
569
570         c->local_rtc = lrtc;
571
572         /* 1. Write new configuration file */
573         r = context_write_data_local_rtc(c);
574         if (r < 0) {
575                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
576                 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
577         }
578
579         /* 2. Tell the kernel our timezone */
580         hwclock_set_timezone(NULL);
581
582         /* 3. Synchronize clocks */
583         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
584
585         if (fix_system) {
586                 struct tm tm;
587
588                 /* Sync system clock from RTC; first,
589                  * initialize the timezone fields of
590                  * struct tm. */
591                 if (c->local_rtc)
592                         tm = *localtime(&ts.tv_sec);
593                 else
594                         tm = *gmtime(&ts.tv_sec);
595
596                 /* Override the main fields of
597                  * struct tm, but not the timezone
598                  * fields */
599                 if (hwclock_get_time(&tm) >= 0) {
600
601                         /* And set the system clock
602                          * with this */
603                         if (c->local_rtc)
604                                 ts.tv_sec = mktime(&tm);
605                         else
606                                 ts.tv_sec = timegm(&tm);
607
608                         clock_settime(CLOCK_REALTIME, &ts);
609                 }
610
611         } else {
612                 struct tm *tm;
613
614                 /* Sync RTC from system clock */
615                 if (c->local_rtc)
616                         tm = localtime(&ts.tv_sec);
617                 else
618                         tm = gmtime(&ts.tv_sec);
619
620                 hwclock_set_time(tm);
621         }
622
623         log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
624
625         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
626
627         return sd_bus_reply_method_return(bus, m, NULL);
628 }
629
630 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata) {
631         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
632         bool relative, interactive;
633         Context *c = userdata;
634         int64_t utc;
635         struct timespec ts;
636         struct tm* tm;
637         int r;
638
639         assert(bus);
640         assert(m);
641         assert(c);
642
643         r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
644         if (r < 0)
645                 return sd_bus_reply_method_errno(bus, m, r, NULL);
646
647         if (!relative && utc <= 0)
648                 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
649
650         if (relative && utc == 0)
651                 return sd_bus_reply_method_return(bus, m, NULL);
652
653         if (relative) {
654                 usec_t n, x;
655
656                 n = now(CLOCK_REALTIME);
657                 x = n + utc;
658
659                 if ((utc > 0 && x < n) ||
660                     (utc < 0 && x > n))
661                         return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
662
663                 timespec_store(&ts, x);
664         } else
665                 timespec_store(&ts, (usec_t) utc);
666
667         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, &error, method_set_time, c);
668         if (r < 0)
669                 return sd_bus_reply_method_errno(bus, m, r, &error);
670         if (r == 0)
671                 return 1;
672
673         /* Set system clock */
674         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
675                 log_error("Failed to set local time: %m");
676                 return sd_bus_reply_method_errnof(bus, m, errno, "Failed to set local time: %m");
677         }
678
679         /* Sync down to RTC */
680         if (c->local_rtc)
681                 tm = localtime(&ts.tv_sec);
682         else
683                 tm = gmtime(&ts.tv_sec);
684
685         hwclock_set_time(tm);
686
687         log_struct(LOG_INFO,
688                    MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
689                    "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
690                    "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
691                    NULL);
692
693         return sd_bus_reply_method_return(bus, m, NULL);
694 }
695
696 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata) {
697         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
698         bool ntp, interactive;
699         Context *c = userdata;
700         int r;
701
702         r = sd_bus_message_read(m, "bb", &ntp, &interactive);
703         if (r < 0)
704                 return sd_bus_reply_method_errno(bus, m, r, NULL);
705
706         if (ntp == c->use_ntp)
707                 return sd_bus_reply_method_return(bus, m, NULL);
708
709         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, &error, method_set_ntp, c);
710         if (r < 0)
711                 return sd_bus_reply_method_errno(bus, m, r, &error);
712         if (r == 0)
713                 return 1;
714
715         c->use_ntp = ntp;
716
717         r = context_enable_ntp(c, bus, &error);
718         if (r < 0)
719                 return sd_bus_reply_method_errno(bus, m, r, &error);
720
721         r = context_start_ntp(c, bus, &error);
722         if (r < 0)
723                 return sd_bus_reply_method_errno(bus, m, r, &error);
724
725         log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
726
727         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
728
729         return sd_bus_reply_method_return(bus, m, NULL);
730 }
731
732 static const sd_bus_vtable timedate_vtable[] = {
733         SD_BUS_VTABLE_START(0),
734         SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
735         SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
736         SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
737         SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
738         SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
739         SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, 0),
740         SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, 0),
741         SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, 0),
742         SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, 0),
743         SD_BUS_VTABLE_END,
744 };
745
746 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
747         _cleanup_bus_unref_ sd_bus *bus = NULL;
748         int r;
749
750         assert(c);
751         assert(event);
752         assert(_bus);
753
754         r = sd_bus_open_system(&bus);
755         if (r < 0) {
756                 log_error("Failed to get system bus connection: %s", strerror(-r));
757                 return r;
758         }
759
760         r = sd_bus_add_object_vtable(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
761         if (r < 0) {
762                 log_error("Failed to register object: %s", strerror(-r));
763                 return r;
764         }
765
766         r = sd_bus_request_name(bus, "org.freedesktop.timedate1", SD_BUS_NAME_DO_NOT_QUEUE);
767         if (r < 0) {
768                 log_error("Failed to register name: %s", strerror(-r));
769                 return r;
770         }
771
772         if (r != SD_BUS_NAME_PRIMARY_OWNER) {
773                 log_error("Failed to acquire name.");
774                 return -EEXIST;
775         }
776
777         r = sd_bus_attach_event(bus, event, 0);
778         if (r < 0) {
779                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
780                 return r;
781         }
782
783         *_bus = bus;
784         bus = NULL;
785
786         return 0;
787 }
788
789 int main(int argc, char *argv[]) {
790         Context context = {
791                 .zone = NULL,
792                 .local_rtc = false,
793                 .can_ntp = -1,
794                 .use_ntp = -1,
795         };
796
797         _cleanup_event_unref_ sd_event *event = NULL;
798         _cleanup_bus_unref_ sd_bus *bus = NULL;
799         int r;
800
801         log_set_target(LOG_TARGET_AUTO);
802         log_set_max_level(LOG_DEBUG);
803         log_parse_environment();
804         log_open();
805
806         umask(0022);
807
808         if (argc != 1) {
809                 log_error("This program takes no arguments.");
810                 r = -EINVAL;
811                 goto finish;
812         }
813
814         r = sd_event_new(&event);
815         if (r < 0) {
816                 log_error("Failed to allocate event loop: %s", strerror(-r));
817                 goto finish;
818         }
819
820         r = connect_bus(&context, event, &bus);
821         if (r < 0)
822                 goto finish;
823
824         r = context_read_data(&context);
825         if (r < 0) {
826                 log_error("Failed to read timezone data: %s", strerror(-r));
827                 goto finish;
828         }
829
830         r = context_read_ntp(&context, bus);
831         if (r < 0) {
832                 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
833                 goto finish;
834         }
835
836         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC);
837         if (r < 0) {
838                 log_error("Failed to run event loop: %s", strerror(-r));
839                 goto finish;
840         }
841
842         sd_bus_flush(bus);
843         r = 0;
844
845 finish:
846         context_free(&context, bus);
847
848         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
849 }