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