chiark / gitweb /
44d329ecc2817c4408bd158139e43f731b500a62
[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 <getopt.h>
26 #include <locale.h>
27
28 #include "sd-bus.h"
29 #include "bus-util.h"
30 #include "bus-error.h"
31 #include "util.h"
32 #include "spawn-polkit-agent.h"
33 #include "build.h"
34 #include "strv.h"
35 #include "pager.h"
36 #include "time-dst.h"
37
38 static bool arg_no_pager = false;
39 static bool arg_ask_password = true;
40 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
41 static char *arg_host = NULL;
42 static bool arg_adjust_system_clock = false;
43
44 static void pager_open_if_enabled(void) {
45
46         if (arg_no_pager)
47                 return;
48
49         pager_open(false);
50 }
51
52 static void polkit_agent_open_if_enabled(void) {
53
54         /* Open the polkit agent as a child process if necessary */
55         if (!arg_ask_password)
56                 return;
57
58         if (arg_transport != BUS_TRANSPORT_LOCAL)
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         bool have_time = false;
99         _cleanup_free_ char *zc = NULL, *zn = NULL;
100         time_t t, tc, tn;
101         int dn = 0;
102         bool is_dstc = false, is_dstn = false;
103         int r;
104
105         assert(i);
106
107         /* Enforce the values of /etc/localtime */
108         if (getenv("TZ")) {
109                 fprintf(stderr, "Warning: Ignoring the TZ variable.\n\n");
110                 unsetenv("TZ");
111         }
112
113         r = setenv("TZ", i->timezone, false);
114         if (r < 0) {
115                 log_error_errno(errno, "Failed to set TZ environment variable: %m");
116                 exit(EXIT_FAILURE);
117         }
118         tzset();
119
120         if (i->time != 0) {
121                 sec = (time_t) (i->time / USEC_PER_SEC);
122                 have_time = true;
123         } else if (IN_SET(arg_transport, BUS_TRANSPORT_REMOTE, BUS_TRANSPORT_MACHINE)) {
124                 sec = time(NULL);
125                 have_time = true;
126         } else
127                 fprintf(stderr, "Warning: Could not get time from timedated and not operating locally.\n\n");
128
129         if (have_time) {
130                 xstrftime(a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm));
131                 printf("      Local time: %.*s\n", (int) sizeof(a), a);
132
133                 xstrftime(a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm));
134                 printf("  Universal time: %.*s\n", (int) sizeof(a), a);
135         } else {
136                 printf("      Local time: %s\n", "n/a");
137                 printf("  Universal time: %s\n", "n/a");
138         }
139
140         if (i->rtc_time > 0) {
141                 time_t rtc_sec;
142
143                 rtc_sec = (time_t)(i->rtc_time / USEC_PER_SEC);
144                 xstrftime(a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm));
145                 printf("        RTC time: %.*s\n", (int) sizeof(a), a);
146         } else
147                 printf("        RTC time: %s\n", "n/a");
148
149         if (have_time)
150                 xstrftime(a, "%Z, %z", localtime_r(&sec, &tm));
151
152         printf("       Time zone: %s (%.*s)\n"
153                "     NTP enabled: %s\n"
154                "NTP synchronized: %s\n"
155                " RTC in local TZ: %s\n",
156                strna(i->timezone), (int) sizeof(a), have_time ? a : "n/a",
157                i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
158                yes_no(i->ntp_synced),
159                yes_no(i->rtc_local));
160
161         if (have_time) {
162                 r = time_get_dst(sec, "/etc/localtime",
163                                  &tc, &zc, &is_dstc,
164                                  &tn, &dn, &zn, &is_dstn);
165                 if (r < 0)
166                         printf("      DST active: %s\n", "n/a");
167                 else {
168                         printf("      DST active: %s\n", yes_no(is_dstc));
169
170                         t = tc - 1;
171                         xstrftime(a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm));
172
173                         xstrftime(b, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm));
174                         printf(" Last DST change: DST %s at\n"
175                                "                  %.*s\n"
176                                "                  %.*s\n",
177                                is_dstc ? "began" : "ended",
178                                (int) sizeof(a), a,
179                                (int) sizeof(b), b);
180
181                         t = tn - 1;
182                         xstrftime(a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm));
183                         xstrftime(b, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm));
184                         printf(" Next DST change: DST %s (the clock jumps %s) at\n"
185                                "                  %.*s\n"
186                                "                  %.*s\n",
187                                is_dstn ? "begins" : "ends",
188                                jump_str(dn, s, sizeof(s)),
189                                (int) sizeof(a), a,
190                                (int) sizeof(b), b);
191                 }
192         } else
193                 printf("      DST active: %s\n", yes_no(is_dstc));
194
195         if (i->rtc_local)
196                 fputs("\n" ANSI_HIGHLIGHT_ON
197                       "Warning: The system is configured to read the RTC time in the local time zone. This\n"
198                       "         mode can not be fully supported. It will create various problems with time\n"
199                       "         zone changes and daylight saving time adjustments. The RTC time is never updated,\n"
200                       "         it relies on external facilities to maintain it. If at all possible, use\n"
201                       "         RTC in UTC by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
202 }
203
204 static int show_status(sd_bus *bus, char **args, unsigned n) {
205         StatusInfo info = {};
206         static const struct bus_properties_map map[]  = {
207                 { "Timezone",        "s", NULL, offsetof(StatusInfo, timezone) },
208                 { "LocalRTC",        "b", NULL, offsetof(StatusInfo, rtc_local) },
209                 { "NTP",             "b", NULL, offsetof(StatusInfo, ntp_enabled) },
210                 { "CanNTP",          "b", NULL, offsetof(StatusInfo, ntp_capable) },
211                 { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) },
212                 { "TimeUSec",        "t", NULL, offsetof(StatusInfo, time) },
213                 { "RTCTimeUSec",     "t", NULL, offsetof(StatusInfo, rtc_time) },
214                 {}
215         };
216         int r;
217
218         assert(bus);
219
220         r = bus_map_all_properties(bus,
221                                    "org.freedesktop.timedate1",
222                                    "/org/freedesktop/timedate1",
223                                    map,
224                                    &info);
225         if (r < 0) {
226                 log_error_errno(r, "Failed to query server: %m");
227                 goto fail;
228         }
229
230         print_status_info(&info);
231
232 fail:
233         free(info.timezone);
234         return r;
235 }
236
237 static int set_time(sd_bus *bus, char **args, unsigned n) {
238         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
239         bool relative = false, interactive = arg_ask_password;
240         usec_t t;
241         int r;
242
243         assert(args);
244         assert(n == 2);
245
246         polkit_agent_open_if_enabled();
247
248         r = parse_timestamp(args[1], &t);
249         if (r < 0) {
250                 log_error("Failed to parse time specification: %s", args[1]);
251                 return r;
252         }
253
254         r = sd_bus_call_method(bus,
255                                "org.freedesktop.timedate1",
256                                "/org/freedesktop/timedate1",
257                                "org.freedesktop.timedate1",
258                                "SetTime",
259                                &error,
260                                NULL,
261                                "xbb", (int64_t)t, relative, interactive);
262         if (r < 0)
263                 log_error("Failed to set time: %s", bus_error_message(&error, -r));
264
265         return r;
266 }
267
268 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
269         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
270         int r;
271
272         assert(args);
273         assert(n == 2);
274
275         polkit_agent_open_if_enabled();
276
277         r = sd_bus_call_method(bus,
278                                "org.freedesktop.timedate1",
279                                "/org/freedesktop/timedate1",
280                                "org.freedesktop.timedate1",
281                                "SetTimezone",
282                                &error,
283                                NULL,
284                                "sb", args[1], arg_ask_password);
285         if (r < 0)
286                 log_error("Failed to set time zone: %s", bus_error_message(&error, -r));
287
288         return r;
289 }
290
291 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
292         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
293         int r, b;
294
295         assert(args);
296         assert(n == 2);
297
298         polkit_agent_open_if_enabled();
299
300         b = parse_boolean(args[1]);
301         if (b < 0) {
302                 log_error("Failed to parse local RTC setting: %s", args[1]);
303                 return b;
304         }
305
306         r = sd_bus_call_method(bus,
307                                "org.freedesktop.timedate1",
308                                "/org/freedesktop/timedate1",
309                                "org.freedesktop.timedate1",
310                                "SetLocalRTC",
311                                &error,
312                                NULL,
313                                "bbb", b, arg_adjust_system_clock, arg_ask_password);
314         if (r < 0)
315                 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
316
317         return r;
318 }
319
320 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
321         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
322         int b, r;
323
324         assert(args);
325         assert(n == 2);
326
327         polkit_agent_open_if_enabled();
328
329         b = parse_boolean(args[1]);
330         if (b < 0) {
331                 log_error("Failed to parse NTP setting: %s", args[1]);
332                 return b;
333         }
334
335         r = sd_bus_call_method(bus,
336                                "org.freedesktop.timedate1",
337                                "/org/freedesktop/timedate1",
338                                "org.freedesktop.timedate1",
339                                "SetNTP",
340                                &error,
341                                NULL,
342                                "bb", b, arg_ask_password);
343         if (r < 0)
344                 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
345
346         return r;
347 }
348
349 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
350         _cleanup_strv_free_ char **zones = NULL;
351         int r;
352
353         assert(args);
354         assert(n == 1);
355
356         r = get_timezones(&zones);
357         if (r < 0)
358                 return log_error_errno(r, "Failed to read list of time zones: %m");
359
360         pager_open_if_enabled();
361         strv_print(zones);
362
363         return 0;
364 }
365
366 static void help(void) {
367         printf("%s [OPTIONS...] COMMAND ...\n\n"
368                "Query or change system time and date settings.\n\n"
369                "  -h --help                Show this help message\n"
370                "     --version             Show package version\n"
371                "     --no-pager            Do not pipe output into a pager\n"
372                "     --no-ask-password     Do not prompt for password\n"
373                "  -H --host=[USER@]HOST    Operate on remote host\n"
374                "  -M --machine=CONTAINER   Operate on local container\n"
375                "     --adjust-system-clock Adjust system clock when changing local RTC mode\n\n"
376                "Commands:\n"
377                "  status                   Show current time settings\n"
378                "  set-time TIME            Set system time\n"
379                "  set-timezone ZONE        Set system time zone\n"
380                "  list-timezones           Show known time zones\n"
381                "  set-local-rtc BOOL       Control whether RTC is in local time\n"
382                "  set-ntp BOOL             Control whether NTP is enabled\n",
383                program_invocation_short_name);
384 }
385
386 static int parse_argv(int argc, char *argv[]) {
387
388         enum {
389                 ARG_VERSION = 0x100,
390                 ARG_NO_PAGER,
391                 ARG_ADJUST_SYSTEM_CLOCK,
392                 ARG_NO_ASK_PASSWORD
393         };
394
395         static const struct option options[] = {
396                 { "help",                no_argument,       NULL, 'h'                     },
397                 { "version",             no_argument,       NULL, ARG_VERSION             },
398                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
399                 { "host",                required_argument, NULL, 'H'                     },
400                 { "machine",             required_argument, NULL, 'M'                     },
401                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
402                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
403                 {}
404         };
405
406         int c;
407
408         assert(argc >= 0);
409         assert(argv);
410
411         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
412
413                 switch (c) {
414
415                 case 'h':
416                         help();
417                         return 0;
418
419                 case ARG_VERSION:
420                         puts(PACKAGE_STRING);
421                         puts(SYSTEMD_FEATURES);
422                         return 0;
423
424                 case 'H':
425                         arg_transport = BUS_TRANSPORT_REMOTE;
426                         arg_host = optarg;
427                         break;
428
429                 case 'M':
430                         arg_transport = BUS_TRANSPORT_MACHINE;
431                         arg_host = optarg;
432                         break;
433
434                 case ARG_NO_ASK_PASSWORD:
435                         arg_ask_password = false;
436                         break;
437
438                 case ARG_ADJUST_SYSTEM_CLOCK:
439                         arg_adjust_system_clock = true;
440                         break;
441
442                 case ARG_NO_PAGER:
443                         arg_no_pager = true;
444                         break;
445
446                 case '?':
447                         return -EINVAL;
448
449                 default:
450                         assert_not_reached("Unhandled option");
451                 }
452
453         return 1;
454 }
455
456 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
457
458         static const struct {
459                 const char* verb;
460                 const enum {
461                         MORE,
462                         LESS,
463                         EQUAL
464                 } argc_cmp;
465                 const int argc;
466                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
467         } verbs[] = {
468                 { "status",                LESS,   1, show_status      },
469                 { "set-time",              EQUAL,  2, set_time         },
470                 { "set-timezone",          EQUAL,  2, set_timezone     },
471                 { "list-timezones",        EQUAL,  1, list_timezones   },
472                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
473                 { "set-ntp",               EQUAL,  2, set_ntp,         },
474         };
475
476         int left;
477         unsigned i;
478
479         assert(argc >= 0);
480         assert(argv);
481
482         left = argc - optind;
483
484         if (left <= 0)
485                 /* Special rule: no arguments means "status" */
486                 i = 0;
487         else {
488                 if (streq(argv[optind], "help")) {
489                         help();
490                         return 0;
491                 }
492
493                 for (i = 0; i < ELEMENTSOF(verbs); i++)
494                         if (streq(argv[optind], verbs[i].verb))
495                                 break;
496
497                 if (i >= ELEMENTSOF(verbs)) {
498                         log_error("Unknown operation %s", argv[optind]);
499                         return -EINVAL;
500                 }
501         }
502
503         switch (verbs[i].argc_cmp) {
504
505         case EQUAL:
506                 if (left != verbs[i].argc) {
507                         log_error("Invalid number of arguments.");
508                         return -EINVAL;
509                 }
510
511                 break;
512
513         case MORE:
514                 if (left < verbs[i].argc) {
515                         log_error("Too few arguments.");
516                         return -EINVAL;
517                 }
518
519                 break;
520
521         case LESS:
522                 if (left > verbs[i].argc) {
523                         log_error("Too many arguments.");
524                         return -EINVAL;
525                 }
526
527                 break;
528
529         default:
530                 assert_not_reached("Unknown comparison operator.");
531         }
532
533         return verbs[i].dispatch(bus, argv + optind, left);
534 }
535
536 int main(int argc, char *argv[]) {
537         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
538         int r;
539
540         setlocale(LC_ALL, "");
541         log_parse_environment();
542         log_open();
543
544         r = parse_argv(argc, argv);
545         if (r <= 0)
546                 goto finish;
547
548         r = bus_open_transport(arg_transport, arg_host, false, &bus);
549         if (r < 0) {
550                 log_error_errno(r, "Failed to create bus connection: %m");
551                 goto finish;
552         }
553
554         r = timedatectl_main(bus, argc, argv);
555
556 finish:
557         pager_close();
558
559         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
560 }