chiark / gitweb /
9ca2eec5a8aa18ea3ae7ad0443c8c5721a3f14ff
[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 <dbus/dbus.h>
23
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "systemd/sd-id128.h"
29 #include "systemd/sd-messages.h"
30 #include "util.h"
31 #include "strv.h"
32 #include "dbus-common.h"
33 #include "polkit.h"
34 #include "def.h"
35 #include "hwclock.h"
36 #include "conf-files.h"
37
38 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
39 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
40
41 #define INTERFACE                                                       \
42         " <interface name=\"org.freedesktop.timedate1\">\n"             \
43         "  <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n"  \
44         "  <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n"  \
45         "  <property name=\"NTP\" type=\"b\" access=\"read\"/>\n"       \
46         "  <method name=\"SetTime\">\n"                                 \
47         "   <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n"     \
48         "   <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n"     \
49         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
50         "  </method>\n"                                                 \
51         "  <method name=\"SetTimezone\">\n"                             \
52         "   <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n"     \
53         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
54         "  </method>\n"                                                 \
55         "  <method name=\"SetLocalRTC\">\n"                             \
56         "   <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n"    \
57         "   <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n"   \
58         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
59         "  </method>\n"                                                 \
60         "  <method name=\"SetNTP\">\n"                                  \
61         "   <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n"      \
62         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
63         "  </method>\n"                                                 \
64         " </interface>\n"
65
66 #define INTROSPECTION                                                   \
67         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
68         "<node>\n"                                                      \
69         INTERFACE                                                       \
70         BUS_PROPERTIES_INTERFACE                                        \
71         BUS_INTROSPECTABLE_INTERFACE                                    \
72         BUS_PEER_INTERFACE                                              \
73         "</node>\n"
74
75 #define INTERFACES_LIST                         \
76         BUS_GENERIC_INTERFACES_LIST             \
77         "org.freedesktop.timedate1\0"
78
79 /* Must start and end with '/' */
80 #define ZONEINFO_PATH "/usr/share/zoneinfo/"
81
82 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
83
84 typedef struct TZ {
85         char *zone;
86         bool local_rtc;
87         int use_ntp;
88 } TZ;
89
90 static TZ tz = {
91         .use_ntp = -1,
92 };
93
94 static usec_t remain_until;
95
96 static void free_data(void) {
97         free(tz.zone);
98         tz.zone = NULL;
99
100         tz.local_rtc = false;
101 }
102
103 static bool valid_timezone(const char *name) {
104         const char *p;
105         char *t;
106         bool slash = false;
107         int r;
108         struct stat st;
109
110         assert(name);
111
112         if (*name == '/' || *name == 0)
113                 return false;
114
115         for (p = name; *p; p++) {
116                 if (!(*p >= '0' && *p <= '9') &&
117                     !(*p >= 'a' && *p <= 'z') &&
118                     !(*p >= 'A' && *p <= 'Z') &&
119                     !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
120                         return false;
121
122                 if (*p == '/') {
123
124                         if (slash)
125                                 return false;
126
127                         slash = true;
128                 } else
129                         slash = false;
130         }
131
132         if (slash)
133                 return false;
134
135         t = strappend(ZONEINFO_PATH, name);
136         if (!t)
137                 return false;
138
139         r = stat(t, &st);
140         free(t);
141
142         if (r < 0)
143                 return false;
144
145         if (!S_ISREG(st.st_mode))
146                 return false;
147
148         return true;
149 }
150
151 static void verify_timezone(void) {
152         char *p, *a = NULL, *b = NULL;
153         size_t l, q;
154         int j, k;
155
156         if (!tz.zone)
157                 return;
158
159         p = strappend(ZONEINFO_PATH, tz.zone);
160         if (!p) {
161                 log_oom();
162                 return;
163         }
164
165         k = read_full_file(p, &b, &q);
166         free(p);
167
168         j = read_full_file("/etc/localtime", &a, &l);
169
170         if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) {
171                 log_warning("/etc/localtime and /etc/timezone out of sync.");
172                 free(tz.zone);
173                 tz.zone = NULL;
174         }
175
176         free(a);
177         free(b);
178 }
179
180 static int read_data(void) {
181         int r;
182         char *t = NULL;
183
184         free_data();
185
186         r = readlink_malloc("/etc/localtime", &t);
187         if (r < 0) {
188                 if (r == -EINVAL)
189                         log_warning("/etc/localtime should be a symbolic link to a timezone data file in " ZONEINFO_PATH);
190                 else
191                         log_warning("Failed to get target of %s: %s", "/etc/localtime", strerror(-r));
192         } else {
193                 /* we only support the trivial relative link of (/etc/)..$ABSOLUTE */
194                 int rel_link_offset = startswith(t, "..") ? strlen("..") : 0;
195
196                 if (!startswith(t + rel_link_offset, ZONEINFO_PATH))
197                         log_warning("/etc/localtime should be a symbolic link to a timezone data file in " ZONEINFO_PATH);
198                 else {
199                         tz.zone = strdup(t + rel_link_offset + strlen(ZONEINFO_PATH));
200                         free(t);
201                         if (!tz.zone)
202                                 return log_oom();
203
204                         goto have_timezone;
205                 }
206         }
207
208         free(t);
209
210         r = read_one_line_file("/etc/timezone", &tz.zone);
211         if (r < 0) {
212                 if (r != -ENOENT)
213                         log_warning("Failed to read /etc/timezone: %s", strerror(-r));
214
215 #ifdef TARGET_FEDORA
216                 r = parse_env_file("/etc/sysconfig/clock", NEWLINE,
217                                    "ZONE", &tz.zone,
218                                    NULL);
219
220                 if (r < 0 && r != -ENOENT)
221                         log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r));
222 #endif
223         }
224
225 have_timezone:
226         if (isempty(tz.zone)) {
227                 free(tz.zone);
228                 tz.zone = NULL;
229         }
230
231         verify_timezone();
232
233         tz.local_rtc = hwclock_is_localtime() > 0;
234
235         return 0;
236 }
237
238 static int write_data_timezone(void) {
239         int r = 0;
240         char *p;
241         struct stat st;
242
243         if (!tz.zone) {
244                 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
245                         r = -errno;
246
247                 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
248                         r = -errno;
249
250                 return r;
251         }
252
253         p = strappend(ZONEINFO_PATH, tz.zone);
254         if (!p)
255                 return log_oom();
256
257         r = symlink(p, "/etc/localtime");
258         free(p);
259
260         if (r < 0)
261                 return -errno;
262
263         if (stat("/etc/timezone", &st) == 0 && S_ISREG(st.st_mode)) {
264                 r = write_one_line_file_atomic("/etc/timezone", tz.zone);
265                 if (r < 0)
266                         return r;
267         }
268
269         return 0;
270 }
271
272 static int write_data_local_rtc(void) {
273         int r;
274         char *s, *w;
275
276         r = read_full_file("/etc/adjtime", &s, NULL);
277         if (r < 0) {
278                 if (r != -ENOENT)
279                         return r;
280
281                 if (!tz.local_rtc)
282                         return 0;
283
284                 w = strdup(NULL_ADJTIME_LOCAL);
285                 if (!w)
286                         return -ENOMEM;
287         } else {
288                 char *p, *e;
289                 size_t a, b;
290
291                 p = strchr(s, '\n');
292                 if (!p) {
293                         free(s);
294                         return -EIO;
295                 }
296
297                 p = strchr(p+1, '\n');
298                 if (!p) {
299                         free(s);
300                         return -EIO;
301                 }
302
303                 p++;
304                 e = strchr(p, '\n');
305                 if (!e) {
306                         free(s);
307                         return -EIO;
308                 }
309
310                 a = p - s;
311                 b = strlen(e);
312
313                 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
314                 if (!w) {
315                         free(s);
316                         return -ENOMEM;
317                 }
318
319                 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
320
321                 if (streq(w, NULL_ADJTIME_UTC)) {
322                         free(w);
323
324                         if (unlink("/etc/adjtime") < 0) {
325                                 if (errno != ENOENT)
326                                         return -errno;
327                         }
328
329                         return 0;
330                 }
331         }
332
333         r = write_one_line_file_atomic("/etc/adjtime", w);
334         free(w);
335
336         return r;
337 }
338
339 static char** get_ntp_services(void) {
340         char **r = NULL, **files, **i;
341         int k;
342
343         k = conf_files_list(&files, ".list",
344                             "/etc/systemd/ntp-units.d",
345                             "/run/systemd/ntp-units.d",
346                             "/usr/local/lib/systemd/ntp-units.d",
347                             "/usr/lib/systemd/ntp-units.d",
348                             NULL);
349         if (k < 0)
350                 return NULL;
351
352         STRV_FOREACH(i, files) {
353                 FILE *f;
354
355                 f = fopen(*i, "re");
356                 if (!f)
357                         continue;
358
359                 for (;;) {
360                         char line[PATH_MAX], *l, **q;
361
362                         if (!fgets(line, sizeof(line), f)) {
363
364                                 if (ferror(f))
365                                         log_error("Failed to read NTP units file: %m");
366
367                                 break;
368                         }
369
370                         l = strstrip(line);
371                         if (l[0] == 0 || l[0] == '#')
372                                 continue;
373
374                         q = strv_append(r, l);
375                         if (!q) {
376                                 log_oom();
377                                 break;
378                         }
379
380                         strv_free(r);
381                         r = q;
382                 }
383
384                 fclose(f);
385         }
386
387         strv_free(files);
388
389         return strv_uniq(r);
390 }
391
392 static int read_ntp(DBusConnection *bus) {
393         DBusMessage *m = NULL, *reply = NULL;
394         DBusError error;
395         int r;
396         char **i, **l;
397
398         assert(bus);
399
400         dbus_error_init(&error);
401
402         l = get_ntp_services();
403         STRV_FOREACH(i, l) {
404                 const char *s;
405
406                 if (m)
407                         dbus_message_unref(m);
408                 m = dbus_message_new_method_call(
409                                 "org.freedesktop.systemd1",
410                                 "/org/freedesktop/systemd1",
411                                 "org.freedesktop.systemd1.Manager",
412                                 "GetUnitFileState");
413                 if (!m) {
414                         r = log_oom();
415                         goto finish;
416                 }
417
418                 if (!dbus_message_append_args(m,
419                                               DBUS_TYPE_STRING, i,
420                                               DBUS_TYPE_INVALID)) {
421                         r = log_oom();
422                         goto finish;
423                 }
424
425                 if (reply)
426                         dbus_message_unref(reply);
427                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
428                 if (!reply) {
429                         if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
430                                 /* This implementation does not exist, try next one */
431                                 dbus_error_free(&error);
432                                 continue;
433                         }
434
435                         log_error("Failed to issue method call: %s", bus_error_message(&error));
436                         r = -EIO;
437                         goto finish;
438                 }
439
440                 if (!dbus_message_get_args(reply, &error,
441                                            DBUS_TYPE_STRING, &s,
442                                            DBUS_TYPE_INVALID)) {
443                         log_error("Failed to parse reply: %s", bus_error_message(&error));
444                         r = -EIO;
445                         goto finish;
446                 }
447
448                 tz.use_ntp =
449                         streq(s, "enabled") ||
450                         streq(s, "enabled-runtime");
451                 r = 0;
452                 goto finish;
453         }
454
455         /* NTP is not installed. */
456         tz.use_ntp = 0;
457         r = 0;
458
459 finish:
460         if (m)
461                 dbus_message_unref(m);
462
463         if (reply)
464                 dbus_message_unref(reply);
465
466         strv_free(l);
467
468         dbus_error_free(&error);
469
470         return r;
471 }
472
473 static int start_ntp(DBusConnection *bus, DBusError *error) {
474         DBusMessage *m = NULL, *reply = NULL;
475         const char *mode = "replace";
476         char **i, **l;
477         int r;
478
479         assert(bus);
480         assert(error);
481
482         l = get_ntp_services();
483         STRV_FOREACH(i, l) {
484                 if (m)
485                         dbus_message_unref(m);
486                 m = dbus_message_new_method_call(
487                                 "org.freedesktop.systemd1",
488                                 "/org/freedesktop/systemd1",
489                                 "org.freedesktop.systemd1.Manager",
490                                 tz.use_ntp ? "StartUnit" : "StopUnit");
491                 if (!m) {
492                         log_error("Could not allocate message.");
493                         r = -ENOMEM;
494                         goto finish;
495                 }
496
497                 if (!dbus_message_append_args(m,
498                                               DBUS_TYPE_STRING, i,
499                                               DBUS_TYPE_STRING, &mode,
500                                               DBUS_TYPE_INVALID)) {
501                         log_error("Could not append arguments to message.");
502                         r = -ENOMEM;
503                         goto finish;
504                 }
505
506                 if (reply)
507                         dbus_message_unref(reply);
508                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
509                 if (!reply) {
510                         if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
511                             streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
512                             streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
513                                 /* This implementation does not exist, try next one */
514                                 dbus_error_free(error);
515                                 continue;
516                         }
517
518                         log_error("Failed to issue method call: %s", bus_error_message(error));
519                         r = -EIO;
520                         goto finish;
521                 }
522
523                 r = 0;
524                 goto finish;
525         }
526
527         /* No implementaiton available... */
528         r = -ENOENT;
529
530 finish:
531         if (m)
532                 dbus_message_unref(m);
533
534         if (reply)
535                 dbus_message_unref(reply);
536
537         strv_free(l);
538
539         return r;
540 }
541
542 static int enable_ntp(DBusConnection *bus, DBusError *error) {
543         DBusMessage *m = NULL, *reply = NULL;
544         int r;
545         DBusMessageIter iter;
546         dbus_bool_t f = FALSE, t = TRUE;
547         char **i, **l;
548
549         assert(bus);
550         assert(error);
551
552         l = get_ntp_services();
553         STRV_FOREACH(i, l) {
554                 char* k[2];
555
556                 if (m)
557                         dbus_message_unref(m);
558                 m = dbus_message_new_method_call(
559                                 "org.freedesktop.systemd1",
560                                 "/org/freedesktop/systemd1",
561                                 "org.freedesktop.systemd1.Manager",
562                                 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
563                 if (!m) {
564                         log_error("Could not allocate message.");
565                         r = -ENOMEM;
566                         goto finish;
567                 }
568
569                 dbus_message_iter_init_append(m, &iter);
570
571                 k[0] = *i;
572                 k[1] = NULL;
573
574                 r = bus_append_strv_iter(&iter, k);
575                 if (r < 0) {
576                         log_error("Failed to append unit files.");
577                         goto finish;
578                 }
579
580                 /* send runtime bool */
581                 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
582                         log_error("Failed to append runtime boolean.");
583                         r = -ENOMEM;
584                         goto finish;
585                 }
586
587                 if (tz.use_ntp) {
588                         /* send force bool */
589                         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
590                                 log_error("Failed to append force boolean.");
591                                 r = -ENOMEM;
592                                 goto finish;
593                         }
594                 }
595
596                 if (reply)
597                         dbus_message_unref(reply);
598                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
599                 if (!reply) {
600                         if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
601                                 /* This implementation does not exist, try next one */
602                                 dbus_error_free(error);
603                                 continue;
604                         }
605
606                         log_error("Failed to issue method call: %s", bus_error_message(error));
607                         r = -EIO;
608                         goto finish;
609                 }
610
611                 dbus_message_unref(m);
612                 m = dbus_message_new_method_call(
613                                 "org.freedesktop.systemd1",
614                                 "/org/freedesktop/systemd1",
615                                 "org.freedesktop.systemd1.Manager",
616                                 "Reload");
617                 if (!m) {
618                         log_error("Could not allocate message.");
619                         r = -ENOMEM;
620                         goto finish;
621                 }
622
623                 dbus_message_unref(reply);
624                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
625                 if (!reply) {
626                         log_error("Failed to issue method call: %s", bus_error_message(error));
627                         r = -EIO;
628                         goto finish;
629                 }
630
631                 r = 0;
632                 goto finish;
633         }
634
635         r = -ENOENT;
636
637 finish:
638         if (m)
639                 dbus_message_unref(m);
640
641         if (reply)
642                 dbus_message_unref(reply);
643
644         strv_free(l);
645
646         return r;
647 }
648
649 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
650         dbus_bool_t db;
651
652         assert(i);
653         assert(property);
654
655         db = tz.use_ntp > 0;
656
657         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
658                 return -ENOMEM;
659
660         return 0;
661 }
662
663 static const BusProperty bus_timedate_properties[] = {
664         { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone),     true },
665         { "LocalRTC", bus_property_append_bool,   "b", offsetof(TZ, local_rtc) },
666         { "NTP",      property_append_ntp,        "b", offsetof(TZ, use_ntp)   },
667         { NULL, }
668 };
669
670 static const BusBoundProperties bps[] = {
671         { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
672         { NULL, }
673 };
674
675 static DBusHandlerResult timedate_message_handler(
676                 DBusConnection *connection,
677                 DBusMessage *message,
678                 void *userdata) {
679
680         DBusMessage *reply = NULL, *changed = NULL;
681         DBusError error;
682         int r;
683
684         assert(connection);
685         assert(message);
686
687         dbus_error_init(&error);
688
689         if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
690                 const char *z;
691                 dbus_bool_t interactive;
692
693                 if (!dbus_message_get_args(
694                                     message,
695                                     &error,
696                                     DBUS_TYPE_STRING, &z,
697                                     DBUS_TYPE_BOOLEAN, &interactive,
698                                     DBUS_TYPE_INVALID))
699                         return bus_send_error_reply(connection, message, &error, -EINVAL);
700
701                 if (!valid_timezone(z))
702                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
703
704                 if (!streq_ptr(z, tz.zone)) {
705                         char *t;
706
707                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
708                         if (r < 0)
709                                 return bus_send_error_reply(connection, message, &error, r);
710
711                         t = strdup(z);
712                         if (!t)
713                                 goto oom;
714
715                         free(tz.zone);
716                         tz.zone = t;
717
718                         /* 1. Write new configuration file */
719                         r = write_data_timezone();
720                         if (r < 0) {
721                                 log_error("Failed to set timezone: %s", strerror(-r));
722                                 return bus_send_error_reply(connection, message, NULL, r);
723                         }
724
725                         if (tz.local_rtc) {
726                                 struct timespec ts;
727                                 struct tm *tm;
728
729                                 /* 2. Teach kernel new timezone */
730                                 hwclock_apply_localtime_delta(NULL);
731
732                                 /* 3. Sync RTC from system clock, with the new delta */
733                                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
734                                 assert_se(tm = localtime(&ts.tv_sec));
735                                 hwclock_set_time(tm);
736                         }
737
738                         log_struct(LOG_INFO,
739                                    "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIMEZONE_CHANGE),
740                                    "TIMEZONE=%s", tz.zone,
741                                    "MESSAGE=Changed timezone to '%s'.", tz.zone,
742                                    NULL);
743
744                         changed = bus_properties_changed_new(
745                                         "/org/freedesktop/timedate1",
746                                         "org.freedesktop.timedate1",
747                                         "Timezone\0");
748                         if (!changed)
749                                 goto oom;
750                 }
751
752         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
753                 dbus_bool_t lrtc;
754                 dbus_bool_t fix_system;
755                 dbus_bool_t interactive;
756
757                 if (!dbus_message_get_args(
758                                     message,
759                                     &error,
760                                     DBUS_TYPE_BOOLEAN, &lrtc,
761                                     DBUS_TYPE_BOOLEAN, &fix_system,
762                                     DBUS_TYPE_BOOLEAN, &interactive,
763                                     DBUS_TYPE_INVALID))
764                         return bus_send_error_reply(connection, message, &error, -EINVAL);
765
766                 if (lrtc != tz.local_rtc) {
767                         struct timespec ts;
768
769                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
770                         if (r < 0)
771                                 return bus_send_error_reply(connection, message, &error, r);
772
773                         tz.local_rtc = lrtc;
774
775                         /* 1. Write new configuration file */
776                         r = write_data_local_rtc();
777                         if (r < 0) {
778                                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
779                                 return bus_send_error_reply(connection, message, NULL, r);
780                         }
781
782                         /* 2. Teach kernel new timezone */
783                         if (tz.local_rtc)
784                                 hwclock_apply_localtime_delta(NULL);
785                         else
786                                 hwclock_reset_localtime_delta();
787
788                         /* 3. Synchronize clocks */
789                         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
790
791                         if (fix_system) {
792                                 struct tm tm;
793
794                                 /* Sync system clock from RTC; first,
795                                  * initialize the timezone fields of
796                                  * struct tm. */
797                                 if (tz.local_rtc)
798                                         tm = *localtime(&ts.tv_sec);
799                                 else
800                                         tm = *gmtime(&ts.tv_sec);
801
802                                 /* Override the main fields of
803                                  * struct tm, but not the timezone
804                                  * fields */
805                                 if (hwclock_get_time(&tm) >= 0) {
806
807                                         /* And set the system clock
808                                          * with this */
809                                         if (tz.local_rtc)
810                                                 ts.tv_sec = mktime(&tm);
811                                         else
812                                                 ts.tv_sec = timegm(&tm);
813
814                                         clock_settime(CLOCK_REALTIME, &ts);
815                                 }
816
817                         } else {
818                                 struct tm *tm;
819
820                                 /* Sync RTC from system clock */
821                                 if (tz.local_rtc)
822                                         tm = localtime(&ts.tv_sec);
823                                 else
824                                         tm = gmtime(&ts.tv_sec);
825
826                                 hwclock_set_time(tm);
827                         }
828
829                         log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
830
831                         changed = bus_properties_changed_new(
832                                         "/org/freedesktop/timedate1",
833                                         "org.freedesktop.timedate1",
834                                         "LocalRTC\0");
835                         if (!changed)
836                                 goto oom;
837                 }
838
839         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
840                 int64_t utc;
841                 dbus_bool_t relative;
842                 dbus_bool_t interactive;
843
844                 if (!dbus_message_get_args(
845                                     message,
846                                     &error,
847                                     DBUS_TYPE_INT64, &utc,
848                                     DBUS_TYPE_BOOLEAN, &relative,
849                                     DBUS_TYPE_BOOLEAN, &interactive,
850                                     DBUS_TYPE_INVALID))
851                         return bus_send_error_reply(connection, message, &error, -EINVAL);
852
853                 if (!relative && utc <= 0)
854                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
855
856                 if (!relative || utc != 0) {
857                         struct timespec ts;
858                         struct tm* tm;
859
860                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
861                         if (r < 0)
862                                 return bus_send_error_reply(connection, message, &error, r);
863
864                         if (relative)
865                                 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
866                         else
867                                 timespec_store(&ts, utc);
868
869                         /* Set system clock */
870                         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
871                                 log_error("Failed to set local time: %m");
872                                 return bus_send_error_reply(connection, message, NULL, -errno);
873                         }
874
875                         /* Sync down to RTC */
876                         if (tz.local_rtc)
877                                 tm = localtime(&ts.tv_sec);
878                         else
879                                 tm = gmtime(&ts.tv_sec);
880
881                         hwclock_set_time(tm);
882
883                         log_struct(LOG_INFO,
884                                    "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIME_CHANGE),
885                                    "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
886                                    "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
887                                    NULL);
888                 }
889         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
890                 dbus_bool_t ntp;
891                 dbus_bool_t interactive;
892
893                 if (!dbus_message_get_args(
894                                     message,
895                                     &error,
896                                     DBUS_TYPE_BOOLEAN, &ntp,
897                                     DBUS_TYPE_BOOLEAN, &interactive,
898                                     DBUS_TYPE_INVALID))
899                         return bus_send_error_reply(connection, message, &error, -EINVAL);
900
901                 if (ntp != !!tz.use_ntp) {
902
903                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
904                         if (r < 0)
905                                 return bus_send_error_reply(connection, message, &error, r);
906
907                         tz.use_ntp = !!ntp;
908
909                         r = enable_ntp(connection, &error);
910                         if (r < 0)
911                                 return bus_send_error_reply(connection, message, &error, r);
912
913                         r = start_ntp(connection, &error);
914                         if (r < 0)
915                                 return bus_send_error_reply(connection, message, &error, r);
916
917                         log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
918
919                         changed = bus_properties_changed_new(
920                                         "/org/freedesktop/timedate1",
921                                         "org.freedesktop.timedate1",
922                                         "NTP\0");
923                         if (!changed)
924                                 goto oom;
925                 }
926
927         } else
928                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
929
930         if (!(reply = dbus_message_new_method_return(message)))
931                 goto oom;
932
933         if (!dbus_connection_send(connection, reply, NULL))
934                 goto oom;
935
936         dbus_message_unref(reply);
937         reply = NULL;
938
939         if (changed) {
940
941                 if (!dbus_connection_send(connection, changed, NULL))
942                         goto oom;
943
944                 dbus_message_unref(changed);
945         }
946
947         return DBUS_HANDLER_RESULT_HANDLED;
948
949 oom:
950         if (reply)
951                 dbus_message_unref(reply);
952
953         if (changed)
954                 dbus_message_unref(changed);
955
956         dbus_error_free(&error);
957
958         return DBUS_HANDLER_RESULT_NEED_MEMORY;
959 }
960
961 static int connect_bus(DBusConnection **_bus) {
962         static const DBusObjectPathVTable timedate_vtable = {
963                 .message_function = timedate_message_handler
964         };
965         DBusError error;
966         DBusConnection *bus = NULL;
967         int r;
968
969         assert(_bus);
970
971         dbus_error_init(&error);
972
973         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
974         if (!bus) {
975                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
976                 r = -ECONNREFUSED;
977                 goto fail;
978         }
979
980         dbus_connection_set_exit_on_disconnect(bus, FALSE);
981
982         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
983             !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
984                 r = log_oom();
985                 goto fail;
986         }
987
988         r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
989         if (dbus_error_is_set(&error)) {
990                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
991                 r = -EEXIST;
992                 goto fail;
993         }
994
995         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
996                 log_error("Failed to acquire name.");
997                 r = -EEXIST;
998                 goto fail;
999         }
1000
1001         if (_bus)
1002                 *_bus = bus;
1003
1004         return 0;
1005
1006 fail:
1007         dbus_connection_close(bus);
1008         dbus_connection_unref(bus);
1009
1010         dbus_error_free(&error);
1011
1012         return r;
1013 }
1014
1015 int main(int argc, char *argv[]) {
1016         int r;
1017         DBusConnection *bus = NULL;
1018         bool exiting = false;
1019
1020         log_set_target(LOG_TARGET_AUTO);
1021         log_parse_environment();
1022         log_open();
1023
1024         umask(0022);
1025
1026         if (argc == 2 && streq(argv[1], "--introspect")) {
1027                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1028                       "<node>\n", stdout);
1029                 fputs(timedate_interface, stdout);
1030                 fputs("</node>\n", stdout);
1031                 return 0;
1032         }
1033
1034         if (argc != 1) {
1035                 log_error("This program takes no arguments.");
1036                 r = -EINVAL;
1037                 goto finish;
1038         }
1039
1040         r = read_data();
1041         if (r < 0) {
1042                 log_error("Failed to read timezone data: %s", strerror(-r));
1043                 goto finish;
1044         }
1045
1046         r = connect_bus(&bus);
1047         if (r < 0)
1048                 goto finish;
1049
1050         r = read_ntp(bus);
1051         if (r < 0) {
1052                 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1053                 goto finish;
1054         }
1055
1056         remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1057         for (;;) {
1058
1059                 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1060                         break;
1061
1062                 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1063                         exiting = true;
1064                         bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
1065                 }
1066         }
1067
1068         r = 0;
1069
1070 finish:
1071         free_data();
1072
1073         if (bus) {
1074                 dbus_connection_flush(bus);
1075                 dbus_connection_close(bus);
1076                 dbus_connection_unref(bus);
1077         }
1078
1079         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1080 }