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