]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedatectl.c
hostnamed: allow UTF8 chars in pretty hostname again
[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
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <stdlib.h>
23#include <stdbool.h>
24#include <unistd.h>
25#include <getopt.h>
26#include <string.h>
27#include <sys/timex.h>
28
29#include "dbus-common.h"
30#include "util.h"
31#include "spawn-polkit-agent.h"
32#include "build.h"
33#include "hwclock.h"
34#include "strv.h"
35#include "pager.h"
36
37static bool arg_fix_system = false;
38static bool arg_no_pager = false;
39static enum transport {
40 TRANSPORT_NORMAL,
41 TRANSPORT_SSH,
42 TRANSPORT_POLKIT
43} arg_transport = TRANSPORT_NORMAL;
44static bool arg_ask_password = true;
45static const char *arg_host = NULL;
46
47static void pager_open_if_enabled(void) {
48
49 if (arg_no_pager)
50 return;
51
52 pager_open();
53}
54
55static void polkit_agent_open_if_enabled(void) {
56
57 /* Open the polkit agent as a child process if necessary */
58
59 if (!arg_ask_password)
60 return;
61
62 polkit_agent_open();
63}
64
65typedef struct StatusInfo {
66 const char *timezone;
67 bool local_rtc;
68 bool ntp;
69} StatusInfo;
70
71static bool ntp_synced(void) {
72
73 struct timex txc;
74
75 zero(txc);
76
77 if (adjtimex(&txc) < 0)
78 return false;
79
80 if (txc.status & STA_UNSYNC)
81 return false;
82
83 return true;
84}
85
86static void print_status_info(StatusInfo *i) {
87 usec_t n;
88 char b[FORMAT_TIMESTAMP_MAX];
89 struct tm tm;
90 time_t sec;
91 int r;
92
93 n = now(CLOCK_REALTIME);
94 sec = (time_t) (n / USEC_PER_SEC);
95
96 zero(tm);
97 assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
98 char_array_0(b);
99 printf(" Local time: %s\n", b);
100
101 zero(tm);
102 assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
103 char_array_0(b);
104 printf(" Universal time: %s\n", b);
105
106 zero(tm);
107 r = hwclock_get_time(&tm);
108 if (r >= 0) {
109 /* Calculcate the week-day */
110 mktime(&tm);
111
112 assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S", &tm) > 0);
113 char_array_0(b);
114 printf(" RTC time: %s\n", b);
115 }
116
117 printf(" Timezone: %s\n"
118 " NTP enabled: %s\n"
119 "NTP synchronized: %s\n"
120 " RTC in local TZ: %s\n",
121 strna(i->timezone),
122 yes_no(i->ntp),
123 yes_no(ntp_synced()),
124 yes_no(i->local_rtc));
125
126 if (i->local_rtc)
127 fputs("\n" ANSI_HIGHLIGHT_ON
128 "Warning: The RTC is configured to maintain time in the local time zone. This\n"
129 " mode is not fully supported and will create various problems with time\n"
130 " zone changes and daylight saving adjustments. If at all possible use\n"
131 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
132}
133
134static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
135 assert(name);
136 assert(iter);
137
138 switch (dbus_message_iter_get_arg_type(iter)) {
139
140 case DBUS_TYPE_STRING: {
141 const char *s;
142
143 dbus_message_iter_get_basic(iter, &s);
144 if (!isempty(s)) {
145 if (streq(name, "Timezone"))
146 i->timezone = s;
147 }
148 break;
149 }
150
151 case DBUS_TYPE_BOOLEAN: {
152 dbus_bool_t b;
153
154 dbus_message_iter_get_basic(iter, &b);
155 if (streq(name, "LocalRTC"))
156 i->local_rtc = b;
157 else if (streq(name, "NTP"))
158 i->ntp = b;
159 }
160 }
161
162 return 0;
163}
164
165static int show_status(DBusConnection *bus, char **args, unsigned n) {
166 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
167 const char *interface = "";
168 int r;
169 DBusMessageIter iter, sub, sub2, sub3;
170 StatusInfo info;
171
172 assert(args);
173
174 r = bus_method_call_with_reply(
175 bus,
176 "org.freedesktop.timedate1",
177 "/org/freedesktop/timedate1",
178 "org.freedesktop.DBus.Properties",
179 "GetAll",
180 &reply,
181 NULL,
182 DBUS_TYPE_STRING, &interface,
183 DBUS_TYPE_INVALID);
184 if (r < 0)
185 return r;
186
187 if (!dbus_message_iter_init(reply, &iter) ||
188 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
189 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
190 log_error("Failed to parse reply.");
191 return -EIO;
192 }
193
194 dbus_message_iter_recurse(&iter, &sub);
195
196 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
197 const char *name;
198
199 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
200 log_error("Failed to parse reply.");
201 return -EIO;
202 }
203
204 dbus_message_iter_recurse(&sub, &sub2);
205
206 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
207 log_error("Failed to parse reply.");
208 return -EIO;
209 }
210
211 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
212 log_error("Failed to parse reply.");
213 return -EIO;
214 }
215
216 dbus_message_iter_recurse(&sub2, &sub3);
217
218 r = status_property(name, &sub3, &info);
219 if (r < 0) {
220 log_error("Failed to parse reply.");
221 return r;
222 }
223
224 dbus_message_iter_next(&sub);
225 }
226
227 print_status_info(&info);
228 return 0;
229}
230
231static int set_time(DBusConnection *bus, char **args, unsigned n) {
232 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
233 dbus_bool_t relative = false, interactive = true;
234 usec_t t;
235 dbus_int64_t u;
236 int r;
237
238 assert(args);
239 assert(n == 2);
240
241 polkit_agent_open_if_enabled();
242
243 r = parse_timestamp(args[1], &t);
244 if (r < 0) {
245 log_error("Failed to parse time specification: %s", args[1]);
246 return r;
247 }
248
249 u = (dbus_uint64_t) t;
250
251 return bus_method_call_with_reply(
252 bus,
253 "org.freedesktop.timedate1",
254 "/org/freedesktop/timedate1",
255 "org.freedesktop.timedate1",
256 "SetTime",
257 &reply,
258 NULL,
259 DBUS_TYPE_INT64, &u,
260 DBUS_TYPE_BOOLEAN, &relative,
261 DBUS_TYPE_BOOLEAN, &interactive,
262 DBUS_TYPE_INVALID);
263}
264
265static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
266 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
267 dbus_bool_t interactive = true;
268
269 assert(args);
270 assert(n == 2);
271
272 polkit_agent_open_if_enabled();
273
274 return bus_method_call_with_reply(
275 bus,
276 "org.freedesktop.timedate1",
277 "/org/freedesktop/timedate1",
278 "org.freedesktop.timedate1",
279 "SetTimezone",
280 &reply,
281 NULL,
282 DBUS_TYPE_STRING, &args[1],
283 DBUS_TYPE_BOOLEAN, &interactive,
284 DBUS_TYPE_INVALID);
285}
286
287static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
288 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
289 dbus_bool_t interactive = true, b, q;
290 int r;
291
292 assert(args);
293 assert(n == 2);
294
295 polkit_agent_open_if_enabled();
296
297 r = parse_boolean(args[1]);
298 if (r < 0) {
299 log_error("Failed to parse local RTC setting: %s", args[1]);
300 return r;
301 }
302
303 b = r;
304 q = arg_fix_system;
305
306 return bus_method_call_with_reply(
307 bus,
308 "org.freedesktop.timedate1",
309 "/org/freedesktop/timedate1",
310 "org.freedesktop.timedate1",
311 "SetLocalRTC",
312 &reply,
313 NULL,
314 DBUS_TYPE_BOOLEAN, &b,
315 DBUS_TYPE_BOOLEAN, &q,
316 DBUS_TYPE_BOOLEAN, &interactive,
317 DBUS_TYPE_INVALID);
318}
319
320static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
321 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
322 dbus_bool_t interactive = true, b;
323 int r;
324
325 assert(args);
326 assert(n == 2);
327
328 polkit_agent_open_if_enabled();
329
330 r = parse_boolean(args[1]);
331 if (r < 0) {
332 log_error("Failed to parse NTP setting: %s", args[1]);
333 return r;
334 }
335
336 b = r;
337
338 return bus_method_call_with_reply(
339 bus,
340 "org.freedesktop.timedate1",
341 "/org/freedesktop/timedate1",
342 "org.freedesktop.timedate1",
343 "SetNTP",
344 &reply,
345 NULL,
346 DBUS_TYPE_BOOLEAN, &b,
347 DBUS_TYPE_BOOLEAN, &interactive,
348 DBUS_TYPE_INVALID);
349}
350
351static int zone_compare(const void *_a, const void *_b) {
352 const char **a = (const char**) _a, **b = (const char**) _b;
353
354 return strcmp(*a, *b);
355}
356
357static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
358 _cleanup_fclose_ FILE *f = NULL;
359 _cleanup_strv_free_ char **zones = NULL;
360 size_t n_zones;
361 char **i;
362
363 assert(args);
364 assert(n == 1);
365
366 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
367 if (!f) {
368 log_error("Failed to open timezone database: %m");
369 return -errno;
370 }
371
372 for (;;) {
373 char l[LINE_MAX], *p, **z, *w;
374 size_t k;
375
376 if (!fgets(l, sizeof(l), f)) {
377 if (feof(f))
378 break;
379
380 log_error("Failed to read timezone database: %m");
381 return -errno;
382 }
383
384 p = strstrip(l);
385
386 if (isempty(p) || *p == '#')
387 continue;
388
389
390 /* Skip over country code */
391 p += strcspn(p, WHITESPACE);
392 p += strspn(p, WHITESPACE);
393
394 /* Skip over coordinates */
395 p += strcspn(p, WHITESPACE);
396 p += strspn(p, WHITESPACE);
397
398 /* Found timezone name */
399 k = strcspn(p, WHITESPACE);
400 if (k <= 0)
401 continue;
402
403 w = strndup(p, k);
404 if (!w)
405 return log_oom();
406
407 z = realloc(zones, sizeof(char*) * (n_zones + 2));
408 if (!z) {
409 free(w);
410 return log_oom();
411 }
412
413 zones = z;
414 zones[n_zones++] = w;
415 }
416
417 if (zones)
418 zones[n_zones] = 0;
419
420 qsort(zones, n_zones, sizeof(char*), zone_compare);
421
422 pager_open_if_enabled();
423
424 STRV_FOREACH(i, zones)
425 puts(*i);
426
427 return 0;
428}
429
430static int help(void) {
431
432 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
433 "Query or control system time and date settings.\n\n"
434 " -h --help Show this help\n"
435 " --version Show package version\n"
436 " --fix-system Adjust system clock when changing local RTC mode\n"
437 " --no-pager Do not pipe output into a pager\n"
438 " --no-ask-password Do not prompt for password\n"
439 " -H --host=[USER@]HOST Operate on remote host\n\n"
440 "Commands:\n"
441 " status Show current time settings\n"
442 " set-time [TIME] Set system time\n"
443 " set-timezone [ZONE] Set system timezone\n"
444 " list-timezones Show known timezones\n"
445 " set-local-rtc [BOOL] Control whether RTC is in local time\n"
446 " set-ntp [BOOL] Control whether NTP is enabled\n",
447 program_invocation_short_name);
448
449 return 0;
450}
451
452static int parse_argv(int argc, char *argv[]) {
453
454 enum {
455 ARG_VERSION = 0x100,
456 ARG_NO_PAGER,
457 ARG_FIX_SYSTEM,
458 ARG_NO_ASK_PASSWORD
459 };
460
461 static const struct option options[] = {
462 { "help", no_argument, NULL, 'h' },
463 { "version", no_argument, NULL, ARG_VERSION },
464 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
465 { "host", required_argument, NULL, 'H' },
466 { "privileged", no_argument, NULL, 'P' },
467 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
468 { "fix-system", no_argument, NULL, ARG_FIX_SYSTEM },
469 { NULL, 0, NULL, 0 }
470 };
471
472 int c;
473
474 assert(argc >= 0);
475 assert(argv);
476
477 while ((c = getopt_long(argc, argv, "+hp:as:H:P", options, NULL)) >= 0) {
478
479 switch (c) {
480
481 case 'h':
482 help();
483 return 0;
484
485 case ARG_VERSION:
486 puts(PACKAGE_STRING);
487 puts(DISTRIBUTION);
488 puts(SYSTEMD_FEATURES);
489 return 0;
490
491 case 'P':
492 arg_transport = TRANSPORT_POLKIT;
493 break;
494
495 case 'H':
496 arg_transport = TRANSPORT_SSH;
497 arg_host = optarg;
498 break;
499
500 case ARG_FIX_SYSTEM:
501 arg_fix_system = true;
502 break;
503
504 case ARG_NO_PAGER:
505 arg_no_pager = true;
506 break;
507
508 case '?':
509 return -EINVAL;
510
511 default:
512 log_error("Unknown option code %c", c);
513 return -EINVAL;
514 }
515 }
516
517 return 1;
518}
519
520static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
521
522 static const struct {
523 const char* verb;
524 const enum {
525 MORE,
526 LESS,
527 EQUAL
528 } argc_cmp;
529 const int argc;
530 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
531 } verbs[] = {
532 { "status", LESS, 1, show_status },
533 { "set-time", EQUAL, 2, set_time },
534 { "set-timezone", EQUAL, 2, set_timezone },
535 { "list-timezones", EQUAL, 1, list_timezones },
536 { "set-local-rtc", EQUAL, 2, set_local_rtc },
537 { "set-ntp", EQUAL, 2, set_ntp, },
538 };
539
540 int left;
541 unsigned i;
542
543 assert(argc >= 0);
544 assert(argv);
545 assert(error);
546
547 left = argc - optind;
548
549 if (left <= 0)
550 /* Special rule: no arguments means "status" */
551 i = 0;
552 else {
553 if (streq(argv[optind], "help")) {
554 help();
555 return 0;
556 }
557
558 for (i = 0; i < ELEMENTSOF(verbs); i++)
559 if (streq(argv[optind], verbs[i].verb))
560 break;
561
562 if (i >= ELEMENTSOF(verbs)) {
563 log_error("Unknown operation %s", argv[optind]);
564 return -EINVAL;
565 }
566 }
567
568 switch (verbs[i].argc_cmp) {
569
570 case EQUAL:
571 if (left != verbs[i].argc) {
572 log_error("Invalid number of arguments.");
573 return -EINVAL;
574 }
575
576 break;
577
578 case MORE:
579 if (left < verbs[i].argc) {
580 log_error("Too few arguments.");
581 return -EINVAL;
582 }
583
584 break;
585
586 case LESS:
587 if (left > verbs[i].argc) {
588 log_error("Too many arguments.");
589 return -EINVAL;
590 }
591
592 break;
593
594 default:
595 assert_not_reached("Unknown comparison operator.");
596 }
597
598 if (!bus) {
599 log_error("Failed to get D-Bus connection: %s", error->message);
600 return -EIO;
601 }
602
603 return verbs[i].dispatch(bus, argv + optind, left);
604}
605
606int main(int argc, char *argv[]) {
607 int r, retval = EXIT_FAILURE;
608 DBusConnection *bus = NULL;
609 DBusError error;
610
611 dbus_error_init(&error);
612
613 log_parse_environment();
614 log_open();
615
616 r = parse_argv(argc, argv);
617 if (r < 0)
618 goto finish;
619 else if (r == 0) {
620 retval = EXIT_SUCCESS;
621 goto finish;
622 }
623
624 if (arg_transport == TRANSPORT_NORMAL)
625 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
626 else if (arg_transport == TRANSPORT_POLKIT)
627 bus_connect_system_polkit(&bus, &error);
628 else if (arg_transport == TRANSPORT_SSH)
629 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
630 else
631 assert_not_reached("Uh, invalid transport...");
632
633 r = timedatectl_main(bus, argc, argv, &error);
634 retval = r < 0 ? EXIT_FAILURE : r;
635
636finish:
637 if (bus) {
638 dbus_connection_flush(bus);
639 dbus_connection_close(bus);
640 dbus_connection_unref(bus);
641 }
642
643 dbus_error_free(&error);
644 dbus_shutdown();
645
646 pager_close();
647
648 return retval;
649}