chiark / gitweb /
bus: use internal helper to read org.freedesktop.DBus.Properties::GetAll variables
[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         polkit_agent_open();
62 }
63
64 typedef struct StatusInfo {
65         usec_t time;
66         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(const 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", 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 show_status(sd_bus *bus, char **args, unsigned n) {
194         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
195         StatusInfo info = {};
196         const struct bus_properties_map map[]  = {
197                 { "s",  "Timezone",        &info.timezone },
198                 { "b",  "LocalRTC",        &info.rtc_local },
199                 { "b",  "NTP",             &info.ntp_enabled },
200                 { "b",  "CanNTP",          &info.ntp_capable },
201                 { "b",  "NTPSynchronized", &info.ntp_synced},
202                 { "t",  "TimeUSec",        &info.time },
203                 { "t",  "RTCTimeUSec",     &info.rtc_time },
204                 {}
205         };
206         int r;
207
208         assert(bus);
209
210         r = bus_map_all_properties(bus,
211                                    "org.freedesktop.timedate1",
212                                    "/org/freedesktop/timedate1",
213                                    map);
214         if (r < 0)
215                 goto fail;
216
217         print_status_info(&info);
218
219 fail:
220         free(info.timezone);
221         return r;
222 }
223
224 static int set_time(sd_bus *bus, char **args, unsigned n) {
225         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
226         bool relative = false, interactive = arg_ask_password;
227         usec_t t;
228         int r;
229
230         assert(args);
231         assert(n == 2);
232
233         polkit_agent_open_if_enabled();
234
235         r = parse_timestamp(args[1], &t);
236         if (r < 0) {
237                 log_error("Failed to parse time specification: %s", args[1]);
238                 return r;
239         }
240
241         r = sd_bus_call_method(bus,
242                                "org.freedesktop.timedate1",
243                                "/org/freedesktop/timedate1",
244                                "org.freedesktop.timedate1",
245                                "SetTime",
246                                &error,
247                                NULL,
248                                "xbb", (int64_t)t, relative, interactive);
249         if (r < 0)
250                 log_error("Failed to set time: %s", bus_error_message(&error, -r));
251
252         return r;
253 }
254
255 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
256         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
257         int r;
258
259         assert(args);
260         assert(n == 2);
261
262         polkit_agent_open_if_enabled();
263
264         r = sd_bus_call_method(bus,
265                                "org.freedesktop.timedate1",
266                                "/org/freedesktop/timedate1",
267                                "org.freedesktop.timedate1",
268                                "SetTimezone",
269                                &error,
270                                NULL,
271                                "sb", args[1], arg_ask_password);
272         if (r < 0)
273                 log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
274
275         return r;
276 }
277
278 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
279         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
280         int r, b;
281
282         assert(args);
283         assert(n == 2);
284
285         polkit_agent_open_if_enabled();
286
287         b = parse_boolean(args[1]);
288         if (b < 0) {
289                 log_error("Failed to parse local RTC setting: %s", args[1]);
290                 return b;
291         }
292
293         r = sd_bus_call_method(bus,
294                                "org.freedesktop.timedate1",
295                                "/org/freedesktop/timedate1",
296                                "org.freedesktop.timedate1",
297                                "SetLocalRTC",
298                                &error,
299                                NULL,
300                                "bbb", b, arg_adjust_system_clock, arg_ask_password);
301         if (r < 0)
302                 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
303
304         return r;
305 }
306
307 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
308         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
309         int b, r;
310
311         assert(args);
312         assert(n == 2);
313
314         polkit_agent_open_if_enabled();
315
316         b = parse_boolean(args[1]);
317         if (b < 0) {
318                 log_error("Failed to parse NTP setting: %s", args[1]);
319                 return b;
320         }
321
322         r = sd_bus_call_method(bus,
323                                "org.freedesktop.timedate1",
324                                "/org/freedesktop/timedate1",
325                                "org.freedesktop.timedate1",
326                                "SetNTP",
327                                &error,
328                                NULL,
329                                "bb", b, arg_ask_password);
330         if (r < 0)
331                 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
332
333         return r;
334 }
335
336 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
337         _cleanup_fclose_ FILE *f = NULL;
338         _cleanup_strv_free_ char **zones = NULL;
339         size_t n_zones = 0;
340
341         assert(args);
342         assert(n == 1);
343
344         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
345         if (!f) {
346                 log_error("Failed to open timezone database: %m");
347                 return -errno;
348         }
349
350         for (;;) {
351                 char l[LINE_MAX], *p, **z, *w;
352                 size_t k;
353
354                 if (!fgets(l, sizeof(l), f)) {
355                         if (feof(f))
356                                 break;
357
358                         log_error("Failed to read timezone database: %m");
359                         return -errno;
360                 }
361
362                 p = strstrip(l);
363
364                 if (isempty(p) || *p == '#')
365                         continue;
366
367
368                 /* Skip over country code */
369                 p += strcspn(p, WHITESPACE);
370                 p += strspn(p, WHITESPACE);
371
372                 /* Skip over coordinates */
373                 p += strcspn(p, WHITESPACE);
374                 p += strspn(p, WHITESPACE);
375
376                 /* Found timezone name */
377                 k = strcspn(p, WHITESPACE);
378                 if (k <= 0)
379                         continue;
380
381                 w = strndup(p, k);
382                 if (!w)
383                         return log_oom();
384
385                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
386                 if (!z) {
387                         free(w);
388                         return log_oom();
389                 }
390
391                 zones = z;
392                 zones[n_zones++] = w;
393         }
394
395         if (zones)
396                 zones[n_zones] = NULL;
397
398         pager_open_if_enabled();
399
400         strv_sort(zones);
401         strv_print(zones);
402
403         return 0;
404 }
405
406 static int help(void) {
407
408         printf("%s [OPTIONS...] COMMAND ...\n\n"
409                "Query or change system time and date settings.\n\n"
410                "  -h --help              Show this help\n"
411                "     --version           Show package version\n"
412                "     --adjust-system-clock\n"
413                "                         Adjust system clock when changing local RTC mode\n"
414                "     --no-pager          Do not pipe output into a pager\n"
415                "     --no-ask-password   Do not prompt for password\n"
416                "  -H --host=[USER@]HOST  Operate on remote host\n"
417                "  -M --machine=CONTAINER Operate on local container\n\n"
418                "Commands:\n"
419                "  status                 Show current time settings\n"
420                "  set-time TIME          Set system time\n"
421                "  set-timezone ZONE      Set system timezone\n"
422                "  list-timezones         Show known timezones\n"
423                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
424                "  set-ntp BOOL           Control whether NTP is enabled\n",
425                program_invocation_short_name);
426
427         return 0;
428 }
429
430 static int parse_argv(int argc, char *argv[]) {
431
432         enum {
433                 ARG_VERSION = 0x100,
434                 ARG_NO_PAGER,
435                 ARG_ADJUST_SYSTEM_CLOCK,
436                 ARG_NO_ASK_PASSWORD
437         };
438
439         static const struct option options[] = {
440                 { "help",                no_argument,       NULL, 'h'                     },
441                 { "version",             no_argument,       NULL, ARG_VERSION             },
442                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
443                 { "host",                required_argument, NULL, 'H'                     },
444                 { "machine",             required_argument, NULL, 'M'                     },
445                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
446                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
447                 { NULL,                  0,                 NULL, 0                       }
448         };
449
450         int c;
451
452         assert(argc >= 0);
453         assert(argv);
454
455         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
456
457                 switch (c) {
458
459                 case 'h':
460                         help();
461                         return 0;
462
463                 case ARG_VERSION:
464                         puts(PACKAGE_STRING);
465                         puts(SYSTEMD_FEATURES);
466                         return 0;
467
468                 case 'H':
469                         arg_transport = BUS_TRANSPORT_REMOTE;
470                         arg_host = optarg;
471                         break;
472
473                 case 'M':
474                         arg_transport = BUS_TRANSPORT_CONTAINER;
475                         arg_host = optarg;
476                         break;
477
478                 case ARG_NO_ASK_PASSWORD:
479                         arg_ask_password = false;
480                         break;
481
482                 case ARG_ADJUST_SYSTEM_CLOCK:
483                         arg_adjust_system_clock = true;
484                         break;
485
486                 case ARG_NO_PAGER:
487                         arg_no_pager = true;
488                         break;
489
490                 case '?':
491                         return -EINVAL;
492
493                 default:
494                         log_error("Unknown option code %c", c);
495                         return -EINVAL;
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         int r, ret = EXIT_FAILURE;
584         _cleanup_bus_unref_ sd_bus *bus = NULL;
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         else if (r == 0) {
594                 ret = EXIT_SUCCESS;
595                 goto finish;
596         }
597
598         r = bus_open_transport(arg_transport, arg_host, false, &bus);
599         if (r < 0) {
600                 log_error("Failed to create bus connection: %s", strerror(-r));
601                 ret = EXIT_FAILURE;
602                 goto finish;
603         }
604
605         r = timedatectl_main(bus, argc, argv);
606         ret = r < 0 ? EXIT_FAILURE : r;
607
608 finish:
609         pager_close();
610
611         return ret;
612 }