chiark / gitweb /
7ce9a824ece1944190c8d17090a622c431bdf2c6
[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                 {}
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                         return help();
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                         assert_not_reached("Unhandled option");
495                 }
496         }
497
498         return 1;
499 }
500
501 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
502
503         static const struct {
504                 const char* verb;
505                 const enum {
506                         MORE,
507                         LESS,
508                         EQUAL
509                 } argc_cmp;
510                 const int argc;
511                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
512         } verbs[] = {
513                 { "status",                LESS,   1, show_status      },
514                 { "set-time",              EQUAL,  2, set_time         },
515                 { "set-timezone",          EQUAL,  2, set_timezone     },
516                 { "list-timezones",        EQUAL,  1, list_timezones   },
517                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
518                 { "set-ntp",               EQUAL,  2, set_ntp,         },
519         };
520
521         int left;
522         unsigned i;
523
524         assert(argc >= 0);
525         assert(argv);
526
527         left = argc - optind;
528
529         if (left <= 0)
530                 /* Special rule: no arguments means "status" */
531                 i = 0;
532         else {
533                 if (streq(argv[optind], "help")) {
534                         help();
535                         return 0;
536                 }
537
538                 for (i = 0; i < ELEMENTSOF(verbs); i++)
539                         if (streq(argv[optind], verbs[i].verb))
540                                 break;
541
542                 if (i >= ELEMENTSOF(verbs)) {
543                         log_error("Unknown operation %s", argv[optind]);
544                         return -EINVAL;
545                 }
546         }
547
548         switch (verbs[i].argc_cmp) {
549
550         case EQUAL:
551                 if (left != verbs[i].argc) {
552                         log_error("Invalid number of arguments.");
553                         return -EINVAL;
554                 }
555
556                 break;
557
558         case MORE:
559                 if (left < verbs[i].argc) {
560                         log_error("Too few arguments.");
561                         return -EINVAL;
562                 }
563
564                 break;
565
566         case LESS:
567                 if (left > verbs[i].argc) {
568                         log_error("Too many arguments.");
569                         return -EINVAL;
570                 }
571
572                 break;
573
574         default:
575                 assert_not_reached("Unknown comparison operator.");
576         }
577
578         return verbs[i].dispatch(bus, argv + optind, left);
579 }
580
581 int main(int argc, char *argv[]) {
582         _cleanup_bus_unref_ sd_bus *bus = NULL;
583         int r;
584
585         setlocale(LC_ALL, "");
586         log_parse_environment();
587         log_open();
588
589         r = parse_argv(argc, argv);
590         if (r <= 0)
591                 goto finish;
592
593         r = bus_open_transport(arg_transport, arg_host, false, &bus);
594         if (r < 0) {
595                 log_error("Failed to create bus connection: %s", strerror(-r));
596                 goto finish;
597         }
598
599         r = timedatectl_main(bus, argc, argv);
600
601 finish:
602         pager_close();
603
604         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
605 }