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