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