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