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