]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedated.c
timesyncd: only update stamp file when we are synchronized
[thirdparty/systemd.git] / src / timedate / timedated.c
CommitLineData
f401e48c
LP
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
5430f7f2
LP
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
f401e48c
LP
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
5430f7f2 16 Lesser General Public License for more details.
f401e48c 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
f401e48c
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
f401e48c
LP
22#include <errno.h>
23#include <string.h>
24#include <unistd.h>
25
40ca29a1
LP
26#include "sd-id128.h"
27#include "sd-messages.h"
28#include "sd-event.h"
29#include "sd-bus.h"
30
f401e48c
LP
31#include "util.h"
32#include "strv.h"
ad740100 33#include "def.h"
24efb112 34#include "clock-util.h"
b32d1675 35#include "conf-files.h"
424a19f8 36#include "path-util.h"
a5c32cff
HH
37#include "fileio-label.h"
38#include "label.h"
40ca29a1
LP
39#include "bus-util.h"
40#include "event-util.h"
f401e48c
LP
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
40ca29a1 45typedef struct Context {
d200735e
MS
46 char *zone;
47 bool local_rtc;
9bcbce42
KS
48 unsigned can_ntp;
49 unsigned use_ntp;
40ca29a1
LP
50 Hashmap *polkit_registry;
51} Context;
f401e48c 52
40ca29a1
LP
53static void context_reset(Context *c) {
54 assert(c);
d200735e 55
40ca29a1
LP
56 free(c->zone);
57 c->zone = NULL;
ad740100 58
40ca29a1
LP
59 c->local_rtc = false;
60 c->can_ntp = c->use_ntp = -1;
61}
62
63static void context_free(Context *c, sd_bus *bus) {
64 assert(c);
f401e48c 65
7e9cf16c 66 context_reset(c);
40ca29a1 67 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
f401e48c
LP
68}
69
70static bool valid_timezone(const char *name) {
71 const char *p;
72 char *t;
73 bool slash = false;
74 int r;
75 struct stat st;
76
77 assert(name);
78
79 if (*name == '/' || *name == 0)
80 return false;
81
82 for (p = name; *p; p++) {
83 if (!(*p >= '0' && *p <= '9') &&
84 !(*p >= 'a' && *p <= 'z') &&
85 !(*p >= 'A' && *p <= 'Z') &&
86 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
87 return false;
88
89 if (*p == '/') {
90
91 if (slash)
92 return false;
93
94 slash = true;
95 } else
96 slash = false;
97 }
98
99 if (slash)
100 return false;
101
424a19f8 102 t = strappend("/usr/share/zoneinfo/", name);
f401e48c
LP
103 if (!t)
104 return false;
105
106 r = stat(t, &st);
107 free(t);
108
109 if (r < 0)
110 return false;
111
112 if (!S_ISREG(st.st_mode))
113 return false;
114
115 return true;
116}
117
40ca29a1 118static int context_read_data(Context *c) {
424a19f8 119 _cleanup_free_ char *t = NULL;
40ca29a1
LP
120 int r;
121
122 assert(c);
f401e48c 123
40ca29a1 124 context_reset(c);
f401e48c 125
92c4ef2d
SL
126 r = readlink_malloc("/etc/localtime", &t);
127 if (r < 0) {
128 if (r == -EINVAL)
07a062a7 129 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
92c4ef2d 130 else
424a19f8 131 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
92c4ef2d 132 } else {
424a19f8 133 const char *e;
92c4ef2d 134
424a19f8
LP
135 e = path_startswith(t, "/usr/share/zoneinfo/");
136 if (!e)
137 e = path_startswith(t, "../usr/share/zoneinfo/");
138
139 if (!e)
07a062a7 140 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
92c4ef2d 141 else {
40ca29a1
LP
142 c->zone = strdup(e);
143 if (!c->zone)
92c4ef2d
SL
144 return log_oom();
145
146 goto have_timezone;
147 }
148 }
149
92c4ef2d 150have_timezone:
40ca29a1
LP
151 if (isempty(c->zone)) {
152 free(c->zone);
153 c->zone = NULL;
a724d2ed 154 }
f401e48c 155
24efb112 156 c->local_rtc = clock_is_localtime() > 0;
f401e48c
LP
157
158 return 0;
159}
160
40ca29a1 161static int context_write_data_timezone(Context *c) {
424a19f8 162 _cleanup_free_ char *p = NULL;
40ca29a1
LP
163 int r = 0;
164
165 assert(c);
e19a21a8 166
40ca29a1 167 if (isempty(c->zone)) {
424a19f8 168 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
f401e48c
LP
169 r = -errno;
170
f401e48c
LP
171 return r;
172 }
173
40ca29a1 174 p = strappend("../usr/share/zoneinfo/", c->zone);
0d0f0c50
SL
175 if (!p)
176 return log_oom();
f401e48c 177
424a19f8 178 r = symlink_atomic(p, "/etc/localtime");
f401e48c 179 if (r < 0)
424a19f8 180 return r;
f401e48c 181
f401e48c
LP
182 return 0;
183}
184
40ca29a1 185static int context_write_data_local_rtc(Context *c) {
f401e48c 186 int r;
7fd1b19b 187 _cleanup_free_ char *s = NULL, *w = NULL;
f401e48c 188
40ca29a1
LP
189 assert(c);
190
f401e48c
LP
191 r = read_full_file("/etc/adjtime", &s, NULL);
192 if (r < 0) {
193 if (r != -ENOENT)
194 return r;
195
40ca29a1 196 if (!c->local_rtc)
f401e48c
LP
197 return 0;
198
199 w = strdup(NULL_ADJTIME_LOCAL);
200 if (!w)
201 return -ENOMEM;
202 } else {
203 char *p, *e;
204 size_t a, b;
205
206 p = strchr(s, '\n');
d257f05a 207 if (!p)
f401e48c 208 return -EIO;
f401e48c
LP
209
210 p = strchr(p+1, '\n');
d257f05a 211 if (!p)
f401e48c 212 return -EIO;
f401e48c
LP
213
214 p++;
215 e = strchr(p, '\n');
d257f05a 216 if (!e)
f401e48c 217 return -EIO;
f401e48c
LP
218
219 a = p - s;
220 b = strlen(e);
221
40ca29a1 222 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
d257f05a 223 if (!w)
f401e48c 224 return -ENOMEM;
f401e48c 225
40ca29a1 226 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
f401e48c
LP
227
228 if (streq(w, NULL_ADJTIME_UTC)) {
d257f05a 229 if (unlink("/etc/adjtime") < 0)
f401e48c
LP
230 if (errno != ENOENT)
231 return -errno;
f401e48c
LP
232
233 return 0;
234 }
235 }
40ca29a1 236
a5c32cff 237 label_init("/etc");
d257f05a 238 return write_string_file_atomic_label("/etc/adjtime", w);
f401e48c
LP
239}
240
ac7019f3 241static char** get_ntp_services(void) {
40ca29a1 242 _cleanup_strv_free_ char **r = NULL, **files = NULL;
d257f05a 243 char **i;
b32d1675
LP
244 int k;
245
7850b3b8 246 k = conf_files_list(&files, ".list", NULL,
b32d1675
LP
247 "/etc/systemd/ntp-units.d",
248 "/run/systemd/ntp-units.d",
249 "/usr/local/lib/systemd/ntp-units.d",
250 "/usr/lib/systemd/ntp-units.d",
251 NULL);
252 if (k < 0)
ac7019f3
LP
253 return NULL;
254
b32d1675 255 STRV_FOREACH(i, files) {
7fd1b19b 256 _cleanup_fclose_ FILE *f;
ac7019f3 257
b32d1675
LP
258 f = fopen(*i, "re");
259 if (!f)
260 continue;
ac7019f3 261
b32d1675 262 for (;;) {
d257f05a 263 char line[PATH_MAX], *l;
ac7019f3 264
b32d1675 265 if (!fgets(line, sizeof(line), f)) {
b32d1675 266 if (ferror(f))
07a062a7 267 log_error("Failed to read NTP unit file: %m");
ac7019f3 268
b32d1675
LP
269 break;
270 }
271
272 l = strstrip(line);
273 if (l[0] == 0 || l[0] == '#')
274 continue;
275
7e7d4da2 276 if (strv_extend(&r, l) < 0) {
0d0f0c50 277 log_oom();
7e7d4da2
HH
278 return NULL;
279 }
ac7019f3 280 }
ac7019f3
LP
281 }
282
d257f05a
ZJS
283 i = r;
284 r = NULL; /* avoid cleanup */
ac7019f3 285
d257f05a 286 return strv_uniq(i);
ac7019f3
LP
287}
288
40ca29a1
LP
289static int context_read_ntp(Context *c, sd_bus *bus) {
290 _cleanup_strv_free_ char **l;
291 char **i;
c5f0532f
LP
292 int r;
293
40ca29a1 294 assert(c);
c5f0532f
LP
295 assert(bus);
296
ac7019f3
LP
297 l = get_ntp_services();
298 STRV_FOREACH(i, l) {
40ca29a1
LP
299 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
300 sd_bus_message *reply = NULL;
ac7019f3
LP
301 const char *s;
302
40ca29a1
LP
303 r = sd_bus_call_method(
304 bus,
ac7019f3
LP
305 "org.freedesktop.systemd1",
306 "/org/freedesktop/systemd1",
307 "org.freedesktop.systemd1.Manager",
40ca29a1
LP
308 "GetUnitFileState",
309 &error,
310 &reply,
311 "s",
312 *i);
c5f0532f 313
40ca29a1 314 if (r < 0) {
07a062a7 315 /* This implementation does not exist. Try the next one. */
40ca29a1 316 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND))
ac7019f3 317 continue;
2aa4c315 318
40ca29a1 319 return r;
2aa4c315
LP
320 }
321
40ca29a1
LP
322 r = sd_bus_message_read(reply, "s", &s);
323 if (r < 0)
324 return r;
c5f0532f 325
40ca29a1
LP
326 c->can_ntp = 1;
327 c->use_ntp =
ac7019f3
LP
328 streq(s, "enabled") ||
329 streq(s, "enabled-runtime");
40ca29a1
LP
330
331 return 0;
c5f0532f
LP
332 }
333
ac7019f3 334 /* NTP is not installed. */
40ca29a1
LP
335 c->can_ntp = 0;
336 c->use_ntp = 0;
c5f0532f 337
40ca29a1 338 return 0;
c5f0532f
LP
339}
340
40ca29a1
LP
341static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
342 _cleanup_strv_free_ char **l = NULL;
343 char **i;
c5f0532f
LP
344 int r;
345
40ca29a1 346 assert(c);
c5f0532f
LP
347 assert(bus);
348 assert(error);
349
ac7019f3
LP
350 l = get_ntp_services();
351 STRV_FOREACH(i, l) {
c5f0532f 352
40ca29a1
LP
353 if (c->use_ntp)
354 r = sd_bus_call_method(
355 bus,
356 "org.freedesktop.systemd1",
357 "/org/freedesktop/systemd1",
358 "org.freedesktop.systemd1.Manager",
359 "StartUnit",
360 error,
361 NULL,
362 "ss", *i, "replace");
363 else
364 r = sd_bus_call_method(
365 bus,
366 "org.freedesktop.systemd1",
367 "/org/freedesktop/systemd1",
368 "org.freedesktop.systemd1.Manager",
369 "StopUnit",
370 error,
371 NULL,
372 "ss", *i, "replace");
ac7019f3 373
40ca29a1
LP
374 if (r < 0) {
375 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
376 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
377 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
07a062a7 378 /* This implementation does not exist. Try the next one. */
40ca29a1 379 sd_bus_error_free(error);
ac7019f3
LP
380 continue;
381 }
c5f0532f 382
40ca29a1 383 return r;
ac7019f3
LP
384 }
385
40ca29a1 386 return 1;
c5f0532f
LP
387 }
388
40ca29a1
LP
389 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
390 return -ENOTSUP;
c5f0532f
LP
391}
392
40ca29a1
LP
393static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
394 _cleanup_strv_free_ char **l = NULL;
395 char **i;
c5f0532f 396 int r;
c5f0532f 397
40ca29a1 398 assert(c);
c5f0532f
LP
399 assert(bus);
400 assert(error);
401
ac7019f3
LP
402 l = get_ntp_services();
403 STRV_FOREACH(i, l) {
40ca29a1
LP
404 if (c->use_ntp)
405 r = sd_bus_call_method(
406 bus,
407 "org.freedesktop.systemd1",
408 "/org/freedesktop/systemd1",
409 "org.freedesktop.systemd1.Manager",
410 "EnableUnitFiles",
411 error,
412 NULL,
413 "asbb", 1, *i, false, true);
414 else
415 r = sd_bus_call_method(
416 bus,
417 "org.freedesktop.systemd1",
418 "/org/freedesktop/systemd1",
419 "org.freedesktop.systemd1.Manager",
420 "DisableUnitFiles",
421 error,
422 NULL,
423 "asb", 1, *i, false);
c5f0532f 424
ac7019f3 425 if (r < 0) {
40ca29a1 426 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
07a062a7 427 /* This implementation does not exist. Try the next one. */
40ca29a1 428 sd_bus_error_free(error);
ac7019f3
LP
429 continue;
430 }
c5f0532f 431
40ca29a1 432 return r;
ac7019f3
LP
433 }
434
40ca29a1
LP
435 r = sd_bus_call_method(
436 bus,
ac7019f3
LP
437 "org.freedesktop.systemd1",
438 "/org/freedesktop/systemd1",
439 "org.freedesktop.systemd1.Manager",
40ca29a1
LP
440 "Reload",
441 error,
442 NULL,
443 NULL);
444 if (r < 0)
445 return r;
ac7019f3 446
40ca29a1 447 return 1;
c5f0532f
LP
448 }
449
40ca29a1
LP
450 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
451 return -ENOTSUP;
452}
c5f0532f 453
6fc60278
LP
454static int property_get_rtc_time(
455 sd_bus *bus,
456 const char *path,
457 const char *interface,
458 const char *property,
459 sd_bus_message *reply,
ebcf1f97
LP
460 void *userdata,
461 sd_bus_error *error) {
6fc60278
LP
462
463 struct tm tm;
464 usec_t t;
465 int r;
466
467 zero(tm);
24efb112 468 r = clock_get_time(&tm);
88e262b6 469 if (r == -EBUSY) {
07a062a7 470 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
88e262b6 471 t = 0;
fe2b58a4 472 } else if (r == -ENOENT) {
07a062a7 473 log_debug("/dev/rtc not found.");
fe2b58a4 474 t = 0; /* no RTC found */
ebcf1f97
LP
475 } else if (r < 0)
476 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
477 else
2f6a5907 478 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
6fc60278 479
ebcf1f97 480 return sd_bus_message_append(reply, "t", t);
6fc60278
LP
481}
482
03cc26dd
LP
483static int property_get_time(
484 sd_bus *bus,
485 const char *path,
486 const char *interface,
487 const char *property,
488 sd_bus_message *reply,
ebcf1f97
LP
489 void *userdata,
490 sd_bus_error *error) {
03cc26dd 491
ebcf1f97 492 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
03cc26dd
LP
493}
494
495static int property_get_ntp_sync(
496 sd_bus *bus,
497 const char *path,
498 const char *interface,
499 const char *property,
500 sd_bus_message *reply,
ebcf1f97
LP
501 void *userdata,
502 sd_bus_error *error) {
03cc26dd 503
ebcf1f97 504 return sd_bus_message_append(reply, "b", ntp_synced());
03cc26dd
LP
505}
506
ebcf1f97 507static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
40ca29a1
LP
508 Context *c = userdata;
509 const char *z;
102d8f81 510 int interactive;
40ca29a1
LP
511 char *t;
512 int r;
c5f0532f 513
7e9cf16c
LP
514 assert(bus);
515 assert(m);
516 assert(c);
517
40ca29a1
LP
518 r = sd_bus_message_read(m, "sb", &z, &interactive);
519 if (r < 0)
ebcf1f97 520 return r;
c5f0532f 521
40ca29a1 522 if (!valid_timezone(z))
ebcf1f97 523 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
ac7019f3 524
40ca29a1 525 if (streq_ptr(z, c->zone))
df2d202e 526 return sd_bus_reply_method_return(m, NULL);
c5f0532f 527
ebcf1f97 528 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, error, method_set_timezone, c);
40ca29a1 529 if (r < 0)
ebcf1f97 530 return r;
40ca29a1 531 if (r == 0)
6fc60278 532 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
6ffe5e37 533
40ca29a1
LP
534 t = strdup(z);
535 if (!t)
ebcf1f97 536 return -ENOMEM;
6ffe5e37 537
40ca29a1
LP
538 free(c->zone);
539 c->zone = t;
6ffe5e37 540
40ca29a1
LP
541 /* 1. Write new configuration file */
542 r = context_write_data_timezone(c);
543 if (r < 0) {
07a062a7
JSJ
544 log_error("Failed to set time zone: %s", strerror(-r));
545 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
40ca29a1 546 }
6ffe5e37 547
40ca29a1 548 /* 2. Tell the kernel our timezone */
24efb112 549 clock_set_timezone(NULL);
6ffe5e37 550
40ca29a1
LP
551 if (c->local_rtc) {
552 struct timespec ts;
553 struct tm *tm;
c5f0532f 554
40ca29a1
LP
555 /* 3. Sync RTC from system clock, with the new delta */
556 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
557 assert_se(tm = localtime(&ts.tv_sec));
24efb112 558 clock_set_time(tm);
40ca29a1 559 }
c5f0532f 560
40ca29a1
LP
561 log_struct(LOG_INFO,
562 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
563 "TIMEZONE=%s", c->zone,
07a062a7 564 "MESSAGE=Changed time zone to '%s'.", c->zone,
40ca29a1 565 NULL);
c5f0532f 566
40ca29a1 567 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
c5f0532f 568
df2d202e 569 return sd_bus_reply_method_return(m, NULL);
c5f0532f
LP
570}
571
ebcf1f97 572static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
102d8f81 573 int lrtc, fix_system, interactive;
40ca29a1
LP
574 Context *c = userdata;
575 struct timespec ts;
f401e48c
LP
576 int r;
577
40ca29a1
LP
578 assert(bus);
579 assert(m);
580 assert(c);
f401e48c 581
40ca29a1
LP
582 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
583 if (r < 0)
ebcf1f97 584 return r;
f401e48c 585
40ca29a1 586 if (lrtc == c->local_rtc)
df2d202e 587 return sd_bus_reply_method_return(m, NULL);
f401e48c 588
ebcf1f97 589 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, error, method_set_local_rtc, c);
40ca29a1 590 if (r < 0)
ebcf1f97 591 return r;
40ca29a1
LP
592 if (r == 0)
593 return 1;
f401e48c 594
40ca29a1 595 c->local_rtc = lrtc;
f401e48c 596
40ca29a1
LP
597 /* 1. Write new configuration file */
598 r = context_write_data_local_rtc(c);
599 if (r < 0) {
600 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
ebcf1f97 601 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
40ca29a1 602 }
f401e48c 603
40ca29a1 604 /* 2. Tell the kernel our timezone */
24efb112 605 clock_set_timezone(NULL);
f401e48c 606
40ca29a1
LP
607 /* 3. Synchronize clocks */
608 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
f401e48c 609
40ca29a1
LP
610 if (fix_system) {
611 struct tm tm;
f401e48c 612
40ca29a1
LP
613 /* Sync system clock from RTC; first,
614 * initialize the timezone fields of
615 * struct tm. */
616 if (c->local_rtc)
617 tm = *localtime(&ts.tv_sec);
618 else
619 tm = *gmtime(&ts.tv_sec);
72edcff5 620
40ca29a1
LP
621 /* Override the main fields of
622 * struct tm, but not the timezone
623 * fields */
24efb112 624 if (clock_get_time(&tm) >= 0) {
2076cf88 625
40ca29a1
LP
626 /* And set the system clock
627 * with this */
628 if (c->local_rtc)
629 ts.tv_sec = mktime(&tm);
630 else
631 ts.tv_sec = timegm(&tm);
2076cf88 632
40ca29a1 633 clock_settime(CLOCK_REALTIME, &ts);
f401e48c
LP
634 }
635
40ca29a1
LP
636 } else {
637 struct tm *tm;
f401e48c 638
40ca29a1
LP
639 /* Sync RTC from system clock */
640 if (c->local_rtc)
641 tm = localtime(&ts.tv_sec);
642 else
643 tm = gmtime(&ts.tv_sec);
2076cf88 644
24efb112 645 clock_set_time(tm);
40ca29a1 646 }
2076cf88 647
40ca29a1 648 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
2076cf88 649
40ca29a1 650 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
2076cf88 651
df2d202e 652 return sd_bus_reply_method_return(m, NULL);
40ca29a1 653}
2076cf88 654
ebcf1f97 655static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
102d8f81 656 int relative, interactive;
40ca29a1
LP
657 Context *c = userdata;
658 int64_t utc;
659 struct timespec ts;
660 struct tm* tm;
661 int r;
2076cf88 662
40ca29a1
LP
663 assert(bus);
664 assert(m);
665 assert(c);
2076cf88 666
40ca29a1
LP
667 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
668 if (r < 0)
ebcf1f97 669 return r;
2076cf88 670
40ca29a1 671 if (!relative && utc <= 0)
ebcf1f97 672 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
2076cf88 673
40ca29a1 674 if (relative && utc == 0)
df2d202e 675 return sd_bus_reply_method_return(m, NULL);
2076cf88 676
40ca29a1
LP
677 if (relative) {
678 usec_t n, x;
f401e48c 679
40ca29a1
LP
680 n = now(CLOCK_REALTIME);
681 x = n + utc;
f401e48c 682
40ca29a1
LP
683 if ((utc > 0 && x < n) ||
684 (utc < 0 && x > n))
ebcf1f97 685 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
f401e48c 686
40ca29a1
LP
687 timespec_store(&ts, x);
688 } else
689 timespec_store(&ts, (usec_t) utc);
2076cf88 690
ebcf1f97 691 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, error, method_set_time, c);
40ca29a1 692 if (r < 0)
ebcf1f97 693 return r;
40ca29a1
LP
694 if (r == 0)
695 return 1;
696
697 /* Set system clock */
698 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
699 log_error("Failed to set local time: %m");
ebcf1f97 700 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
40ca29a1 701 }
2076cf88 702
40ca29a1
LP
703 /* Sync down to RTC */
704 if (c->local_rtc)
705 tm = localtime(&ts.tv_sec);
706 else
707 tm = gmtime(&ts.tv_sec);
f401e48c 708
24efb112 709 clock_set_time(tm);
f401e48c 710
40ca29a1
LP
711 log_struct(LOG_INFO,
712 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
de0671ee 713 "REALTIME="USEC_FMT, timespec_load(&ts),
40ca29a1
LP
714 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
715 NULL);
f401e48c 716
df2d202e 717 return sd_bus_reply_method_return(m, NULL);
40ca29a1 718}
f401e48c 719
ebcf1f97 720static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
102d8f81 721 int ntp, interactive;
40ca29a1
LP
722 Context *c = userdata;
723 int r;
f401e48c 724
40ca29a1
LP
725 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
726 if (r < 0)
ebcf1f97 727 return r;
f401e48c 728
102d8f81 729 if ((bool)ntp == c->use_ntp)
df2d202e 730 return sd_bus_reply_method_return(m, NULL);
f401e48c 731
ebcf1f97 732 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, error, method_set_ntp, c);
40ca29a1 733 if (r < 0)
ebcf1f97 734 return r;
40ca29a1
LP
735 if (r == 0)
736 return 1;
f401e48c 737
40ca29a1 738 c->use_ntp = ntp;
f401e48c 739
ebcf1f97 740 r = context_enable_ntp(c, bus, error);
40ca29a1 741 if (r < 0)
ebcf1f97 742 return r;
40ca29a1 743
ebcf1f97 744 r = context_start_ntp(c, bus, error);
40ca29a1 745 if (r < 0)
ebcf1f97 746 return r;
f401e48c 747
40ca29a1 748 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
f401e48c 749
40ca29a1 750 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
f401e48c 751
df2d202e 752 return sd_bus_reply_method_return(m, NULL);
f401e48c
LP
753}
754
adacb957
LP
755#include <sys/capability.h>
756
40ca29a1
LP
757static const sd_bus_vtable timedate_vtable[] = {
758 SD_BUS_VTABLE_START(0),
759 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
760 SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
761 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
762 SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
03cc26dd
LP
763 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
764 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
6fc60278 765 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
adacb957
LP
766 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
767 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
768 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
769 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
40ca29a1
LP
770 SD_BUS_VTABLE_END,
771};
772
773static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
774 _cleanup_bus_unref_ sd_bus *bus = NULL;
f401e48c
LP
775 int r;
776
40ca29a1
LP
777 assert(c);
778 assert(event);
f401e48c
LP
779 assert(_bus);
780
76b54375 781 r = sd_bus_default_system(&bus);
40ca29a1
LP
782 if (r < 0) {
783 log_error("Failed to get system bus connection: %s", strerror(-r));
784 return r;
f401e48c
LP
785 }
786
19befb2d 787 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
40ca29a1
LP
788 if (r < 0) {
789 log_error("Failed to register object: %s", strerror(-r));
790 return r;
f401e48c
LP
791 }
792
5bb658a1 793 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
40ca29a1
LP
794 if (r < 0) {
795 log_error("Failed to register name: %s", strerror(-r));
796 return r;
add10b5a
LP
797 }
798
40ca29a1
LP
799 r = sd_bus_attach_event(bus, event, 0);
800 if (r < 0) {
801 log_error("Failed to attach bus to event loop: %s", strerror(-r));
802 return r;
803 }
f401e48c 804
40ca29a1
LP
805 *_bus = bus;
806 bus = NULL;
f401e48c 807
40ca29a1 808 return 0;
f401e48c
LP
809}
810
811int main(int argc, char *argv[]) {
40ca29a1
LP
812 Context context = {
813 .zone = NULL,
814 .local_rtc = false,
815 .can_ntp = -1,
816 .use_ntp = -1,
817 };
818
819 _cleanup_event_unref_ sd_event *event = NULL;
820 _cleanup_bus_unref_ sd_bus *bus = NULL;
f401e48c 821 int r;
f401e48c
LP
822
823 log_set_target(LOG_TARGET_AUTO);
824 log_parse_environment();
825 log_open();
826
4c12626c
LP
827 umask(0022);
828
f401e48c
LP
829 if (argc != 1) {
830 log_error("This program takes no arguments.");
831 r = -EINVAL;
832 goto finish;
833 }
834
afc6adb5 835 r = sd_event_default(&event);
f401e48c 836 if (r < 0) {
40ca29a1 837 log_error("Failed to allocate event loop: %s", strerror(-r));
f401e48c
LP
838 goto finish;
839 }
840
cde93897
LP
841 sd_event_set_watchdog(event, true);
842
40ca29a1 843 r = connect_bus(&context, event, &bus);
f401e48c
LP
844 if (r < 0)
845 goto finish;
846
40ca29a1 847 r = context_read_data(&context);
c5f0532f 848 if (r < 0) {
07a062a7 849 log_error("Failed to read time zone data: %s", strerror(-r));
c5f0532f
LP
850 goto finish;
851 }
852
40ca29a1
LP
853 r = context_read_ntp(&context, bus);
854 if (r < 0) {
855 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
856 goto finish;
857 }
ad740100 858
37224a5f 859 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
40ca29a1
LP
860 if (r < 0) {
861 log_error("Failed to run event loop: %s", strerror(-r));
862 goto finish;
ad740100 863 }
f401e48c 864
f401e48c 865finish:
40ca29a1 866 context_free(&context, bus);
f401e48c
LP
867
868 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
869}