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