]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/timedate/timedatectl.c
bus: use internal helper to read org.freedesktop.DBus.Properties::GetAll variables
[thirdparty/systemd.git] / src / timedate / timedatectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7 Copyright 2013 Kay Sievers
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <stdlib.h>
24 #include <stdbool.h>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <locale.h>
28 #include <string.h>
29 #include <sys/timex.h>
30
31 #include "sd-bus.h"
32 #include "bus-util.h"
33 #include "bus-error.h"
34 #include "util.h"
35 #include "spawn-polkit-agent.h"
36 #include "build.h"
37 #include "strv.h"
38 #include "pager.h"
39 #include "time-dst.h"
40
41 static bool arg_no_pager = false;
42 static bool arg_ask_password = true;
43 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
44 static char *arg_host = NULL;
45 static bool arg_adjust_system_clock = false;
46
47 static void pager_open_if_enabled(void) {
48
49 if (arg_no_pager)
50 return;
51
52 pager_open(false);
53 }
54
55 static void polkit_agent_open_if_enabled(void) {
56
57 /* Open the polkit agent as a child process if necessary */
58 if (!arg_ask_password)
59 return;
60
61 polkit_agent_open();
62 }
63
64 typedef struct StatusInfo {
65 usec_t time;
66 char *timezone;
67
68 usec_t rtc_time;
69 bool rtc_local;
70
71 bool ntp_enabled;
72 bool ntp_capable;
73 bool ntp_synced;
74 } StatusInfo;
75
76 static const char *jump_str(int delta_minutes, char *s, size_t size) {
77 if (delta_minutes == 60)
78 return "one hour forward";
79 if (delta_minutes == -60)
80 return "one hour backwards";
81 if (delta_minutes < 0) {
82 snprintf(s, size, "%i minutes backwards", -delta_minutes);
83 return s;
84 }
85 if (delta_minutes > 0) {
86 snprintf(s, size, "%i minutes forward", delta_minutes);
87 return s;
88 }
89 return "";
90 }
91
92 static void print_status_info(const StatusInfo *i) {
93 char a[FORMAT_TIMESTAMP_MAX];
94 char b[FORMAT_TIMESTAMP_MAX];
95 char s[32];
96 struct tm tm;
97 time_t sec;
98 char *zc, *zn;
99 time_t t, tc, tn;
100 int dn;
101 bool is_dstc, is_dstn;
102 int r;
103
104 assert(i);
105
106 /* Enforce the values of /etc/localtime */
107 if (getenv("TZ")) {
108 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
109 unsetenv("TZ");
110 }
111
112 sec = (time_t) (i->time / USEC_PER_SEC);
113
114 zero(tm);
115 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
116 char_array_0(a);
117 printf(" Local time: %s\n", a);
118
119 zero(tm);
120 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
121 char_array_0(a);
122 printf(" Universal time: %s\n", a);
123
124 if (i->rtc_time > 0) {
125 time_t rtc_sec;
126
127 rtc_sec = (time_t)(i->rtc_time / USEC_PER_SEC);
128 zero(tm);
129 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)) > 0);
130 char_array_0(a);
131 printf(" RTC time: %s\n", a);
132 } else
133 printf(" RTC time: n/a\n");
134
135 zero(tm);
136 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
137 char_array_0(a);
138 printf(" Timezone: %s (%s)\n"
139 " NTP enabled: %s\n"
140 "NTP synchronized: %s\n"
141 " RTC in local TZ: %s\n",
142 strna(i->timezone), a,
143 i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
144 yes_no(i->ntp_synced),
145 yes_no(i->rtc_local));
146
147 r = time_get_dst(sec, "/etc/localtime",
148 &tc, &zc, &is_dstc,
149 &tn, &dn, &zn, &is_dstn);
150 if (r < 0)
151 printf(" DST active: n/a\n");
152 else {
153 printf(" DST active: %s\n", yes_no(is_dstc));
154
155 t = tc - 1;
156 zero(tm);
157 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
158 char_array_0(a);
159
160 zero(tm);
161 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
162 char_array_0(b);
163 printf(" Last DST change: DST %s at\n"
164 " %s\n"
165 " %s\n",
166 is_dstc ? "began" : "ended", a, b);
167
168 t = tn - 1;
169 zero(tm);
170 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
171 char_array_0(a);
172
173 zero(tm);
174 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
175 char_array_0(b);
176 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
177 " %s\n"
178 " %s\n",
179 is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
180
181 free(zc);
182 free(zn);
183 }
184
185 if (i->rtc_local)
186 fputs("\n" ANSI_HIGHLIGHT_ON
187 "Warning: The RTC is configured to maintain time in the local timezone. This\n"
188 " mode is not fully supported and will create various problems with time\n"
189 " zone changes and daylight saving adjustments. If at all possible use\n"
190 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
191 }
192
193 static int show_status(sd_bus *bus, char **args, unsigned n) {
194 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
195 StatusInfo info = {};
196 const struct bus_properties_map map[] = {
197 { "s", "Timezone", &info.timezone },
198 { "b", "LocalRTC", &info.rtc_local },
199 { "b", "NTP", &info.ntp_enabled },
200 { "b", "CanNTP", &info.ntp_capable },
201 { "b", "NTPSynchronized", &info.ntp_synced},
202 { "t", "TimeUSec", &info.time },
203 { "t", "RTCTimeUSec", &info.rtc_time },
204 {}
205 };
206 int r;
207
208 assert(bus);
209
210 r = bus_map_all_properties(bus,
211 "org.freedesktop.timedate1",
212 "/org/freedesktop/timedate1",
213 map);
214 if (r < 0)
215 goto fail;
216
217 print_status_info(&info);
218
219 fail:
220 free(info.timezone);
221 return r;
222 }
223
224 static int set_time(sd_bus *bus, char **args, unsigned n) {
225 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
226 bool relative = false, interactive = arg_ask_password;
227 usec_t t;
228 int r;
229
230 assert(args);
231 assert(n == 2);
232
233 polkit_agent_open_if_enabled();
234
235 r = parse_timestamp(args[1], &t);
236 if (r < 0) {
237 log_error("Failed to parse time specification: %s", args[1]);
238 return r;
239 }
240
241 r = sd_bus_call_method(bus,
242 "org.freedesktop.timedate1",
243 "/org/freedesktop/timedate1",
244 "org.freedesktop.timedate1",
245 "SetTime",
246 &error,
247 NULL,
248 "xbb", (int64_t)t, relative, interactive);
249 if (r < 0)
250 log_error("Failed to set time: %s", bus_error_message(&error, -r));
251
252 return r;
253 }
254
255 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
256 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
257 int r;
258
259 assert(args);
260 assert(n == 2);
261
262 polkit_agent_open_if_enabled();
263
264 r = sd_bus_call_method(bus,
265 "org.freedesktop.timedate1",
266 "/org/freedesktop/timedate1",
267 "org.freedesktop.timedate1",
268 "SetTimezone",
269 &error,
270 NULL,
271 "sb", args[1], arg_ask_password);
272 if (r < 0)
273 log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
274
275 return r;
276 }
277
278 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
279 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
280 int r, b;
281
282 assert(args);
283 assert(n == 2);
284
285 polkit_agent_open_if_enabled();
286
287 b = parse_boolean(args[1]);
288 if (b < 0) {
289 log_error("Failed to parse local RTC setting: %s", args[1]);
290 return b;
291 }
292
293 r = sd_bus_call_method(bus,
294 "org.freedesktop.timedate1",
295 "/org/freedesktop/timedate1",
296 "org.freedesktop.timedate1",
297 "SetLocalRTC",
298 &error,
299 NULL,
300 "bbb", b, arg_adjust_system_clock, arg_ask_password);
301 if (r < 0)
302 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
303
304 return r;
305 }
306
307 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
308 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
309 int b, r;
310
311 assert(args);
312 assert(n == 2);
313
314 polkit_agent_open_if_enabled();
315
316 b = parse_boolean(args[1]);
317 if (b < 0) {
318 log_error("Failed to parse NTP setting: %s", args[1]);
319 return b;
320 }
321
322 r = sd_bus_call_method(bus,
323 "org.freedesktop.timedate1",
324 "/org/freedesktop/timedate1",
325 "org.freedesktop.timedate1",
326 "SetNTP",
327 &error,
328 NULL,
329 "bb", b, arg_ask_password);
330 if (r < 0)
331 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
332
333 return r;
334 }
335
336 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
337 _cleanup_fclose_ FILE *f = NULL;
338 _cleanup_strv_free_ char **zones = NULL;
339 size_t n_zones = 0;
340
341 assert(args);
342 assert(n == 1);
343
344 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
345 if (!f) {
346 log_error("Failed to open timezone database: %m");
347 return -errno;
348 }
349
350 for (;;) {
351 char l[LINE_MAX], *p, **z, *w;
352 size_t k;
353
354 if (!fgets(l, sizeof(l), f)) {
355 if (feof(f))
356 break;
357
358 log_error("Failed to read timezone database: %m");
359 return -errno;
360 }
361
362 p = strstrip(l);
363
364 if (isempty(p) || *p == '#')
365 continue;
366
367
368 /* Skip over country code */
369 p += strcspn(p, WHITESPACE);
370 p += strspn(p, WHITESPACE);
371
372 /* Skip over coordinates */
373 p += strcspn(p, WHITESPACE);
374 p += strspn(p, WHITESPACE);
375
376 /* Found timezone name */
377 k = strcspn(p, WHITESPACE);
378 if (k <= 0)
379 continue;
380
381 w = strndup(p, k);
382 if (!w)
383 return log_oom();
384
385 z = realloc(zones, sizeof(char*) * (n_zones + 2));
386 if (!z) {
387 free(w);
388 return log_oom();
389 }
390
391 zones = z;
392 zones[n_zones++] = w;
393 }
394
395 if (zones)
396 zones[n_zones] = NULL;
397
398 pager_open_if_enabled();
399
400 strv_sort(zones);
401 strv_print(zones);
402
403 return 0;
404 }
405
406 static int help(void) {
407
408 printf("%s [OPTIONS...] COMMAND ...\n\n"
409 "Query or change system time and date settings.\n\n"
410 " -h --help Show this help\n"
411 " --version Show package version\n"
412 " --adjust-system-clock\n"
413 " Adjust system clock when changing local RTC mode\n"
414 " --no-pager Do not pipe output into a pager\n"
415 " --no-ask-password Do not prompt for password\n"
416 " -H --host=[USER@]HOST Operate on remote host\n"
417 " -M --machine=CONTAINER Operate on local container\n\n"
418 "Commands:\n"
419 " status Show current time settings\n"
420 " set-time TIME Set system time\n"
421 " set-timezone ZONE Set system timezone\n"
422 " list-timezones Show known timezones\n"
423 " set-local-rtc BOOL Control whether RTC is in local time\n"
424 " set-ntp BOOL Control whether NTP is enabled\n",
425 program_invocation_short_name);
426
427 return 0;
428 }
429
430 static int parse_argv(int argc, char *argv[]) {
431
432 enum {
433 ARG_VERSION = 0x100,
434 ARG_NO_PAGER,
435 ARG_ADJUST_SYSTEM_CLOCK,
436 ARG_NO_ASK_PASSWORD
437 };
438
439 static const struct option options[] = {
440 { "help", no_argument, NULL, 'h' },
441 { "version", no_argument, NULL, ARG_VERSION },
442 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
443 { "host", required_argument, NULL, 'H' },
444 { "machine", required_argument, NULL, 'M' },
445 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
446 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
447 { NULL, 0, NULL, 0 }
448 };
449
450 int c;
451
452 assert(argc >= 0);
453 assert(argv);
454
455 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
456
457 switch (c) {
458
459 case 'h':
460 help();
461 return 0;
462
463 case ARG_VERSION:
464 puts(PACKAGE_STRING);
465 puts(SYSTEMD_FEATURES);
466 return 0;
467
468 case 'H':
469 arg_transport = BUS_TRANSPORT_REMOTE;
470 arg_host = optarg;
471 break;
472
473 case 'M':
474 arg_transport = BUS_TRANSPORT_CONTAINER;
475 arg_host = optarg;
476 break;
477
478 case ARG_NO_ASK_PASSWORD:
479 arg_ask_password = false;
480 break;
481
482 case ARG_ADJUST_SYSTEM_CLOCK:
483 arg_adjust_system_clock = true;
484 break;
485
486 case ARG_NO_PAGER:
487 arg_no_pager = true;
488 break;
489
490 case '?':
491 return -EINVAL;
492
493 default:
494 log_error("Unknown option code %c", c);
495 return -EINVAL;
496 }
497 }
498
499 return 1;
500 }
501
502 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
503
504 static const struct {
505 const char* verb;
506 const enum {
507 MORE,
508 LESS,
509 EQUAL
510 } argc_cmp;
511 const int argc;
512 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
513 } verbs[] = {
514 { "status", LESS, 1, show_status },
515 { "set-time", EQUAL, 2, set_time },
516 { "set-timezone", EQUAL, 2, set_timezone },
517 { "list-timezones", EQUAL, 1, list_timezones },
518 { "set-local-rtc", EQUAL, 2, set_local_rtc },
519 { "set-ntp", EQUAL, 2, set_ntp, },
520 };
521
522 int left;
523 unsigned i;
524
525 assert(argc >= 0);
526 assert(argv);
527
528 left = argc - optind;
529
530 if (left <= 0)
531 /* Special rule: no arguments means "status" */
532 i = 0;
533 else {
534 if (streq(argv[optind], "help")) {
535 help();
536 return 0;
537 }
538
539 for (i = 0; i < ELEMENTSOF(verbs); i++)
540 if (streq(argv[optind], verbs[i].verb))
541 break;
542
543 if (i >= ELEMENTSOF(verbs)) {
544 log_error("Unknown operation %s", argv[optind]);
545 return -EINVAL;
546 }
547 }
548
549 switch (verbs[i].argc_cmp) {
550
551 case EQUAL:
552 if (left != verbs[i].argc) {
553 log_error("Invalid number of arguments.");
554 return -EINVAL;
555 }
556
557 break;
558
559 case MORE:
560 if (left < verbs[i].argc) {
561 log_error("Too few arguments.");
562 return -EINVAL;
563 }
564
565 break;
566
567 case LESS:
568 if (left > verbs[i].argc) {
569 log_error("Too many arguments.");
570 return -EINVAL;
571 }
572
573 break;
574
575 default:
576 assert_not_reached("Unknown comparison operator.");
577 }
578
579 return verbs[i].dispatch(bus, argv + optind, left);
580 }
581
582 int main(int argc, char *argv[]) {
583 int r, ret = EXIT_FAILURE;
584 _cleanup_bus_unref_ sd_bus *bus = NULL;
585
586 setlocale(LC_ALL, "");
587 log_parse_environment();
588 log_open();
589
590 r = parse_argv(argc, argv);
591 if (r < 0)
592 goto finish;
593 else if (r == 0) {
594 ret = EXIT_SUCCESS;
595 goto finish;
596 }
597
598 r = bus_open_transport(arg_transport, arg_host, false, &bus);
599 if (r < 0) {
600 log_error("Failed to create bus connection: %s", strerror(-r));
601 ret = EXIT_FAILURE;
602 goto finish;
603 }
604
605 r = timedatectl_main(bus, argc, argv);
606 ret = r < 0 ? EXIT_FAILURE : r;
607
608 finish:
609 pager_close();
610
611 return ret;
612 }