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