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