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