]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedated.c
bus: add a macro to simplify reading of UUIDs
[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"
bbc98d32 34#include "hwclock.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)
424a19f8 129 log_warning("/etc/localtime should be a symbolic link to a timezone 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)
140 log_warning("/etc/localtime should be a symbolic link to a timezone 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
40ca29a1 156 c->local_rtc = hwclock_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)) {
ac7019f3 266
b32d1675
LP
267 if (ferror(f))
268 log_error("Failed to read NTP units file: %m");
ac7019f3 269
b32d1675
LP
270 break;
271 }
272
273 l = strstrip(line);
274 if (l[0] == 0 || l[0] == '#')
275 continue;
276
7e7d4da2 277 if (strv_extend(&r, l) < 0) {
0d0f0c50 278 log_oom();
7e7d4da2
HH
279 return NULL;
280 }
ac7019f3 281 }
ac7019f3
LP
282 }
283
d257f05a
ZJS
284 i = r;
285 r = NULL; /* avoid cleanup */
ac7019f3 286
d257f05a 287 return strv_uniq(i);
ac7019f3
LP
288}
289
40ca29a1
LP
290static int context_read_ntp(Context *c, sd_bus *bus) {
291 _cleanup_strv_free_ char **l;
292 char **i;
c5f0532f
LP
293 int r;
294
40ca29a1 295 assert(c);
c5f0532f
LP
296 assert(bus);
297
ac7019f3
LP
298 l = get_ntp_services();
299 STRV_FOREACH(i, l) {
40ca29a1
LP
300 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
301 sd_bus_message *reply = NULL;
ac7019f3
LP
302 const char *s;
303
40ca29a1
LP
304 r = sd_bus_call_method(
305 bus,
ac7019f3
LP
306 "org.freedesktop.systemd1",
307 "/org/freedesktop/systemd1",
308 "org.freedesktop.systemd1.Manager",
40ca29a1
LP
309 "GetUnitFileState",
310 &error,
311 &reply,
312 "s",
313 *i);
c5f0532f 314
40ca29a1
LP
315 if (r < 0) {
316 /* This implementation does not exist, try next one */
317 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND))
ac7019f3 318 continue;
2aa4c315 319
40ca29a1 320 return r;
2aa4c315
LP
321 }
322
40ca29a1
LP
323 r = sd_bus_message_read(reply, "s", &s);
324 if (r < 0)
325 return r;
c5f0532f 326
40ca29a1
LP
327 c->can_ntp = 1;
328 c->use_ntp =
ac7019f3
LP
329 streq(s, "enabled") ||
330 streq(s, "enabled-runtime");
40ca29a1
LP
331
332 return 0;
c5f0532f
LP
333 }
334
ac7019f3 335 /* NTP is not installed. */
40ca29a1
LP
336 c->can_ntp = 0;
337 c->use_ntp = 0;
c5f0532f 338
40ca29a1 339 return 0;
c5f0532f
LP
340}
341
40ca29a1
LP
342static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
343 _cleanup_strv_free_ char **l = NULL;
344 char **i;
c5f0532f
LP
345 int r;
346
40ca29a1 347 assert(c);
c5f0532f
LP
348 assert(bus);
349 assert(error);
350
ac7019f3
LP
351 l = get_ntp_services();
352 STRV_FOREACH(i, l) {
c5f0532f 353
40ca29a1
LP
354 if (c->use_ntp)
355 r = sd_bus_call_method(
356 bus,
357 "org.freedesktop.systemd1",
358 "/org/freedesktop/systemd1",
359 "org.freedesktop.systemd1.Manager",
360 "StartUnit",
361 error,
362 NULL,
363 "ss", *i, "replace");
364 else
365 r = sd_bus_call_method(
366 bus,
367 "org.freedesktop.systemd1",
368 "/org/freedesktop/systemd1",
369 "org.freedesktop.systemd1.Manager",
370 "StopUnit",
371 error,
372 NULL,
373 "ss", *i, "replace");
ac7019f3 374
40ca29a1
LP
375 if (r < 0) {
376 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
377 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
378 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
ac7019f3 379 /* This implementation does not exist, try next one */
40ca29a1 380 sd_bus_error_free(error);
ac7019f3
LP
381 continue;
382 }
c5f0532f 383
40ca29a1 384 return r;
ac7019f3
LP
385 }
386
40ca29a1 387 return 1;
c5f0532f
LP
388 }
389
40ca29a1
LP
390 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
391 return -ENOTSUP;
c5f0532f
LP
392}
393
40ca29a1
LP
394static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
395 _cleanup_strv_free_ char **l = NULL;
396 char **i;
c5f0532f 397 int r;
c5f0532f 398
40ca29a1 399 assert(c);
c5f0532f
LP
400 assert(bus);
401 assert(error);
402
ac7019f3
LP
403 l = get_ntp_services();
404 STRV_FOREACH(i, l) {
40ca29a1
LP
405 if (c->use_ntp)
406 r = sd_bus_call_method(
407 bus,
408 "org.freedesktop.systemd1",
409 "/org/freedesktop/systemd1",
410 "org.freedesktop.systemd1.Manager",
411 "EnableUnitFiles",
412 error,
413 NULL,
414 "asbb", 1, *i, false, true);
415 else
416 r = sd_bus_call_method(
417 bus,
418 "org.freedesktop.systemd1",
419 "/org/freedesktop/systemd1",
420 "org.freedesktop.systemd1.Manager",
421 "DisableUnitFiles",
422 error,
423 NULL,
424 "asb", 1, *i, false);
c5f0532f 425
ac7019f3 426 if (r < 0) {
40ca29a1 427 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
ac7019f3 428 /* This implementation does not exist, try next one */
40ca29a1 429 sd_bus_error_free(error);
ac7019f3
LP
430 continue;
431 }
c5f0532f 432
40ca29a1 433 return r;
ac7019f3
LP
434 }
435
40ca29a1
LP
436 r = sd_bus_call_method(
437 bus,
ac7019f3
LP
438 "org.freedesktop.systemd1",
439 "/org/freedesktop/systemd1",
440 "org.freedesktop.systemd1.Manager",
40ca29a1
LP
441 "Reload",
442 error,
443 NULL,
444 NULL);
445 if (r < 0)
446 return r;
ac7019f3 447
40ca29a1 448 return 1;
c5f0532f
LP
449 }
450
40ca29a1
LP
451 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
452 return -ENOTSUP;
453}
c5f0532f 454
6fc60278
LP
455static int property_get_rtc_time(
456 sd_bus *bus,
457 const char *path,
458 const char *interface,
459 const char *property,
460 sd_bus_message *reply,
461 sd_bus_error *error,
462 void *userdata) {
463
464 struct tm tm;
465 usec_t t;
466 int r;
467
468 zero(tm);
469 r = hwclock_get_time(&tm);
470 if (r < 0) {
471 sd_bus_error_set_errnof(error, -r, "Failed to read RTC: %s", strerror(-r));
472 return r;
473 }
474
475 t = (usec_t) mktime(&tm) * USEC_PER_SEC;
476
477 r = sd_bus_message_append(reply, "t", t);
478 if (r < 0)
479 return r;
480
481 return 1;
482}
483
03cc26dd
LP
484static int property_get_time(
485 sd_bus *bus,
486 const char *path,
487 const char *interface,
488 const char *property,
489 sd_bus_message *reply,
490 sd_bus_error *error,
491 void *userdata) {
492
493 int r;
494
495 r = sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
496 if (r < 0)
497 return r;
498
499 return 1;
500}
501
502static int property_get_ntp_sync(
503 sd_bus *bus,
504 const char *path,
505 const char *interface,
506 const char *property,
507 sd_bus_message *reply,
508 sd_bus_error *error,
509 void *userdata) {
510
511 int r;
512
513 r = sd_bus_message_append(reply, "b", ntp_synced());
514 if (r < 0)
515 return r;
516
517 return 1;
518}
519
40ca29a1
LP
520static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata) {
521 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
522 Context *c = userdata;
523 const char *z;
9bcbce42 524 unsigned interactive;
40ca29a1
LP
525 char *t;
526 int r;
c5f0532f 527
7e9cf16c
LP
528 assert(bus);
529 assert(m);
530 assert(c);
531
40ca29a1
LP
532 r = sd_bus_message_read(m, "sb", &z, &interactive);
533 if (r < 0)
534 return sd_bus_reply_method_errno(bus, m, r, NULL);
c5f0532f 535
40ca29a1
LP
536 if (!valid_timezone(z))
537 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
ac7019f3 538
40ca29a1
LP
539 if (streq_ptr(z, c->zone))
540 return sd_bus_reply_method_return(bus, m, NULL);
c5f0532f 541
40ca29a1
LP
542 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, &error, method_set_timezone, c);
543 if (r < 0)
544 return sd_bus_reply_method_errno(bus, m, r, &error);
545 if (r == 0)
6fc60278 546 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
6ffe5e37 547
40ca29a1
LP
548 t = strdup(z);
549 if (!t)
550 return log_oom();
6ffe5e37 551
40ca29a1
LP
552 free(c->zone);
553 c->zone = t;
6ffe5e37 554
40ca29a1
LP
555 /* 1. Write new configuration file */
556 r = context_write_data_timezone(c);
557 if (r < 0) {
558 log_error("Failed to set timezone: %s", strerror(-r));
559 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set timezone: %s", strerror(-r));
560 }
6ffe5e37 561
40ca29a1
LP
562 /* 2. Tell the kernel our timezone */
563 hwclock_set_timezone(NULL);
6ffe5e37 564
40ca29a1
LP
565 if (c->local_rtc) {
566 struct timespec ts;
567 struct tm *tm;
c5f0532f 568
40ca29a1
LP
569 /* 3. Sync RTC from system clock, with the new delta */
570 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
571 assert_se(tm = localtime(&ts.tv_sec));
572 hwclock_set_time(tm);
573 }
c5f0532f 574
40ca29a1
LP
575 log_struct(LOG_INFO,
576 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
577 "TIMEZONE=%s", c->zone,
578 "MESSAGE=Changed timezone to '%s'.", c->zone,
579 NULL);
c5f0532f 580
40ca29a1 581 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
c5f0532f 582
40ca29a1 583 return sd_bus_reply_method_return(bus, m, NULL);
c5f0532f
LP
584}
585
40ca29a1
LP
586static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata) {
587 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
9bcbce42 588 unsigned lrtc, fix_system, interactive;
40ca29a1
LP
589 Context *c = userdata;
590 struct timespec ts;
f401e48c
LP
591 int r;
592
40ca29a1
LP
593 assert(bus);
594 assert(m);
595 assert(c);
f401e48c 596
40ca29a1
LP
597 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
598 if (r < 0)
599 return sd_bus_reply_method_errno(bus, m, r, NULL);
f401e48c 600
40ca29a1
LP
601 if (lrtc == c->local_rtc)
602 return sd_bus_reply_method_return(bus, m, NULL);
f401e48c 603
40ca29a1
LP
604 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, &error, method_set_local_rtc, c);
605 if (r < 0)
606 return sd_bus_reply_method_errno(bus, m, r, &error);
607 if (r == 0)
608 return 1;
f401e48c 609
40ca29a1 610 c->local_rtc = lrtc;
f401e48c 611
40ca29a1
LP
612 /* 1. Write new configuration file */
613 r = context_write_data_local_rtc(c);
614 if (r < 0) {
615 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
616 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
617 }
f401e48c 618
40ca29a1
LP
619 /* 2. Tell the kernel our timezone */
620 hwclock_set_timezone(NULL);
f401e48c 621
40ca29a1
LP
622 /* 3. Synchronize clocks */
623 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
f401e48c 624
40ca29a1
LP
625 if (fix_system) {
626 struct tm tm;
f401e48c 627
40ca29a1
LP
628 /* Sync system clock from RTC; first,
629 * initialize the timezone fields of
630 * struct tm. */
631 if (c->local_rtc)
632 tm = *localtime(&ts.tv_sec);
633 else
634 tm = *gmtime(&ts.tv_sec);
72edcff5 635
40ca29a1
LP
636 /* Override the main fields of
637 * struct tm, but not the timezone
638 * fields */
639 if (hwclock_get_time(&tm) >= 0) {
2076cf88 640
40ca29a1
LP
641 /* And set the system clock
642 * with this */
643 if (c->local_rtc)
644 ts.tv_sec = mktime(&tm);
645 else
646 ts.tv_sec = timegm(&tm);
2076cf88 647
40ca29a1 648 clock_settime(CLOCK_REALTIME, &ts);
f401e48c
LP
649 }
650
40ca29a1
LP
651 } else {
652 struct tm *tm;
f401e48c 653
40ca29a1
LP
654 /* Sync RTC from system clock */
655 if (c->local_rtc)
656 tm = localtime(&ts.tv_sec);
657 else
658 tm = gmtime(&ts.tv_sec);
2076cf88 659
40ca29a1
LP
660 hwclock_set_time(tm);
661 }
2076cf88 662
40ca29a1 663 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
2076cf88 664
40ca29a1 665 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
2076cf88 666
40ca29a1
LP
667 return sd_bus_reply_method_return(bus, m, NULL);
668}
2076cf88 669
40ca29a1
LP
670static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata) {
671 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
9bcbce42 672 unsigned relative, interactive;
40ca29a1
LP
673 Context *c = userdata;
674 int64_t utc;
675 struct timespec ts;
676 struct tm* tm;
677 int r;
2076cf88 678
40ca29a1
LP
679 assert(bus);
680 assert(m);
681 assert(c);
2076cf88 682
40ca29a1
LP
683 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
684 if (r < 0)
685 return sd_bus_reply_method_errno(bus, m, r, NULL);
2076cf88 686
40ca29a1
LP
687 if (!relative && utc <= 0)
688 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
2076cf88 689
40ca29a1
LP
690 if (relative && utc == 0)
691 return sd_bus_reply_method_return(bus, m, NULL);
2076cf88 692
40ca29a1
LP
693 if (relative) {
694 usec_t n, x;
f401e48c 695
40ca29a1
LP
696 n = now(CLOCK_REALTIME);
697 x = n + utc;
f401e48c 698
40ca29a1
LP
699 if ((utc > 0 && x < n) ||
700 (utc < 0 && x > n))
701 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
f401e48c 702
40ca29a1
LP
703 timespec_store(&ts, x);
704 } else
705 timespec_store(&ts, (usec_t) utc);
2076cf88 706
40ca29a1
LP
707 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, &error, method_set_time, c);
708 if (r < 0)
709 return sd_bus_reply_method_errno(bus, m, r, &error);
710 if (r == 0)
711 return 1;
712
713 /* Set system clock */
714 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
715 log_error("Failed to set local time: %m");
716 return sd_bus_reply_method_errnof(bus, m, errno, "Failed to set local time: %m");
717 }
2076cf88 718
40ca29a1
LP
719 /* Sync down to RTC */
720 if (c->local_rtc)
721 tm = localtime(&ts.tv_sec);
722 else
723 tm = gmtime(&ts.tv_sec);
f401e48c 724
40ca29a1 725 hwclock_set_time(tm);
f401e48c 726
40ca29a1
LP
727 log_struct(LOG_INFO,
728 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
729 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
730 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
731 NULL);
f401e48c 732
40ca29a1
LP
733 return sd_bus_reply_method_return(bus, m, NULL);
734}
f401e48c 735
40ca29a1
LP
736static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata) {
737 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
9bcbce42 738 unsigned ntp, interactive;
40ca29a1
LP
739 Context *c = userdata;
740 int r;
f401e48c 741
40ca29a1
LP
742 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
743 if (r < 0)
744 return sd_bus_reply_method_errno(bus, m, r, NULL);
f401e48c 745
40ca29a1
LP
746 if (ntp == c->use_ntp)
747 return sd_bus_reply_method_return(bus, m, NULL);
f401e48c 748
40ca29a1
LP
749 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, &error, method_set_ntp, c);
750 if (r < 0)
751 return sd_bus_reply_method_errno(bus, m, r, &error);
752 if (r == 0)
753 return 1;
f401e48c 754
40ca29a1 755 c->use_ntp = ntp;
f401e48c 756
40ca29a1
LP
757 r = context_enable_ntp(c, bus, &error);
758 if (r < 0)
759 return sd_bus_reply_method_errno(bus, m, r, &error);
760
761 r = context_start_ntp(c, bus, &error);
762 if (r < 0)
763 return sd_bus_reply_method_errno(bus, m, r, &error);
f401e48c 764
40ca29a1 765 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
f401e48c 766
40ca29a1 767 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
f401e48c 768
40ca29a1 769 return sd_bus_reply_method_return(bus, m, NULL);
f401e48c
LP
770}
771
40ca29a1
LP
772static const sd_bus_vtable timedate_vtable[] = {
773 SD_BUS_VTABLE_START(0),
774 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
775 SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
776 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
777 SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
03cc26dd
LP
778 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
779 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
6fc60278 780 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
40ca29a1
LP
781 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, 0),
782 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, 0),
783 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, 0),
784 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, 0),
785 SD_BUS_VTABLE_END,
786};
787
788static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
789 _cleanup_bus_unref_ sd_bus *bus = NULL;
f401e48c
LP
790 int r;
791
40ca29a1
LP
792 assert(c);
793 assert(event);
f401e48c
LP
794 assert(_bus);
795
40ca29a1
LP
796 r = sd_bus_open_system(&bus);
797 if (r < 0) {
798 log_error("Failed to get system bus connection: %s", strerror(-r));
799 return r;
f401e48c
LP
800 }
801
40ca29a1
LP
802 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
803 if (r < 0) {
804 log_error("Failed to register object: %s", strerror(-r));
805 return r;
f401e48c
LP
806 }
807
40ca29a1
LP
808 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", SD_BUS_NAME_DO_NOT_QUEUE);
809 if (r < 0) {
810 log_error("Failed to register name: %s", strerror(-r));
811 return r;
add10b5a
LP
812 }
813
40ca29a1 814 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
add10b5a 815 log_error("Failed to acquire name.");
40ca29a1 816 return -EEXIST;
f401e48c
LP
817 }
818
40ca29a1
LP
819 r = sd_bus_attach_event(bus, event, 0);
820 if (r < 0) {
821 log_error("Failed to attach bus to event loop: %s", strerror(-r));
822 return r;
823 }
f401e48c 824
40ca29a1
LP
825 *_bus = bus;
826 bus = NULL;
f401e48c 827
40ca29a1 828 return 0;
f401e48c
LP
829}
830
831int main(int argc, char *argv[]) {
40ca29a1
LP
832 Context context = {
833 .zone = NULL,
834 .local_rtc = false,
835 .can_ntp = -1,
836 .use_ntp = -1,
837 };
838
839 _cleanup_event_unref_ sd_event *event = NULL;
840 _cleanup_bus_unref_ sd_bus *bus = NULL;
f401e48c 841 int r;
f401e48c
LP
842
843 log_set_target(LOG_TARGET_AUTO);
844 log_parse_environment();
845 log_open();
846
4c12626c
LP
847 umask(0022);
848
f401e48c
LP
849 if (argc != 1) {
850 log_error("This program takes no arguments.");
851 r = -EINVAL;
852 goto finish;
853 }
854
40ca29a1 855 r = sd_event_new(&event);
f401e48c 856 if (r < 0) {
40ca29a1 857 log_error("Failed to allocate event loop: %s", strerror(-r));
f401e48c
LP
858 goto finish;
859 }
860
40ca29a1 861 r = connect_bus(&context, event, &bus);
f401e48c
LP
862 if (r < 0)
863 goto finish;
864
40ca29a1 865 r = context_read_data(&context);
c5f0532f 866 if (r < 0) {
40ca29a1 867 log_error("Failed to read timezone data: %s", strerror(-r));
c5f0532f
LP
868 goto finish;
869 }
870
40ca29a1
LP
871 r = context_read_ntp(&context, bus);
872 if (r < 0) {
873 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
874 goto finish;
875 }
ad740100 876
40ca29a1
LP
877 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC);
878 if (r < 0) {
879 log_error("Failed to run event loop: %s", strerror(-r));
880 goto finish;
ad740100 881 }
f401e48c
LP
882
883 r = 0;
884
885finish:
40ca29a1 886 context_free(&context, bus);
f401e48c
LP
887
888 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
889}