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