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