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