chiark / gitweb /
97306e9a3236cb4d6e285aa56a65f5f35d8c6062
[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 General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU 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 = "ntpd.service", *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                 log_error("Failed to issue method call: %s", bus_error_message(&error));
338                 r = -EIO;
339                 goto finish;
340         }
341
342         if (!dbus_message_get_args(reply, &error,
343                                    DBUS_TYPE_STRING, &s,
344                                    DBUS_TYPE_INVALID)) {
345                 log_error("Failed to parse reply: %s", bus_error_message(&error));
346                 r = -EIO;
347                 goto finish;
348         }
349
350         tz.use_ntp =
351                 streq(s, "enabled") ||
352                 streq(s, "enabled-runtime");
353         r = 0;
354
355 finish:
356         if (m)
357                 dbus_message_unref(m);
358
359         if (reply)
360                 dbus_message_unref(reply);
361
362         dbus_error_free(&error);
363
364         return r;
365 }
366
367 static int start_ntp(DBusConnection *bus, DBusError *error) {
368         DBusMessage *m = NULL, *reply = NULL;
369         const char *name = "ntpd.service", *mode = "replace";
370         int r;
371
372         assert(bus);
373         assert(error);
374
375         m = dbus_message_new_method_call(
376                         "org.freedesktop.systemd1",
377                         "/org/freedesktop/systemd1",
378                         "org.freedesktop.systemd1.Manager",
379                         tz.use_ntp ? "StartUnit" : "StopUnit");
380         if (!m) {
381                 log_error("Could not allocate message.");
382                 r = -ENOMEM;
383                 goto finish;
384         }
385
386         if (!dbus_message_append_args(m,
387                                       DBUS_TYPE_STRING, &name,
388                                       DBUS_TYPE_STRING, &mode,
389                                       DBUS_TYPE_INVALID)) {
390                 log_error("Could not append arguments to message.");
391                 r = -ENOMEM;
392                 goto finish;
393         }
394
395         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
396         if (!reply) {
397                 log_error("Failed to issue method call: %s", bus_error_message(error));
398                 r = -EIO;
399                 goto finish;
400         }
401
402         r = 0;
403
404 finish:
405         if (m)
406                 dbus_message_unref(m);
407
408         if (reply)
409                 dbus_message_unref(reply);
410
411         return r;
412 }
413
414 static int enable_ntp(DBusConnection *bus, DBusError *error) {
415         DBusMessage *m = NULL, *reply = NULL;
416         const char * const names[] = { "ntpd.service", NULL };
417         int r;
418         DBusMessageIter iter;
419         dbus_bool_t f = FALSE, t = TRUE;
420
421         assert(bus);
422         assert(error);
423
424         m = dbus_message_new_method_call(
425                         "org.freedesktop.systemd1",
426                         "/org/freedesktop/systemd1",
427                         "org.freedesktop.systemd1.Manager",
428                         tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
429
430         if (!m) {
431                 log_error("Could not allocate message.");
432                 r = -ENOMEM;
433                 goto finish;
434         }
435
436         dbus_message_iter_init_append(m, &iter);
437
438         r = bus_append_strv_iter(&iter, (char**) names);
439         if (r < 0) {
440                 log_error("Failed to append unit files.");
441                 goto finish;
442         }
443         /* send runtime bool */
444         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
445                 log_error("Failed to append runtime boolean.");
446                 r = -ENOMEM;
447                 goto finish;
448         }
449
450         if (tz.use_ntp) {
451                 /* send force bool */
452                 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
453                         log_error("Failed to append force boolean.");
454                         r = -ENOMEM;
455                         goto finish;
456                 }
457         }
458
459         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
460         if (!reply) {
461                 log_error("Failed to issue method call: %s", bus_error_message(error));
462                 r = -EIO;
463                 goto finish;
464         }
465
466         dbus_message_unref(m);
467         m = dbus_message_new_method_call(
468                         "org.freedesktop.systemd1",
469                         "/org/freedesktop/systemd1",
470                         "org.freedesktop.systemd1.Manager",
471                         "Reload");
472         if (!m) {
473                 log_error("Could not allocate message.");
474                 r = -ENOMEM;
475                 goto finish;
476         }
477
478         dbus_message_unref(reply);
479         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
480         if (!reply) {
481                 log_error("Failed to issue method call: %s", bus_error_message(error));
482                 r = -EIO;
483                 goto finish;
484         }
485
486         r = 0;
487
488 finish:
489         if (m)
490                 dbus_message_unref(m);
491
492         if (reply)
493                 dbus_message_unref(reply);
494
495         return r;
496 }
497
498 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
499         dbus_bool_t db;
500
501         assert(i);
502         assert(property);
503
504         db = tz.use_ntp > 0;
505
506         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
507                 return -ENOMEM;
508
509         return 0;
510 }
511
512 static const BusProperty bus_timedate_properties[] = {
513         { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone),     true },
514         { "LocalRTC", bus_property_append_bool,   "b", offsetof(TZ, local_rtc) },
515         { "NTP",      property_append_ntp,        "b", offsetof(TZ, use_ntp)   },
516         { NULL, }
517 };
518
519 static const BusBoundProperties bps[] = {
520         { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
521         { NULL, }
522 };
523
524 static DBusHandlerResult timedate_message_handler(
525                 DBusConnection *connection,
526                 DBusMessage *message,
527                 void *userdata) {
528
529         DBusMessage *reply = NULL, *changed = NULL;
530         DBusError error;
531         int r;
532
533         assert(connection);
534         assert(message);
535
536         dbus_error_init(&error);
537
538         if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
539                 const char *z;
540                 dbus_bool_t interactive;
541
542                 if (!dbus_message_get_args(
543                                     message,
544                                     &error,
545                                     DBUS_TYPE_STRING, &z,
546                                     DBUS_TYPE_BOOLEAN, &interactive,
547                                     DBUS_TYPE_INVALID))
548                         return bus_send_error_reply(connection, message, &error, -EINVAL);
549
550                 if (!valid_timezone(z))
551                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
552
553                 if (!streq_ptr(z, tz.zone)) {
554                         char *t;
555
556                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, &error);
557                         if (r < 0)
558                                 return bus_send_error_reply(connection, message, &error, r);
559
560                         t = strdup(z);
561                         if (!t)
562                                 goto oom;
563
564                         free(tz.zone);
565                         tz.zone = t;
566
567                         /* 1. Write new configuration file */
568                         r = write_data_timezone();
569                         if (r < 0) {
570                                 log_error("Failed to set timezone: %s", strerror(-r));
571                                 return bus_send_error_reply(connection, message, NULL, r);
572                         }
573
574                         if (tz.local_rtc) {
575                                 struct timespec ts;
576                                 struct tm *tm;
577
578                                 /* 2. Teach kernel new timezone */
579                                 hwclock_apply_localtime_delta(NULL);
580
581                                 /* 3. Sync RTC from system clock, with the new delta */
582                                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
583                                 assert_se(tm = localtime(&ts.tv_sec));
584                                 hwclock_set_time(tm);
585                         }
586
587                         log_info("Changed timezone to '%s'.", tz.zone);
588
589                         changed = bus_properties_changed_new(
590                                         "/org/freedesktop/timedate1",
591                                         "org.freedesktop.timedate1",
592                                         "Timezone\0");
593                         if (!changed)
594                                 goto oom;
595                 }
596
597         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
598                 dbus_bool_t lrtc;
599                 dbus_bool_t fix_system;
600                 dbus_bool_t interactive;
601
602                 if (!dbus_message_get_args(
603                                     message,
604                                     &error,
605                                     DBUS_TYPE_BOOLEAN, &lrtc,
606                                     DBUS_TYPE_BOOLEAN, &fix_system,
607                                     DBUS_TYPE_BOOLEAN, &interactive,
608                                     DBUS_TYPE_INVALID))
609                         return bus_send_error_reply(connection, message, &error, -EINVAL);
610
611                 if (lrtc != tz.local_rtc) {
612                         struct timespec ts;
613
614                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, &error);
615                         if (r < 0)
616                                 return bus_send_error_reply(connection, message, &error, r);
617
618                         tz.local_rtc = lrtc;
619
620                         /* 1. Write new configuration file */
621                         r = write_data_local_rtc();
622                         if (r < 0) {
623                                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
624                                 return bus_send_error_reply(connection, message, NULL, r);
625                         }
626
627                         /* 2. Teach kernel new timezone */
628                         if (tz.local_rtc)
629                                 hwclock_apply_localtime_delta(NULL);
630                         else
631                                 hwclock_reset_localtime_delta();
632
633                         /* 3. Synchronize clocks */
634                         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
635
636                         if (fix_system) {
637                                 struct tm tm;
638
639                                 /* Sync system clock from RTC; first,
640                                  * initialize the timezone fields of
641                                  * struct tm. */
642                                 if (tz.local_rtc)
643                                         tm = *localtime(&ts.tv_sec);
644                                 else
645                                         tm = *gmtime(&ts.tv_sec);
646
647                                 /* Override the main fields of
648                                  * struct tm, but not the timezone
649                                  * fields */
650                                 if (hwclock_get_time(&tm) >= 0) {
651
652                                         /* And set the system clock
653                                          * with this */
654                                         if (tz.local_rtc)
655                                                 ts.tv_sec = mktime(&tm);
656                                         else
657                                                 ts.tv_sec = timegm(&tm);
658
659                                         clock_settime(CLOCK_REALTIME, &ts);
660                                 }
661
662                         } else {
663                                 struct tm *tm;
664
665                                 /* Sync RTC from system clock */
666                                 if (tz.local_rtc)
667                                         tm = localtime(&ts.tv_sec);
668                                 else
669                                         tm = gmtime(&ts.tv_sec);
670
671                                 hwclock_set_time(tm);
672                         }
673
674                         log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
675
676                         changed = bus_properties_changed_new(
677                                         "/org/freedesktop/timedate1",
678                                         "org.freedesktop.timedate1",
679                                         "LocalRTC\0");
680                         if (!changed)
681                                 goto oom;
682                 }
683
684         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
685                 int64_t utc;
686                 dbus_bool_t relative;
687                 dbus_bool_t interactive;
688
689                 if (!dbus_message_get_args(
690                                     message,
691                                     &error,
692                                     DBUS_TYPE_INT64, &utc,
693                                     DBUS_TYPE_BOOLEAN, &relative,
694                                     DBUS_TYPE_BOOLEAN, &interactive,
695                                     DBUS_TYPE_INVALID))
696                         return bus_send_error_reply(connection, message, &error, -EINVAL);
697
698                 if (!relative && utc <= 0)
699                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
700
701                 if (!relative || utc != 0) {
702                         struct timespec ts;
703                         struct tm* tm;
704
705                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, &error);
706                         if (r < 0)
707                                 return bus_send_error_reply(connection, message, &error, r);
708
709                         if (relative)
710                                 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
711                         else
712                                 timespec_store(&ts, utc);
713
714                         /* Set system clock */
715                         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
716                                 log_error("Failed to set local time: %m");
717                                 return bus_send_error_reply(connection, message, NULL, -errno);
718                         }
719
720                         /* Sync down to RTC */
721                         if (tz.local_rtc)
722                                 tm = localtime(&ts.tv_sec);
723                         else
724                                 tm = gmtime(&ts.tv_sec);
725
726                         hwclock_set_time(tm);
727
728                         log_info("Changed local time to %s", ctime(&ts.tv_sec));
729                 }
730         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
731                 dbus_bool_t ntp;
732                 dbus_bool_t interactive;
733
734                 if (!dbus_message_get_args(
735                                     message,
736                                     &error,
737                                     DBUS_TYPE_BOOLEAN, &ntp,
738                                     DBUS_TYPE_BOOLEAN, &interactive,
739                                     DBUS_TYPE_INVALID))
740                         return bus_send_error_reply(connection, message, &error, -EINVAL);
741
742                 if (ntp != !!tz.use_ntp) {
743
744                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, &error);
745                         if (r < 0)
746                                 return bus_send_error_reply(connection, message, &error, r);
747
748                         tz.use_ntp = !!ntp;
749
750                         r = enable_ntp(connection, &error);
751                         if (r < 0)
752                                 return bus_send_error_reply(connection, message, &error, r);
753
754                         r = start_ntp(connection, &error);
755                         if (r < 0)
756                                 return bus_send_error_reply(connection, message, &error, r);
757
758                         log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
759
760                         changed = bus_properties_changed_new(
761                                         "/org/freedesktop/timedate1",
762                                         "org.freedesktop.timedate1",
763                                         "NTP\0");
764                         if (!changed)
765                                 goto oom;
766                 }
767
768         } else
769                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
770
771         if (!(reply = dbus_message_new_method_return(message)))
772                 goto oom;
773
774         if (!dbus_connection_send(connection, reply, NULL))
775                 goto oom;
776
777         dbus_message_unref(reply);
778         reply = NULL;
779
780         if (changed) {
781
782                 if (!dbus_connection_send(connection, changed, NULL))
783                         goto oom;
784
785                 dbus_message_unref(changed);
786         }
787
788         return DBUS_HANDLER_RESULT_HANDLED;
789
790 oom:
791         if (reply)
792                 dbus_message_unref(reply);
793
794         if (changed)
795                 dbus_message_unref(changed);
796
797         dbus_error_free(&error);
798
799         return DBUS_HANDLER_RESULT_NEED_MEMORY;
800 }
801
802 static int connect_bus(DBusConnection **_bus) {
803         static const DBusObjectPathVTable timedate_vtable = {
804                 .message_function = timedate_message_handler
805         };
806         DBusError error;
807         DBusConnection *bus = NULL;
808         int r;
809
810         assert(_bus);
811
812         dbus_error_init(&error);
813
814         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
815         if (!bus) {
816                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
817                 r = -ECONNREFUSED;
818                 goto fail;
819         }
820
821         dbus_connection_set_exit_on_disconnect(bus, FALSE);
822
823         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
824             !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
825                 log_error("Not enough memory");
826                 r = -ENOMEM;
827                 goto fail;
828         }
829
830         r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
831         if (dbus_error_is_set(&error)) {
832                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
833                 r = -EEXIST;
834                 goto fail;
835         }
836
837         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
838                 log_error("Failed to acquire name.");
839                 r = -EEXIST;
840                 goto fail;
841         }
842
843         if (_bus)
844                 *_bus = bus;
845
846         return 0;
847
848 fail:
849         dbus_connection_close(bus);
850         dbus_connection_unref(bus);
851
852         dbus_error_free(&error);
853
854         return r;
855 }
856
857 int main(int argc, char *argv[]) {
858         int r;
859         DBusConnection *bus = NULL;
860         bool exiting = false;
861
862         log_set_target(LOG_TARGET_AUTO);
863         log_parse_environment();
864         log_open();
865
866         umask(0022);
867
868         if (argc == 2 && streq(argv[1], "--introspect")) {
869                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
870                       "<node>\n", stdout);
871                 fputs(timedate_interface, stdout);
872                 fputs("</node>\n", stdout);
873                 return 0;
874         }
875
876         if (argc != 1) {
877                 log_error("This program takes no arguments.");
878                 r = -EINVAL;
879                 goto finish;
880         }
881
882         r = read_data();
883         if (r < 0) {
884                 log_error("Failed to read timezone data: %s", strerror(-r));
885                 goto finish;
886         }
887
888         r = connect_bus(&bus);
889         if (r < 0)
890                 goto finish;
891
892         r = read_ntp(bus);
893         if (r < 0) {
894                 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
895                 goto finish;
896         }
897
898         remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
899         for (;;) {
900
901                 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
902                         break;
903
904                 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
905                         exiting = true;
906                         bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
907                 }
908         }
909
910         r = 0;
911
912 finish:
913         free_data();
914
915         if (bus) {
916                 dbus_connection_flush(bus);
917                 dbus_connection_close(bus);
918                 dbus_connection_unref(bus);
919         }
920
921         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
922 }