chiark / gitweb /
timedatectl: break line to not exceed 80 columns
[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", localtime_r(&sec, &tm)) > 0);
149         char_array_0(a);
150         printf("        Timezone: %s\n"
151                "      UTC offset: %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                yes_no(i->ntp),
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: %s → %s, DST became %s\n"
178                        "                  %s\n"
179                        "                  %s\n",
180                        strna(zn), strna(zc), is_dstc ? "active" : "inactive", 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: %s → %s, DST will become %s\n"
191                        "                  the clock will jump %s\n"
192                        "                  %s\n"
193                        "                  %s\n",
194                        strna(zc), strna(zn), is_dstn ? "active" : "inactive", jump_str(dn, s, sizeof(s)), a, b);
195
196                 free(zc);
197                 free(zn);
198         }
199
200         if (i->local_rtc)
201                 fputs("\n" ANSI_HIGHLIGHT_ON
202                       "Warning: The RTC is configured to maintain time in the local time zone. This\n"
203                       "         mode is not fully supported and will create various problems with time\n"
204                       "         zone changes and daylight saving adjustments. If at all possible use\n"
205                       "         RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
206 }
207
208 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
209         assert(name);
210         assert(iter);
211
212         switch (dbus_message_iter_get_arg_type(iter)) {
213
214         case DBUS_TYPE_STRING: {
215                 const char *s;
216
217                 dbus_message_iter_get_basic(iter, &s);
218                 if (!isempty(s)) {
219                         if (streq(name, "Timezone"))
220                                 i->timezone = s;
221                 }
222                 break;
223         }
224
225         case DBUS_TYPE_BOOLEAN: {
226                 dbus_bool_t b;
227
228                 dbus_message_iter_get_basic(iter, &b);
229                 if (streq(name, "LocalRTC"))
230                         i->local_rtc = b;
231                 else if (streq(name, "NTP"))
232                         i->ntp = b;
233         }
234         }
235
236         return 0;
237 }
238
239 static int show_status(DBusConnection *bus, char **args, unsigned n) {
240         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
241         const char *interface = "";
242         int r;
243         DBusMessageIter iter, sub, sub2, sub3;
244         StatusInfo info;
245
246         assert(args);
247
248         r = bus_method_call_with_reply(
249                         bus,
250                         "org.freedesktop.timedate1",
251                         "/org/freedesktop/timedate1",
252                         "org.freedesktop.DBus.Properties",
253                         "GetAll",
254                         &reply,
255                         NULL,
256                         DBUS_TYPE_STRING, &interface,
257                         DBUS_TYPE_INVALID);
258         if (r < 0)
259                 return r;
260
261         if (!dbus_message_iter_init(reply, &iter) ||
262             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
263             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
264                 log_error("Failed to parse reply.");
265                 return -EIO;
266         }
267
268         zero(info);
269         dbus_message_iter_recurse(&iter, &sub);
270
271         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
272                 const char *name;
273
274                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
275                         log_error("Failed to parse reply.");
276                         return -EIO;
277                 }
278
279                 dbus_message_iter_recurse(&sub, &sub2);
280
281                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
282                         log_error("Failed to parse reply.");
283                         return -EIO;
284                 }
285
286                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
287                         log_error("Failed to parse reply.");
288                         return -EIO;
289                 }
290
291                 dbus_message_iter_recurse(&sub2, &sub3);
292
293                 r = status_property(name, &sub3, &info);
294                 if (r < 0) {
295                         log_error("Failed to parse reply.");
296                         return r;
297                 }
298
299                 dbus_message_iter_next(&sub);
300         }
301
302         print_status_info(&info);
303         return 0;
304 }
305
306 static int set_time(DBusConnection *bus, char **args, unsigned n) {
307         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
308         dbus_bool_t relative = false, interactive = true;
309         usec_t t;
310         dbus_int64_t u;
311         int r;
312
313         assert(args);
314         assert(n == 2);
315
316         polkit_agent_open_if_enabled();
317
318         r = parse_timestamp(args[1], &t);
319         if (r < 0) {
320                 log_error("Failed to parse time specification: %s", args[1]);
321                 return r;
322         }
323
324         u = (dbus_uint64_t) t;
325
326         return bus_method_call_with_reply(
327                         bus,
328                         "org.freedesktop.timedate1",
329                         "/org/freedesktop/timedate1",
330                         "org.freedesktop.timedate1",
331                         "SetTime",
332                         &reply,
333                         NULL,
334                         DBUS_TYPE_INT64, &u,
335                         DBUS_TYPE_BOOLEAN, &relative,
336                         DBUS_TYPE_BOOLEAN, &interactive,
337                         DBUS_TYPE_INVALID);
338 }
339
340 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
341         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
342         dbus_bool_t interactive = true;
343
344         assert(args);
345         assert(n == 2);
346
347         polkit_agent_open_if_enabled();
348
349         return bus_method_call_with_reply(
350                         bus,
351                         "org.freedesktop.timedate1",
352                         "/org/freedesktop/timedate1",
353                         "org.freedesktop.timedate1",
354                         "SetTimezone",
355                         &reply,
356                         NULL,
357                         DBUS_TYPE_STRING, &args[1],
358                         DBUS_TYPE_BOOLEAN, &interactive,
359                         DBUS_TYPE_INVALID);
360 }
361
362 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
363         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
364         dbus_bool_t interactive = true, b, q;
365         int r;
366
367         assert(args);
368         assert(n == 2);
369
370         polkit_agent_open_if_enabled();
371
372         r = parse_boolean(args[1]);
373         if (r < 0) {
374                 log_error("Failed to parse local RTC setting: %s", args[1]);
375                 return r;
376         }
377
378         b = r;
379         q = arg_adjust_system_clock;
380
381         return bus_method_call_with_reply(
382                         bus,
383                         "org.freedesktop.timedate1",
384                         "/org/freedesktop/timedate1",
385                         "org.freedesktop.timedate1",
386                         "SetLocalRTC",
387                         &reply,
388                         NULL,
389                         DBUS_TYPE_BOOLEAN, &b,
390                         DBUS_TYPE_BOOLEAN, &q,
391                         DBUS_TYPE_BOOLEAN, &interactive,
392                         DBUS_TYPE_INVALID);
393 }
394
395 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
396         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
397         dbus_bool_t interactive = true, b;
398         int r;
399
400         assert(args);
401         assert(n == 2);
402
403         polkit_agent_open_if_enabled();
404
405         r = parse_boolean(args[1]);
406         if (r < 0) {
407                 log_error("Failed to parse NTP setting: %s", args[1]);
408                 return r;
409         }
410
411         b = r;
412
413         return bus_method_call_with_reply(
414                         bus,
415                         "org.freedesktop.timedate1",
416                         "/org/freedesktop/timedate1",
417                         "org.freedesktop.timedate1",
418                         "SetNTP",
419                         &reply,
420                         NULL,
421                         DBUS_TYPE_BOOLEAN, &b,
422                         DBUS_TYPE_BOOLEAN, &interactive,
423                         DBUS_TYPE_INVALID);
424 }
425
426 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
427         _cleanup_fclose_ FILE *f = NULL;
428         _cleanup_strv_free_ char **zones = NULL;
429         size_t n_zones = 0;
430         char **i;
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_FOREACH(i, zones)
493                 puts(*i);
494
495         return 0;
496 }
497
498 static int help(void) {
499
500         printf("%s [OPTIONS...] COMMAND ...\n\n"
501                "Query or change system time and date settings.\n\n"
502                "  -h --help              Show this help\n"
503                "     --version           Show package version\n"
504                "     --adjust-system-clock\n"
505                "                         Adjust system clock when changing local RTC mode\n"
506                "     --no-pager          Do not pipe output into a pager\n"
507                "     --no-ask-password   Do not prompt for password\n"
508                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
509                "Commands:\n"
510                "  status                 Show current time settings\n"
511                "  set-time TIME          Set system time\n"
512                "  set-timezone ZONE      Set system timezone\n"
513                "  list-timezones         Show known timezones\n"
514                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
515                "  set-ntp BOOL           Control whether NTP is enabled\n",
516                program_invocation_short_name);
517
518         return 0;
519 }
520
521 static int parse_argv(int argc, char *argv[]) {
522
523         enum {
524                 ARG_VERSION = 0x100,
525                 ARG_NO_PAGER,
526                 ARG_ADJUST_SYSTEM_CLOCK,
527                 ARG_NO_ASK_PASSWORD
528         };
529
530         static const struct option options[] = {
531                 { "help",                no_argument,       NULL, 'h'                     },
532                 { "version",             no_argument,       NULL, ARG_VERSION             },
533                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
534                 { "host",                required_argument, NULL, 'H'                     },
535                 { "privileged",          no_argument,       NULL, 'P'                     },
536                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
537                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
538                 { NULL,                  0,                 NULL, 0                       }
539         };
540
541         int c;
542
543         assert(argc >= 0);
544         assert(argv);
545
546         while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
547
548                 switch (c) {
549
550                 case 'h':
551                         help();
552                         return 0;
553
554                 case ARG_VERSION:
555                         puts(PACKAGE_STRING);
556                         puts(DISTRIBUTION);
557                         puts(SYSTEMD_FEATURES);
558                         return 0;
559
560                 case 'P':
561                         arg_transport = TRANSPORT_POLKIT;
562                         break;
563
564                 case 'H':
565                         arg_transport = TRANSPORT_SSH;
566                         arg_host = optarg;
567                         break;
568
569                 case ARG_ADJUST_SYSTEM_CLOCK:
570                         arg_adjust_system_clock = true;
571                         break;
572
573                 case ARG_NO_PAGER:
574                         arg_no_pager = true;
575                         break;
576
577                 case '?':
578                         return -EINVAL;
579
580                 default:
581                         log_error("Unknown option code %c", c);
582                         return -EINVAL;
583                 }
584         }
585
586         return 1;
587 }
588
589 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
590
591         static const struct {
592                 const char* verb;
593                 const enum {
594                         MORE,
595                         LESS,
596                         EQUAL
597                 } argc_cmp;
598                 const int argc;
599                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
600         } verbs[] = {
601                 { "status",                LESS,   1, show_status      },
602                 { "set-time",              EQUAL,  2, set_time         },
603                 { "set-timezone",          EQUAL,  2, set_timezone     },
604                 { "list-timezones",        EQUAL,  1, list_timezones   },
605                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
606                 { "set-ntp",               EQUAL,  2, set_ntp,         },
607         };
608
609         int left;
610         unsigned i;
611
612         assert(argc >= 0);
613         assert(argv);
614         assert(error);
615
616         left = argc - optind;
617
618         if (left <= 0)
619                 /* Special rule: no arguments means "status" */
620                 i = 0;
621         else {
622                 if (streq(argv[optind], "help")) {
623                         help();
624                         return 0;
625                 }
626
627                 for (i = 0; i < ELEMENTSOF(verbs); i++)
628                         if (streq(argv[optind], verbs[i].verb))
629                                 break;
630
631                 if (i >= ELEMENTSOF(verbs)) {
632                         log_error("Unknown operation %s", argv[optind]);
633                         return -EINVAL;
634                 }
635         }
636
637         switch (verbs[i].argc_cmp) {
638
639         case EQUAL:
640                 if (left != verbs[i].argc) {
641                         log_error("Invalid number of arguments.");
642                         return -EINVAL;
643                 }
644
645                 break;
646
647         case MORE:
648                 if (left < verbs[i].argc) {
649                         log_error("Too few arguments.");
650                         return -EINVAL;
651                 }
652
653                 break;
654
655         case LESS:
656                 if (left > verbs[i].argc) {
657                         log_error("Too many arguments.");
658                         return -EINVAL;
659                 }
660
661                 break;
662
663         default:
664                 assert_not_reached("Unknown comparison operator.");
665         }
666
667         if (!bus) {
668                 log_error("Failed to get D-Bus connection: %s", error->message);
669                 return -EIO;
670         }
671
672         return verbs[i].dispatch(bus, argv + optind, left);
673 }
674
675 int main(int argc, char *argv[]) {
676         int r, retval = EXIT_FAILURE;
677         DBusConnection *bus = NULL;
678         DBusError error;
679
680         dbus_error_init(&error);
681
682         setlocale(LC_ALL, "");
683         log_parse_environment();
684         log_open();
685
686         r = parse_argv(argc, argv);
687         if (r < 0)
688                 goto finish;
689         else if (r == 0) {
690                 retval = EXIT_SUCCESS;
691                 goto finish;
692         }
693
694         if (arg_transport == TRANSPORT_NORMAL)
695                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
696         else if (arg_transport == TRANSPORT_POLKIT)
697                 bus_connect_system_polkit(&bus, &error);
698         else if (arg_transport == TRANSPORT_SSH)
699                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
700         else
701                 assert_not_reached("Uh, invalid transport...");
702
703         r = timedatectl_main(bus, argc, argv, &error);
704         retval = r < 0 ? EXIT_FAILURE : r;
705
706 finish:
707         if (bus) {
708                 dbus_connection_flush(bus);
709                 dbus_connection_close(bus);
710                 dbus_connection_unref(bus);
711         }
712
713         dbus_error_free(&error);
714         dbus_shutdown();
715
716         pager_close();
717
718         return retval;
719 }