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