chiark / gitweb /
timedatectl: shorten output to print single < 80 char line
[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, DST became %s\n"
178                        "                  %s\n"
179                        "                  %s\n",
180                        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, DST will become %s, the clock will jump %s\n"
191                        "                  %s\n"
192                        "                  %s\n",
193                        is_dstn ? "active" : "inactive", 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         }
233         }
234
235         return 0;
236 }
237
238 static int show_status(DBusConnection *bus, char **args, unsigned n) {
239         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
240         const char *interface = "";
241         int r;
242         DBusMessageIter iter, sub, sub2, sub3;
243         StatusInfo info;
244
245         assert(args);
246
247         r = bus_method_call_with_reply(
248                         bus,
249                         "org.freedesktop.timedate1",
250                         "/org/freedesktop/timedate1",
251                         "org.freedesktop.DBus.Properties",
252                         "GetAll",
253                         &reply,
254                         NULL,
255                         DBUS_TYPE_STRING, &interface,
256                         DBUS_TYPE_INVALID);
257         if (r < 0)
258                 return r;
259
260         if (!dbus_message_iter_init(reply, &iter) ||
261             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
262             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
263                 log_error("Failed to parse reply.");
264                 return -EIO;
265         }
266
267         zero(info);
268         dbus_message_iter_recurse(&iter, &sub);
269
270         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
271                 const char *name;
272
273                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
274                         log_error("Failed to parse reply.");
275                         return -EIO;
276                 }
277
278                 dbus_message_iter_recurse(&sub, &sub2);
279
280                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
281                         log_error("Failed to parse reply.");
282                         return -EIO;
283                 }
284
285                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
286                         log_error("Failed to parse reply.");
287                         return -EIO;
288                 }
289
290                 dbus_message_iter_recurse(&sub2, &sub3);
291
292                 r = status_property(name, &sub3, &info);
293                 if (r < 0) {
294                         log_error("Failed to parse reply.");
295                         return r;
296                 }
297
298                 dbus_message_iter_next(&sub);
299         }
300
301         print_status_info(&info);
302         return 0;
303 }
304
305 static int set_time(DBusConnection *bus, char **args, unsigned n) {
306         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
307         dbus_bool_t relative = false, interactive = true;
308         usec_t t;
309         dbus_int64_t u;
310         int r;
311
312         assert(args);
313         assert(n == 2);
314
315         polkit_agent_open_if_enabled();
316
317         r = parse_timestamp(args[1], &t);
318         if (r < 0) {
319                 log_error("Failed to parse time specification: %s", args[1]);
320                 return r;
321         }
322
323         u = (dbus_uint64_t) t;
324
325         return bus_method_call_with_reply(
326                         bus,
327                         "org.freedesktop.timedate1",
328                         "/org/freedesktop/timedate1",
329                         "org.freedesktop.timedate1",
330                         "SetTime",
331                         &reply,
332                         NULL,
333                         DBUS_TYPE_INT64, &u,
334                         DBUS_TYPE_BOOLEAN, &relative,
335                         DBUS_TYPE_BOOLEAN, &interactive,
336                         DBUS_TYPE_INVALID);
337 }
338
339 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
340         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
341         dbus_bool_t interactive = true;
342
343         assert(args);
344         assert(n == 2);
345
346         polkit_agent_open_if_enabled();
347
348         return bus_method_call_with_reply(
349                         bus,
350                         "org.freedesktop.timedate1",
351                         "/org/freedesktop/timedate1",
352                         "org.freedesktop.timedate1",
353                         "SetTimezone",
354                         &reply,
355                         NULL,
356                         DBUS_TYPE_STRING, &args[1],
357                         DBUS_TYPE_BOOLEAN, &interactive,
358                         DBUS_TYPE_INVALID);
359 }
360
361 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
362         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
363         dbus_bool_t interactive = true, b, q;
364         int r;
365
366         assert(args);
367         assert(n == 2);
368
369         polkit_agent_open_if_enabled();
370
371         r = parse_boolean(args[1]);
372         if (r < 0) {
373                 log_error("Failed to parse local RTC setting: %s", args[1]);
374                 return r;
375         }
376
377         b = r;
378         q = arg_adjust_system_clock;
379
380         return bus_method_call_with_reply(
381                         bus,
382                         "org.freedesktop.timedate1",
383                         "/org/freedesktop/timedate1",
384                         "org.freedesktop.timedate1",
385                         "SetLocalRTC",
386                         &reply,
387                         NULL,
388                         DBUS_TYPE_BOOLEAN, &b,
389                         DBUS_TYPE_BOOLEAN, &q,
390                         DBUS_TYPE_BOOLEAN, &interactive,
391                         DBUS_TYPE_INVALID);
392 }
393
394 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
395         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
396         dbus_bool_t interactive = true, b;
397         int r;
398
399         assert(args);
400         assert(n == 2);
401
402         polkit_agent_open_if_enabled();
403
404         r = parse_boolean(args[1]);
405         if (r < 0) {
406                 log_error("Failed to parse NTP setting: %s", args[1]);
407                 return r;
408         }
409
410         b = r;
411
412         return bus_method_call_with_reply(
413                         bus,
414                         "org.freedesktop.timedate1",
415                         "/org/freedesktop/timedate1",
416                         "org.freedesktop.timedate1",
417                         "SetNTP",
418                         &reply,
419                         NULL,
420                         DBUS_TYPE_BOOLEAN, &b,
421                         DBUS_TYPE_BOOLEAN, &interactive,
422                         DBUS_TYPE_INVALID);
423 }
424
425 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
426         _cleanup_fclose_ FILE *f = NULL;
427         _cleanup_strv_free_ char **zones = NULL;
428         size_t n_zones = 0;
429         char **i;
430
431         assert(args);
432         assert(n == 1);
433
434         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
435         if (!f) {
436                 log_error("Failed to open timezone database: %m");
437                 return -errno;
438         }
439
440         for (;;) {
441                 char l[LINE_MAX], *p, **z, *w;
442                 size_t k;
443
444                 if (!fgets(l, sizeof(l), f)) {
445                         if (feof(f))
446                                 break;
447
448                         log_error("Failed to read timezone database: %m");
449                         return -errno;
450                 }
451
452                 p = strstrip(l);
453
454                 if (isempty(p) || *p == '#')
455                         continue;
456
457
458                 /* Skip over country code */
459                 p += strcspn(p, WHITESPACE);
460                 p += strspn(p, WHITESPACE);
461
462                 /* Skip over coordinates */
463                 p += strcspn(p, WHITESPACE);
464                 p += strspn(p, WHITESPACE);
465
466                 /* Found timezone name */
467                 k = strcspn(p, WHITESPACE);
468                 if (k <= 0)
469                         continue;
470
471                 w = strndup(p, k);
472                 if (!w)
473                         return log_oom();
474
475                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
476                 if (!z) {
477                         free(w);
478                         return log_oom();
479                 }
480
481                 zones = z;
482                 zones[n_zones++] = w;
483         }
484
485         if (zones)
486                 zones[n_zones] = NULL;
487
488         pager_open_if_enabled();
489
490         strv_sort(zones);
491         STRV_FOREACH(i, zones)
492                 puts(*i);
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(DISTRIBUTION);
556                         puts(SYSTEMD_FEATURES);
557                         return 0;
558
559                 case 'P':
560                         arg_transport = TRANSPORT_POLKIT;
561                         break;
562
563                 case 'H':
564                         arg_transport = TRANSPORT_SSH;
565                         arg_host = optarg;
566                         break;
567
568                 case ARG_ADJUST_SYSTEM_CLOCK:
569                         arg_adjust_system_clock = true;
570                         break;
571
572                 case ARG_NO_PAGER:
573                         arg_no_pager = true;
574                         break;
575
576                 case '?':
577                         return -EINVAL;
578
579                 default:
580                         log_error("Unknown option code %c", c);
581                         return -EINVAL;
582                 }
583         }
584
585         return 1;
586 }
587
588 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
589
590         static const struct {
591                 const char* verb;
592                 const enum {
593                         MORE,
594                         LESS,
595                         EQUAL
596                 } argc_cmp;
597                 const int argc;
598                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
599         } verbs[] = {
600                 { "status",                LESS,   1, show_status      },
601                 { "set-time",              EQUAL,  2, set_time         },
602                 { "set-timezone",          EQUAL,  2, set_timezone     },
603                 { "list-timezones",        EQUAL,  1, list_timezones   },
604                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
605                 { "set-ntp",               EQUAL,  2, set_ntp,         },
606         };
607
608         int left;
609         unsigned i;
610
611         assert(argc >= 0);
612         assert(argv);
613         assert(error);
614
615         left = argc - optind;
616
617         if (left <= 0)
618                 /* Special rule: no arguments means "status" */
619                 i = 0;
620         else {
621                 if (streq(argv[optind], "help")) {
622                         help();
623                         return 0;
624                 }
625
626                 for (i = 0; i < ELEMENTSOF(verbs); i++)
627                         if (streq(argv[optind], verbs[i].verb))
628                                 break;
629
630                 if (i >= ELEMENTSOF(verbs)) {
631                         log_error("Unknown operation %s", argv[optind]);
632                         return -EINVAL;
633                 }
634         }
635
636         switch (verbs[i].argc_cmp) {
637
638         case EQUAL:
639                 if (left != verbs[i].argc) {
640                         log_error("Invalid number of arguments.");
641                         return -EINVAL;
642                 }
643
644                 break;
645
646         case MORE:
647                 if (left < verbs[i].argc) {
648                         log_error("Too few arguments.");
649                         return -EINVAL;
650                 }
651
652                 break;
653
654         case LESS:
655                 if (left > verbs[i].argc) {
656                         log_error("Too many arguments.");
657                         return -EINVAL;
658                 }
659
660                 break;
661
662         default:
663                 assert_not_reached("Unknown comparison operator.");
664         }
665
666         if (!bus) {
667                 log_error("Failed to get D-Bus connection: %s", error->message);
668                 return -EIO;
669         }
670
671         return verbs[i].dispatch(bus, argv + optind, left);
672 }
673
674 int main(int argc, char *argv[]) {
675         int r, retval = EXIT_FAILURE;
676         DBusConnection *bus = NULL;
677         DBusError error;
678
679         dbus_error_init(&error);
680
681         setlocale(LC_ALL, "");
682         log_parse_environment();
683         log_open();
684
685         r = parse_argv(argc, argv);
686         if (r < 0)
687                 goto finish;
688         else if (r == 0) {
689                 retval = EXIT_SUCCESS;
690                 goto finish;
691         }
692
693         if (arg_transport == TRANSPORT_NORMAL)
694                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
695         else if (arg_transport == TRANSPORT_POLKIT)
696                 bus_connect_system_polkit(&bus, &error);
697         else if (arg_transport == TRANSPORT_SSH)
698                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
699         else
700                 assert_not_reached("Uh, invalid transport...");
701
702         r = timedatectl_main(bus, argc, argv, &error);
703         retval = r < 0 ? EXIT_FAILURE : r;
704
705 finish:
706         if (bus) {
707                 dbus_connection_flush(bus);
708                 dbus_connection_close(bus);
709                 dbus_connection_unref(bus);
710         }
711
712         dbus_error_free(&error);
713         dbus_shutdown();
714
715         pager_close();
716
717         return retval;
718 }