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