chiark / gitweb /
timedatectl: assorted simplifications
[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_adjust_system_clock = false;
42 static bool arg_no_pager = false;
43 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
44 static bool arg_ask_password = true;
45 static char *arg_host = NULL;
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         const 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(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 %Z", 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 get_timedate_property_bool(sd_bus *bus, const char *name, bool *target) {
194         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
195         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
196         int r, b;
197
198         assert(name);
199
200         r = sd_bus_get_property_trivial(
201                         bus,
202                         "org.freedesktop.timedate1",
203                         "/org/freedesktop/timedate1",
204                         "org.freedesktop.timedate1",
205                         name,
206                         &error,
207                         'b', &b);
208         if (r < 0) {
209                 log_error("Failed to get property: %s %s", name, bus_error_message(&error, -r));
210                 return r;
211         }
212
213         *target = b;
214         return 0;
215 }
216
217 static int get_timedate_property_usec(sd_bus *bus, const char *name, usec_t *target) {
218         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
219         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
220         int r;
221
222         assert(name);
223
224         r = sd_bus_get_property_trivial(
225                         bus,
226                         "org.freedesktop.timedate1",
227                         "/org/freedesktop/timedate1",
228                         "org.freedesktop.timedate1",
229                         name,
230                         &error,
231                         't', target);
232         if (r < 0) {
233                 log_error("Failed to get property: %s %s", name, bus_error_message(&error, -r));
234                 return r;
235         }
236
237         return 0;
238 }
239
240 static int show_status(sd_bus *bus, char **args, unsigned n) {
241         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
242         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
243         int r;
244         StatusInfo info = {};
245
246         assert(bus);
247
248         r = sd_bus_get_property(
249                         bus,
250                         "org.freedesktop.timedate1",
251                         "/org/freedesktop/timedate1",
252                         "org.freedesktop.timedate1",
253                         "Timezone",
254                         &error,
255                         &reply,
256                         "s");
257         if (r < 0) {
258                 log_error("Failed to get property: Timezone %s", bus_error_message(&error, -r));
259                 return r;
260         }
261
262         r = sd_bus_message_read(reply, "s", &info.timezone);
263         if (r < 0)
264                 return r;
265
266         r = get_timedate_property_bool(bus, "LocalRTC", &info.rtc_local);
267         if (r < 0)
268                 return r;
269
270         r = get_timedate_property_bool(bus, "NTP", &info.ntp_enabled);
271         if (r < 0)
272                 return r;
273
274         r = get_timedate_property_bool(bus, "CanNTP", &info.ntp_capable);
275         if (r < 0)
276                 return r;
277
278         r = get_timedate_property_bool(bus, "NTPSynchronized", &info.ntp_synced);
279         if (r < 0)
280                 return r;
281
282         r = get_timedate_property_usec(bus, "TimeUSec", &info.time);
283         if (r < 0)
284                 return r;
285
286         r = get_timedate_property_usec(bus, "RTCTimeUSec", &info.rtc_time);
287         if (r < 0)
288                 return r;
289
290         print_status_info(&info);
291         return 0;
292 }
293
294 static int set_time(sd_bus *bus, char **args, unsigned n) {
295         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
296         bool relative = false, interactive = arg_ask_password;
297         usec_t t;
298         int r;
299
300         assert(args);
301         assert(n == 2);
302
303         polkit_agent_open_if_enabled();
304
305         r = parse_timestamp(args[1], &t);
306         if (r < 0) {
307                 log_error("Failed to parse time specification: %s", args[1]);
308                 return r;
309         }
310
311         r = sd_bus_call_method(bus,
312                                "org.freedesktop.timedate1",
313                                "/org/freedesktop/timedate1",
314                                "org.freedesktop.timedate1",
315                                "SetTime",
316                                &error,
317                                NULL,
318                                "xbb", (int64_t)t, relative, interactive);
319         if (r < 0)
320                 log_error("Failed to set time: %s", bus_error_message(&error, -r));
321
322         return r;
323 }
324
325 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
326         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
327         int r;
328
329         assert(args);
330         assert(n == 2);
331
332         polkit_agent_open_if_enabled();
333
334         r = sd_bus_call_method(bus,
335                                "org.freedesktop.timedate1",
336                                "/org/freedesktop/timedate1",
337                                "org.freedesktop.timedate1",
338                                "SetTimezone",
339                                &error,
340                                NULL,
341                                "sb", args[1], arg_ask_password);
342         if (r < 0)
343                 log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
344
345         return r;
346 }
347
348 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
349         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
350         int r, b;
351
352         assert(args);
353         assert(n == 2);
354
355         polkit_agent_open_if_enabled();
356
357         b = parse_boolean(args[1]);
358         if (b < 0) {
359                 log_error("Failed to parse local RTC setting: %s", args[1]);
360                 return b;
361         }
362
363         r = sd_bus_call_method(bus,
364                                "org.freedesktop.timedate1",
365                                "/org/freedesktop/timedate1",
366                                "org.freedesktop.timedate1",
367                                "SetLocalRTC",
368                                &error,
369                                NULL,
370                                "bbb", b, arg_adjust_system_clock, arg_ask_password);
371         if (r < 0)
372                 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
373
374         return r;
375 }
376
377 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
378         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
379         int b, r;
380
381         assert(args);
382         assert(n == 2);
383
384         polkit_agent_open_if_enabled();
385
386         b = parse_boolean(args[1]);
387         if (b < 0) {
388                 log_error("Failed to parse NTP setting: %s", args[1]);
389                 return b;
390         }
391
392         r = sd_bus_call_method(bus,
393                                "org.freedesktop.timedate1",
394                                "/org/freedesktop/timedate1",
395                                "org.freedesktop.timedate1",
396                                "SetNTP",
397                                &error,
398                                NULL,
399                                "bb", b, arg_ask_password);
400         if (r < 0)
401                 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
402
403         return r;
404 }
405
406 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
407         _cleanup_fclose_ FILE *f = NULL;
408         _cleanup_strv_free_ char **zones = NULL;
409         size_t n_zones = 0;
410
411         assert(args);
412         assert(n == 1);
413
414         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
415         if (!f) {
416                 log_error("Failed to open timezone database: %m");
417                 return -errno;
418         }
419
420         for (;;) {
421                 char l[LINE_MAX], *p, **z, *w;
422                 size_t k;
423
424                 if (!fgets(l, sizeof(l), f)) {
425                         if (feof(f))
426                                 break;
427
428                         log_error("Failed to read timezone database: %m");
429                         return -errno;
430                 }
431
432                 p = strstrip(l);
433
434                 if (isempty(p) || *p == '#')
435                         continue;
436
437
438                 /* Skip over country code */
439                 p += strcspn(p, WHITESPACE);
440                 p += strspn(p, WHITESPACE);
441
442                 /* Skip over coordinates */
443                 p += strcspn(p, WHITESPACE);
444                 p += strspn(p, WHITESPACE);
445
446                 /* Found timezone name */
447                 k = strcspn(p, WHITESPACE);
448                 if (k <= 0)
449                         continue;
450
451                 w = strndup(p, k);
452                 if (!w)
453                         return log_oom();
454
455                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
456                 if (!z) {
457                         free(w);
458                         return log_oom();
459                 }
460
461                 zones = z;
462                 zones[n_zones++] = w;
463         }
464
465         if (zones)
466                 zones[n_zones] = NULL;
467
468         pager_open_if_enabled();
469
470         strv_sort(zones);
471         strv_print(zones);
472
473         return 0;
474 }
475
476 static int help(void) {
477
478         printf("%s [OPTIONS...] COMMAND ...\n\n"
479                "Query or change system time and date settings.\n\n"
480                "  -h --help              Show this help\n"
481                "     --version           Show package version\n"
482                "     --adjust-system-clock\n"
483                "                         Adjust system clock when changing local RTC mode\n"
484                "     --no-pager          Do not pipe output into a pager\n"
485                "     --no-ask-password   Do not prompt for password\n"
486                "  -H --host=[USER@]HOST  Operate on remote host\n"
487                "  -M --machine=CONTAINER Operate on local container\n\n"
488                "Commands:\n"
489                "  status                 Show current time settings\n"
490                "  set-time TIME          Set system time\n"
491                "  set-timezone ZONE      Set system timezone\n"
492                "  list-timezones         Show known timezones\n"
493                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
494                "  set-ntp BOOL           Control whether NTP is enabled\n",
495                program_invocation_short_name);
496
497         return 0;
498 }
499
500 static int parse_argv(int argc, char *argv[]) {
501
502         enum {
503                 ARG_VERSION = 0x100,
504                 ARG_NO_PAGER,
505                 ARG_ADJUST_SYSTEM_CLOCK,
506                 ARG_NO_ASK_PASSWORD
507         };
508
509         static const struct option options[] = {
510                 { "help",                no_argument,       NULL, 'h'                     },
511                 { "version",             no_argument,       NULL, ARG_VERSION             },
512                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
513                 { "host",                required_argument, NULL, 'H'                     },
514                 { "machine",             required_argument, NULL, 'M'                     },
515                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
516                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
517                 { NULL,                  0,                 NULL, 0                       }
518         };
519
520         int c;
521
522         assert(argc >= 0);
523         assert(argv);
524
525         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
526
527                 switch (c) {
528
529                 case 'h':
530                         help();
531                         return 0;
532
533                 case ARG_VERSION:
534                         puts(PACKAGE_STRING);
535                         puts(SYSTEMD_FEATURES);
536                         return 0;
537
538                 case 'H':
539                         arg_transport = BUS_TRANSPORT_REMOTE;
540                         arg_host = optarg;
541                         break;
542
543                 case 'M':
544                         arg_transport = BUS_TRANSPORT_CONTAINER;
545                         arg_host = optarg;
546                         break;
547
548                 case ARG_NO_ASK_PASSWORD:
549                         arg_ask_password = false;
550                         break;
551
552                 case ARG_ADJUST_SYSTEM_CLOCK:
553                         arg_adjust_system_clock = true;
554                         break;
555
556                 case ARG_NO_PAGER:
557                         arg_no_pager = true;
558                         break;
559
560                 case '?':
561                         return -EINVAL;
562
563                 default:
564                         log_error("Unknown option code %c", c);
565                         return -EINVAL;
566                 }
567         }
568
569         return 1;
570 }
571
572 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
573
574         static const struct {
575                 const char* verb;
576                 const enum {
577                         MORE,
578                         LESS,
579                         EQUAL
580                 } argc_cmp;
581                 const int argc;
582                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
583         } verbs[] = {
584                 { "status",                LESS,   1, show_status      },
585                 { "set-time",              EQUAL,  2, set_time         },
586                 { "set-timezone",          EQUAL,  2, set_timezone     },
587                 { "list-timezones",        EQUAL,  1, list_timezones   },
588                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
589                 { "set-ntp",               EQUAL,  2, set_ntp,         },
590         };
591
592         int left;
593         unsigned i;
594
595         assert(argc >= 0);
596         assert(argv);
597
598         left = argc - optind;
599
600         if (left <= 0)
601                 /* Special rule: no arguments means "status" */
602                 i = 0;
603         else {
604                 if (streq(argv[optind], "help")) {
605                         help();
606                         return 0;
607                 }
608
609                 for (i = 0; i < ELEMENTSOF(verbs); i++)
610                         if (streq(argv[optind], verbs[i].verb))
611                                 break;
612
613                 if (i >= ELEMENTSOF(verbs)) {
614                         log_error("Unknown operation %s", argv[optind]);
615                         return -EINVAL;
616                 }
617         }
618
619         switch (verbs[i].argc_cmp) {
620
621         case EQUAL:
622                 if (left != verbs[i].argc) {
623                         log_error("Invalid number of arguments.");
624                         return -EINVAL;
625                 }
626
627                 break;
628
629         case MORE:
630                 if (left < verbs[i].argc) {
631                         log_error("Too few arguments.");
632                         return -EINVAL;
633                 }
634
635                 break;
636
637         case LESS:
638                 if (left > verbs[i].argc) {
639                         log_error("Too many arguments.");
640                         return -EINVAL;
641                 }
642
643                 break;
644
645         default:
646                 assert_not_reached("Unknown comparison operator.");
647         }
648
649         return verbs[i].dispatch(bus, argv + optind, left);
650 }
651
652 int main(int argc, char *argv[]) {
653         int r, ret = EXIT_FAILURE;
654         _cleanup_bus_unref_ sd_bus *bus = NULL;
655
656         setlocale(LC_ALL, "");
657         log_parse_environment();
658         log_open();
659
660         r = parse_argv(argc, argv);
661         if (r < 0)
662                 goto finish;
663         else if (r == 0) {
664                 ret = EXIT_SUCCESS;
665                 goto finish;
666         }
667
668         r = bus_open_transport(arg_transport, arg_host, false, &bus);
669         if (r < 0) {
670                 log_error("Failed to create bus connection: %s", strerror(-r));
671                 ret = EXIT_FAILURE;
672                 goto finish;
673         }
674
675         r = timedatectl_main(bus, argc, argv);
676         ret = r < 0 ? EXIT_FAILURE : r;
677
678 finish:
679         pager_close();
680
681         return ret;
682 }