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