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