chiark / gitweb /
6a1c19a5748598df26f49949047028b9735265e9
[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();
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 } StatusInfo;
72
73 static bool ntp_synced(void) {
74         struct timex txc;
75
76         zero(txc);
77         if (adjtimex(&txc) < 0)
78                 return false;
79
80         if (txc.status & STA_UNSYNC)
81                 return false;
82
83         return true;
84 }
85
86 static const char *jump_str(int delta_minutes, char *s, size_t size) {
87         if (delta_minutes == 60)
88                 return "one hour forward";
89         if (delta_minutes == -60)
90                 return "one hour backwards";
91         if (delta_minutes < 0) {
92                 snprintf(s, size, "%i minutes backwards", -delta_minutes);
93                 return s;
94         }
95         if (delta_minutes > 0) {
96                 snprintf(s, size, "%i minutes forward", delta_minutes);
97                 return s;
98         }
99         return "";
100 }
101
102 static void print_status_info(StatusInfo *i) {
103         usec_t n;
104         char a[FORMAT_TIMESTAMP_MAX];
105         char b[FORMAT_TIMESTAMP_MAX];
106         char s[32];
107         struct tm tm;
108         time_t sec;
109         char *zc, *zn;
110         time_t t, tc, tn;
111         int dn;
112         bool is_dstc, is_dstn;
113         int r;
114
115         assert(i);
116
117         /* enforce the values of /etc/localtime */
118         if (getenv("TZ")) {
119                 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
120                 unsetenv("TZ");
121         }
122
123         n = now(CLOCK_REALTIME);
124         sec = (time_t) (n / USEC_PER_SEC);
125
126         zero(tm);
127         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
128         char_array_0(a);
129         printf("      Local time: %s\n", a);
130
131         zero(tm);
132         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
133         char_array_0(a);
134         printf("  Universal time: %s\n", a);
135
136         zero(tm);
137         r = hwclock_get_time(&tm);
138         if (r >= 0) {
139                 /* Calculcate the week-day */
140                 mktime(&tm);
141
142                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
143                 char_array_0(a);
144                 printf("        RTC time: %s\n", a);
145         }
146
147         zero(tm);
148         assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
149         char_array_0(a);
150         printf("        Timezone: %s (%s)\n"
151                "     NTP enabled: %s\n"
152                "NTP synchronized: %s\n"
153                " RTC in local TZ: %s\n",
154                strna(i->timezone),
155                a,
156                yes_no(i->ntp),
157                yes_no(ntp_synced()),
158                yes_no(i->local_rtc));
159
160         r = time_get_dst(sec, "/etc/localtime",
161                          &tc, &zc, &is_dstc,
162                          &tn, &dn, &zn, &is_dstn);
163         if (r < 0)
164                 printf("      DST active: n/a\n");
165         else {
166                 printf("      DST active: %s\n", yes_no(is_dstc));
167
168                 t = tc - 1;
169                 zero(tm);
170                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
171                 char_array_0(a);
172
173                 zero(tm);
174                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
175                 char_array_0(b);
176                 printf(" Last DST change: DST %s at\n"
177                        "                  %s\n"
178                        "                  %s\n",
179                        is_dstc ? "began" : "ended", a, b);
180
181                 t = tn - 1;
182                 zero(tm);
183                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
184                 char_array_0(a);
185
186                 zero(tm);
187                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
188                 char_array_0(b);
189                 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
190                        "                  %s\n"
191                        "                  %s\n",
192                        is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
193
194                 free(zc);
195                 free(zn);
196         }
197
198         if (i->local_rtc)
199                 fputs("\n" ANSI_HIGHLIGHT_ON
200                       "Warning: The RTC is configured to maintain time in the local time zone. This\n"
201                       "         mode is not fully supported and will create various problems with time\n"
202                       "         zone changes and daylight saving adjustments. If at all possible use\n"
203                       "         RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
204 }
205
206 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
207         assert(name);
208         assert(iter);
209
210         switch (dbus_message_iter_get_arg_type(iter)) {
211
212         case DBUS_TYPE_STRING: {
213                 const char *s;
214
215                 dbus_message_iter_get_basic(iter, &s);
216                 if (!isempty(s)) {
217                         if (streq(name, "Timezone"))
218                                 i->timezone = s;
219                 }
220                 break;
221         }
222
223         case DBUS_TYPE_BOOLEAN: {
224                 dbus_bool_t b;
225
226                 dbus_message_iter_get_basic(iter, &b);
227                 if (streq(name, "LocalRTC"))
228                         i->local_rtc = b;
229                 else if (streq(name, "NTP"))
230                         i->ntp = b;
231         }
232         }
233
234         return 0;
235 }
236
237 static int show_status(DBusConnection *bus, char **args, unsigned n) {
238         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
239         const char *interface = "";
240         int r;
241         DBusMessageIter iter, sub, sub2, sub3;
242         StatusInfo info;
243
244         assert(args);
245
246         r = bus_method_call_with_reply(
247                         bus,
248                         "org.freedesktop.timedate1",
249                         "/org/freedesktop/timedate1",
250                         "org.freedesktop.DBus.Properties",
251                         "GetAll",
252                         &reply,
253                         NULL,
254                         DBUS_TYPE_STRING, &interface,
255                         DBUS_TYPE_INVALID);
256         if (r < 0)
257                 return r;
258
259         if (!dbus_message_iter_init(reply, &iter) ||
260             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
261             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
262                 log_error("Failed to parse reply.");
263                 return -EIO;
264         }
265
266         zero(info);
267         dbus_message_iter_recurse(&iter, &sub);
268
269         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
270                 const char *name;
271
272                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
273                         log_error("Failed to parse reply.");
274                         return -EIO;
275                 }
276
277                 dbus_message_iter_recurse(&sub, &sub2);
278
279                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
280                         log_error("Failed to parse reply.");
281                         return -EIO;
282                 }
283
284                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
285                         log_error("Failed to parse reply.");
286                         return -EIO;
287                 }
288
289                 dbus_message_iter_recurse(&sub2, &sub3);
290
291                 r = status_property(name, &sub3, &info);
292                 if (r < 0) {
293                         log_error("Failed to parse reply.");
294                         return r;
295                 }
296
297                 dbus_message_iter_next(&sub);
298         }
299
300         print_status_info(&info);
301         return 0;
302 }
303
304 static int set_time(DBusConnection *bus, char **args, unsigned n) {
305         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
306         dbus_bool_t relative = false, interactive = true;
307         usec_t t;
308         dbus_int64_t u;
309         int r;
310
311         assert(args);
312         assert(n == 2);
313
314         polkit_agent_open_if_enabled();
315
316         r = parse_timestamp(args[1], &t);
317         if (r < 0) {
318                 log_error("Failed to parse time specification: %s", args[1]);
319                 return r;
320         }
321
322         u = (dbus_uint64_t) t;
323
324         return bus_method_call_with_reply(
325                         bus,
326                         "org.freedesktop.timedate1",
327                         "/org/freedesktop/timedate1",
328                         "org.freedesktop.timedate1",
329                         "SetTime",
330                         &reply,
331                         NULL,
332                         DBUS_TYPE_INT64, &u,
333                         DBUS_TYPE_BOOLEAN, &relative,
334                         DBUS_TYPE_BOOLEAN, &interactive,
335                         DBUS_TYPE_INVALID);
336 }
337
338 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
339         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
340         dbus_bool_t interactive = true;
341
342         assert(args);
343         assert(n == 2);
344
345         polkit_agent_open_if_enabled();
346
347         return bus_method_call_with_reply(
348                         bus,
349                         "org.freedesktop.timedate1",
350                         "/org/freedesktop/timedate1",
351                         "org.freedesktop.timedate1",
352                         "SetTimezone",
353                         &reply,
354                         NULL,
355                         DBUS_TYPE_STRING, &args[1],
356                         DBUS_TYPE_BOOLEAN, &interactive,
357                         DBUS_TYPE_INVALID);
358 }
359
360 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
361         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
362         dbus_bool_t interactive = true, b, q;
363         int r;
364
365         assert(args);
366         assert(n == 2);
367
368         polkit_agent_open_if_enabled();
369
370         r = parse_boolean(args[1]);
371         if (r < 0) {
372                 log_error("Failed to parse local RTC setting: %s", args[1]);
373                 return r;
374         }
375
376         b = r;
377         q = arg_adjust_system_clock;
378
379         return bus_method_call_with_reply(
380                         bus,
381                         "org.freedesktop.timedate1",
382                         "/org/freedesktop/timedate1",
383                         "org.freedesktop.timedate1",
384                         "SetLocalRTC",
385                         &reply,
386                         NULL,
387                         DBUS_TYPE_BOOLEAN, &b,
388                         DBUS_TYPE_BOOLEAN, &q,
389                         DBUS_TYPE_BOOLEAN, &interactive,
390                         DBUS_TYPE_INVALID);
391 }
392
393 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
394         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
395         dbus_bool_t interactive = true, b;
396         int r;
397
398         assert(args);
399         assert(n == 2);
400
401         polkit_agent_open_if_enabled();
402
403         r = parse_boolean(args[1]);
404         if (r < 0) {
405                 log_error("Failed to parse NTP setting: %s", args[1]);
406                 return r;
407         }
408
409         b = r;
410
411         return bus_method_call_with_reply(
412                         bus,
413                         "org.freedesktop.timedate1",
414                         "/org/freedesktop/timedate1",
415                         "org.freedesktop.timedate1",
416                         "SetNTP",
417                         &reply,
418                         NULL,
419                         DBUS_TYPE_BOOLEAN, &b,
420                         DBUS_TYPE_BOOLEAN, &interactive,
421                         DBUS_TYPE_INVALID);
422 }
423
424 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
425         _cleanup_fclose_ FILE *f = NULL;
426         _cleanup_strv_free_ char **zones = NULL;
427         size_t n_zones = 0;
428         char **i;
429
430         assert(args);
431         assert(n == 1);
432
433         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
434         if (!f) {
435                 log_error("Failed to open timezone database: %m");
436                 return -errno;
437         }
438
439         for (;;) {
440                 char l[LINE_MAX], *p, **z, *w;
441                 size_t k;
442
443                 if (!fgets(l, sizeof(l), f)) {
444                         if (feof(f))
445                                 break;
446
447                         log_error("Failed to read timezone database: %m");
448                         return -errno;
449                 }
450
451                 p = strstrip(l);
452
453                 if (isempty(p) || *p == '#')
454                         continue;
455
456
457                 /* Skip over country code */
458                 p += strcspn(p, WHITESPACE);
459                 p += strspn(p, WHITESPACE);
460
461                 /* Skip over coordinates */
462                 p += strcspn(p, WHITESPACE);
463                 p += strspn(p, WHITESPACE);
464
465                 /* Found timezone name */
466                 k = strcspn(p, WHITESPACE);
467                 if (k <= 0)
468                         continue;
469
470                 w = strndup(p, k);
471                 if (!w)
472                         return log_oom();
473
474                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
475                 if (!z) {
476                         free(w);
477                         return log_oom();
478                 }
479
480                 zones = z;
481                 zones[n_zones++] = w;
482         }
483
484         if (zones)
485                 zones[n_zones] = NULL;
486
487         pager_open_if_enabled();
488
489         strv_sort(zones);
490         STRV_FOREACH(i, zones)
491                 puts(*i);
492
493         return 0;
494 }
495
496 static int help(void) {
497
498         printf("%s [OPTIONS...] COMMAND ...\n\n"
499                "Query or change system time and date settings.\n\n"
500                "  -h --help              Show this help\n"
501                "     --version           Show package version\n"
502                "     --adjust-system-clock\n"
503                "                         Adjust system clock when changing local RTC mode\n"
504                "     --no-pager          Do not pipe output into a pager\n"
505                "     --no-ask-password   Do not prompt for password\n"
506                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
507                "Commands:\n"
508                "  status                 Show current time settings\n"
509                "  set-time TIME          Set system time\n"
510                "  set-timezone ZONE      Set system timezone\n"
511                "  list-timezones         Show known timezones\n"
512                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
513                "  set-ntp BOOL           Control whether NTP is enabled\n",
514                program_invocation_short_name);
515
516         return 0;
517 }
518
519 static int parse_argv(int argc, char *argv[]) {
520
521         enum {
522                 ARG_VERSION = 0x100,
523                 ARG_NO_PAGER,
524                 ARG_ADJUST_SYSTEM_CLOCK,
525                 ARG_NO_ASK_PASSWORD
526         };
527
528         static const struct option options[] = {
529                 { "help",                no_argument,       NULL, 'h'                     },
530                 { "version",             no_argument,       NULL, ARG_VERSION             },
531                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
532                 { "host",                required_argument, NULL, 'H'                     },
533                 { "privileged",          no_argument,       NULL, 'P'                     },
534                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
535                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
536                 { NULL,                  0,                 NULL, 0                       }
537         };
538
539         int c;
540
541         assert(argc >= 0);
542         assert(argv);
543
544         while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
545
546                 switch (c) {
547
548                 case 'h':
549                         help();
550                         return 0;
551
552                 case ARG_VERSION:
553                         puts(PACKAGE_STRING);
554                         puts(DISTRIBUTION);
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 }