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