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