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