chiark / gitweb /
Fix --no-ask-password
[elogind.git] / src / timedate / timedatectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <locale.h>
27 #include <string.h>
28 #include <sys/timex.h>
29
30 #include "dbus-common.h"
31 #include "util.h"
32 #include "spawn-polkit-agent.h"
33 #include "build.h"
34 #include "hwclock.h"
35 #include "strv.h"
36 #include "pager.h"
37 #include "time-dst.h"
38
39 static bool arg_adjust_system_clock = false;
40 static bool arg_no_pager = false;
41 static enum transport {
42         TRANSPORT_NORMAL,
43         TRANSPORT_SSH,
44         TRANSPORT_POLKIT
45 } arg_transport = TRANSPORT_NORMAL;
46 static bool arg_ask_password = true;
47 static const char *arg_host = NULL;
48
49 static void pager_open_if_enabled(void) {
50
51         if (arg_no_pager)
52                 return;
53
54         pager_open(false);
55 }
56
57 static void polkit_agent_open_if_enabled(void) {
58
59         /* Open the polkit agent as a child process if necessary */
60
61         if (!arg_ask_password)
62                 return;
63
64         polkit_agent_open();
65 }
66
67 typedef struct StatusInfo {
68         const char *timezone;
69         bool local_rtc;
70         bool ntp;
71         bool can_ntp;
72 } StatusInfo;
73
74 static bool ntp_synced(void) {
75         struct timex txc = {};
76
77         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                i->can_ntp ? yes_no(i->ntp) : "n/a",
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                 else if (streq(name, "CanNTP"))
232                         i->can_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         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 = arg_ask_password;
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 = arg_ask_password;
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 = arg_ask_password, 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 = arg_ask_password, 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
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_print(zones);
491
492         return 0;
493 }
494
495 static int help(void) {
496
497         printf("%s [OPTIONS...] COMMAND ...\n\n"
498                "Query or change system time and date settings.\n\n"
499                "  -h --help              Show this help\n"
500                "     --version           Show package version\n"
501                "     --adjust-system-clock\n"
502                "                         Adjust system clock when changing local RTC mode\n"
503                "     --no-pager          Do not pipe output into a pager\n"
504                "  -P --privileged        Acquire privileges before execution\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(SYSTEMD_FEATURES);
555                         return 0;
556
557                 case 'P':
558                         arg_transport = TRANSPORT_POLKIT;
559                         break;
560
561                 case 'H':
562                         arg_transport = TRANSPORT_SSH;
563                         arg_host = optarg;
564                         break;
565
566                 case ARG_NO_ASK_PASSWORD:
567                         arg_ask_password = false;
568                         break;
569
570                 case ARG_ADJUST_SYSTEM_CLOCK:
571                         arg_adjust_system_clock = true;
572                         break;
573
574                 case ARG_NO_PAGER:
575                         arg_no_pager = true;
576                         break;
577
578                 case '?':
579                         return -EINVAL;
580
581                 default:
582                         log_error("Unknown option code %c", c);
583                         return -EINVAL;
584                 }
585         }
586
587         return 1;
588 }
589
590 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
591
592         static const struct {
593                 const char* verb;
594                 const enum {
595                         MORE,
596                         LESS,
597                         EQUAL
598                 } argc_cmp;
599                 const int argc;
600                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
601         } verbs[] = {
602                 { "status",                LESS,   1, show_status      },
603                 { "set-time",              EQUAL,  2, set_time         },
604                 { "set-timezone",          EQUAL,  2, set_timezone     },
605                 { "list-timezones",        EQUAL,  1, list_timezones   },
606                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
607                 { "set-ntp",               EQUAL,  2, set_ntp,         },
608         };
609
610         int left;
611         unsigned i;
612
613         assert(argc >= 0);
614         assert(argv);
615         assert(error);
616
617         left = argc - optind;
618
619         if (left <= 0)
620                 /* Special rule: no arguments means "status" */
621                 i = 0;
622         else {
623                 if (streq(argv[optind], "help")) {
624                         help();
625                         return 0;
626                 }
627
628                 for (i = 0; i < ELEMENTSOF(verbs); i++)
629                         if (streq(argv[optind], verbs[i].verb))
630                                 break;
631
632                 if (i >= ELEMENTSOF(verbs)) {
633                         log_error("Unknown operation %s", argv[optind]);
634                         return -EINVAL;
635                 }
636         }
637
638         switch (verbs[i].argc_cmp) {
639
640         case EQUAL:
641                 if (left != verbs[i].argc) {
642                         log_error("Invalid number of arguments.");
643                         return -EINVAL;
644                 }
645
646                 break;
647
648         case MORE:
649                 if (left < verbs[i].argc) {
650                         log_error("Too few arguments.");
651                         return -EINVAL;
652                 }
653
654                 break;
655
656         case LESS:
657                 if (left > verbs[i].argc) {
658                         log_error("Too many arguments.");
659                         return -EINVAL;
660                 }
661
662                 break;
663
664         default:
665                 assert_not_reached("Unknown comparison operator.");
666         }
667
668         if (!bus) {
669                 log_error("Failed to get D-Bus connection: %s", error->message);
670                 return -EIO;
671         }
672
673         return verbs[i].dispatch(bus, argv + optind, left);
674 }
675
676 int main(int argc, char *argv[]) {
677         int r, retval = EXIT_FAILURE;
678         DBusConnection *bus = NULL;
679         DBusError error;
680
681         dbus_error_init(&error);
682
683         setlocale(LC_ALL, "");
684         log_parse_environment();
685         log_open();
686
687         r = parse_argv(argc, argv);
688         if (r < 0)
689                 goto finish;
690         else if (r == 0) {
691                 retval = EXIT_SUCCESS;
692                 goto finish;
693         }
694
695         if (arg_transport == TRANSPORT_NORMAL)
696                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
697         else if (arg_transport == TRANSPORT_POLKIT)
698                 bus_connect_system_polkit(&bus, &error);
699         else if (arg_transport == TRANSPORT_SSH)
700                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
701         else
702                 assert_not_reached("Uh, invalid transport...");
703
704         r = timedatectl_main(bus, argc, argv, &error);
705         retval = r < 0 ? EXIT_FAILURE : r;
706
707 finish:
708         if (bus) {
709                 dbus_connection_flush(bus);
710                 dbus_connection_close(bus);
711                 dbus_connection_unref(bus);
712         }
713
714         dbus_error_free(&error);
715         dbus_shutdown();
716
717         pager_close();
718
719         return retval;
720 }