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