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