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