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