]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedated.c
timedate: assorted improvements
[thirdparty/systemd.git] / src / timedate / timedated.c
CommitLineData
f401e48c
LP
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
5430f7f2
LP
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
f401e48c
LP
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
5430f7f2 16 Lesser General Public License for more details.
f401e48c 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
f401e48c
LP
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
877d54e9
LP
28#include "systemd/sd-id128.h"
29#include "systemd/sd-messages.h"
f401e48c
LP
30#include "util.h"
31#include "strv.h"
32#include "dbus-common.h"
33#include "polkit.h"
ad740100 34#include "def.h"
bbc98d32 35#include "hwclock.h"
b32d1675 36#include "conf-files.h"
424a19f8 37#include "path-util.h"
f401e48c
LP
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
91f9dcaf 42#define INTERFACE \
f401e48c
LP
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" \
c5f0532f 46 " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
f401e48c
LP
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" \
91f9dcaf 58 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
f401e48c
LP
59 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
60 " </method>\n" \
c5f0532f
LP
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" \
91f9dcaf
LP
65 " </interface>\n"
66
67#define INTROSPECTION \
68 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
69 "<node>\n" \
70 INTERFACE \
f401e48c
LP
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 \
3417e2c3 78 "org.freedesktop.timedate1\0"
f401e48c 79
91f9dcaf
LP
80const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
81
d200735e
MS
82typedef struct TZ {
83 char *zone;
84 bool local_rtc;
85 int use_ntp;
86} TZ;
f401e48c 87
d200735e
MS
88static TZ tz = {
89 .use_ntp = -1,
90};
91
92static usec_t remain_until;
ad740100 93
f401e48c 94static void free_data(void) {
d200735e
MS
95 free(tz.zone);
96 tz.zone = NULL;
f401e48c 97
d200735e 98 tz.local_rtc = false;
f401e48c
LP
99}
100
101static 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
424a19f8 133 t = strappend("/usr/share/zoneinfo/", name);
f401e48c
LP
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
f401e48c
LP
149static int read_data(void) {
150 int r;
424a19f8 151 _cleanup_free_ char *t = NULL;
f401e48c
LP
152
153 free_data();
154
92c4ef2d
SL
155 r = readlink_malloc("/etc/localtime", &t);
156 if (r < 0) {
157 if (r == -EINVAL)
424a19f8 158 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
92c4ef2d 159 else
424a19f8 160 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
92c4ef2d 161 } else {
424a19f8 162 const char *e;
92c4ef2d 163
424a19f8
LP
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/.");
92c4ef2d 170 else {
424a19f8 171 tz.zone = strdup(e);
92c4ef2d
SL
172 if (!tz.zone)
173 return log_oom();
174
175 goto have_timezone;
176 }
177 }
178
424a19f8 179#ifdef HAVE_SYSV_COMPAT
d200735e 180 r = read_one_line_file("/etc/timezone", &tz.zone);
a724d2ed
LP
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,
d200735e 187 "ZONE", &tz.zone,
a724d2ed
LP
188 NULL);
189
190 if (r < 0 && r != -ENOENT)
191 log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r));
192#endif
193 }
424a19f8 194#endif
a724d2ed 195
92c4ef2d 196have_timezone:
d200735e
MS
197 if (isempty(tz.zone)) {
198 free(tz.zone);
199 tz.zone = NULL;
a724d2ed 200 }
f401e48c 201
d200735e 202 tz.local_rtc = hwclock_is_localtime() > 0;
f401e48c
LP
203
204 return 0;
205}
206
207static int write_data_timezone(void) {
208 int r = 0;
424a19f8 209 _cleanup_free_ char *p = NULL;
92c4ef2d 210 struct stat st;
f401e48c 211
d200735e 212 if (!tz.zone) {
424a19f8 213 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
f401e48c
LP
214 r = -errno;
215
424a19f8
LP
216#ifdef HAVE_SYSV_COMPAT
217 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
f401e48c 218 r = -errno;
424a19f8 219#endif
f401e48c
LP
220
221 return r;
222 }
223
424a19f8 224 p = strappend("../usr/share/zoneinfo/", tz.zone);
0d0f0c50
SL
225 if (!p)
226 return log_oom();
f401e48c 227
424a19f8 228 r = symlink_atomic(p, "/etc/localtime");
f401e48c 229 if (r < 0)
424a19f8 230 return r;
f401e48c 231
424a19f8 232#ifdef HAVE_SYSV_COMPAT
92c4ef2d
SL
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 }
424a19f8 238#endif
f401e48c
LP
239
240 return 0;
241}
242
243static 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
d200735e 252 if (!tz.local_rtc)
f401e48c
LP
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');
8ea913b2 276 if (!e) {
f401e48c
LP
277 free(s);
278 return -EIO;
279 }
280
281 a = p - s;
282 b = strlen(e);
283
d200735e 284 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
f401e48c
LP
285 if (!w) {
286 free(s);
287 return -ENOMEM;
288 }
289
d200735e 290 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
f401e48c
LP
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
ac7019f3 310static char** get_ntp_services(void) {
b32d1675
LP
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)
ac7019f3
LP
321 return NULL;
322
b32d1675
LP
323 STRV_FOREACH(i, files) {
324 FILE *f;
ac7019f3 325
b32d1675
LP
326 f = fopen(*i, "re");
327 if (!f)
328 continue;
ac7019f3 329
b32d1675
LP
330 for (;;) {
331 char line[PATH_MAX], *l, **q;
ac7019f3 332
b32d1675 333 if (!fgets(line, sizeof(line), f)) {
ac7019f3 334
b32d1675
LP
335 if (ferror(f))
336 log_error("Failed to read NTP units file: %m");
ac7019f3 337
b32d1675
LP
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) {
0d0f0c50 347 log_oom();
b32d1675
LP
348 break;
349 }
ac7019f3 350
b32d1675
LP
351 strv_free(r);
352 r = q;
ac7019f3
LP
353 }
354
b32d1675 355 fclose(f);
ac7019f3
LP
356 }
357
b32d1675 358 strv_free(files);
ac7019f3 359
f6c13ce4 360 return strv_uniq(r);
ac7019f3
LP
361}
362
c5f0532f
LP
363static int read_ntp(DBusConnection *bus) {
364 DBusMessage *m = NULL, *reply = NULL;
c5f0532f
LP
365 DBusError error;
366 int r;
ac7019f3 367 char **i, **l;
c5f0532f
LP
368
369 assert(bus);
370
371 dbus_error_init(&error);
372
ac7019f3
LP
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) {
0d0f0c50 385 r = log_oom();
ac7019f3
LP
386 goto finish;
387 }
c5f0532f 388
ac7019f3
LP
389 if (!dbus_message_append_args(m,
390 DBUS_TYPE_STRING, i,
391 DBUS_TYPE_INVALID)) {
0d0f0c50 392 r = log_oom();
ac7019f3
LP
393 goto finish;
394 }
c5f0532f 395
ac7019f3
LP
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 }
2aa4c315 405
ac7019f3
LP
406 log_error("Failed to issue method call: %s", bus_error_message(&error));
407 r = -EIO;
2aa4c315
LP
408 goto finish;
409 }
410
ac7019f3
LP
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 }
c5f0532f 418
ac7019f3
LP
419 tz.use_ntp =
420 streq(s, "enabled") ||
421 streq(s, "enabled-runtime");
422 r = 0;
c5f0532f
LP
423 goto finish;
424 }
425
ac7019f3
LP
426 /* NTP is not installed. */
427 tz.use_ntp = 0;
c5f0532f
LP
428 r = 0;
429
430finish:
431 if (m)
432 dbus_message_unref(m);
433
434 if (reply)
435 dbus_message_unref(reply);
436
ac7019f3
LP
437 strv_free(l);
438
c5f0532f
LP
439 dbus_error_free(&error);
440
441 return r;
442}
443
444static int start_ntp(DBusConnection *bus, DBusError *error) {
445 DBusMessage *m = NULL, *reply = NULL;
ac7019f3
LP
446 const char *mode = "replace";
447 char **i, **l;
c5f0532f
LP
448 int r;
449
450 assert(bus);
451 assert(error);
452
ac7019f3
LP
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 }
c5f0532f 467
ac7019f3
LP
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 }
c5f0532f 488
ac7019f3
LP
489 log_error("Failed to issue method call: %s", bus_error_message(error));
490 r = -EIO;
491 goto finish;
492 }
493
494 r = 0;
c5f0532f
LP
495 goto finish;
496 }
497
ac7019f3
LP
498 /* No implementaiton available... */
499 r = -ENOENT;
c5f0532f
LP
500
501finish:
502 if (m)
503 dbus_message_unref(m);
504
505 if (reply)
506 dbus_message_unref(reply);
507
ac7019f3
LP
508 strv_free(l);
509
c5f0532f
LP
510 return r;
511}
512
513static int enable_ntp(DBusConnection *bus, DBusError *error) {
514 DBusMessage *m = NULL, *reply = NULL;
c5f0532f
LP
515 int r;
516 DBusMessageIter iter;
517 dbus_bool_t f = FALSE, t = TRUE;
ac7019f3 518 char **i, **l;
c5f0532f
LP
519
520 assert(bus);
521 assert(error);
522
ac7019f3
LP
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 }
c5f0532f 539
ac7019f3 540 dbus_message_iter_init_append(m, &iter);
c5f0532f 541
ac7019f3
LP
542 k[0] = *i;
543 k[1] = NULL;
c5f0532f 544
ac7019f3
LP
545 r = bus_append_strv_iter(&iter, k);
546 if (r < 0) {
547 log_error("Failed to append unit files.");
548 goto finish;
549 }
c5f0532f 550
ac7019f3
LP
551 /* send runtime bool */
552 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
553 log_error("Failed to append runtime boolean.");
c5f0532f
LP
554 r = -ENOMEM;
555 goto finish;
556 }
c5f0532f 557
ac7019f3
LP
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 }
c5f0532f 566
ac7019f3
LP
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 }
c5f0532f 576
ac7019f3
LP
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;
c5f0532f
LP
603 goto finish;
604 }
605
ac7019f3 606 r = -ENOENT;
c5f0532f
LP
607
608finish:
609 if (m)
610 dbus_message_unref(m);
611
612 if (reply)
613 dbus_message_unref(reply);
614
ac7019f3
LP
615 strv_free(l);
616
c5f0532f
LP
617 return r;
618}
619
620static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
621 dbus_bool_t db;
622
623 assert(i);
624 assert(property);
625
d200735e 626 db = tz.use_ntp > 0;
c5f0532f
LP
627
628 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
629 return -ENOMEM;
630
631 return 0;
632}
633
d200735e
MS
634static 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
641static const BusBoundProperties bps[] = {
642 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
643 { NULL, }
644};
645
f401e48c
LP
646static DBusHandlerResult timedate_message_handler(
647 DBusConnection *connection,
648 DBusMessage *message,
649 void *userdata) {
650
f401e48c
LP
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
d200735e 675 if (!streq_ptr(z, tz.zone)) {
f401e48c
LP
676 char *t;
677
89f13440 678 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
f401e48c
LP
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
d200735e
MS
686 free(tz.zone);
687 tz.zone = t;
f401e48c 688
2076cf88 689 /* 1. Write new configuration file */
f401e48c
LP
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
d200735e 696 if (tz.local_rtc) {
2076cf88
LP
697 struct timespec ts;
698 struct tm *tm;
699
700 /* 2. Teach kernel new timezone */
ff4daf5a 701 hwclock_apply_localtime_delta(NULL);
2076cf88
LP
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
877d54e9
LP
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);
f401e48c
LP
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;
05a4abb9 725 dbus_bool_t fix_system;
f401e48c
LP
726 dbus_bool_t interactive;
727
728 if (!dbus_message_get_args(
729 message,
730 &error,
731 DBUS_TYPE_BOOLEAN, &lrtc,
05a4abb9 732 DBUS_TYPE_BOOLEAN, &fix_system,
f401e48c
LP
733 DBUS_TYPE_BOOLEAN, &interactive,
734 DBUS_TYPE_INVALID))
735 return bus_send_error_reply(connection, message, &error, -EINVAL);
736
d200735e 737 if (lrtc != tz.local_rtc) {
2076cf88
LP
738 struct timespec ts;
739
89f13440 740 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
f401e48c
LP
741 if (r < 0)
742 return bus_send_error_reply(connection, message, &error, r);
743
d200735e 744 tz.local_rtc = lrtc;
f401e48c 745
2076cf88 746 /* 1. Write new configuration file */
f401e48c
LP
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
2076cf88 753 /* 2. Teach kernel new timezone */
d200735e 754 if (tz.local_rtc)
ff4daf5a 755 hwclock_apply_localtime_delta(NULL);
2076cf88
LP
756 else
757 hwclock_reset_localtime_delta();
758
759 /* 3. Synchronize clocks */
760 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
761
05a4abb9 762 if (fix_system) {
2076cf88
LP
763 struct tm tm;
764
765 /* Sync system clock from RTC; first,
766 * initialize the timezone fields of
767 * struct tm. */
d200735e 768 if (tz.local_rtc)
2076cf88
LP
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 */
d200735e 780 if (tz.local_rtc)
2076cf88
LP
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 */
d200735e 792 if (tz.local_rtc)
2076cf88
LP
793 tm = localtime(&ts.tv_sec);
794 else
795 tm = gmtime(&ts.tv_sec);
796
797 hwclock_set_time(tm);
798 }
799
d200735e 800 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
f401e48c
LP
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;
2076cf88 829 struct tm* tm;
f401e48c 830
89f13440 831 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
f401e48c
LP
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
2076cf88 840 /* Set system clock */
f401e48c
LP
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
2076cf88 846 /* Sync down to RTC */
d200735e 847 if (tz.local_rtc)
2076cf88
LP
848 tm = localtime(&ts.tv_sec);
849 else
850 tm = gmtime(&ts.tv_sec);
851
852 hwclock_set_time(tm);
853
877d54e9
LP
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);
f401e48c 859 }
c5f0532f
LP
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
d200735e 872 if (ntp != !!tz.use_ntp) {
c5f0532f 873
89f13440 874 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
c5f0532f
LP
875 if (r < 0)
876 return bus_send_error_reply(connection, message, &error, r);
877
d200735e 878 tz.use_ntp = !!ntp;
c5f0532f
LP
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
d200735e 888 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
c5f0532f
LP
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 }
f401e48c
LP
897
898 } else
d200735e 899 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
f401e48c
LP
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
920oom:
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
932static 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) {
a2e52832 946 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
f401e48c
LP
947 r = -ECONNREFUSED;
948 goto fail;
949 }
950
ad740100
LP
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)) {
0d0f0c50 955 r = log_oom();
f401e48c
LP
956 goto fail;
957 }
958
add10b5a
LP
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.");
f401e48c
LP
968 r = -EEXIST;
969 goto fail;
970 }
971
972 if (_bus)
973 *_bus = bus;
974
975 return 0;
976
977fail:
978 dbus_connection_close(bus);
979 dbus_connection_unref(bus);
980
981 dbus_error_free(&error);
982
983 return r;
984}
985
986int main(int argc, char *argv[]) {
987 int r;
988 DBusConnection *bus = NULL;
ad740100 989 bool exiting = false;
f401e48c
LP
990
991 log_set_target(LOG_TARGET_AUTO);
992 log_parse_environment();
993 log_open();
994
4c12626c
LP
995 umask(0022);
996
91f9dcaf
LP
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
f401e48c
LP
1005 if (argc != 1) {
1006 log_error("This program takes no arguments.");
1007 r = -EINVAL;
1008 goto finish;
1009 }
1010
f401e48c
LP
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
c5f0532f
LP
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
ad740100
LP
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 }
f401e48c
LP
1038
1039 r = 0;
1040
1041finish:
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}