chiark / gitweb /
tmpfiles: log unaccessible FUSE mount points only as debug message
[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 char *arg_host = NULL;
48 static char *arg_user = NULL;
49
50 static void pager_open_if_enabled(void) {
51
52         if (arg_no_pager)
53                 return;
54
55         pager_open(false);
56 }
57
58 static void polkit_agent_open_if_enabled(void) {
59
60         /* Open the polkit agent as a child process if necessary */
61
62         if (!arg_ask_password)
63                 return;
64
65         polkit_agent_open();
66 }
67
68 typedef struct StatusInfo {
69         const char *timezone;
70         bool local_rtc;
71         bool ntp;
72         bool can_ntp;
73 } StatusInfo;
74
75 static const char *jump_str(int delta_minutes, char *s, size_t size) {
76         if (delta_minutes == 60)
77                 return "one hour forward";
78         if (delta_minutes == -60)
79                 return "one hour backwards";
80         if (delta_minutes < 0) {
81                 snprintf(s, size, "%i minutes backwards", -delta_minutes);
82                 return s;
83         }
84         if (delta_minutes > 0) {
85                 snprintf(s, size, "%i minutes forward", delta_minutes);
86                 return s;
87         }
88         return "";
89 }
90
91 static void print_status_info(StatusInfo *i) {
92         usec_t n;
93         char a[FORMAT_TIMESTAMP_MAX];
94         char b[FORMAT_TIMESTAMP_MAX];
95         char s[32];
96         struct tm tm;
97         time_t sec;
98         char *zc, *zn;
99         time_t t, tc, tn;
100         int dn;
101         bool is_dstc, is_dstn;
102         int r;
103
104         assert(i);
105
106         /* enforce the values of /etc/localtime */
107         if (getenv("TZ")) {
108                 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
109                 unsetenv("TZ");
110         }
111
112         n = now(CLOCK_REALTIME);
113         sec = (time_t) (n / USEC_PER_SEC);
114
115         zero(tm);
116         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
117         char_array_0(a);
118         printf("      Local time: %s\n", a);
119
120         zero(tm);
121         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
122         char_array_0(a);
123         printf("  Universal time: %s\n", a);
124
125         zero(tm);
126         r = hwclock_get_time(&tm);
127         if (r >= 0) {
128                 /* Calculcate the week-day */
129                 mktime(&tm);
130
131                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
132                 char_array_0(a);
133                 printf("        RTC time: %s\n", a);
134         }
135
136         zero(tm);
137         assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
138         char_array_0(a);
139         printf("        Timezone: %s (%s)\n"
140                "     NTP enabled: %s\n"
141                "NTP synchronized: %s\n"
142                " RTC in local TZ: %s\n",
143                strna(i->timezone),
144                a,
145                i->can_ntp ? yes_no(i->ntp) : "n/a",
146                yes_no(ntp_synced()),
147                yes_no(i->local_rtc));
148
149         r = time_get_dst(sec, "/etc/localtime",
150                          &tc, &zc, &is_dstc,
151                          &tn, &dn, &zn, &is_dstn);
152         if (r < 0)
153                 printf("      DST active: n/a\n");
154         else {
155                 printf("      DST active: %s\n", yes_no(is_dstc));
156
157                 t = tc - 1;
158                 zero(tm);
159                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
160                 char_array_0(a);
161
162                 zero(tm);
163                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
164                 char_array_0(b);
165                 printf(" Last DST change: DST %s at\n"
166                        "                  %s\n"
167                        "                  %s\n",
168                        is_dstc ? "began" : "ended", a, b);
169
170                 t = tn - 1;
171                 zero(tm);
172                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
173                 char_array_0(a);
174
175                 zero(tm);
176                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
177                 char_array_0(b);
178                 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
179                        "                  %s\n"
180                        "                  %s\n",
181                        is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
182
183                 free(zc);
184                 free(zn);
185         }
186
187         if (i->local_rtc)
188                 fputs("\n" ANSI_HIGHLIGHT_ON
189                       "Warning: The RTC is configured to maintain time in the local timezone. This\n"
190                       "         mode is not fully supported and will create various problems with time\n"
191                       "         zone changes and daylight saving adjustments. If at all possible use\n"
192                       "         RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
193 }
194
195 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
196         assert(name);
197         assert(iter);
198
199         switch (dbus_message_iter_get_arg_type(iter)) {
200
201         case DBUS_TYPE_STRING: {
202                 const char *s;
203
204                 dbus_message_iter_get_basic(iter, &s);
205                 if (!isempty(s)) {
206                         if (streq(name, "Timezone"))
207                                 i->timezone = s;
208                 }
209                 break;
210         }
211
212         case DBUS_TYPE_BOOLEAN: {
213                 dbus_bool_t b;
214
215                 dbus_message_iter_get_basic(iter, &b);
216                 if (streq(name, "LocalRTC"))
217                         i->local_rtc = b;
218                 else if (streq(name, "NTP"))
219                         i->ntp = b;
220                 else if (streq(name, "CanNTP"))
221                         i->can_ntp = b;
222         }
223         }
224
225         return 0;
226 }
227
228 static int show_status(DBusConnection *bus, char **args, unsigned n) {
229         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
230         const char *interface = "";
231         int r;
232         DBusMessageIter iter, sub, sub2, sub3;
233         StatusInfo info = {};
234
235         assert(args);
236
237         r = bus_method_call_with_reply(
238                         bus,
239                         "org.freedesktop.timedate1",
240                         "/org/freedesktop/timedate1",
241                         "org.freedesktop.DBus.Properties",
242                         "GetAll",
243                         &reply,
244                         NULL,
245                         DBUS_TYPE_STRING, &interface,
246                         DBUS_TYPE_INVALID);
247         if (r < 0)
248                 return r;
249
250         if (!dbus_message_iter_init(reply, &iter) ||
251             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
252             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
253                 log_error("Failed to parse reply.");
254                 return -EIO;
255         }
256
257         dbus_message_iter_recurse(&iter, &sub);
258
259         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
260                 const char *name;
261
262                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
263                         log_error("Failed to parse reply.");
264                         return -EIO;
265                 }
266
267                 dbus_message_iter_recurse(&sub, &sub2);
268
269                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
270                         log_error("Failed to parse reply.");
271                         return -EIO;
272                 }
273
274                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
275                         log_error("Failed to parse reply.");
276                         return -EIO;
277                 }
278
279                 dbus_message_iter_recurse(&sub2, &sub3);
280
281                 r = status_property(name, &sub3, &info);
282                 if (r < 0) {
283                         log_error("Failed to parse reply.");
284                         return r;
285                 }
286
287                 dbus_message_iter_next(&sub);
288         }
289
290         print_status_info(&info);
291         return 0;
292 }
293
294 static int set_time(DBusConnection *bus, char **args, unsigned n) {
295         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
296         dbus_bool_t relative = false, interactive = arg_ask_password;
297         usec_t t;
298         dbus_int64_t u;
299         int r;
300
301         assert(args);
302         assert(n == 2);
303
304         polkit_agent_open_if_enabled();
305
306         r = parse_timestamp(args[1], &t);
307         if (r < 0) {
308                 log_error("Failed to parse time specification: %s", args[1]);
309                 return r;
310         }
311
312         u = (dbus_uint64_t) t;
313
314         return bus_method_call_with_reply(
315                         bus,
316                         "org.freedesktop.timedate1",
317                         "/org/freedesktop/timedate1",
318                         "org.freedesktop.timedate1",
319                         "SetTime",
320                         &reply,
321                         NULL,
322                         DBUS_TYPE_INT64, &u,
323                         DBUS_TYPE_BOOLEAN, &relative,
324                         DBUS_TYPE_BOOLEAN, &interactive,
325                         DBUS_TYPE_INVALID);
326 }
327
328 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
329         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
330         dbus_bool_t interactive = arg_ask_password;
331
332         assert(args);
333         assert(n == 2);
334
335         polkit_agent_open_if_enabled();
336
337         return bus_method_call_with_reply(
338                         bus,
339                         "org.freedesktop.timedate1",
340                         "/org/freedesktop/timedate1",
341                         "org.freedesktop.timedate1",
342                         "SetTimezone",
343                         &reply,
344                         NULL,
345                         DBUS_TYPE_STRING, &args[1],
346                         DBUS_TYPE_BOOLEAN, &interactive,
347                         DBUS_TYPE_INVALID);
348 }
349
350 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
351         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
352         dbus_bool_t interactive = arg_ask_password, b, q;
353         int r;
354
355         assert(args);
356         assert(n == 2);
357
358         polkit_agent_open_if_enabled();
359
360         r = parse_boolean(args[1]);
361         if (r < 0) {
362                 log_error("Failed to parse local RTC setting: %s", args[1]);
363                 return r;
364         }
365
366         b = r;
367         q = arg_adjust_system_clock;
368
369         return bus_method_call_with_reply(
370                         bus,
371                         "org.freedesktop.timedate1",
372                         "/org/freedesktop/timedate1",
373                         "org.freedesktop.timedate1",
374                         "SetLocalRTC",
375                         &reply,
376                         NULL,
377                         DBUS_TYPE_BOOLEAN, &b,
378                         DBUS_TYPE_BOOLEAN, &q,
379                         DBUS_TYPE_BOOLEAN, &interactive,
380                         DBUS_TYPE_INVALID);
381 }
382
383 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
384         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
385         dbus_bool_t interactive = arg_ask_password, b;
386         int r;
387
388         assert(args);
389         assert(n == 2);
390
391         polkit_agent_open_if_enabled();
392
393         r = parse_boolean(args[1]);
394         if (r < 0) {
395                 log_error("Failed to parse NTP setting: %s", args[1]);
396                 return r;
397         }
398
399         b = r;
400
401         return bus_method_call_with_reply(
402                         bus,
403                         "org.freedesktop.timedate1",
404                         "/org/freedesktop/timedate1",
405                         "org.freedesktop.timedate1",
406                         "SetNTP",
407                         &reply,
408                         NULL,
409                         DBUS_TYPE_BOOLEAN, &b,
410                         DBUS_TYPE_BOOLEAN, &interactive,
411                         DBUS_TYPE_INVALID);
412 }
413
414 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
415         _cleanup_fclose_ FILE *f = NULL;
416         _cleanup_strv_free_ char **zones = NULL;
417         size_t n_zones = 0;
418
419         assert(args);
420         assert(n == 1);
421
422         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
423         if (!f) {
424                 log_error("Failed to open timezone database: %m");
425                 return -errno;
426         }
427
428         for (;;) {
429                 char l[LINE_MAX], *p, **z, *w;
430                 size_t k;
431
432                 if (!fgets(l, sizeof(l), f)) {
433                         if (feof(f))
434                                 break;
435
436                         log_error("Failed to read timezone database: %m");
437                         return -errno;
438                 }
439
440                 p = strstrip(l);
441
442                 if (isempty(p) || *p == '#')
443                         continue;
444
445
446                 /* Skip over country code */
447                 p += strcspn(p, WHITESPACE);
448                 p += strspn(p, WHITESPACE);
449
450                 /* Skip over coordinates */
451                 p += strcspn(p, WHITESPACE);
452                 p += strspn(p, WHITESPACE);
453
454                 /* Found timezone name */
455                 k = strcspn(p, WHITESPACE);
456                 if (k <= 0)
457                         continue;
458
459                 w = strndup(p, k);
460                 if (!w)
461                         return log_oom();
462
463                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
464                 if (!z) {
465                         free(w);
466                         return log_oom();
467                 }
468
469                 zones = z;
470                 zones[n_zones++] = w;
471         }
472
473         if (zones)
474                 zones[n_zones] = NULL;
475
476         pager_open_if_enabled();
477
478         strv_sort(zones);
479         strv_print(zones);
480
481         return 0;
482 }
483
484 static int help(void) {
485
486         printf("%s [OPTIONS...] COMMAND ...\n\n"
487                "Query or change system time and date settings.\n\n"
488                "  -h --help              Show this help\n"
489                "     --version           Show package version\n"
490                "     --adjust-system-clock\n"
491                "                         Adjust system clock when changing local RTC mode\n"
492                "     --no-pager          Do not pipe output into a pager\n"
493                "  -P --privileged        Acquire privileges before execution\n"
494                "     --no-ask-password   Do not prompt for password\n"
495                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
496                "Commands:\n"
497                "  status                 Show current time settings\n"
498                "  set-time TIME          Set system time\n"
499                "  set-timezone ZONE      Set system timezone\n"
500                "  list-timezones         Show known timezones\n"
501                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
502                "  set-ntp BOOL           Control whether NTP is enabled\n",
503                program_invocation_short_name);
504
505         return 0;
506 }
507
508 static int parse_argv(int argc, char *argv[]) {
509
510         enum {
511                 ARG_VERSION = 0x100,
512                 ARG_NO_PAGER,
513                 ARG_ADJUST_SYSTEM_CLOCK,
514                 ARG_NO_ASK_PASSWORD
515         };
516
517         static const struct option options[] = {
518                 { "help",                no_argument,       NULL, 'h'                     },
519                 { "version",             no_argument,       NULL, ARG_VERSION             },
520                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
521                 { "host",                required_argument, NULL, 'H'                     },
522                 { "privileged",          no_argument,       NULL, 'P'                     },
523                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
524                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
525                 { NULL,                  0,                 NULL, 0                       }
526         };
527
528         int c;
529
530         assert(argc >= 0);
531         assert(argv);
532
533         while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
534
535                 switch (c) {
536
537                 case 'h':
538                         help();
539                         return 0;
540
541                 case ARG_VERSION:
542                         puts(PACKAGE_STRING);
543                         puts(SYSTEMD_FEATURES);
544                         return 0;
545
546                 case 'P':
547                         arg_transport = TRANSPORT_POLKIT;
548                         break;
549
550                 case 'H':
551                         arg_transport = TRANSPORT_SSH;
552                         parse_user_at_host(optarg, &arg_user, &arg_host);
553                         break;
554
555                 case ARG_NO_ASK_PASSWORD:
556                         arg_ask_password = false;
557                         break;
558
559                 case ARG_ADJUST_SYSTEM_CLOCK:
560                         arg_adjust_system_clock = true;
561                         break;
562
563                 case ARG_NO_PAGER:
564                         arg_no_pager = true;
565                         break;
566
567                 case '?':
568                         return -EINVAL;
569
570                 default:
571                         log_error("Unknown option code %c", c);
572                         return -EINVAL;
573                 }
574         }
575
576         return 1;
577 }
578
579 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
580
581         static const struct {
582                 const char* verb;
583                 const enum {
584                         MORE,
585                         LESS,
586                         EQUAL
587                 } argc_cmp;
588                 const int argc;
589                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
590         } verbs[] = {
591                 { "status",                LESS,   1, show_status      },
592                 { "set-time",              EQUAL,  2, set_time         },
593                 { "set-timezone",          EQUAL,  2, set_timezone     },
594                 { "list-timezones",        EQUAL,  1, list_timezones   },
595                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
596                 { "set-ntp",               EQUAL,  2, set_ntp,         },
597         };
598
599         int left;
600         unsigned i;
601
602         assert(argc >= 0);
603         assert(argv);
604         assert(error);
605
606         left = argc - optind;
607
608         if (left <= 0)
609                 /* Special rule: no arguments means "status" */
610                 i = 0;
611         else {
612                 if (streq(argv[optind], "help")) {
613                         help();
614                         return 0;
615                 }
616
617                 for (i = 0; i < ELEMENTSOF(verbs); i++)
618                         if (streq(argv[optind], verbs[i].verb))
619                                 break;
620
621                 if (i >= ELEMENTSOF(verbs)) {
622                         log_error("Unknown operation %s", argv[optind]);
623                         return -EINVAL;
624                 }
625         }
626
627         switch (verbs[i].argc_cmp) {
628
629         case EQUAL:
630                 if (left != verbs[i].argc) {
631                         log_error("Invalid number of arguments.");
632                         return -EINVAL;
633                 }
634
635                 break;
636
637         case MORE:
638                 if (left < verbs[i].argc) {
639                         log_error("Too few arguments.");
640                         return -EINVAL;
641                 }
642
643                 break;
644
645         case LESS:
646                 if (left > verbs[i].argc) {
647                         log_error("Too many arguments.");
648                         return -EINVAL;
649                 }
650
651                 break;
652
653         default:
654                 assert_not_reached("Unknown comparison operator.");
655         }
656
657         if (!bus) {
658                 log_error("Failed to get D-Bus connection: %s", error->message);
659                 return -EIO;
660         }
661
662         return verbs[i].dispatch(bus, argv + optind, left);
663 }
664
665 int main(int argc, char *argv[]) {
666         int r, retval = EXIT_FAILURE;
667         DBusConnection *bus = NULL;
668         DBusError error;
669
670         dbus_error_init(&error);
671
672         setlocale(LC_ALL, "");
673         log_parse_environment();
674         log_open();
675
676         r = parse_argv(argc, argv);
677         if (r < 0)
678                 goto finish;
679         else if (r == 0) {
680                 retval = EXIT_SUCCESS;
681                 goto finish;
682         }
683
684         if (arg_transport == TRANSPORT_NORMAL)
685                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
686         else if (arg_transport == TRANSPORT_POLKIT)
687                 bus_connect_system_polkit(&bus, &error);
688         else if (arg_transport == TRANSPORT_SSH)
689                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
690         else
691                 assert_not_reached("Uh, invalid transport...");
692
693         r = timedatectl_main(bus, argc, argv, &error);
694         retval = r < 0 ? EXIT_FAILURE : r;
695
696 finish:
697         if (bus) {
698                 dbus_connection_flush(bus);
699                 dbus_connection_close(bus);
700                 dbus_connection_unref(bus);
701         }
702
703         dbus_error_free(&error);
704         dbus_shutdown();
705
706         pager_close();
707
708         return retval;
709 }