chiark / gitweb /
ef2ea0830006bfd1cfee75fb4745a008ebe5a5f5
[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 } 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
429         assert(args);
430         assert(n == 1);
431
432         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
433         if (!f) {
434                 log_error("Failed to open timezone database: %m");
435                 return -errno;
436         }
437
438         for (;;) {
439                 char l[LINE_MAX], *p, **z, *w;
440                 size_t k;
441
442                 if (!fgets(l, sizeof(l), f)) {
443                         if (feof(f))
444                                 break;
445
446                         log_error("Failed to read timezone database: %m");
447                         return -errno;
448                 }
449
450                 p = strstrip(l);
451
452                 if (isempty(p) || *p == '#')
453                         continue;
454
455
456                 /* Skip over country code */
457                 p += strcspn(p, WHITESPACE);
458                 p += strspn(p, WHITESPACE);
459
460                 /* Skip over coordinates */
461                 p += strcspn(p, WHITESPACE);
462                 p += strspn(p, WHITESPACE);
463
464                 /* Found timezone name */
465                 k = strcspn(p, WHITESPACE);
466                 if (k <= 0)
467                         continue;
468
469                 w = strndup(p, k);
470                 if (!w)
471                         return log_oom();
472
473                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
474                 if (!z) {
475                         free(w);
476                         return log_oom();
477                 }
478
479                 zones = z;
480                 zones[n_zones++] = w;
481         }
482
483         if (zones)
484                 zones[n_zones] = NULL;
485
486         pager_open_if_enabled();
487
488         strv_sort(zones);
489         strv_print(zones);
490
491         return 0;
492 }
493
494 static int help(void) {
495
496         printf("%s [OPTIONS...] COMMAND ...\n\n"
497                "Query or change system time and date settings.\n\n"
498                "  -h --help              Show this help\n"
499                "     --version           Show package version\n"
500                "     --adjust-system-clock\n"
501                "                         Adjust system clock when changing local RTC mode\n"
502                "     --no-pager          Do not pipe output into a pager\n"
503                "     --no-ask-password   Do not prompt for password\n"
504                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
505                "Commands:\n"
506                "  status                 Show current time settings\n"
507                "  set-time TIME          Set system time\n"
508                "  set-timezone ZONE      Set system timezone\n"
509                "  list-timezones         Show known timezones\n"
510                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
511                "  set-ntp BOOL           Control whether NTP is enabled\n",
512                program_invocation_short_name);
513
514         return 0;
515 }
516
517 static int parse_argv(int argc, char *argv[]) {
518
519         enum {
520                 ARG_VERSION = 0x100,
521                 ARG_NO_PAGER,
522                 ARG_ADJUST_SYSTEM_CLOCK,
523                 ARG_NO_ASK_PASSWORD
524         };
525
526         static const struct option options[] = {
527                 { "help",                no_argument,       NULL, 'h'                     },
528                 { "version",             no_argument,       NULL, ARG_VERSION             },
529                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
530                 { "host",                required_argument, NULL, 'H'                     },
531                 { "privileged",          no_argument,       NULL, 'P'                     },
532                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
533                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
534                 { NULL,                  0,                 NULL, 0                       }
535         };
536
537         int c;
538
539         assert(argc >= 0);
540         assert(argv);
541
542         while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
543
544                 switch (c) {
545
546                 case 'h':
547                         help();
548                         return 0;
549
550                 case ARG_VERSION:
551                         puts(PACKAGE_STRING);
552                         puts(SYSTEMD_FEATURES);
553                         return 0;
554
555                 case 'P':
556                         arg_transport = TRANSPORT_POLKIT;
557                         break;
558
559                 case 'H':
560                         arg_transport = TRANSPORT_SSH;
561                         arg_host = optarg;
562                         break;
563
564                 case ARG_ADJUST_SYSTEM_CLOCK:
565                         arg_adjust_system_clock = true;
566                         break;
567
568                 case ARG_NO_PAGER:
569                         arg_no_pager = true;
570                         break;
571
572                 case '?':
573                         return -EINVAL;
574
575                 default:
576                         log_error("Unknown option code %c", c);
577                         return -EINVAL;
578                 }
579         }
580
581         return 1;
582 }
583
584 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
585
586         static const struct {
587                 const char* verb;
588                 const enum {
589                         MORE,
590                         LESS,
591                         EQUAL
592                 } argc_cmp;
593                 const int argc;
594                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
595         } verbs[] = {
596                 { "status",                LESS,   1, show_status      },
597                 { "set-time",              EQUAL,  2, set_time         },
598                 { "set-timezone",          EQUAL,  2, set_timezone     },
599                 { "list-timezones",        EQUAL,  1, list_timezones   },
600                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
601                 { "set-ntp",               EQUAL,  2, set_ntp,         },
602         };
603
604         int left;
605         unsigned i;
606
607         assert(argc >= 0);
608         assert(argv);
609         assert(error);
610
611         left = argc - optind;
612
613         if (left <= 0)
614                 /* Special rule: no arguments means "status" */
615                 i = 0;
616         else {
617                 if (streq(argv[optind], "help")) {
618                         help();
619                         return 0;
620                 }
621
622                 for (i = 0; i < ELEMENTSOF(verbs); i++)
623                         if (streq(argv[optind], verbs[i].verb))
624                                 break;
625
626                 if (i >= ELEMENTSOF(verbs)) {
627                         log_error("Unknown operation %s", argv[optind]);
628                         return -EINVAL;
629                 }
630         }
631
632         switch (verbs[i].argc_cmp) {
633
634         case EQUAL:
635                 if (left != verbs[i].argc) {
636                         log_error("Invalid number of arguments.");
637                         return -EINVAL;
638                 }
639
640                 break;
641
642         case MORE:
643                 if (left < verbs[i].argc) {
644                         log_error("Too few arguments.");
645                         return -EINVAL;
646                 }
647
648                 break;
649
650         case LESS:
651                 if (left > verbs[i].argc) {
652                         log_error("Too many arguments.");
653                         return -EINVAL;
654                 }
655
656                 break;
657
658         default:
659                 assert_not_reached("Unknown comparison operator.");
660         }
661
662         if (!bus) {
663                 log_error("Failed to get D-Bus connection: %s", error->message);
664                 return -EIO;
665         }
666
667         return verbs[i].dispatch(bus, argv + optind, left);
668 }
669
670 int main(int argc, char *argv[]) {
671         int r, retval = EXIT_FAILURE;
672         DBusConnection *bus = NULL;
673         DBusError error;
674
675         dbus_error_init(&error);
676
677         setlocale(LC_ALL, "");
678         log_parse_environment();
679         log_open();
680
681         r = parse_argv(argc, argv);
682         if (r < 0)
683                 goto finish;
684         else if (r == 0) {
685                 retval = EXIT_SUCCESS;
686                 goto finish;
687         }
688
689         if (arg_transport == TRANSPORT_NORMAL)
690                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
691         else if (arg_transport == TRANSPORT_POLKIT)
692                 bus_connect_system_polkit(&bus, &error);
693         else if (arg_transport == TRANSPORT_SSH)
694                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
695         else
696                 assert_not_reached("Uh, invalid transport...");
697
698         r = timedatectl_main(bus, argc, argv, &error);
699         retval = r < 0 ? EXIT_FAILURE : r;
700
701 finish:
702         if (bus) {
703                 dbus_connection_flush(bus);
704                 dbus_connection_close(bus);
705                 dbus_connection_unref(bus);
706         }
707
708         dbus_error_free(&error);
709         dbus_shutdown();
710
711         pager_close();
712
713         return retval;
714 }