]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/timedate/timedated.c
Merge pull request #1062 from poettering/cgroups-show
[thirdparty/systemd.git] / src / timedate / timedated.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include "sd-messages.h"
27 #include "sd-event.h"
28 #include "sd-bus.h"
29
30 #include "util.h"
31 #include "strv.h"
32 #include "def.h"
33 #include "clock-util.h"
34 #include "path-util.h"
35 #include "fileio-label.h"
36 #include "bus-util.h"
37 #include "bus-error.h"
38 #include "bus-common-errors.h"
39 #include "event-util.h"
40 #include "selinux-util.h"
41
42 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
43 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
44
45 static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = {
46 SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP),
47 SD_BUS_ERROR_MAP_END
48 };
49
50 typedef struct Context {
51 char *zone;
52 bool local_rtc;
53 bool can_ntp;
54 bool use_ntp;
55 Hashmap *polkit_registry;
56 } Context;
57
58 static void context_free(Context *c) {
59 assert(c);
60
61 free(c->zone);
62 bus_verify_polkit_async_registry_free(c->polkit_registry);
63 }
64
65 static int context_read_data(Context *c) {
66 _cleanup_free_ char *t = NULL;
67 int r;
68
69 assert(c);
70
71 r = get_timezone(&t);
72 if (r == -EINVAL)
73 log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
74 else if (r < 0)
75 log_warning_errno(r, "Failed to get target of /etc/localtime: %m");
76
77 free(c->zone);
78 c->zone = t;
79 t = NULL;
80
81 c->local_rtc = clock_is_localtime() > 0;
82
83 return 0;
84 }
85
86 static int context_write_data_timezone(Context *c) {
87 _cleanup_free_ char *p = NULL;
88 int r = 0;
89
90 assert(c);
91
92 if (isempty(c->zone)) {
93 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
94 r = -errno;
95
96 return r;
97 }
98
99 p = strappend("../usr/share/zoneinfo/", c->zone);
100 if (!p)
101 return log_oom();
102
103 r = symlink_atomic(p, "/etc/localtime");
104 if (r < 0)
105 return r;
106
107 return 0;
108 }
109
110 static int context_write_data_local_rtc(Context *c) {
111 int r;
112 _cleanup_free_ char *s = NULL, *w = NULL;
113
114 assert(c);
115
116 r = read_full_file("/etc/adjtime", &s, NULL);
117 if (r < 0) {
118 if (r != -ENOENT)
119 return r;
120
121 if (!c->local_rtc)
122 return 0;
123
124 w = strdup(NULL_ADJTIME_LOCAL);
125 if (!w)
126 return -ENOMEM;
127 } else {
128 char *p, *e;
129 size_t a, b;
130
131 p = strchr(s, '\n');
132 if (!p)
133 return -EIO;
134
135 p = strchr(p+1, '\n');
136 if (!p)
137 return -EIO;
138
139 p++;
140 e = strchr(p, '\n');
141 if (!e)
142 return -EIO;
143
144 a = p - s;
145 b = strlen(e);
146
147 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
148 if (!w)
149 return -ENOMEM;
150
151 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
152
153 if (streq(w, NULL_ADJTIME_UTC)) {
154 if (unlink("/etc/adjtime") < 0)
155 if (errno != ENOENT)
156 return -errno;
157
158 return 0;
159 }
160 }
161
162 mac_selinux_init("/etc");
163 return write_string_file_atomic_label("/etc/adjtime", w);
164 }
165
166 static int context_read_ntp(Context *c, sd_bus *bus) {
167 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
168 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
169 const char *s;
170 int r;
171
172 assert(c);
173 assert(bus);
174
175 r = sd_bus_call_method(
176 bus,
177 "org.freedesktop.systemd1",
178 "/org/freedesktop/systemd1",
179 "org.freedesktop.systemd1.Manager",
180 "GetUnitFileState",
181 &error,
182 &reply,
183 "s",
184 "systemd-timesyncd.service");
185
186 if (r < 0) {
187 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
188 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
189 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
190 return 0;
191
192 return r;
193 }
194
195 r = sd_bus_message_read(reply, "s", &s);
196 if (r < 0)
197 return r;
198
199 c->can_ntp = true;
200 c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
201
202 return 0;
203 }
204
205 static int context_start_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
206 int r;
207
208 assert(bus);
209 assert(error);
210
211 r = sd_bus_call_method(
212 bus,
213 "org.freedesktop.systemd1",
214 "/org/freedesktop/systemd1",
215 "org.freedesktop.systemd1.Manager",
216 enabled ? "StartUnit" : "StopUnit",
217 error,
218 NULL,
219 "ss",
220 "systemd-timesyncd.service",
221 "replace");
222 if (r < 0) {
223 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
224 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
225 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
226 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
227
228 return r;
229 }
230
231 return 0;
232 }
233
234 static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
235 int r;
236
237 assert(bus);
238 assert(error);
239
240 if (enabled)
241 r = sd_bus_call_method(
242 bus,
243 "org.freedesktop.systemd1",
244 "/org/freedesktop/systemd1",
245 "org.freedesktop.systemd1.Manager",
246 "EnableUnitFiles",
247 error,
248 NULL,
249 "asbb", 1,
250 "systemd-timesyncd.service",
251 false, true);
252 else
253 r = sd_bus_call_method(
254 bus,
255 "org.freedesktop.systemd1",
256 "/org/freedesktop/systemd1",
257 "org.freedesktop.systemd1.Manager",
258 "DisableUnitFiles",
259 error,
260 NULL,
261 "asb", 1,
262 "systemd-timesyncd.service",
263 false);
264
265 if (r < 0) {
266 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
267 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
268
269 return r;
270 }
271
272 r = sd_bus_call_method(
273 bus,
274 "org.freedesktop.systemd1",
275 "/org/freedesktop/systemd1",
276 "org.freedesktop.systemd1.Manager",
277 "Reload",
278 error,
279 NULL,
280 NULL);
281 if (r < 0)
282 return r;
283
284 return 0;
285 }
286
287 static int property_get_rtc_time(
288 sd_bus *bus,
289 const char *path,
290 const char *interface,
291 const char *property,
292 sd_bus_message *reply,
293 void *userdata,
294 sd_bus_error *error) {
295
296 struct tm tm;
297 usec_t t;
298 int r;
299
300 zero(tm);
301 r = clock_get_hwclock(&tm);
302 if (r == -EBUSY) {
303 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
304 t = 0;
305 } else if (r == -ENOENT) {
306 log_debug("/dev/rtc not found.");
307 t = 0; /* no RTC found */
308 } else if (r < 0)
309 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
310 else
311 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
312
313 return sd_bus_message_append(reply, "t", t);
314 }
315
316 static int property_get_time(
317 sd_bus *bus,
318 const char *path,
319 const char *interface,
320 const char *property,
321 sd_bus_message *reply,
322 void *userdata,
323 sd_bus_error *error) {
324
325 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
326 }
327
328 static int property_get_ntp_sync(
329 sd_bus *bus,
330 const char *path,
331 const char *interface,
332 const char *property,
333 sd_bus_message *reply,
334 void *userdata,
335 sd_bus_error *error) {
336
337 return sd_bus_message_append(reply, "b", ntp_synced());
338 }
339
340 static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) {
341 Context *c = userdata;
342 const char *z;
343 int interactive;
344 char *t;
345 int r;
346
347 assert(m);
348 assert(c);
349
350 r = sd_bus_message_read(m, "sb", &z, &interactive);
351 if (r < 0)
352 return r;
353
354 if (!timezone_is_valid(z))
355 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
356
357 if (streq_ptr(z, c->zone))
358 return sd_bus_reply_method_return(m, NULL);
359
360 r = bus_verify_polkit_async(
361 m,
362 CAP_SYS_TIME,
363 "org.freedesktop.timedate1.set-timezone",
364 interactive,
365 UID_INVALID,
366 &c->polkit_registry,
367 error);
368 if (r < 0)
369 return r;
370 if (r == 0)
371 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
372
373 t = strdup(z);
374 if (!t)
375 return -ENOMEM;
376
377 free(c->zone);
378 c->zone = t;
379
380 /* 1. Write new configuration file */
381 r = context_write_data_timezone(c);
382 if (r < 0) {
383 log_error_errno(r, "Failed to set time zone: %m");
384 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m");
385 }
386
387 /* 2. Tell the kernel our timezone */
388 clock_set_timezone(NULL);
389
390 if (c->local_rtc) {
391 struct timespec ts;
392 struct tm *tm;
393
394 /* 3. Sync RTC from system clock, with the new delta */
395 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
396 assert_se(tm = localtime(&ts.tv_sec));
397 clock_set_hwclock(tm);
398 }
399
400 log_struct(LOG_INFO,
401 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
402 "TIMEZONE=%s", c->zone,
403 LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
404 NULL);
405
406 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
407
408 return sd_bus_reply_method_return(m, NULL);
409 }
410
411 static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) {
412 int lrtc, fix_system, interactive;
413 Context *c = userdata;
414 struct timespec ts;
415 int r;
416
417 assert(m);
418 assert(c);
419
420 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
421 if (r < 0)
422 return r;
423
424 if (lrtc == c->local_rtc)
425 return sd_bus_reply_method_return(m, NULL);
426
427 r = bus_verify_polkit_async(
428 m,
429 CAP_SYS_TIME,
430 "org.freedesktop.timedate1.set-local-rtc",
431 interactive,
432 UID_INVALID,
433 &c->polkit_registry,
434 error);
435 if (r < 0)
436 return r;
437 if (r == 0)
438 return 1;
439
440 c->local_rtc = lrtc;
441
442 /* 1. Write new configuration file */
443 r = context_write_data_local_rtc(c);
444 if (r < 0) {
445 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
446 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m");
447 }
448
449 /* 2. Tell the kernel our timezone */
450 clock_set_timezone(NULL);
451
452 /* 3. Synchronize clocks */
453 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
454
455 if (fix_system) {
456 struct tm tm;
457
458 /* Sync system clock from RTC; first,
459 * initialize the timezone fields of
460 * struct tm. */
461 if (c->local_rtc)
462 tm = *localtime(&ts.tv_sec);
463 else
464 tm = *gmtime(&ts.tv_sec);
465
466 /* Override the main fields of
467 * struct tm, but not the timezone
468 * fields */
469 if (clock_get_hwclock(&tm) >= 0) {
470
471 /* And set the system clock
472 * with this */
473 if (c->local_rtc)
474 ts.tv_sec = mktime(&tm);
475 else
476 ts.tv_sec = timegm(&tm);
477
478 clock_settime(CLOCK_REALTIME, &ts);
479 }
480
481 } else {
482 struct tm *tm;
483
484 /* Sync RTC from system clock */
485 if (c->local_rtc)
486 tm = localtime(&ts.tv_sec);
487 else
488 tm = gmtime(&ts.tv_sec);
489
490 clock_set_hwclock(tm);
491 }
492
493 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
494
495 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
496
497 return sd_bus_reply_method_return(m, NULL);
498 }
499
500 static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) {
501 int relative, interactive;
502 Context *c = userdata;
503 int64_t utc;
504 struct timespec ts;
505 usec_t start;
506 struct tm* tm;
507 int r;
508
509 assert(m);
510 assert(c);
511
512 if (c->use_ntp)
513 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
514
515 /* this only gets used if dbus does not provide a timestamp */
516 start = now(CLOCK_MONOTONIC);
517
518 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
519 if (r < 0)
520 return r;
521
522 if (!relative && utc <= 0)
523 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
524
525 if (relative && utc == 0)
526 return sd_bus_reply_method_return(m, NULL);
527
528 if (relative) {
529 usec_t n, x;
530
531 n = now(CLOCK_REALTIME);
532 x = n + utc;
533
534 if ((utc > 0 && x < n) ||
535 (utc < 0 && x > n))
536 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
537
538 timespec_store(&ts, x);
539 } else
540 timespec_store(&ts, (usec_t) utc);
541
542 r = bus_verify_polkit_async(
543 m,
544 CAP_SYS_TIME,
545 "org.freedesktop.timedate1.set-time",
546 interactive,
547 UID_INVALID,
548 &c->polkit_registry,
549 error);
550 if (r < 0)
551 return r;
552 if (r == 0)
553 return 1;
554
555 /* adjust ts for time spent in program */
556 r = sd_bus_message_get_monotonic_usec(m, &start);
557 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
558 if (r < 0 && r != -ENODATA)
559 return r;
560
561 timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start));
562
563 /* Set system clock */
564 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
565 log_error_errno(errno, "Failed to set local time: %m");
566 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
567 }
568
569 /* Sync down to RTC */
570 if (c->local_rtc)
571 tm = localtime(&ts.tv_sec);
572 else
573 tm = gmtime(&ts.tv_sec);
574 clock_set_hwclock(tm);
575
576 log_struct(LOG_INFO,
577 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
578 "REALTIME="USEC_FMT, timespec_load(&ts),
579 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
580 NULL);
581
582 return sd_bus_reply_method_return(m, NULL);
583 }
584
585 static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) {
586 int enabled, interactive;
587 Context *c = userdata;
588 int r;
589
590 assert(m);
591 assert(c);
592
593 r = sd_bus_message_read(m, "bb", &enabled, &interactive);
594 if (r < 0)
595 return r;
596
597 if ((bool)enabled == c->use_ntp)
598 return sd_bus_reply_method_return(m, NULL);
599
600 r = bus_verify_polkit_async(
601 m,
602 CAP_SYS_TIME,
603 "org.freedesktop.timedate1.set-ntp",
604 interactive,
605 UID_INVALID,
606 &c->polkit_registry,
607 error);
608 if (r < 0)
609 return r;
610 if (r == 0)
611 return 1;
612
613 r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled);
614 if (r < 0)
615 return r;
616
617 r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled);
618 if (r < 0)
619 return r;
620
621 c->use_ntp = enabled;
622 log_info("Set NTP to %s", enabled ? "enabled" : "disabled");
623
624 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
625
626 return sd_bus_reply_method_return(m, NULL);
627 }
628
629 static const sd_bus_vtable timedate_vtable[] = {
630 SD_BUS_VTABLE_START(0),
631 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
632 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
633 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
634 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
635 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
636 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
637 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
638 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
639 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
640 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
641 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
642 SD_BUS_VTABLE_END,
643 };
644
645 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
646 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
647 int r;
648
649 assert(c);
650 assert(event);
651 assert(_bus);
652
653 r = sd_bus_default_system(&bus);
654 if (r < 0)
655 return log_error_errno(r, "Failed to get system bus connection: %m");
656
657 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
658 if (r < 0)
659 return log_error_errno(r, "Failed to register object: %m");
660
661 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
662 if (r < 0)
663 return log_error_errno(r, "Failed to register name: %m");
664
665 r = sd_bus_attach_event(bus, event, 0);
666 if (r < 0)
667 return log_error_errno(r, "Failed to attach bus to event loop: %m");
668
669 *_bus = bus;
670 bus = NULL;
671
672 return 0;
673 }
674
675 int main(int argc, char *argv[]) {
676 Context context = {};
677 _cleanup_event_unref_ sd_event *event = NULL;
678 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
679 int r;
680
681 log_set_target(LOG_TARGET_AUTO);
682 log_parse_environment();
683 log_open();
684
685 umask(0022);
686
687 if (argc != 1) {
688 log_error("This program takes no arguments.");
689 r = -EINVAL;
690 goto finish;
691 }
692
693 r = sd_event_default(&event);
694 if (r < 0) {
695 log_error_errno(r, "Failed to allocate event loop: %m");
696 goto finish;
697 }
698
699 sd_event_set_watchdog(event, true);
700
701 r = connect_bus(&context, event, &bus);
702 if (r < 0)
703 goto finish;
704
705 (void) sd_bus_negotiate_timestamp(bus, true);
706
707 r = context_read_data(&context);
708 if (r < 0) {
709 log_error_errno(r, "Failed to read time zone data: %m");
710 goto finish;
711 }
712
713 r = context_read_ntp(&context, bus);
714 if (r < 0) {
715 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
716 goto finish;
717 }
718
719 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
720 if (r < 0) {
721 log_error_errno(r, "Failed to run event loop: %m");
722 goto finish;
723 }
724
725 finish:
726 context_free(&context);
727
728 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
729 }