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