chiark / gitweb /
0e74c25b05abab13dbceca2979f9027b61caa297
[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 "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         unsigned can_ntp;
49         unsigned 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         context_reset(c);
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 time zone 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 time zone 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 = clock_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                                 if (ferror(f))
267                                         log_error("Failed to read NTP unit file: %m");
268
269                                 break;
270                         }
271
272                         l = strstrip(line);
273                         if (l[0] == 0 || l[0] == '#')
274                                 continue;
275
276                         if (strv_extend(&r, l) < 0) {
277                                 log_oom();
278                                 return NULL;
279                         }
280                 }
281         }
282
283         i = r;
284         r = NULL; /* avoid cleanup */
285
286         return strv_uniq(i);
287 }
288
289 static int context_read_ntp(Context *c, sd_bus *bus) {
290         _cleanup_strv_free_ char **l;
291         char **i;
292         int r;
293
294         assert(c);
295         assert(bus);
296
297         l = get_ntp_services();
298         STRV_FOREACH(i, l) {
299                 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
300                 sd_bus_message *reply = NULL;
301                 const char *s;
302
303                 r = sd_bus_call_method(
304                                 bus,
305                                 "org.freedesktop.systemd1",
306                                 "/org/freedesktop/systemd1",
307                                 "org.freedesktop.systemd1.Manager",
308                                 "GetUnitFileState",
309                                 &error,
310                                 &reply,
311                                 "s",
312                                 *i);
313
314                 if (r < 0) {
315                         /* This implementation does not exist. Try the next one. */
316                         if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND))
317                                 continue;
318
319                         return r;
320                 }
321
322                 r = sd_bus_message_read(reply, "s", &s);
323                 if (r < 0)
324                         return r;
325
326                 c->can_ntp = 1;
327                 c->use_ntp =
328                         streq(s, "enabled") ||
329                         streq(s, "enabled-runtime");
330
331                 return 0;
332         }
333
334         /* NTP is not installed. */
335         c->can_ntp = 0;
336         c->use_ntp = 0;
337
338         return 0;
339 }
340
341 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
342         _cleanup_strv_free_ char **l = NULL;
343         char **i;
344         int r;
345
346         assert(c);
347         assert(bus);
348         assert(error);
349
350         l = get_ntp_services();
351         STRV_FOREACH(i, l) {
352
353                 if (c->use_ntp)
354                         r = sd_bus_call_method(
355                                         bus,
356                                         "org.freedesktop.systemd1",
357                                         "/org/freedesktop/systemd1",
358                                         "org.freedesktop.systemd1.Manager",
359                                         "StartUnit",
360                                         error,
361                                         NULL,
362                                         "ss", *i, "replace");
363                 else
364                         r = sd_bus_call_method(
365                                         bus,
366                                         "org.freedesktop.systemd1",
367                                         "/org/freedesktop/systemd1",
368                                         "org.freedesktop.systemd1.Manager",
369                                         "StopUnit",
370                                         error,
371                                         NULL,
372                                         "ss", *i, "replace");
373
374                 if (r < 0) {
375                         if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
376                             sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
377                             sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
378                                 /* This implementation does not exist. Try the next one. */
379                                 sd_bus_error_free(error);
380                                 continue;
381                         }
382
383                         return r;
384                 }
385
386                 return 1;
387         }
388
389         sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
390         return -ENOTSUP;
391 }
392
393 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
394         _cleanup_strv_free_ char **l = NULL;
395         char **i;
396         int r;
397
398         assert(c);
399         assert(bus);
400         assert(error);
401
402         l = get_ntp_services();
403         STRV_FOREACH(i, l) {
404                 if (c->use_ntp)
405                         r = sd_bus_call_method(
406                                         bus,
407                                         "org.freedesktop.systemd1",
408                                         "/org/freedesktop/systemd1",
409                                         "org.freedesktop.systemd1.Manager",
410                                         "EnableUnitFiles",
411                                         error,
412                                         NULL,
413                                         "asbb", 1, *i, false, true);
414                 else
415                         r = sd_bus_call_method(
416                                         bus,
417                                         "org.freedesktop.systemd1",
418                                         "/org/freedesktop/systemd1",
419                                         "org.freedesktop.systemd1.Manager",
420                                         "DisableUnitFiles",
421                                         error,
422                                         NULL,
423                                         "asb", 1, *i, false);
424
425                 if (r < 0) {
426                         if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
427                                 /* This implementation does not exist. Try the next one. */
428                                 sd_bus_error_free(error);
429                                 continue;
430                         }
431
432                         return r;
433                 }
434
435                 r = sd_bus_call_method(
436                                 bus,
437                                 "org.freedesktop.systemd1",
438                                 "/org/freedesktop/systemd1",
439                                 "org.freedesktop.systemd1.Manager",
440                                 "Reload",
441                                 error,
442                                 NULL,
443                                 NULL);
444                 if (r < 0)
445                         return r;
446
447                 return 1;
448         }
449
450         sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
451         return -ENOTSUP;
452 }
453
454 static int property_get_rtc_time(
455                 sd_bus *bus,
456                 const char *path,
457                 const char *interface,
458                 const char *property,
459                 sd_bus_message *reply,
460                 void *userdata,
461                 sd_bus_error *error) {
462
463         struct tm tm;
464         usec_t t;
465         int r;
466
467         zero(tm);
468         r = clock_get_hwclock(&tm);
469         if (r == -EBUSY) {
470                 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
471                 t = 0;
472         } else if (r == -ENOENT) {
473                 log_debug("/dev/rtc not found.");
474                 t = 0; /* no RTC found */
475         } else if (r < 0)
476                 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
477         else
478                 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
479
480         return sd_bus_message_append(reply, "t", t);
481 }
482
483 static int property_get_time(
484                 sd_bus *bus,
485                 const char *path,
486                 const char *interface,
487                 const char *property,
488                 sd_bus_message *reply,
489                 void *userdata,
490                 sd_bus_error *error) {
491
492         return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
493 }
494
495 static int property_get_ntp_sync(
496                 sd_bus *bus,
497                 const char *path,
498                 const char *interface,
499                 const char *property,
500                 sd_bus_message *reply,
501                 void *userdata,
502                 sd_bus_error *error) {
503
504         return sd_bus_message_append(reply, "b", ntp_synced());
505 }
506
507 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
508         Context *c = userdata;
509         const char *z;
510         int interactive;
511         char *t;
512         int r;
513
514         assert(bus);
515         assert(m);
516         assert(c);
517
518         r = sd_bus_message_read(m, "sb", &z, &interactive);
519         if (r < 0)
520                 return r;
521
522         if (!valid_timezone(z))
523                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
524
525         if (streq_ptr(z, c->zone))
526                 return sd_bus_reply_method_return(m, NULL);
527
528         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, error, method_set_timezone, c);
529         if (r < 0)
530                 return r;
531         if (r == 0)
532                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
533
534         t = strdup(z);
535         if (!t)
536                 return -ENOMEM;
537
538         free(c->zone);
539         c->zone = t;
540
541         /* 1. Write new configuration file */
542         r = context_write_data_timezone(c);
543         if (r < 0) {
544                 log_error("Failed to set time zone: %s", strerror(-r));
545                 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
546         }
547
548         /* 2. Tell the kernel our timezone */
549         clock_set_timezone(NULL);
550
551         if (c->local_rtc) {
552                 struct timespec ts;
553                 struct tm *tm;
554
555                 /* 3. Sync RTC from system clock, with the new delta */
556                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
557                 assert_se(tm = localtime(&ts.tv_sec));
558                 clock_set_hwclock(tm);
559         }
560
561         log_struct(LOG_INFO,
562                    MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
563                    "TIMEZONE=%s", c->zone,
564                    "MESSAGE=Changed time zone to '%s'.", c->zone,
565                    NULL);
566
567         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
568
569         return sd_bus_reply_method_return(m, NULL);
570 }
571
572 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
573         int lrtc, fix_system, interactive;
574         Context *c = userdata;
575         struct timespec ts;
576         int r;
577
578         assert(bus);
579         assert(m);
580         assert(c);
581
582         r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
583         if (r < 0)
584                 return r;
585
586         if (lrtc == c->local_rtc)
587                 return sd_bus_reply_method_return(m, NULL);
588
589         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, error, method_set_local_rtc, c);
590         if (r < 0)
591                 return r;
592         if (r == 0)
593                 return 1;
594
595         c->local_rtc = lrtc;
596
597         /* 1. Write new configuration file */
598         r = context_write_data_local_rtc(c);
599         if (r < 0) {
600                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
601                 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
602         }
603
604         /* 2. Tell the kernel our timezone */
605         clock_set_timezone(NULL);
606
607         /* 3. Synchronize clocks */
608         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
609
610         if (fix_system) {
611                 struct tm tm;
612
613                 /* Sync system clock from RTC; first,
614                  * initialize the timezone fields of
615                  * struct tm. */
616                 if (c->local_rtc)
617                         tm = *localtime(&ts.tv_sec);
618                 else
619                         tm = *gmtime(&ts.tv_sec);
620
621                 /* Override the main fields of
622                  * struct tm, but not the timezone
623                  * fields */
624                 if (clock_get_hwclock(&tm) >= 0) {
625
626                         /* And set the system clock
627                          * with this */
628                         if (c->local_rtc)
629                                 ts.tv_sec = mktime(&tm);
630                         else
631                                 ts.tv_sec = timegm(&tm);
632
633                         clock_settime(CLOCK_REALTIME, &ts);
634                 }
635
636         } else {
637                 struct tm *tm;
638
639                 /* Sync RTC from system clock */
640                 if (c->local_rtc)
641                         tm = localtime(&ts.tv_sec);
642                 else
643                         tm = gmtime(&ts.tv_sec);
644
645                 clock_set_hwclock(tm);
646         }
647
648         log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
649
650         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
651
652         return sd_bus_reply_method_return(m, NULL);
653 }
654
655 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
656         int relative, interactive;
657         Context *c = userdata;
658         int64_t utc;
659         struct timespec ts;
660         struct tm* tm;
661         int r;
662
663         assert(bus);
664         assert(m);
665         assert(c);
666
667         r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
668         if (r < 0)
669                 return r;
670
671         if (!relative && utc <= 0)
672                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
673
674         if (relative && utc == 0)
675                 return sd_bus_reply_method_return(m, NULL);
676
677         if (relative) {
678                 usec_t n, x;
679
680                 n = now(CLOCK_REALTIME);
681                 x = n + utc;
682
683                 if ((utc > 0 && x < n) ||
684                     (utc < 0 && x > n))
685                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
686
687                 timespec_store(&ts, x);
688         } else
689                 timespec_store(&ts, (usec_t) utc);
690
691         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, error, method_set_time, c);
692         if (r < 0)
693                 return r;
694         if (r == 0)
695                 return 1;
696
697         /* Set system clock */
698         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
699                 log_error("Failed to set local time: %m");
700                 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
701         }
702
703         /* Sync down to RTC */
704         if (c->local_rtc)
705                 tm = localtime(&ts.tv_sec);
706         else
707                 tm = gmtime(&ts.tv_sec);
708         clock_set_hwclock(tm);
709
710         log_struct(LOG_INFO,
711                    MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
712                    "REALTIME="USEC_FMT, timespec_load(&ts),
713                    "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
714                    NULL);
715
716         return sd_bus_reply_method_return(m, NULL);
717 }
718
719 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
720         int ntp, interactive;
721         Context *c = userdata;
722         int r;
723
724         r = sd_bus_message_read(m, "bb", &ntp, &interactive);
725         if (r < 0)
726                 return r;
727
728         if ((bool)ntp == c->use_ntp)
729                 return sd_bus_reply_method_return(m, NULL);
730
731         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, error, method_set_ntp, c);
732         if (r < 0)
733                 return r;
734         if (r == 0)
735                 return 1;
736
737         c->use_ntp = ntp;
738
739         r = context_enable_ntp(c, bus, error);
740         if (r < 0)
741                 return r;
742
743         r = context_start_ntp(c, bus, error);
744         if (r < 0)
745                 return r;
746
747         log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
748
749         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
750
751         return sd_bus_reply_method_return(m, NULL);
752 }
753
754 #include <sys/capability.h>
755
756 static const sd_bus_vtable timedate_vtable[] = {
757         SD_BUS_VTABLE_START(0),
758         SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
759         SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
760         SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
761         SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
762         SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
763         SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
764         SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
765         SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
766         SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
767         SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
768         SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
769         SD_BUS_VTABLE_END,
770 };
771
772 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
773         _cleanup_bus_unref_ sd_bus *bus = NULL;
774         int r;
775
776         assert(c);
777         assert(event);
778         assert(_bus);
779
780         r = sd_bus_default_system(&bus);
781         if (r < 0) {
782                 log_error("Failed to get system bus connection: %s", strerror(-r));
783                 return r;
784         }
785
786         r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
787         if (r < 0) {
788                 log_error("Failed to register object: %s", strerror(-r));
789                 return r;
790         }
791
792         r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
793         if (r < 0) {
794                 log_error("Failed to register name: %s", strerror(-r));
795                 return r;
796         }
797
798         r = sd_bus_attach_event(bus, event, 0);
799         if (r < 0) {
800                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
801                 return r;
802         }
803
804         *_bus = bus;
805         bus = NULL;
806
807         return 0;
808 }
809
810 int main(int argc, char *argv[]) {
811         Context context = {
812                 .zone = NULL,
813                 .local_rtc = false,
814                 .can_ntp = -1,
815                 .use_ntp = -1,
816         };
817
818         _cleanup_event_unref_ sd_event *event = NULL;
819         _cleanup_bus_unref_ sd_bus *bus = NULL;
820         int r;
821
822         log_set_target(LOG_TARGET_AUTO);
823         log_parse_environment();
824         log_open();
825
826         umask(0022);
827
828         if (argc != 1) {
829                 log_error("This program takes no arguments.");
830                 r = -EINVAL;
831                 goto finish;
832         }
833
834         r = sd_event_default(&event);
835         if (r < 0) {
836                 log_error("Failed to allocate event loop: %s", strerror(-r));
837                 goto finish;
838         }
839
840         sd_event_set_watchdog(event, true);
841
842         r = connect_bus(&context, event, &bus);
843         if (r < 0)
844                 goto finish;
845
846         r = context_read_data(&context);
847         if (r < 0) {
848                 log_error("Failed to read time zone data: %s", strerror(-r));
849                 goto finish;
850         }
851
852         r = context_read_ntp(&context, bus);
853         if (r < 0) {
854                 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
855                 goto finish;
856         }
857
858         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
859         if (r < 0) {
860                 log_error("Failed to run event loop: %s", strerror(-r));
861                 goto finish;
862         }
863
864 finish:
865         context_free(&context, bus);
866
867         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
868 }