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