chiark / gitweb /
9b81513c75456664b2749c5898318b2a54e470d5
[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                 log_error("Failed to query server: %s", strerror(-r));
219                 goto fail;
220         }
221
222         print_status_info(&info);
223
224 fail:
225         free(info.timezone);
226         return r;
227 }
228
229 static int set_time(sd_bus *bus, char **args, unsigned n) {
230         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
231         bool relative = false, interactive = arg_ask_password;
232         usec_t t;
233         int r;
234
235         assert(args);
236         assert(n == 2);
237
238         polkit_agent_open_if_enabled();
239
240         r = parse_timestamp(args[1], &t);
241         if (r < 0) {
242                 log_error("Failed to parse time specification: %s", args[1]);
243                 return r;
244         }
245
246         r = sd_bus_call_method(bus,
247                                "org.freedesktop.timedate1",
248                                "/org/freedesktop/timedate1",
249                                "org.freedesktop.timedate1",
250                                "SetTime",
251                                &error,
252                                NULL,
253                                "xbb", (int64_t)t, relative, interactive);
254         if (r < 0)
255                 log_error("Failed to set time: %s", bus_error_message(&error, -r));
256
257         return r;
258 }
259
260 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
261         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
262         int r;
263
264         assert(args);
265         assert(n == 2);
266
267         polkit_agent_open_if_enabled();
268
269         r = sd_bus_call_method(bus,
270                                "org.freedesktop.timedate1",
271                                "/org/freedesktop/timedate1",
272                                "org.freedesktop.timedate1",
273                                "SetTimezone",
274                                &error,
275                                NULL,
276                                "sb", args[1], arg_ask_password);
277         if (r < 0)
278                 log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
279
280         return r;
281 }
282
283 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
284         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
285         int r, b;
286
287         assert(args);
288         assert(n == 2);
289
290         polkit_agent_open_if_enabled();
291
292         b = parse_boolean(args[1]);
293         if (b < 0) {
294                 log_error("Failed to parse local RTC setting: %s", args[1]);
295                 return b;
296         }
297
298         r = sd_bus_call_method(bus,
299                                "org.freedesktop.timedate1",
300                                "/org/freedesktop/timedate1",
301                                "org.freedesktop.timedate1",
302                                "SetLocalRTC",
303                                &error,
304                                NULL,
305                                "bbb", b, arg_adjust_system_clock, arg_ask_password);
306         if (r < 0)
307                 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
308
309         return r;
310 }
311
312 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
313         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
314         int b, r;
315
316         assert(args);
317         assert(n == 2);
318
319         polkit_agent_open_if_enabled();
320
321         b = parse_boolean(args[1]);
322         if (b < 0) {
323                 log_error("Failed to parse NTP setting: %s", args[1]);
324                 return b;
325         }
326
327         r = sd_bus_call_method(bus,
328                                "org.freedesktop.timedate1",
329                                "/org/freedesktop/timedate1",
330                                "org.freedesktop.timedate1",
331                                "SetNTP",
332                                &error,
333                                NULL,
334                                "bb", b, arg_ask_password);
335         if (r < 0)
336                 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
337
338         return r;
339 }
340
341 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
342         _cleanup_fclose_ FILE *f = NULL;
343         _cleanup_strv_free_ char **zones = NULL;
344         size_t n_zones = 0;
345
346         assert(args);
347         assert(n == 1);
348
349         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
350         if (!f) {
351                 log_error("Failed to open timezone database: %m");
352                 return -errno;
353         }
354
355         for (;;) {
356                 char l[LINE_MAX], *p, **z, *w;
357                 size_t k;
358
359                 if (!fgets(l, sizeof(l), f)) {
360                         if (feof(f))
361                                 break;
362
363                         log_error("Failed to read timezone database: %m");
364                         return -errno;
365                 }
366
367                 p = strstrip(l);
368
369                 if (isempty(p) || *p == '#')
370                         continue;
371
372
373                 /* Skip over country code */
374                 p += strcspn(p, WHITESPACE);
375                 p += strspn(p, WHITESPACE);
376
377                 /* Skip over coordinates */
378                 p += strcspn(p, WHITESPACE);
379                 p += strspn(p, WHITESPACE);
380
381                 /* Found timezone name */
382                 k = strcspn(p, WHITESPACE);
383                 if (k <= 0)
384                         continue;
385
386                 w = strndup(p, k);
387                 if (!w)
388                         return log_oom();
389
390                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
391                 if (!z) {
392                         free(w);
393                         return log_oom();
394                 }
395
396                 zones = z;
397                 zones[n_zones++] = w;
398         }
399
400         if (zones)
401                 zones[n_zones] = NULL;
402
403         pager_open_if_enabled();
404
405         strv_sort(zones);
406         strv_print(zones);
407
408         return 0;
409 }
410
411 static int help(void) {
412
413         printf("%s [OPTIONS...] COMMAND ...\n\n"
414                "Query or change system time and date settings.\n\n"
415                "  -h --help                Show this help\n"
416                "     --version             Show package version\n"
417                "     --no-pager            Do not pipe output into a pager\n"
418                "     --no-ask-password     Do not prompt for password\n"
419                "  -H --host=[USER@]HOST    Operate on remote host\n"
420                "  -M --machine=CONTAINER   Operate on local container\n"
421                "     --adjust-system-clock Adjust system clock when changing local RTC mode\n\n"
422                "Commands:\n"
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",
429                program_invocation_short_name);
430
431         return 0;
432 }
433
434 static int parse_argv(int argc, char *argv[]) {
435
436         enum {
437                 ARG_VERSION = 0x100,
438                 ARG_NO_PAGER,
439                 ARG_ADJUST_SYSTEM_CLOCK,
440                 ARG_NO_ASK_PASSWORD
441         };
442
443         static const struct option options[] = {
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'                     },
448                 { "machine",             required_argument, NULL, 'M'                     },
449                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
450                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
451                 {}
452         };
453
454         int c;
455
456         assert(argc >= 0);
457         assert(argv);
458
459         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
460
461                 switch (c) {
462
463                 case 'h':
464                         return help();
465
466                 case ARG_VERSION:
467                         puts(PACKAGE_STRING);
468                         puts(SYSTEMD_FEATURES);
469                         return 0;
470
471                 case 'H':
472                         arg_transport = BUS_TRANSPORT_REMOTE;
473                         arg_host = optarg;
474                         break;
475
476                 case 'M':
477                         arg_transport = BUS_TRANSPORT_CONTAINER;
478                         arg_host = optarg;
479                         break;
480
481                 case ARG_NO_ASK_PASSWORD:
482                         arg_ask_password = false;
483                         break;
484
485                 case ARG_ADJUST_SYSTEM_CLOCK:
486                         arg_adjust_system_clock = true;
487                         break;
488
489                 case ARG_NO_PAGER:
490                         arg_no_pager = true;
491                         break;
492
493                 case '?':
494                         return -EINVAL;
495
496                 default:
497                         assert_not_reached("Unhandled option");
498                 }
499         }
500
501         return 1;
502 }
503
504 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
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;
514                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
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);
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
581         return verbs[i].dispatch(bus, argv + optind, left);
582 }
583
584 int main(int argc, char *argv[]) {
585         _cleanup_bus_unref_ sd_bus *bus = NULL;
586         int r;
587
588         setlocale(LC_ALL, "");
589         log_parse_environment();
590         log_open();
591
592         r = parse_argv(argc, argv);
593         if (r <= 0)
594                 goto finish;
595
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));
599                 goto finish;
600         }
601
602         r = timedatectl_main(bus, argc, argv);
603
604 finish:
605         pager_close();
606
607         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
608 }