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