]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedated.c
basic/log: add the log_struct terminator to macro
[thirdparty/systemd.git] / src / timedate / timedated.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
f401e48c
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2011 Lennart Poettering
f401e48c
LP
6***/
7
f401e48c
LP
8#include <errno.h>
9#include <string.h>
10#include <unistd.h>
11
40ca29a1 12#include "sd-bus.h"
f4f15635
LP
13#include "sd-event.h"
14#include "sd-messages.h"
40ca29a1 15
b5efdb8a 16#include "alloc-util.h"
96aad8d1 17#include "bus-common-errors.h"
f4f15635
LP
18#include "bus-error.h"
19#include "bus-util.h"
20#include "clock-util.h"
21#include "def.h"
f4f15635
LP
22#include "fileio-label.h"
23#include "fs-util.h"
5d280742
YW
24#include "hashmap.h"
25#include "list.h"
f4f15635 26#include "path-util.h"
d7b8eec7 27#include "selinux-util.h"
5d280742 28#include "string-util.h"
f4f15635 29#include "strv.h"
5d280742
YW
30#include "unit-def.h"
31#include "unit-name.h"
ee104e11 32#include "user-util.h"
f4f15635 33#include "util.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
5d280742
YW
38typedef struct UnitStatusInfo {
39 char *name;
40 char *load_state;
41 char *unit_file_state;
42 char *active_state;
43
44 LIST_FIELDS(struct UnitStatusInfo, units);
45} UnitStatusInfo;
46
40ca29a1 47typedef struct Context {
d200735e
MS
48 char *zone;
49 bool local_rtc;
40ca29a1 50 Hashmap *polkit_registry;
5d280742
YW
51
52 LIST_HEAD(UnitStatusInfo, units);
40ca29a1 53} Context;
f401e48c 54
5d280742
YW
55static void unit_status_info_clear(UnitStatusInfo *p) {
56 assert(p);
57
58 p->load_state = mfree(p->load_state);
59 p->unit_file_state = mfree(p->unit_file_state);
60 p->active_state = mfree(p->active_state);
61}
62
63static void unit_status_info_free(UnitStatusInfo *p) {
64 assert(p);
65
66 unit_status_info_clear(p);
67 free(p->name);
68 free(p);
69}
70
36e34057 71static void context_free(Context *c) {
5d280742
YW
72 UnitStatusInfo *p;
73
40ca29a1 74 assert(c);
f401e48c 75
82d115d9 76 free(c->zone);
36e34057 77 bus_verify_polkit_async_registry_free(c->polkit_registry);
5d280742
YW
78
79 while ((p = c->units)) {
80 LIST_REMOVE(units, c->units, p);
81 unit_status_info_free(p);
82 }
83}
84
85static int context_add_ntp_service(Context *c, const char *s) {
5d280742
YW
86 UnitStatusInfo *u;
87
88 if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
89 return -EINVAL;
90
91 /* Do not add this if it is already listed */
92 LIST_FOREACH(units, u, c->units)
93 if (streq(u->name, s))
94 return 0;
95
96 u = new0(UnitStatusInfo, 1);
97 if (!u)
98 return -ENOMEM;
99
100 u->name = strdup(s);
101 if (!u->name) {
102 free(u);
103 return -ENOMEM;
104 }
105
106 LIST_APPEND(units, c->units, u);
107
108 return 0;
109}
110
111static int context_parse_ntp_services(Context *c) {
112 const char *env, *p;
113 int r;
114
115 assert(c);
116
117 env = getenv("SYSTEMD_TIMEDATED_NTP_SERVICES");
118 if (!env) {
119 r = context_add_ntp_service(c, "systemd-timesyncd.service");
120 if (r < 0)
121 log_warning_errno(r, "Failed to add NTP service \"systemd-timesyncd.service\", ignoring: %m");
122
123 return 0;
124 }
125
126 for (p = env;;) {
127 _cleanup_free_ char *word = NULL;
128
129 r = extract_first_word(&p, &word, ":", 0);
130 if (r == 0)
131 break;
132 if (r == -ENOMEM)
133 return log_oom();
134 if (r < 0) {
135 log_error("Invalid syntax, ignoring: %s", env);
136 break;
137 }
138
139 r = context_add_ntp_service(c, word);
140 if (r < 0)
141 log_warning_errno(r, "Failed to add NTP service \"%s\", ignoring: %m", word);
142 }
143
144 return 0;
145}
146
147static int context_ntp_service_is_active(Context *c) {
148 UnitStatusInfo *info;
149 int count = 0;
150
151 assert(c);
152
153 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
154
155 LIST_FOREACH(units, info, c->units)
156 count += streq_ptr(info->active_state, "active");
157
158 return count;
159}
160
161static int context_ntp_service_is_enabled(Context *c) {
162 UnitStatusInfo *info;
163 int count = 0;
164
165 assert(c);
166
167 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
168
169 LIST_FOREACH(units, info, c->units)
170 count += STRPTR_IN_SET(info->unit_file_state, "enabled", "enabled-runtime");
171
172 return count;
173}
174
175static int context_ntp_service_exists(Context *c) {
176 UnitStatusInfo *info;
177 int count = 0;
178
179 assert(c);
180
181 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
182
183 LIST_FOREACH(units, info, c->units)
184 count += streq_ptr(info->load_state, "loaded");
185
186 return count;
f401e48c
LP
187}
188
40ca29a1 189static int context_read_data(Context *c) {
424a19f8 190 _cleanup_free_ char *t = NULL;
40ca29a1
LP
191 int r;
192
193 assert(c);
f401e48c 194
5c904ba5
LP
195 r = get_timezone(&t);
196 if (r == -EINVAL)
197 log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
198 else if (r < 0)
199 log_warning_errno(r, "Failed to get target of /etc/localtime: %m");
92c4ef2d 200
f9ecfd3b 201 free_and_replace(c->zone, t);
f401e48c 202
6369641d 203 c->local_rtc = clock_is_localtime(NULL) > 0;
f401e48c
LP
204
205 return 0;
206}
207
40ca29a1 208static int context_write_data_timezone(Context *c) {
424a19f8 209 _cleanup_free_ char *p = NULL;
40ca29a1
LP
210 int r = 0;
211
212 assert(c);
e19a21a8 213
40ca29a1 214 if (isempty(c->zone)) {
424a19f8 215 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
f401e48c
LP
216 r = -errno;
217
f401e48c
LP
218 return r;
219 }
220
40ca29a1 221 p = strappend("../usr/share/zoneinfo/", c->zone);
0d0f0c50
SL
222 if (!p)
223 return log_oom();
f401e48c 224
424a19f8 225 r = symlink_atomic(p, "/etc/localtime");
f401e48c 226 if (r < 0)
424a19f8 227 return r;
f401e48c 228
f401e48c
LP
229 return 0;
230}
231
40ca29a1 232static int context_write_data_local_rtc(Context *c) {
f401e48c 233 int r;
7fd1b19b 234 _cleanup_free_ char *s = NULL, *w = NULL;
f401e48c 235
40ca29a1
LP
236 assert(c);
237
f401e48c
LP
238 r = read_full_file("/etc/adjtime", &s, NULL);
239 if (r < 0) {
240 if (r != -ENOENT)
241 return r;
242
40ca29a1 243 if (!c->local_rtc)
f401e48c
LP
244 return 0;
245
246 w = strdup(NULL_ADJTIME_LOCAL);
247 if (!w)
248 return -ENOMEM;
249 } else {
c9410dd4 250 char *p;
8f462d87 251 const char *e = "\n"; /* default if there is less than 3 lines */
c9410dd4 252 const char *prepend = "";
f401e48c
LP
253 size_t a, b;
254
c9410dd4 255 p = strchrnul(s, '\n');
cb971cc0 256 if (*p == '\0')
c9410dd4
MP
257 /* only one line, no \n terminator */
258 prepend = "\n0\n";
cb971cc0 259 else if (p[1] == '\0') {
c9410dd4
MP
260 /* only one line, with \n terminator */
261 ++p;
262 prepend = "0\n";
263 } else {
264 p = strchr(p+1, '\n');
265 if (!p) {
266 /* only two lines, no \n terminator */
267 prepend = "\n";
268 p = s + strlen(s);
269 } else {
270 char *end;
271 /* third line might have a \n terminator or not */
272 p++;
273 end = strchr(p, '\n');
274 /* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */
275 if (end)
276 e = end;
277 }
278 }
f401e48c
LP
279
280 a = p - s;
281 b = strlen(e);
282
c9410dd4 283 w = new(char, a + (c->local_rtc ? 5 : 3) + strlen(prepend) + b + 1);
d257f05a 284 if (!w)
f401e48c 285 return -ENOMEM;
f401e48c 286
c9410dd4 287 *(char*) mempcpy(stpcpy(stpcpy(mempcpy(w, s, a), prepend), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
f401e48c
LP
288
289 if (streq(w, NULL_ADJTIME_UTC)) {
d257f05a 290 if (unlink("/etc/adjtime") < 0)
f401e48c
LP
291 if (errno != ENOENT)
292 return -errno;
f401e48c
LP
293
294 return 0;
295 }
296 }
40ca29a1 297
c3dacc8b 298 mac_selinux_init();
d257f05a 299 return write_string_file_atomic_label("/etc/adjtime", w);
f401e48c
LP
300}
301
5d280742
YW
302static int context_update_ntp_status(Context *c, sd_bus *bus, sd_bus_message *m) {
303 static const struct bus_properties_map map[] = {
304 { "LoadState", "s", NULL, offsetof(UnitStatusInfo, load_state) },
305 { "ActiveState", "s", NULL, offsetof(UnitStatusInfo, active_state) },
306 { "UnitFileState", "s", NULL, offsetof(UnitStatusInfo, unit_file_state) },
307 {}
308 };
309 static sd_bus_message *_m = NULL;
310 UnitStatusInfo *u;
c5f0532f
LP
311 int r;
312
40ca29a1 313 assert(c);
c5f0532f
LP
314 assert(bus);
315
5d280742
YW
316 /* Suppress multiple call of context_update_ntp_status() within single DBus transaction. */
317 if (m && m == _m)
318 return 0;
2aa4c315 319
5d280742 320 _m = m;
2aa4c315 321
5d280742
YW
322 LIST_FOREACH(units, u, c->units) {
323 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
5d280742 324 _cleanup_free_ char *path = NULL;
c5f0532f 325
5d280742 326 unit_status_info_clear(u);
40ca29a1 327
5d280742
YW
328 path = unit_dbus_path_from_name(u->name);
329 if (!path)
330 return -ENOMEM;
331
332 r = bus_map_all_properties(
333 bus,
334 "org.freedesktop.systemd1",
335 path,
336 map,
337 BUS_MAP_STRDUP,
338 &error,
339 NULL,
340 u);
341 if (r < 0)
342 return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r));
343 }
c5f0532f 344
40ca29a1 345 return 0;
c5f0532f
LP
346}
347
5d280742 348static int unit_start_or_stop(UnitStatusInfo *u, sd_bus *bus, sd_bus_error *error, bool start) {
c5f0532f
LP
349 int r;
350
5d280742 351 assert(u);
c5f0532f
LP
352 assert(bus);
353 assert(error);
354
5d280742
YW
355 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
356
357 if (streq(u->active_state, "active") == start)
358 return 0;
359
81b84399
ZJS
360 r = sd_bus_call_method(
361 bus,
362 "org.freedesktop.systemd1",
363 "/org/freedesktop/systemd1",
364 "org.freedesktop.systemd1.Manager",
5d280742 365 start ? "StartUnit" : "StopUnit",
81b84399
ZJS
366 error,
367 NULL,
368 "ss",
5d280742 369 u->name,
81b84399 370 "replace");
5d280742 371 if (r < 0)
b72ddf0f 372 return r;
c5f0532f 373
b72ddf0f 374 return 0;
c5f0532f
LP
375}
376
5d280742 377static int unit_enable_or_disable(UnitStatusInfo *u, sd_bus *bus, sd_bus_error *error, bool enable) {
c5f0532f 378 int r;
c5f0532f 379
5d280742 380 assert(u);
c5f0532f
LP
381 assert(bus);
382 assert(error);
383
5d280742
YW
384 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
385
386 if (streq(u->unit_file_state, "enabled") == enable)
387 return 0;
388
389 if (enable)
40ca29a1
LP
390 r = sd_bus_call_method(
391 bus,
ac7019f3
LP
392 "org.freedesktop.systemd1",
393 "/org/freedesktop/systemd1",
394 "org.freedesktop.systemd1.Manager",
b72ddf0f 395 "EnableUnitFiles",
40ca29a1
LP
396 error,
397 NULL,
b72ddf0f 398 "asbb", 1,
5d280742 399 u->name,
b72ddf0f
KS
400 false, true);
401 else
402 r = sd_bus_call_method(
403 bus,
404 "org.freedesktop.systemd1",
405 "/org/freedesktop/systemd1",
406 "org.freedesktop.systemd1.Manager",
407 "DisableUnitFiles",
408 error,
409 NULL,
410 "asb", 1,
5d280742 411 u->name,
b72ddf0f 412 false);
5d280742 413 if (r < 0)
b72ddf0f 414 return r;
c5f0532f 415
b72ddf0f
KS
416 r = sd_bus_call_method(
417 bus,
418 "org.freedesktop.systemd1",
419 "/org/freedesktop/systemd1",
420 "org.freedesktop.systemd1.Manager",
421 "Reload",
422 error,
423 NULL,
424 NULL);
5d280742
YW
425 if (r < 0)
426 return r;
b72ddf0f 427 return 0;
40ca29a1 428}
c5f0532f 429
6cc379b5
YW
430static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_time, "t", now(CLOCK_REALTIME));
431static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_ntp_sync, "b", ntp_synced());
432
6fc60278
LP
433static int property_get_rtc_time(
434 sd_bus *bus,
435 const char *path,
436 const char *interface,
437 const char *property,
438 sd_bus_message *reply,
ebcf1f97
LP
439 void *userdata,
440 sd_bus_error *error) {
6fc60278
LP
441
442 struct tm tm;
443 usec_t t;
444 int r;
445
446 zero(tm);
60989612 447 r = clock_get_hwclock(&tm);
88e262b6 448 if (r == -EBUSY) {
07a062a7 449 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
88e262b6 450 t = 0;
fe2b58a4 451 } else if (r == -ENOENT) {
07a062a7 452 log_debug("/dev/rtc not found.");
fe2b58a4 453 t = 0; /* no RTC found */
ebcf1f97 454 } else if (r < 0)
10a87006 455 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
ebcf1f97 456 else
2f6a5907 457 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
6fc60278 458
ebcf1f97 459 return sd_bus_message_append(reply, "t", t);
6fc60278
LP
460}
461
5d280742
YW
462static int property_get_can_ntp(
463 sd_bus *bus,
464 const char *path,
465 const char *interface,
466 const char *property,
467 sd_bus_message *reply,
468 void *userdata,
469 sd_bus_error *error) {
470
471 Context *c = userdata;
472 int r;
473
474 assert(c);
475 assert(bus);
476 assert(property);
477 assert(reply);
478 assert(error);
479
480 r = context_update_ntp_status(c, bus, reply);
481 if (r < 0)
482 return r;
483
484 return sd_bus_message_append(reply, "b", context_ntp_service_exists(c) > 0);
485}
486
487static int property_get_ntp(
488 sd_bus *bus,
489 const char *path,
490 const char *interface,
491 const char *property,
492 sd_bus_message *reply,
493 void *userdata,
494 sd_bus_error *error) {
495
496 Context *c = userdata;
497 int r;
498
499 assert(c);
500 assert(bus);
501 assert(property);
502 assert(reply);
503 assert(error);
504
505 r = context_update_ntp_status(c, bus, reply);
506 if (r < 0)
507 return r;
508
509 return sd_bus_message_append(reply, "b", context_ntp_service_is_active(c) > 0);
510}
511
19070062 512static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) {
40ca29a1 513 Context *c = userdata;
2c3def62 514 int interactive, r;
40ca29a1 515 const char *z;
c5f0532f 516
7e9cf16c
LP
517 assert(m);
518 assert(c);
519
40ca29a1
LP
520 r = sd_bus_message_read(m, "sb", &z, &interactive);
521 if (r < 0)
ebcf1f97 522 return r;
c5f0532f 523
089fb865 524 if (!timezone_is_valid(z, LOG_DEBUG))
ebcf1f97 525 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
ac7019f3 526
539a68e0 527 if (streq_ptr(z, c->zone))
df2d202e 528 return sd_bus_reply_method_return(m, NULL);
c5f0532f 529
c529695e
LP
530 r = bus_verify_polkit_async(
531 m,
532 CAP_SYS_TIME,
533 "org.freedesktop.timedate1.set-timezone",
403ed0e5 534 NULL,
c529695e
LP
535 interactive,
536 UID_INVALID,
537 &c->polkit_registry,
538 error);
40ca29a1 539 if (r < 0)
ebcf1f97 540 return r;
40ca29a1 541 if (r == 0)
6fc60278 542 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
6ffe5e37 543
539a68e0
YW
544 r = free_and_strdup(&c->zone, z);
545 if (r < 0)
546 return r;
547
40ca29a1
LP
548 /* 1. Write new configuration file */
549 r = context_write_data_timezone(c);
550 if (r < 0) {
da927ba9 551 log_error_errno(r, "Failed to set time zone: %m");
10a87006 552 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m");
40ca29a1 553 }
6ffe5e37 554
8a50b96f
LP
555 /* 2. Make glibc notice the new timezone */
556 tzset();
557
558 /* 3. Tell the kernel our timezone */
2a7ff45f
LP
559 r = clock_set_timezone(NULL);
560 if (r < 0)
561 log_debug_errno(r, "Failed to tell kernel about timezone, ignoring: %m");
6ffe5e37 562
40ca29a1
LP
563 if (c->local_rtc) {
564 struct timespec ts;
565 struct tm *tm;
c5f0532f 566
8a50b96f 567 /* 4. Sync RTC from system clock, with the new delta */
40ca29a1
LP
568 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
569 assert_se(tm = localtime(&ts.tv_sec));
2a7ff45f
LP
570
571 r = clock_set_hwclock(tm);
572 if (r < 0)
573 log_debug_errno(r, "Failed to sync time to hardware clock, ignoring: %m");
40ca29a1 574 }
c5f0532f 575
40ca29a1 576 log_struct(LOG_INFO,
2b044526 577 "MESSAGE_ID=" SD_MESSAGE_TIMEZONE_CHANGE_STR,
40ca29a1 578 "TIMEZONE=%s", c->zone,
8a50b96f
LP
579 "TIMEZONE_SHORTNAME=%s", tzname[daylight],
580 "DAYLIGHT=%i", daylight,
a1230ff9 581 LOG_MESSAGE("Changed time zone to '%s' (%s).", c->zone, tzname[daylight]));
c5f0532f 582
19070062 583 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
c5f0532f 584
df2d202e 585 return sd_bus_reply_method_return(m, NULL);
c5f0532f
LP
586}
587
19070062 588static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) {
102d8f81 589 int lrtc, fix_system, interactive;
40ca29a1
LP
590 Context *c = userdata;
591 struct timespec ts;
f401e48c
LP
592 int r;
593
40ca29a1
LP
594 assert(m);
595 assert(c);
f401e48c 596
40ca29a1
LP
597 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
598 if (r < 0)
ebcf1f97 599 return r;
f401e48c 600
40ca29a1 601 if (lrtc == c->local_rtc)
df2d202e 602 return sd_bus_reply_method_return(m, NULL);
f401e48c 603
c529695e
LP
604 r = bus_verify_polkit_async(
605 m,
606 CAP_SYS_TIME,
607 "org.freedesktop.timedate1.set-local-rtc",
403ed0e5 608 NULL,
c529695e
LP
609 interactive,
610 UID_INVALID,
611 &c->polkit_registry,
612 error);
40ca29a1 613 if (r < 0)
ebcf1f97 614 return r;
40ca29a1
LP
615 if (r == 0)
616 return 1;
f401e48c 617
40ca29a1 618 c->local_rtc = lrtc;
f401e48c 619
40ca29a1
LP
620 /* 1. Write new configuration file */
621 r = context_write_data_local_rtc(c);
622 if (r < 0) {
da927ba9 623 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
10a87006 624 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m");
40ca29a1 625 }
f401e48c 626
40ca29a1 627 /* 2. Tell the kernel our timezone */
2a7ff45f
LP
628 r = clock_set_timezone(NULL);
629 if (r < 0)
630 log_debug_errno(r, "Failed to tell kernel about timezone, ignoring: %m");
f401e48c 631
40ca29a1
LP
632 /* 3. Synchronize clocks */
633 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
f401e48c 634
40ca29a1
LP
635 if (fix_system) {
636 struct tm tm;
f401e48c 637
2a7ff45f 638 /* Sync system clock from RTC; first, initialize the timezone fields of struct tm. */
40ca29a1
LP
639 if (c->local_rtc)
640 tm = *localtime(&ts.tv_sec);
641 else
642 tm = *gmtime(&ts.tv_sec);
72edcff5 643
2a7ff45f
LP
644 /* Override the main fields of struct tm, but not the timezone fields */
645 r = clock_get_hwclock(&tm);
646 if (r < 0)
647 log_debug_errno(r, "Failed to get hardware clock, ignoring: %m");
648 else {
649 /* And set the system clock with this */
40ca29a1
LP
650 if (c->local_rtc)
651 ts.tv_sec = mktime(&tm);
652 else
653 ts.tv_sec = timegm(&tm);
2076cf88 654
2a7ff45f
LP
655 if (clock_settime(CLOCK_REALTIME, &ts) < 0)
656 log_debug_errno(errno, "Failed to update system clock, ignoring: %m");
f401e48c
LP
657 }
658
40ca29a1
LP
659 } else {
660 struct tm *tm;
f401e48c 661
40ca29a1
LP
662 /* Sync RTC from system clock */
663 if (c->local_rtc)
664 tm = localtime(&ts.tv_sec);
665 else
666 tm = gmtime(&ts.tv_sec);
2076cf88 667
2a7ff45f
LP
668 r = clock_set_hwclock(tm);
669 if (r < 0)
670 log_debug_errno(r, "Failed to sync time to hardware clock, ignoring: %m");
40ca29a1 671 }
2076cf88 672
40ca29a1 673 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
2076cf88 674
19070062 675 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
2076cf88 676
df2d202e 677 return sd_bus_reply_method_return(m, NULL);
40ca29a1 678}
2076cf88 679
19070062 680static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) {
5d280742
YW
681 sd_bus *bus = sd_bus_message_get_bus(m);
682 int relative, interactive, r;
40ca29a1
LP
683 Context *c = userdata;
684 int64_t utc;
685 struct timespec ts;
2479df30 686 usec_t start;
40ca29a1 687 struct tm* tm;
2076cf88 688
40ca29a1
LP
689 assert(m);
690 assert(c);
2076cf88 691
5d280742
YW
692 r = context_update_ntp_status(c, bus, m);
693 if (r < 0)
694 return sd_bus_error_set_errnof(error, r, "Failed to update context: %m");
695
696 if (context_ntp_service_is_active(c) > 0)
e9e5ea88 697 return sd_bus_error_set(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
82d115d9 698
6829cec4
SL
699 /* this only gets used if dbus does not provide a timestamp */
700 start = now(CLOCK_MONOTONIC);
701
40ca29a1
LP
702 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
703 if (r < 0)
ebcf1f97 704 return r;
2076cf88 705
40ca29a1 706 if (!relative && utc <= 0)
e9e5ea88 707 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
2076cf88 708
40ca29a1 709 if (relative && utc == 0)
df2d202e 710 return sd_bus_reply_method_return(m, NULL);
2076cf88 711
40ca29a1
LP
712 if (relative) {
713 usec_t n, x;
f401e48c 714
40ca29a1
LP
715 n = now(CLOCK_REALTIME);
716 x = n + utc;
f401e48c 717
40ca29a1
LP
718 if ((utc > 0 && x < n) ||
719 (utc < 0 && x > n))
e9e5ea88 720 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
f401e48c 721
40ca29a1
LP
722 timespec_store(&ts, x);
723 } else
724 timespec_store(&ts, (usec_t) utc);
2076cf88 725
c529695e
LP
726 r = bus_verify_polkit_async(
727 m,
728 CAP_SYS_TIME,
729 "org.freedesktop.timedate1.set-time",
403ed0e5 730 NULL,
c529695e
LP
731 interactive,
732 UID_INVALID,
733 &c->polkit_registry,
734 error);
40ca29a1 735 if (r < 0)
ebcf1f97 736 return r;
40ca29a1
LP
737 if (r == 0)
738 return 1;
739
2479df30
SL
740 /* adjust ts for time spent in program */
741 r = sd_bus_message_get_monotonic_usec(m, &start);
6829cec4 742 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
2479df30
SL
743 if (r < 0 && r != -ENODATA)
744 return r;
6829cec4
SL
745
746 timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start));
2479df30 747
40ca29a1
LP
748 /* Set system clock */
749 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
56f64d95 750 log_error_errno(errno, "Failed to set local time: %m");
ebcf1f97 751 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
40ca29a1 752 }
2076cf88 753
40ca29a1
LP
754 /* Sync down to RTC */
755 if (c->local_rtc)
756 tm = localtime(&ts.tv_sec);
757 else
758 tm = gmtime(&ts.tv_sec);
2a7ff45f
LP
759
760 r = clock_set_hwclock(tm);
761 if (r < 0)
762 log_debug_errno(r, "Failed to update hardware clock, ignoring: %m");
f401e48c 763
40ca29a1 764 log_struct(LOG_INFO,
2b044526 765 "MESSAGE_ID=" SD_MESSAGE_TIME_CHANGE_STR,
de0671ee 766 "REALTIME="USEC_FMT, timespec_load(&ts),
a1230ff9 767 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)));
f401e48c 768
df2d202e 769 return sd_bus_reply_method_return(m, NULL);
40ca29a1 770}
f401e48c 771
19070062 772static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) {
5d280742 773 sd_bus *bus = sd_bus_message_get_bus(m);
40ca29a1 774 Context *c = userdata;
5d280742
YW
775 UnitStatusInfo *u;
776 int enable, interactive, q, r;
f401e48c 777
19070062 778 assert(m);
5d280742 779 assert(bus);
19070062
LP
780 assert(c);
781
5d280742 782 r = sd_bus_message_read(m, "bb", &enable, &interactive);
40ca29a1 783 if (r < 0)
ebcf1f97 784 return r;
f401e48c 785
5d280742
YW
786 r = context_update_ntp_status(c, bus, m);
787 if (r < 0)
788 return r;
789
790 if (context_ntp_service_exists(c) <= 0)
791 return sd_bus_error_set(error, BUS_ERROR_NO_NTP_SUPPORT, "NTP not supported");
f401e48c 792
c529695e
LP
793 r = bus_verify_polkit_async(
794 m,
795 CAP_SYS_TIME,
796 "org.freedesktop.timedate1.set-ntp",
403ed0e5 797 NULL,
c529695e
LP
798 interactive,
799 UID_INVALID,
800 &c->polkit_registry,
801 error);
40ca29a1 802 if (r < 0)
ebcf1f97 803 return r;
40ca29a1
LP
804 if (r == 0)
805 return 1;
f401e48c 806
5d280742
YW
807 if (!enable)
808 LIST_FOREACH(units, u, c->units) {
809 if (!streq(u->load_state, "loaded"))
810 continue;
811
812 q = unit_enable_or_disable(u, bus, error, enable);
813 if (q < 0)
814 r = q;
815
816 q = unit_start_or_stop(u, bus, error, enable);
817 if (q < 0)
818 r = q;
819 }
820
821 else if (context_ntp_service_is_enabled(c) <= 0)
822 LIST_FOREACH(units, u, c->units) {
823 if (!streq(u->load_state, "loaded"))
824 continue;
825
826 r = unit_enable_or_disable(u, bus, error, enable);
827 if (r < 0)
828 continue;
829
830 r = unit_start_or_stop(u, bus, error, enable);
831 break;
832 }
833
834 else if (context_ntp_service_is_active(c) <= 0)
835 LIST_FOREACH(units, u, c->units) {
836 if (!streq(u->load_state, "loaded") ||
837 !streq(u->unit_file_state, "enabled"))
838 continue;
839
840 r = unit_start_or_stop(u, bus, error, enable);
841 break;
842 }
40ca29a1 843
40ca29a1 844 if (r < 0)
ebcf1f97 845 return r;
f401e48c 846
5d280742 847 log_info("Set NTP to %sd", enable_disable(enable));
f401e48c 848
5d280742 849 (void) sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
f401e48c 850
df2d202e 851 return sd_bus_reply_method_return(m, NULL);
f401e48c
LP
852}
853
40ca29a1
LP
854static const sd_bus_vtable timedate_vtable[] = {
855 SD_BUS_VTABLE_START(0),
856 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
82d115d9 857 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
5d280742
YW
858 SD_BUS_PROPERTY("CanNTP", "b", property_get_can_ntp, 0, 0),
859 SD_BUS_PROPERTY("NTP", "b", property_get_ntp, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
03cc26dd
LP
860 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
861 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
6fc60278 862 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
adacb957
LP
863 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
864 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
865 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
866 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
40ca29a1
LP
867 SD_BUS_VTABLE_END,
868};
869
870static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
4afd3348 871 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
f401e48c
LP
872 int r;
873
40ca29a1
LP
874 assert(c);
875 assert(event);
f401e48c
LP
876 assert(_bus);
877
76b54375 878 r = sd_bus_default_system(&bus);
f647962d
MS
879 if (r < 0)
880 return log_error_errno(r, "Failed to get system bus connection: %m");
f401e48c 881
19befb2d 882 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
f647962d
MS
883 if (r < 0)
884 return log_error_errno(r, "Failed to register object: %m");
f401e48c 885
0c0b9306 886 r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.timedate1", 0, NULL, NULL);
f647962d 887 if (r < 0)
0c0b9306 888 return log_error_errno(r, "Failed to request name: %m");
add10b5a 889
40ca29a1 890 r = sd_bus_attach_event(bus, event, 0);
f647962d
MS
891 if (r < 0)
892 return log_error_errno(r, "Failed to attach bus to event loop: %m");
f401e48c 893
1cc6c93a 894 *_bus = TAKE_PTR(bus);
f401e48c 895
40ca29a1 896 return 0;
f401e48c
LP
897}
898
899int main(int argc, char *argv[]) {
82d115d9 900 Context context = {};
4afd3348
LP
901 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
902 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
f401e48c 903 int r;
f401e48c
LP
904
905 log_set_target(LOG_TARGET_AUTO);
906 log_parse_environment();
907 log_open();
908
4c12626c
LP
909 umask(0022);
910
f401e48c
LP
911 if (argc != 1) {
912 log_error("This program takes no arguments.");
913 r = -EINVAL;
914 goto finish;
915 }
916
afc6adb5 917 r = sd_event_default(&event);
f401e48c 918 if (r < 0) {
da927ba9 919 log_error_errno(r, "Failed to allocate event loop: %m");
f401e48c
LP
920 goto finish;
921 }
922
cde93897
LP
923 sd_event_set_watchdog(event, true);
924
40ca29a1 925 r = connect_bus(&context, event, &bus);
f401e48c
LP
926 if (r < 0)
927 goto finish;
928
dc751688 929 (void) sd_bus_negotiate_timestamp(bus, true);
2479df30 930
40ca29a1 931 r = context_read_data(&context);
c5f0532f 932 if (r < 0) {
da927ba9 933 log_error_errno(r, "Failed to read time zone data: %m");
c5f0532f
LP
934 goto finish;
935 }
936
5d280742
YW
937 r = context_parse_ntp_services(&context);
938 if (r < 0)
40ca29a1 939 goto finish;
ad740100 940
37224a5f 941 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
40ca29a1 942 if (r < 0) {
da927ba9 943 log_error_errno(r, "Failed to run event loop: %m");
40ca29a1 944 goto finish;
ad740100 945 }
f401e48c 946
f401e48c 947finish:
36e34057 948 context_free(&context);
f401e48c
LP
949
950 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
951}