]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedatectl.c
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
[thirdparty/systemd.git] / src / timedate / timedatectl.c
CommitLineData
6d0274f1
LP
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
2f6a5907 7 Copyright 2013 Kay Sievers
6d0274f1
LP
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
6d0274f1 23#include <getopt.h>
a9cdc94f 24#include <locale.h>
3f6fd1ba
LP
25#include <stdbool.h>
26#include <stdlib.h>
6d0274f1 27
a281d9c7 28#include "sd-bus.h"
3f6fd1ba 29
a281d9c7 30#include "bus-error.h"
3f6fd1ba
LP
31#include "bus-util.h"
32#include "pager.h"
6bedfcbb 33#include "parse-util.h"
6d0274f1 34#include "spawn-polkit-agent.h"
6d0274f1 35#include "strv.h"
288a74cc 36#include "terminal-util.h"
3f6fd1ba 37#include "util.h"
6d0274f1 38
6d0274f1 39static bool arg_no_pager = false;
6d0274f1 40static bool arg_ask_password = true;
e1636421 41static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
7085053a 42static char *arg_host = NULL;
e1636421 43static bool arg_adjust_system_clock = false;
6d0274f1
LP
44
45static void pager_open_if_enabled(void) {
46
47 if (arg_no_pager)
48 return;
49
1b12a7b5 50 pager_open(false);
6d0274f1
LP
51}
52
53static void polkit_agent_open_if_enabled(void) {
54
55 /* Open the polkit agent as a child process if necessary */
6d0274f1
LP
56 if (!arg_ask_password)
57 return;
58
46e65dcc
LP
59 if (arg_transport != BUS_TRANSPORT_LOCAL)
60 return;
61
6d0274f1
LP
62 polkit_agent_open();
63}
64
65typedef struct StatusInfo {
2f6a5907 66 usec_t time;
ffc06c35 67 char *timezone;
2f6a5907
KS
68
69 usec_t rtc_time;
70 bool rtc_local;
71
72 bool ntp_enabled;
73 bool ntp_capable;
74 bool ntp_synced;
6d0274f1
LP
75} StatusInfo;
76
e7e55dbd
DH
77static void status_info_clear(StatusInfo *info) {
78 if (info) {
79 free(info->timezone);
80 zero(*info);
81 }
82}
83
ffc06c35 84static void print_status_info(const StatusInfo *i) {
f18ca9dc 85 char a[FORMAT_TIMESTAMP_MAX];
6d0274f1
LP
86 struct tm tm;
87 time_t sec;
9ff09bcb 88 bool have_time = false;
d95a74ed 89 const char *old_tz = NULL, *tz;
6d0274f1
LP
90 int r;
91
59965986
LP
92 assert(i);
93
d95a74ed
LP
94 /* Save the old $TZ */
95 tz = getenv("TZ");
96 if (tz)
97 old_tz = strdupa(tz);
2311eb2f 98
d95a74ed 99 /* Set the new $TZ */
bdeb9e60 100 if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0)
d95a74ed
LP
101 log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m");
102 else
103 tzset();
3e5e74d5 104
9ff09bcb
SL
105 if (i->time != 0) {
106 sec = (time_t) (i->time / USEC_PER_SEC);
107 have_time = true;
d95a74ed 108 } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) {
9ff09bcb
SL
109 sec = time(NULL);
110 have_time = true;
111 } else
d95a74ed 112 log_warning("Could not get time from timedated and not operating locally, ignoring.");
6d0274f1 113
9ff09bcb 114 if (have_time) {
5ffa8c81
ZJS
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);
9ff09bcb
SL
120 } else {
121 printf(" Local time: %s\n", "n/a");
122 printf(" Universal time: %s\n", "n/a");
123 }
6d0274f1 124
2f6a5907
KS
125 if (i->rtc_time > 0) {
126 time_t rtc_sec;
6d0274f1 127
d95a74ed 128 rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC);
5ffa8c81
ZJS
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);
2f6a5907 131 } else
9ff09bcb 132 printf(" RTC time: %s\n", "n/a");
6d0274f1 133
5ffa8c81
ZJS
134 if (have_time)
135 xstrftime(a, "%Z, %z", localtime_r(&sec, &tm));
2667cc25 136
d95a74ed
LP
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
5ffa8c81 147 printf(" Time zone: %s (%.*s)\n"
b90930c7 148 " Network time on: %s\n"
6d0274f1
LP
149 "NTP synchronized: %s\n"
150 " RTC in local TZ: %s\n",
5ffa8c81 151 strna(i->timezone), (int) sizeof(a), have_time ? a : "n/a",
2f6a5907
KS
152 i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
153 yes_no(i->ntp_synced),
154 yes_no(i->rtc_local));
6d0274f1 155
2f6a5907 156 if (i->rtc_local)
1fc464f6 157 fputs("\n" ANSI_HIGHLIGHT
ab59f412
VM
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"
1fc464f6 163 " 'timedatectl set-local-rtc 0'." ANSI_NORMAL "\n", stdout);
6d0274f1
LP
164}
165
a281d9c7 166static int show_status(sd_bus *bus, char **args, unsigned n) {
e7e55dbd 167 _cleanup_(status_info_clear) StatusInfo info = {};
9f6eb1cd
KS
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) },
ffc06c35
KS
176 {}
177 };
178 int r;
6d0274f1 179
a281d9c7 180 assert(bus);
6d0274f1 181
ffc06c35
KS
182 r = bus_map_all_properties(bus,
183 "org.freedesktop.timedate1",
184 "/org/freedesktop/timedate1",
9f6eb1cd
KS
185 map,
186 &info);
e7e55dbd
DH
187 if (r < 0)
188 return log_error_errno(r, "Failed to query server: %m");
6d0274f1
LP
189
190 print_status_info(&info);
ffc06c35 191
ffc06c35 192 return r;
6d0274f1
LP
193}
194
a281d9c7 195static int set_time(sd_bus *bus, char **args, unsigned n) {
4afd3348 196 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
a281d9c7 197 bool relative = false, interactive = arg_ask_password;
6d0274f1 198 usec_t t;
6d0274f1
LP
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
a281d9c7
TA
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;
6d0274f1
LP
224}
225
a281d9c7 226static int set_timezone(sd_bus *bus, char **args, unsigned n) {
4afd3348 227 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
a281d9c7 228 int r;
6d0274f1
LP
229
230 assert(args);
231 assert(n == 2);
232
233 polkit_agent_open_if_enabled();
234
a281d9c7
TA
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,
e5609878 242 "sb", args[1], arg_ask_password);
a281d9c7 243 if (r < 0)
07a062a7 244 log_error("Failed to set time zone: %s", bus_error_message(&error, -r));
a281d9c7
TA
245
246 return r;
6d0274f1
LP
247}
248
a281d9c7 249static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
4afd3348 250 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
e5609878 251 int r, b;
6d0274f1
LP
252
253 assert(args);
254 assert(n == 2);
255
256 polkit_agent_open_if_enabled();
257
e5609878
LP
258 b = parse_boolean(args[1]);
259 if (b < 0) {
6d0274f1 260 log_error("Failed to parse local RTC setting: %s", args[1]);
e5609878 261 return b;
6d0274f1
LP
262 }
263
a281d9c7
TA
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,
e5609878 271 "bbb", b, arg_adjust_system_clock, arg_ask_password);
a281d9c7
TA
272 if (r < 0)
273 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
274
275 return r;
6d0274f1
LP
276}
277
a281d9c7 278static int set_ntp(sd_bus *bus, char **args, unsigned n) {
4afd3348 279 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
e5609878 280 int b, r;
6d0274f1
LP
281
282 assert(args);
283 assert(n == 2);
284
285 polkit_agent_open_if_enabled();
286
e5609878
LP
287 b = parse_boolean(args[1]);
288 if (b < 0) {
6d0274f1 289 log_error("Failed to parse NTP setting: %s", args[1]);
e5609878 290 return b;
6d0274f1
LP
291 }
292
a281d9c7
TA
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,
e5609878 300 "bb", b, arg_ask_password);
a281d9c7
TA
301 if (r < 0)
302 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
303
304 return r;
6d0274f1
LP
305}
306
a281d9c7 307static int list_timezones(sd_bus *bus, char **args, unsigned n) {
6d0274f1 308 _cleanup_strv_free_ char **zones = NULL;
75683450 309 int r;
6d0274f1
LP
310
311 assert(args);
312 assert(n == 1);
313
75683450 314 r = get_timezones(&zones);
f647962d
MS
315 if (r < 0)
316 return log_error_errno(r, "Failed to read list of time zones: %m");
6d0274f1 317
6d0274f1 318 pager_open_if_enabled();
7c2d8094 319 strv_print(zones);
6d0274f1
LP
320
321 return 0;
322}
323
601185b4 324static void help(void) {
7591abd4
LP
325 printf("%s [OPTIONS...] COMMAND ...\n\n"
326 "Query or change system time and date settings.\n\n"
07a062a7 327 " -h --help Show this help message\n"
4f8f66cb
ZJS
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"
6d0274f1 334 "Commands:\n"
4f8f66cb
ZJS
335 " status Show current time settings\n"
336 " set-time TIME Set system time\n"
07a062a7
JSJ
337 " set-timezone ZONE Set system time zone\n"
338 " list-timezones Show known time zones\n"
4f8f66cb 339 " set-local-rtc BOOL Control whether RTC is in local time\n"
3906ab4a 340 " set-ntp BOOL Enable or disable network time synchronization\n",
6d0274f1 341 program_invocation_short_name);
6d0274f1
LP
342}
343
344static int parse_argv(int argc, char *argv[]) {
345
346 enum {
347 ARG_VERSION = 0x100,
348 ARG_NO_PAGER,
c9783430 349 ARG_ADJUST_SYSTEM_CLOCK,
6d0274f1
LP
350 ARG_NO_ASK_PASSWORD
351 };
352
353 static const struct option options[] = {
c9783430
LP
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' },
a281d9c7 358 { "machine", required_argument, NULL, 'M' },
c9783430
LP
359 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
360 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
eb9da376 361 {}
6d0274f1
LP
362 };
363
364 int c;
365
366 assert(argc >= 0);
367 assert(argv);
368
601185b4 369 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
6d0274f1
LP
370
371 switch (c) {
372
373 case 'h':
601185b4
ZJS
374 help();
375 return 0;
6d0274f1
LP
376
377 case ARG_VERSION:
3f6fd1ba 378 return version();
6d0274f1 379
a281d9c7
TA
380 case 'H':
381 arg_transport = BUS_TRANSPORT_REMOTE;
382 arg_host = optarg;
6d0274f1
LP
383 break;
384
a281d9c7 385 case 'M':
de33fc62 386 arg_transport = BUS_TRANSPORT_MACHINE;
a281d9c7 387 arg_host = optarg;
6d0274f1
LP
388 break;
389
546158bc
JJ
390 case ARG_NO_ASK_PASSWORD:
391 arg_ask_password = false;
392 break;
393
c9783430
LP
394 case ARG_ADJUST_SYSTEM_CLOCK:
395 arg_adjust_system_clock = true;
6d0274f1
LP
396 break;
397
398 case ARG_NO_PAGER:
399 arg_no_pager = true;
400 break;
401
402 case '?':
403 return -EINVAL;
404
405 default:
eb9da376 406 assert_not_reached("Unhandled option");
6d0274f1 407 }
6d0274f1
LP
408
409 return 1;
410}
411
a281d9c7 412static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
6d0274f1
LP
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;
a281d9c7 422 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
6d0274f1
LP
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);
6d0274f1
LP
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
6d0274f1
LP
489 return verbs[i].dispatch(bus, argv + optind, left);
490}
491
492int main(int argc, char *argv[]) {
4afd3348 493 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
84f6181c 494 int r;
6d0274f1 495
a9cdc94f 496 setlocale(LC_ALL, "");
6d0274f1
LP
497 log_parse_environment();
498 log_open();
499
500 r = parse_argv(argc, argv);
84f6181c 501 if (r <= 0)
6d0274f1 502 goto finish;
6d0274f1 503
266f3e26 504 r = bus_connect_transport(arg_transport, arg_host, false, &bus);
a281d9c7 505 if (r < 0) {
da927ba9 506 log_error_errno(r, "Failed to create bus connection: %m");
a281d9c7 507 goto finish;
6d0274f1
LP
508 }
509
a281d9c7 510 r = timedatectl_main(bus, argc, argv);
6d0274f1 511
a281d9c7 512finish:
6d0274f1
LP
513 pager_close();
514
84f6181c 515 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
6d0274f1 516}