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