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