chiark / gitweb /
timedatectl: port to sd-bus
[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
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <locale.h>
27 #include <string.h>
28 #include <sys/timex.h>
29
30 #include "sd-bus.h"
31 #include "bus-util.h"
32 #include "bus-error.h"
33 #include "util.h"
34 #include "spawn-polkit-agent.h"
35 #include "build.h"
36 #include "hwclock.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
59         if (!arg_ask_password)
60                 return;
61
62         polkit_agent_open();
63 }
64
65 typedef struct StatusInfo {
66         const char *timezone;
67         bool local_rtc;
68         bool ntp;
69         bool can_ntp;
70 } StatusInfo;
71
72 static const char *jump_str(int delta_minutes, char *s, size_t size) {
73         if (delta_minutes == 60)
74                 return "one hour forward";
75         if (delta_minutes == -60)
76                 return "one hour backwards";
77         if (delta_minutes < 0) {
78                 snprintf(s, size, "%i minutes backwards", -delta_minutes);
79                 return s;
80         }
81         if (delta_minutes > 0) {
82                 snprintf(s, size, "%i minutes forward", delta_minutes);
83                 return s;
84         }
85         return "";
86 }
87
88 static void print_status_info(StatusInfo *i) {
89         usec_t n;
90         char a[FORMAT_TIMESTAMP_MAX];
91         char b[FORMAT_TIMESTAMP_MAX];
92         char s[32];
93         struct tm tm;
94         time_t sec;
95         char *zc, *zn;
96         time_t t, tc, tn;
97         int dn;
98         bool is_dstc, is_dstn;
99         int r;
100
101         assert(i);
102
103         /* enforce the values of /etc/localtime */
104         if (getenv("TZ")) {
105                 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
106                 unsetenv("TZ");
107         }
108
109         n = now(CLOCK_REALTIME);
110         sec = (time_t) (n / USEC_PER_SEC);
111
112         zero(tm);
113         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
114         char_array_0(a);
115         printf("      Local time: %s\n", a);
116
117         zero(tm);
118         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
119         char_array_0(a);
120         printf("  Universal time: %s\n", a);
121
122         zero(tm);
123         r = hwclock_get_time(&tm);
124         if (r >= 0) {
125                 /* Calculate the week-day */
126                 mktime(&tm);
127
128                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
129                 char_array_0(a);
130                 printf("        RTC time: %s\n", a);
131         }
132
133         zero(tm);
134         assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
135         char_array_0(a);
136         printf("        Timezone: %s (%s)\n"
137                "     NTP enabled: %s\n"
138                "NTP synchronized: %s\n"
139                " RTC in local TZ: %s\n",
140                strna(i->timezone),
141                a,
142                i->can_ntp ? yes_no(i->ntp) : "n/a",
143                yes_no(ntp_synced()),
144                yes_no(i->local_rtc));
145
146         r = time_get_dst(sec, "/etc/localtime",
147                          &tc, &zc, &is_dstc,
148                          &tn, &dn, &zn, &is_dstn);
149         if (r < 0)
150                 printf("      DST active: n/a\n");
151         else {
152                 printf("      DST active: %s\n", yes_no(is_dstc));
153
154                 t = tc - 1;
155                 zero(tm);
156                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
157                 char_array_0(a);
158
159                 zero(tm);
160                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
161                 char_array_0(b);
162                 printf(" Last DST change: DST %s at\n"
163                        "                  %s\n"
164                        "                  %s\n",
165                        is_dstc ? "began" : "ended", a, b);
166
167                 t = tn - 1;
168                 zero(tm);
169                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
170                 char_array_0(a);
171
172                 zero(tm);
173                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
174                 char_array_0(b);
175                 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
176                        "                  %s\n"
177                        "                  %s\n",
178                        is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
179
180                 free(zc);
181                 free(zn);
182         }
183
184         if (i->local_rtc)
185                 fputs("\n" ANSI_HIGHLIGHT_ON
186                       "Warning: The RTC is configured to maintain time in the local timezone. This\n"
187                       "         mode is not fully supported and will create various problems with time\n"
188                       "         zone changes and daylight saving adjustments. If at all possible use\n"
189                       "         RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
190 }
191
192 static int get_timedate_property_bool(sd_bus *bus, const char *name, bool *target) {
193         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
194         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
195         int r;
196
197         assert(name);
198
199         r = sd_bus_get_property(bus,
200                                 "org.freedesktop.timedate1",
201                                 "/org/freedesktop/timedate1",
202                                 "org.freedesktop.timedate1",
203                                 name,
204                                 &error,
205                                 &reply,
206                                 "b");
207         if (r < 0) {
208                 log_error("Failed to get property: %s %s", name, bus_error_message(&error, -r));
209                 return r;
210         }
211
212         r = sd_bus_message_read(reply, "b", target);
213         if (r < 0) {
214                 log_error("Failed to parse reply.");
215                 return r;
216         }
217
218         return 0;
219 }
220
221 static int show_status(sd_bus *bus, char **args, unsigned n) {
222         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
223         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
224         int r;
225         StatusInfo info = {};
226
227         assert(bus);
228
229         r = sd_bus_get_property(bus,
230                                 "org.freedesktop.timedate1",
231                                 "/org/freedesktop/timedate1",
232                                 "org.freedesktop.timedate1",
233                                 "Timezone",
234                                 &error,
235                                 &reply,
236                                 "s");
237         if (r < 0) {
238                 log_error("Failed to get property: Timezone %s", bus_error_message(&error, -r));
239                 return r;
240         }
241
242         r = sd_bus_message_read(reply, "s", &info.timezone);
243         if (r < 0)
244                 return r;
245
246         r = get_timedate_property_bool(bus, "LocalRTC", &info.local_rtc);
247         if (r < 0)
248                 return r;
249
250         r = get_timedate_property_bool(bus, "NTP", &info.ntp);
251         if (r < 0)
252                 return r;
253
254         r = get_timedate_property_bool(bus, "CanNTP", &info.can_ntp);
255         if (r < 0)
256                 return r;
257
258         print_status_info(&info);
259         return 0;
260 }
261
262 static int set_time(sd_bus *bus, char **args, unsigned n) {
263         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
264         bool relative = false, interactive = arg_ask_password;
265         usec_t t;
266         int r;
267
268         assert(args);
269         assert(n == 2);
270
271         polkit_agent_open_if_enabled();
272
273         r = parse_timestamp(args[1], &t);
274         if (r < 0) {
275                 log_error("Failed to parse time specification: %s", args[1]);
276                 return r;
277         }
278
279         r = sd_bus_call_method(bus,
280                                "org.freedesktop.timedate1",
281                                "/org/freedesktop/timedate1",
282                                "org.freedesktop.timedate1",
283                                "SetTime",
284                                &error,
285                                NULL,
286                                "xbb", (int64_t)t, relative, interactive);
287         if (r < 0)
288                 log_error("Failed to set time: %s", bus_error_message(&error, -r));
289
290         return r;
291 }
292
293 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
294         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
295         bool interactive = arg_ask_password;
296         int r;
297
298         assert(args);
299         assert(n == 2);
300
301         polkit_agent_open_if_enabled();
302
303         r = sd_bus_call_method(bus,
304                                "org.freedesktop.timedate1",
305                                "/org/freedesktop/timedate1",
306                                "org.freedesktop.timedate1",
307                                "SetTimezone",
308                                &error,
309                                NULL,
310                                "sb", args[1], interactive);
311         if (r < 0)
312                 log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
313
314         return r;
315 }
316
317 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
318         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
319         bool interactive = arg_ask_password, b, q;
320         int r;
321
322         assert(args);
323         assert(n == 2);
324
325         polkit_agent_open_if_enabled();
326
327         r = parse_boolean(args[1]);
328         if (r < 0) {
329                 log_error("Failed to parse local RTC setting: %s", args[1]);
330                 return r;
331         }
332
333         b = r;
334         q = arg_adjust_system_clock;
335
336         r = sd_bus_call_method(bus,
337                                "org.freedesktop.timedate1",
338                                "/org/freedesktop/timedate1",
339                                "org.freedesktop.timedate1",
340                                "SetLocalRTC",
341                                &error,
342                                NULL,
343                                "bbb", b, q, interactive);
344         if (r < 0)
345                 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
346
347         return r;
348 }
349
350 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
351         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
352         bool interactive = arg_ask_password, b;
353         int r;
354
355         assert(args);
356         assert(n == 2);
357
358         polkit_agent_open_if_enabled();
359
360         r = parse_boolean(args[1]);
361         if (r < 0) {
362                 log_error("Failed to parse NTP setting: %s", args[1]);
363                 return r;
364         }
365
366         b = r;
367
368         r = sd_bus_call_method(bus,
369                                "org.freedesktop.timedate1",
370                                "/org/freedesktop/timedate1",
371                                "org.freedesktop.timedate1",
372                                "SetNTP",
373                                &error,
374                                NULL,
375                                "bb", b, interactive);
376         if (r < 0)
377                 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
378
379         return r;
380 }
381
382 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
383         _cleanup_fclose_ FILE *f = NULL;
384         _cleanup_strv_free_ char **zones = NULL;
385         size_t n_zones = 0;
386
387         assert(args);
388         assert(n == 1);
389
390         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
391         if (!f) {
392                 log_error("Failed to open timezone database: %m");
393                 return -errno;
394         }
395
396         for (;;) {
397                 char l[LINE_MAX], *p, **z, *w;
398                 size_t k;
399
400                 if (!fgets(l, sizeof(l), f)) {
401                         if (feof(f))
402                                 break;
403
404                         log_error("Failed to read timezone database: %m");
405                         return -errno;
406                 }
407
408                 p = strstrip(l);
409
410                 if (isempty(p) || *p == '#')
411                         continue;
412
413
414                 /* Skip over country code */
415                 p += strcspn(p, WHITESPACE);
416                 p += strspn(p, WHITESPACE);
417
418                 /* Skip over coordinates */
419                 p += strcspn(p, WHITESPACE);
420                 p += strspn(p, WHITESPACE);
421
422                 /* Found timezone name */
423                 k = strcspn(p, WHITESPACE);
424                 if (k <= 0)
425                         continue;
426
427                 w = strndup(p, k);
428                 if (!w)
429                         return log_oom();
430
431                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
432                 if (!z) {
433                         free(w);
434                         return log_oom();
435                 }
436
437                 zones = z;
438                 zones[n_zones++] = w;
439         }
440
441         if (zones)
442                 zones[n_zones] = NULL;
443
444         pager_open_if_enabled();
445
446         strv_sort(zones);
447         strv_print(zones);
448
449         return 0;
450 }
451
452 static int help(void) {
453
454         printf("%s [OPTIONS...] COMMAND ...\n\n"
455                "Query or change system time and date settings.\n\n"
456                "  -h --help              Show this help\n"
457                "     --version           Show package version\n"
458                "     --adjust-system-clock\n"
459                "                         Adjust system clock when changing local RTC mode\n"
460                "     --no-pager          Do not pipe output into a pager\n"
461                "     --no-ask-password   Do not prompt for password\n"
462                "  -H --host=[USER@]HOST  Operate on remote host\n"
463                "  -M --machine=CONTAINER Operate on local container\n\n"
464                "Commands:\n"
465                "  status                 Show current time settings\n"
466                "  set-time TIME          Set system time\n"
467                "  set-timezone ZONE      Set system timezone\n"
468                "  list-timezones         Show known timezones\n"
469                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
470                "  set-ntp BOOL           Control whether NTP is enabled\n",
471                program_invocation_short_name);
472
473         return 0;
474 }
475
476 static int parse_argv(int argc, char *argv[]) {
477
478         enum {
479                 ARG_VERSION = 0x100,
480                 ARG_NO_PAGER,
481                 ARG_ADJUST_SYSTEM_CLOCK,
482                 ARG_NO_ASK_PASSWORD
483         };
484
485         static const struct option options[] = {
486                 { "help",                no_argument,       NULL, 'h'                     },
487                 { "version",             no_argument,       NULL, ARG_VERSION             },
488                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
489                 { "host",                required_argument, NULL, 'H'                     },
490                 { "machine",             required_argument, NULL, 'M'                     },
491                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
492                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
493                 { NULL,                  0,                 NULL, 0                       }
494         };
495
496         int c;
497
498         assert(argc >= 0);
499         assert(argv);
500
501         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
502
503                 switch (c) {
504
505                 case 'h':
506                         help();
507                         return 0;
508
509                 case ARG_VERSION:
510                         puts(PACKAGE_STRING);
511                         puts(SYSTEMD_FEATURES);
512                         return 0;
513
514                 case 'H':
515                         arg_transport = BUS_TRANSPORT_REMOTE;
516                         arg_host = optarg;
517                         break;
518
519                 case 'M':
520                         arg_transport = BUS_TRANSPORT_CONTAINER;
521                         arg_host = optarg;
522                         break;
523
524                 case ARG_NO_ASK_PASSWORD:
525                         arg_ask_password = false;
526                         break;
527
528                 case ARG_ADJUST_SYSTEM_CLOCK:
529                         arg_adjust_system_clock = true;
530                         break;
531
532                 case ARG_NO_PAGER:
533                         arg_no_pager = true;
534                         break;
535
536                 case '?':
537                         return -EINVAL;
538
539                 default:
540                         log_error("Unknown option code %c", c);
541                         return -EINVAL;
542                 }
543         }
544
545         return 1;
546 }
547
548 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
549
550         static const struct {
551                 const char* verb;
552                 const enum {
553                         MORE,
554                         LESS,
555                         EQUAL
556                 } argc_cmp;
557                 const int argc;
558                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
559         } verbs[] = {
560                 { "status",                LESS,   1, show_status      },
561                 { "set-time",              EQUAL,  2, set_time         },
562                 { "set-timezone",          EQUAL,  2, set_timezone     },
563                 { "list-timezones",        EQUAL,  1, list_timezones   },
564                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
565                 { "set-ntp",               EQUAL,  2, set_ntp,         },
566         };
567
568         int left;
569         unsigned i;
570
571         assert(argc >= 0);
572         assert(argv);
573
574         left = argc - optind;
575
576         if (left <= 0)
577                 /* Special rule: no arguments means "status" */
578                 i = 0;
579         else {
580                 if (streq(argv[optind], "help")) {
581                         help();
582                         return 0;
583                 }
584
585                 for (i = 0; i < ELEMENTSOF(verbs); i++)
586                         if (streq(argv[optind], verbs[i].verb))
587                                 break;
588
589                 if (i >= ELEMENTSOF(verbs)) {
590                         log_error("Unknown operation %s", argv[optind]);
591                         return -EINVAL;
592                 }
593         }
594
595         switch (verbs[i].argc_cmp) {
596
597         case EQUAL:
598                 if (left != verbs[i].argc) {
599                         log_error("Invalid number of arguments.");
600                         return -EINVAL;
601                 }
602
603                 break;
604
605         case MORE:
606                 if (left < verbs[i].argc) {
607                         log_error("Too few arguments.");
608                         return -EINVAL;
609                 }
610
611                 break;
612
613         case LESS:
614                 if (left > verbs[i].argc) {
615                         log_error("Too many arguments.");
616                         return -EINVAL;
617                 }
618
619                 break;
620
621         default:
622                 assert_not_reached("Unknown comparison operator.");
623         }
624
625         return verbs[i].dispatch(bus, argv + optind, left);
626 }
627
628 int main(int argc, char *argv[]) {
629         int r, ret = EXIT_FAILURE;
630         _cleanup_bus_unref_ sd_bus *bus = NULL;
631
632         setlocale(LC_ALL, "");
633         log_parse_environment();
634         log_open();
635
636         r = parse_argv(argc, argv);
637         if (r < 0)
638                 goto finish;
639         else if (r == 0) {
640                 ret = EXIT_SUCCESS;
641                 goto finish;
642         }
643
644         r = bus_open_transport(arg_transport, arg_host, false, &bus);
645         if (r < 0) {
646                 log_error("Failed to create bus connection: %s", strerror(-r));
647                 ret = EXIT_FAILURE;
648                 goto finish;
649         }
650
651         r = timedatectl_main(bus, argc, argv);
652         ret = r < 0 ? EXIT_FAILURE : r;
653
654 finish:
655         pager_close();
656
657         return ret;
658 }