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