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