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