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