chiark / gitweb /
treewide: use log_*_errno whenever %m is in the format string
[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_errno(r, "Failed to query server: %m");
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                 return log_error_errno(r, "Failed to read list of time zones: %m");
368
369         pager_open_if_enabled();
370         strv_print(zones);
371
372         return 0;
373 }
374
375 static void help(void) {
376         printf("%s [OPTIONS...] COMMAND ...\n\n"
377                "Query or change system time and date settings.\n\n"
378                "  -h --help                Show this help message\n"
379                "     --version             Show package version\n"
380                "     --no-pager            Do not pipe output into a pager\n"
381                "     --no-ask-password     Do not prompt for password\n"
382                "  -H --host=[USER@]HOST    Operate on remote host\n"
383                "  -M --machine=CONTAINER   Operate on local container\n"
384                "     --adjust-system-clock Adjust system clock when changing local RTC mode\n\n"
385                "Commands:\n"
386                "  status                   Show current time settings\n"
387                "  set-time TIME            Set system time\n"
388                "  set-timezone ZONE        Set system time zone\n"
389                "  list-timezones           Show known time zones\n"
390                "  set-local-rtc BOOL       Control whether RTC is in local time\n"
391                "  set-ntp BOOL             Control whether NTP is enabled\n",
392                program_invocation_short_name);
393 }
394
395 static int parse_argv(int argc, char *argv[]) {
396
397         enum {
398                 ARG_VERSION = 0x100,
399                 ARG_NO_PAGER,
400                 ARG_ADJUST_SYSTEM_CLOCK,
401                 ARG_NO_ASK_PASSWORD
402         };
403
404         static const struct option options[] = {
405                 { "help",                no_argument,       NULL, 'h'                     },
406                 { "version",             no_argument,       NULL, ARG_VERSION             },
407                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
408                 { "host",                required_argument, NULL, 'H'                     },
409                 { "machine",             required_argument, NULL, 'M'                     },
410                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
411                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
412                 {}
413         };
414
415         int c;
416
417         assert(argc >= 0);
418         assert(argv);
419
420         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
421
422                 switch (c) {
423
424                 case 'h':
425                         help();
426                         return 0;
427
428                 case ARG_VERSION:
429                         puts(PACKAGE_STRING);
430                         puts(SYSTEMD_FEATURES);
431                         return 0;
432
433                 case 'H':
434                         arg_transport = BUS_TRANSPORT_REMOTE;
435                         arg_host = optarg;
436                         break;
437
438                 case 'M':
439                         arg_transport = BUS_TRANSPORT_CONTAINER;
440                         arg_host = optarg;
441                         break;
442
443                 case ARG_NO_ASK_PASSWORD:
444                         arg_ask_password = false;
445                         break;
446
447                 case ARG_ADJUST_SYSTEM_CLOCK:
448                         arg_adjust_system_clock = true;
449                         break;
450
451                 case ARG_NO_PAGER:
452                         arg_no_pager = true;
453                         break;
454
455                 case '?':
456                         return -EINVAL;
457
458                 default:
459                         assert_not_reached("Unhandled option");
460                 }
461
462         return 1;
463 }
464
465 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
466
467         static const struct {
468                 const char* verb;
469                 const enum {
470                         MORE,
471                         LESS,
472                         EQUAL
473                 } argc_cmp;
474                 const int argc;
475                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
476         } verbs[] = {
477                 { "status",                LESS,   1, show_status      },
478                 { "set-time",              EQUAL,  2, set_time         },
479                 { "set-timezone",          EQUAL,  2, set_timezone     },
480                 { "list-timezones",        EQUAL,  1, list_timezones   },
481                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
482                 { "set-ntp",               EQUAL,  2, set_ntp,         },
483         };
484
485         int left;
486         unsigned i;
487
488         assert(argc >= 0);
489         assert(argv);
490
491         left = argc - optind;
492
493         if (left <= 0)
494                 /* Special rule: no arguments means "status" */
495                 i = 0;
496         else {
497                 if (streq(argv[optind], "help")) {
498                         help();
499                         return 0;
500                 }
501
502                 for (i = 0; i < ELEMENTSOF(verbs); i++)
503                         if (streq(argv[optind], verbs[i].verb))
504                                 break;
505
506                 if (i >= ELEMENTSOF(verbs)) {
507                         log_error("Unknown operation %s", argv[optind]);
508                         return -EINVAL;
509                 }
510         }
511
512         switch (verbs[i].argc_cmp) {
513
514         case EQUAL:
515                 if (left != verbs[i].argc) {
516                         log_error("Invalid number of arguments.");
517                         return -EINVAL;
518                 }
519
520                 break;
521
522         case MORE:
523                 if (left < verbs[i].argc) {
524                         log_error("Too few arguments.");
525                         return -EINVAL;
526                 }
527
528                 break;
529
530         case LESS:
531                 if (left > verbs[i].argc) {
532                         log_error("Too many arguments.");
533                         return -EINVAL;
534                 }
535
536                 break;
537
538         default:
539                 assert_not_reached("Unknown comparison operator.");
540         }
541
542         return verbs[i].dispatch(bus, argv + optind, left);
543 }
544
545 int main(int argc, char *argv[]) {
546         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
547         int r;
548
549         setlocale(LC_ALL, "");
550         log_parse_environment();
551         log_open();
552
553         r = parse_argv(argc, argv);
554         if (r <= 0)
555                 goto finish;
556
557         r = bus_open_transport(arg_transport, arg_host, false, &bus);
558         if (r < 0) {
559                 log_error_errno(r, "Failed to create bus connection: %m");
560                 goto finish;
561         }
562
563         r = timedatectl_main(bus, argc, argv);
564
565 finish:
566         pager_close();
567
568         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
569 }