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