]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedated.c
test: fix minor memory leak in test-event
[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);
88e262b6
LP
470 if (r == -EBUSY) {
471 log_warning("/dev/rtc is busy, is somebody keeping it open continously? That's not a good idea... Returning a bogus RTC timestamp.");
472 t = 0;
473 } else if (r < 0) {
6fc60278
LP
474 sd_bus_error_set_errnof(error, -r, "Failed to read RTC: %s", strerror(-r));
475 return r;
88e262b6 476 } else
2f6a5907 477 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
6fc60278
LP
478
479 r = sd_bus_message_append(reply, "t", t);
480 if (r < 0)
481 return r;
482
483 return 1;
484}
485
03cc26dd
LP
486static int property_get_time(
487 sd_bus *bus,
488 const char *path,
489 const char *interface,
490 const char *property,
491 sd_bus_message *reply,
492 sd_bus_error *error,
493 void *userdata) {
494
495 int r;
496
497 r = sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
498 if (r < 0)
499 return r;
500
501 return 1;
502}
503
504static int property_get_ntp_sync(
505 sd_bus *bus,
506 const char *path,
507 const char *interface,
508 const char *property,
509 sd_bus_message *reply,
510 sd_bus_error *error,
511 void *userdata) {
512
513 int r;
514
515 r = sd_bus_message_append(reply, "b", ntp_synced());
516 if (r < 0)
517 return r;
518
519 return 1;
520}
521
40ca29a1
LP
522static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata) {
523 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
524 Context *c = userdata;
525 const char *z;
102d8f81 526 int interactive;
40ca29a1
LP
527 char *t;
528 int r;
c5f0532f 529
7e9cf16c
LP
530 assert(bus);
531 assert(m);
532 assert(c);
533
40ca29a1
LP
534 r = sd_bus_message_read(m, "sb", &z, &interactive);
535 if (r < 0)
536 return sd_bus_reply_method_errno(bus, m, r, NULL);
c5f0532f 537
40ca29a1
LP
538 if (!valid_timezone(z))
539 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
ac7019f3 540
40ca29a1
LP
541 if (streq_ptr(z, c->zone))
542 return sd_bus_reply_method_return(bus, m, NULL);
c5f0532f 543
40ca29a1
LP
544 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, &error, method_set_timezone, c);
545 if (r < 0)
546 return sd_bus_reply_method_errno(bus, m, r, &error);
547 if (r == 0)
6fc60278 548 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
6ffe5e37 549
40ca29a1
LP
550 t = strdup(z);
551 if (!t)
552 return log_oom();
6ffe5e37 553
40ca29a1
LP
554 free(c->zone);
555 c->zone = t;
6ffe5e37 556
40ca29a1
LP
557 /* 1. Write new configuration file */
558 r = context_write_data_timezone(c);
559 if (r < 0) {
560 log_error("Failed to set timezone: %s", strerror(-r));
561 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set timezone: %s", strerror(-r));
562 }
6ffe5e37 563
40ca29a1
LP
564 /* 2. Tell the kernel our timezone */
565 hwclock_set_timezone(NULL);
6ffe5e37 566
40ca29a1
LP
567 if (c->local_rtc) {
568 struct timespec ts;
569 struct tm *tm;
c5f0532f 570
40ca29a1
LP
571 /* 3. Sync RTC from system clock, with the new delta */
572 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
573 assert_se(tm = localtime(&ts.tv_sec));
574 hwclock_set_time(tm);
575 }
c5f0532f 576
40ca29a1
LP
577 log_struct(LOG_INFO,
578 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
579 "TIMEZONE=%s", c->zone,
580 "MESSAGE=Changed timezone to '%s'.", c->zone,
581 NULL);
c5f0532f 582
40ca29a1 583 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
c5f0532f 584
40ca29a1 585 return sd_bus_reply_method_return(bus, m, NULL);
c5f0532f
LP
586}
587
40ca29a1
LP
588static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata) {
589 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
102d8f81 590 int lrtc, fix_system, interactive;
40ca29a1
LP
591 Context *c = userdata;
592 struct timespec ts;
f401e48c
LP
593 int r;
594
40ca29a1
LP
595 assert(bus);
596 assert(m);
597 assert(c);
f401e48c 598
40ca29a1
LP
599 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
600 if (r < 0)
601 return sd_bus_reply_method_errno(bus, m, r, NULL);
f401e48c 602
40ca29a1
LP
603 if (lrtc == c->local_rtc)
604 return sd_bus_reply_method_return(bus, m, NULL);
f401e48c 605
40ca29a1
LP
606 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, &error, method_set_local_rtc, c);
607 if (r < 0)
608 return sd_bus_reply_method_errno(bus, m, r, &error);
609 if (r == 0)
610 return 1;
f401e48c 611
40ca29a1 612 c->local_rtc = lrtc;
f401e48c 613
40ca29a1
LP
614 /* 1. Write new configuration file */
615 r = context_write_data_local_rtc(c);
616 if (r < 0) {
617 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
618 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
619 }
f401e48c 620
40ca29a1
LP
621 /* 2. Tell the kernel our timezone */
622 hwclock_set_timezone(NULL);
f401e48c 623
40ca29a1
LP
624 /* 3. Synchronize clocks */
625 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
f401e48c 626
40ca29a1
LP
627 if (fix_system) {
628 struct tm tm;
f401e48c 629
40ca29a1
LP
630 /* Sync system clock from RTC; first,
631 * initialize the timezone fields of
632 * struct tm. */
633 if (c->local_rtc)
634 tm = *localtime(&ts.tv_sec);
635 else
636 tm = *gmtime(&ts.tv_sec);
72edcff5 637
40ca29a1
LP
638 /* Override the main fields of
639 * struct tm, but not the timezone
640 * fields */
641 if (hwclock_get_time(&tm) >= 0) {
2076cf88 642
40ca29a1
LP
643 /* And set the system clock
644 * with this */
645 if (c->local_rtc)
646 ts.tv_sec = mktime(&tm);
647 else
648 ts.tv_sec = timegm(&tm);
2076cf88 649
40ca29a1 650 clock_settime(CLOCK_REALTIME, &ts);
f401e48c
LP
651 }
652
40ca29a1
LP
653 } else {
654 struct tm *tm;
f401e48c 655
40ca29a1
LP
656 /* Sync RTC from system clock */
657 if (c->local_rtc)
658 tm = localtime(&ts.tv_sec);
659 else
660 tm = gmtime(&ts.tv_sec);
2076cf88 661
40ca29a1
LP
662 hwclock_set_time(tm);
663 }
2076cf88 664
40ca29a1 665 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
2076cf88 666
40ca29a1 667 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
2076cf88 668
40ca29a1
LP
669 return sd_bus_reply_method_return(bus, m, NULL);
670}
2076cf88 671
40ca29a1
LP
672static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata) {
673 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
102d8f81 674 int relative, interactive;
40ca29a1
LP
675 Context *c = userdata;
676 int64_t utc;
677 struct timespec ts;
678 struct tm* tm;
679 int r;
2076cf88 680
40ca29a1
LP
681 assert(bus);
682 assert(m);
683 assert(c);
2076cf88 684
40ca29a1
LP
685 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
686 if (r < 0)
687 return sd_bus_reply_method_errno(bus, m, r, NULL);
2076cf88 688
40ca29a1
LP
689 if (!relative && utc <= 0)
690 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
2076cf88 691
40ca29a1
LP
692 if (relative && utc == 0)
693 return sd_bus_reply_method_return(bus, m, NULL);
2076cf88 694
40ca29a1
LP
695 if (relative) {
696 usec_t n, x;
f401e48c 697
40ca29a1
LP
698 n = now(CLOCK_REALTIME);
699 x = n + utc;
f401e48c 700
40ca29a1
LP
701 if ((utc > 0 && x < n) ||
702 (utc < 0 && x > n))
703 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
f401e48c 704
40ca29a1
LP
705 timespec_store(&ts, x);
706 } else
707 timespec_store(&ts, (usec_t) utc);
2076cf88 708
40ca29a1
LP
709 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, &error, method_set_time, c);
710 if (r < 0)
711 return sd_bus_reply_method_errno(bus, m, r, &error);
712 if (r == 0)
713 return 1;
714
715 /* Set system clock */
716 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
717 log_error("Failed to set local time: %m");
718 return sd_bus_reply_method_errnof(bus, m, errno, "Failed to set local time: %m");
719 }
2076cf88 720
40ca29a1
LP
721 /* Sync down to RTC */
722 if (c->local_rtc)
723 tm = localtime(&ts.tv_sec);
724 else
725 tm = gmtime(&ts.tv_sec);
f401e48c 726
40ca29a1 727 hwclock_set_time(tm);
f401e48c 728
40ca29a1
LP
729 log_struct(LOG_INFO,
730 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
731 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
732 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
733 NULL);
f401e48c 734
40ca29a1
LP
735 return sd_bus_reply_method_return(bus, m, NULL);
736}
f401e48c 737
40ca29a1
LP
738static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata) {
739 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
102d8f81 740 int ntp, interactive;
40ca29a1
LP
741 Context *c = userdata;
742 int r;
f401e48c 743
40ca29a1
LP
744 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
745 if (r < 0)
746 return sd_bus_reply_method_errno(bus, m, r, NULL);
f401e48c 747
102d8f81 748 if ((bool)ntp == c->use_ntp)
40ca29a1 749 return sd_bus_reply_method_return(bus, m, NULL);
f401e48c 750
40ca29a1
LP
751 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, &error, method_set_ntp, c);
752 if (r < 0)
753 return sd_bus_reply_method_errno(bus, m, r, &error);
754 if (r == 0)
755 return 1;
f401e48c 756
40ca29a1 757 c->use_ntp = ntp;
f401e48c 758
40ca29a1
LP
759 r = context_enable_ntp(c, bus, &error);
760 if (r < 0)
761 return sd_bus_reply_method_errno(bus, m, r, &error);
762
763 r = context_start_ntp(c, bus, &error);
764 if (r < 0)
765 return sd_bus_reply_method_errno(bus, m, r, &error);
f401e48c 766
40ca29a1 767 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
f401e48c 768
40ca29a1 769 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
f401e48c 770
40ca29a1 771 return sd_bus_reply_method_return(bus, m, NULL);
f401e48c
LP
772}
773
40ca29a1
LP
774static const sd_bus_vtable timedate_vtable[] = {
775 SD_BUS_VTABLE_START(0),
776 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
777 SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
778 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
779 SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
03cc26dd
LP
780 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
781 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
6fc60278 782 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
40ca29a1
LP
783 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, 0),
784 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, 0),
785 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, 0),
786 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, 0),
787 SD_BUS_VTABLE_END,
788};
789
790static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
791 _cleanup_bus_unref_ sd_bus *bus = NULL;
f401e48c
LP
792 int r;
793
40ca29a1
LP
794 assert(c);
795 assert(event);
f401e48c
LP
796 assert(_bus);
797
40ca29a1
LP
798 r = sd_bus_open_system(&bus);
799 if (r < 0) {
800 log_error("Failed to get system bus connection: %s", strerror(-r));
801 return r;
f401e48c
LP
802 }
803
40ca29a1
LP
804 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
805 if (r < 0) {
806 log_error("Failed to register object: %s", strerror(-r));
807 return r;
f401e48c
LP
808 }
809
40ca29a1
LP
810 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", SD_BUS_NAME_DO_NOT_QUEUE);
811 if (r < 0) {
812 log_error("Failed to register name: %s", strerror(-r));
813 return r;
add10b5a
LP
814 }
815
40ca29a1 816 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
add10b5a 817 log_error("Failed to acquire name.");
40ca29a1 818 return -EEXIST;
f401e48c
LP
819 }
820
40ca29a1
LP
821 r = sd_bus_attach_event(bus, event, 0);
822 if (r < 0) {
823 log_error("Failed to attach bus to event loop: %s", strerror(-r));
824 return r;
825 }
f401e48c 826
40ca29a1
LP
827 *_bus = bus;
828 bus = NULL;
f401e48c 829
40ca29a1 830 return 0;
f401e48c
LP
831}
832
833int main(int argc, char *argv[]) {
40ca29a1
LP
834 Context context = {
835 .zone = NULL,
836 .local_rtc = false,
837 .can_ntp = -1,
838 .use_ntp = -1,
839 };
840
841 _cleanup_event_unref_ sd_event *event = NULL;
842 _cleanup_bus_unref_ sd_bus *bus = NULL;
f401e48c 843 int r;
f401e48c
LP
844
845 log_set_target(LOG_TARGET_AUTO);
846 log_parse_environment();
847 log_open();
848
4c12626c
LP
849 umask(0022);
850
f401e48c
LP
851 if (argc != 1) {
852 log_error("This program takes no arguments.");
853 r = -EINVAL;
854 goto finish;
855 }
856
40ca29a1 857 r = sd_event_new(&event);
f401e48c 858 if (r < 0) {
40ca29a1 859 log_error("Failed to allocate event loop: %s", strerror(-r));
f401e48c
LP
860 goto finish;
861 }
862
40ca29a1 863 r = connect_bus(&context, event, &bus);
f401e48c
LP
864 if (r < 0)
865 goto finish;
866
40ca29a1 867 r = context_read_data(&context);
c5f0532f 868 if (r < 0) {
40ca29a1 869 log_error("Failed to read timezone data: %s", strerror(-r));
c5f0532f
LP
870 goto finish;
871 }
872
40ca29a1
LP
873 r = context_read_ntp(&context, bus);
874 if (r < 0) {
875 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
876 goto finish;
877 }
ad740100 878
40ca29a1
LP
879 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC);
880 if (r < 0) {
881 log_error("Failed to run event loop: %s", strerror(-r));
882 goto finish;
ad740100 883 }
f401e48c
LP
884
885 r = 0;
886
887finish:
40ca29a1 888 context_free(&context, bus);
f401e48c
LP
889
890 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
891}