chiark / gitweb /
2ead4b99993b91c44f099b0e25a52ee7d0bf8cf0
[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         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                             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 property_get_time(
485                 sd_bus *bus,
486                 const char *path,
487                 const char *interface,
488                 const char *property,
489                 sd_bus_message *reply,
490                 sd_bus_error *error,
491                 void *userdata) {
492
493         int r;
494
495         r = sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
496         if (r < 0)
497                 return r;
498
499         return 1;
500 }
501
502 static int property_get_ntp_sync(
503                 sd_bus *bus,
504                 const char *path,
505                 const char *interface,
506                 const char *property,
507                 sd_bus_message *reply,
508                 sd_bus_error *error,
509                 void *userdata) {
510
511         int r;
512
513         r = sd_bus_message_append(reply, "b", ntp_synced());
514         if (r < 0)
515                 return r;
516
517         return 1;
518 }
519
520 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata) {
521         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
522         Context *c = userdata;
523         const char *z;
524         bool interactive;
525         char *t;
526         int r;
527
528         assert(bus);
529         assert(m);
530         assert(c);
531
532         r = sd_bus_message_read(m, "sb", &z, &interactive);
533         if (r < 0)
534                 return sd_bus_reply_method_errno(bus, m, r, NULL);
535
536         if (!valid_timezone(z))
537                 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
538
539         if (streq_ptr(z, c->zone))
540                 return sd_bus_reply_method_return(bus, m, NULL);
541
542         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, &error, method_set_timezone, c);
543         if (r < 0)
544                 return sd_bus_reply_method_errno(bus, m, r, &error);
545         if (r == 0)
546                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
547
548         t = strdup(z);
549         if (!t)
550                 return log_oom();
551
552         free(c->zone);
553         c->zone = t;
554
555         /* 1. Write new configuration file */
556         r = context_write_data_timezone(c);
557         if (r < 0) {
558                 log_error("Failed to set timezone: %s", strerror(-r));
559                 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set timezone: %s", strerror(-r));
560         }
561
562         /* 2. Tell the kernel our timezone */
563         hwclock_set_timezone(NULL);
564
565         if (c->local_rtc) {
566                 struct timespec ts;
567                 struct tm *tm;
568
569                 /* 3. Sync RTC from system clock, with the new delta */
570                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
571                 assert_se(tm = localtime(&ts.tv_sec));
572                 hwclock_set_time(tm);
573         }
574
575         log_struct(LOG_INFO,
576                    MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
577                    "TIMEZONE=%s", c->zone,
578                    "MESSAGE=Changed timezone to '%s'.", c->zone,
579                    NULL);
580
581         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
582
583         return sd_bus_reply_method_return(bus, m, NULL);
584 }
585
586 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata) {
587         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
588         bool lrtc, fix_system, interactive;
589         Context *c = userdata;
590         struct timespec ts;
591         int r;
592
593         assert(bus);
594         assert(m);
595         assert(c);
596
597         r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
598         if (r < 0)
599                 return sd_bus_reply_method_errno(bus, m, r, NULL);
600
601         if (lrtc == c->local_rtc)
602                 return sd_bus_reply_method_return(bus, m, NULL);
603
604         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, &error, method_set_local_rtc, c);
605         if (r < 0)
606                 return sd_bus_reply_method_errno(bus, m, r, &error);
607         if (r == 0)
608                 return 1;
609
610         c->local_rtc = lrtc;
611
612         /* 1. Write new configuration file */
613         r = context_write_data_local_rtc(c);
614         if (r < 0) {
615                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
616                 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
617         }
618
619         /* 2. Tell the kernel our timezone */
620         hwclock_set_timezone(NULL);
621
622         /* 3. Synchronize clocks */
623         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
624
625         if (fix_system) {
626                 struct tm tm;
627
628                 /* Sync system clock from RTC; first,
629                  * initialize the timezone fields of
630                  * struct tm. */
631                 if (c->local_rtc)
632                         tm = *localtime(&ts.tv_sec);
633                 else
634                         tm = *gmtime(&ts.tv_sec);
635
636                 /* Override the main fields of
637                  * struct tm, but not the timezone
638                  * fields */
639                 if (hwclock_get_time(&tm) >= 0) {
640
641                         /* And set the system clock
642                          * with this */
643                         if (c->local_rtc)
644                                 ts.tv_sec = mktime(&tm);
645                         else
646                                 ts.tv_sec = timegm(&tm);
647
648                         clock_settime(CLOCK_REALTIME, &ts);
649                 }
650
651         } else {
652                 struct tm *tm;
653
654                 /* Sync RTC from system clock */
655                 if (c->local_rtc)
656                         tm = localtime(&ts.tv_sec);
657                 else
658                         tm = gmtime(&ts.tv_sec);
659
660                 hwclock_set_time(tm);
661         }
662
663         log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
664
665         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
666
667         return sd_bus_reply_method_return(bus, m, NULL);
668 }
669
670 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata) {
671         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
672         bool relative, interactive;
673         Context *c = userdata;
674         int64_t utc;
675         struct timespec ts;
676         struct tm* tm;
677         int r;
678
679         assert(bus);
680         assert(m);
681         assert(c);
682
683         r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
684         if (r < 0)
685                 return sd_bus_reply_method_errno(bus, m, r, NULL);
686
687         if (!relative && utc <= 0)
688                 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
689
690         if (relative && utc == 0)
691                 return sd_bus_reply_method_return(bus, m, NULL);
692
693         if (relative) {
694                 usec_t n, x;
695
696                 n = now(CLOCK_REALTIME);
697                 x = n + utc;
698
699                 if ((utc > 0 && x < n) ||
700                     (utc < 0 && x > n))
701                         return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
702
703                 timespec_store(&ts, x);
704         } else
705                 timespec_store(&ts, (usec_t) utc);
706
707         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, &error, method_set_time, c);
708         if (r < 0)
709                 return sd_bus_reply_method_errno(bus, m, r, &error);
710         if (r == 0)
711                 return 1;
712
713         /* Set system clock */
714         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
715                 log_error("Failed to set local time: %m");
716                 return sd_bus_reply_method_errnof(bus, m, errno, "Failed to set local time: %m");
717         }
718
719         /* Sync down to RTC */
720         if (c->local_rtc)
721                 tm = localtime(&ts.tv_sec);
722         else
723                 tm = gmtime(&ts.tv_sec);
724
725         hwclock_set_time(tm);
726
727         log_struct(LOG_INFO,
728                    MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
729                    "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
730                    "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
731                    NULL);
732
733         return sd_bus_reply_method_return(bus, m, NULL);
734 }
735
736 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata) {
737         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
738         bool ntp, interactive;
739         Context *c = userdata;
740         int r;
741
742         r = sd_bus_message_read(m, "bb", &ntp, &interactive);
743         if (r < 0)
744                 return sd_bus_reply_method_errno(bus, m, r, NULL);
745
746         if (ntp == c->use_ntp)
747                 return sd_bus_reply_method_return(bus, m, NULL);
748
749         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, &error, method_set_ntp, c);
750         if (r < 0)
751                 return sd_bus_reply_method_errno(bus, m, r, &error);
752         if (r == 0)
753                 return 1;
754
755         c->use_ntp = ntp;
756
757         r = context_enable_ntp(c, bus, &error);
758         if (r < 0)
759                 return sd_bus_reply_method_errno(bus, m, r, &error);
760
761         r = context_start_ntp(c, bus, &error);
762         if (r < 0)
763                 return sd_bus_reply_method_errno(bus, m, r, &error);
764
765         log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
766
767         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
768
769         return sd_bus_reply_method_return(bus, m, NULL);
770 }
771
772 static const sd_bus_vtable timedate_vtable[] = {
773         SD_BUS_VTABLE_START(0),
774         SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
775         SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
776         SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
777         SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
778         SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
779         SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
780         SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
781         SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, 0),
782         SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, 0),
783         SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, 0),
784         SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, 0),
785         SD_BUS_VTABLE_END,
786 };
787
788 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
789         _cleanup_bus_unref_ sd_bus *bus = NULL;
790         int r;
791
792         assert(c);
793         assert(event);
794         assert(_bus);
795
796         r = sd_bus_open_system(&bus);
797         if (r < 0) {
798                 log_error("Failed to get system bus connection: %s", strerror(-r));
799                 return r;
800         }
801
802         r = sd_bus_add_object_vtable(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
803         if (r < 0) {
804                 log_error("Failed to register object: %s", strerror(-r));
805                 return r;
806         }
807
808         r = sd_bus_request_name(bus, "org.freedesktop.timedate1", SD_BUS_NAME_DO_NOT_QUEUE);
809         if (r < 0) {
810                 log_error("Failed to register name: %s", strerror(-r));
811                 return r;
812         }
813
814         if (r != SD_BUS_NAME_PRIMARY_OWNER) {
815                 log_error("Failed to acquire name.");
816                 return -EEXIST;
817         }
818
819         r = sd_bus_attach_event(bus, event, 0);
820         if (r < 0) {
821                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
822                 return r;
823         }
824
825         *_bus = bus;
826         bus = NULL;
827
828         return 0;
829 }
830
831 int main(int argc, char *argv[]) {
832         Context context = {
833                 .zone = NULL,
834                 .local_rtc = false,
835                 .can_ntp = -1,
836                 .use_ntp = -1,
837         };
838
839         _cleanup_event_unref_ sd_event *event = NULL;
840         _cleanup_bus_unref_ sd_bus *bus = NULL;
841         int r;
842
843         log_set_target(LOG_TARGET_AUTO);
844         log_parse_environment();
845         log_open();
846
847         umask(0022);
848
849         if (argc != 1) {
850                 log_error("This program takes no arguments.");
851                 r = -EINVAL;
852                 goto finish;
853         }
854
855         r = sd_event_new(&event);
856         if (r < 0) {
857                 log_error("Failed to allocate event loop: %s", strerror(-r));
858                 goto finish;
859         }
860
861         r = connect_bus(&context, event, &bus);
862         if (r < 0)
863                 goto finish;
864
865         r = context_read_data(&context);
866         if (r < 0) {
867                 log_error("Failed to read timezone data: %s", strerror(-r));
868                 goto finish;
869         }
870
871         r = context_read_ntp(&context, bus);
872         if (r < 0) {
873                 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
874                 goto finish;
875         }
876
877         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC);
878         if (r < 0) {
879                 log_error("Failed to run event loop: %s", strerror(-r));
880                 goto finish;
881         }
882
883         r = 0;
884
885 finish:
886         context_free(&context, bus);
887
888         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
889 }