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