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