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