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