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