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