chiark / gitweb /
man: improve grammar and word formatting in numerous man pages
[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 bool ntp_synced(void) {
76         struct timex txc = {};
77
78         if (adjtimex(&txc) < 0)
79                 return false;
80
81         if (txc.status & STA_UNSYNC)
82                 return false;
83
84         return true;
85 }
86
87 static const char *jump_str(int delta_minutes, char *s, size_t size) {
88         if (delta_minutes == 60)
89                 return "one hour forward";
90         if (delta_minutes == -60)
91                 return "one hour backwards";
92         if (delta_minutes < 0) {
93                 snprintf(s, size, "%i minutes backwards", -delta_minutes);
94                 return s;
95         }
96         if (delta_minutes > 0) {
97                 snprintf(s, size, "%i minutes forward", delta_minutes);
98                 return s;
99         }
100         return "";
101 }
102
103 static void print_status_info(StatusInfo *i) {
104         usec_t n;
105         char a[FORMAT_TIMESTAMP_MAX];
106         char b[FORMAT_TIMESTAMP_MAX];
107         char s[32];
108         struct tm tm;
109         time_t sec;
110         char *zc, *zn;
111         time_t t, tc, tn;
112         int dn;
113         bool is_dstc, is_dstn;
114         int r;
115
116         assert(i);
117
118         /* enforce the values of /etc/localtime */
119         if (getenv("TZ")) {
120                 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
121                 unsetenv("TZ");
122         }
123
124         n = now(CLOCK_REALTIME);
125         sec = (time_t) (n / USEC_PER_SEC);
126
127         zero(tm);
128         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
129         char_array_0(a);
130         printf("      Local time: %s\n", a);
131
132         zero(tm);
133         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
134         char_array_0(a);
135         printf("  Universal time: %s\n", a);
136
137         zero(tm);
138         r = hwclock_get_time(&tm);
139         if (r >= 0) {
140                 /* Calculcate the week-day */
141                 mktime(&tm);
142
143                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
144                 char_array_0(a);
145                 printf("        RTC time: %s\n", a);
146         }
147
148         zero(tm);
149         assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
150         char_array_0(a);
151         printf("        Timezone: %s (%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                i->can_ntp ? yes_no(i->ntp) : "n/a",
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 %s at\n"
178                        "                  %s\n"
179                        "                  %s\n",
180                        is_dstc ? "began" : "ended", 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 %s (the clock jumps %s) at\n"
191                        "                  %s\n"
192                        "                  %s\n",
193                        is_dstn ? "begins" : "ends", 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 timezone. 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                 else if (streq(name, "CanNTP"))
233                         i->can_ntp = b;
234         }
235         }
236
237         return 0;
238 }
239
240 static int show_status(DBusConnection *bus, char **args, unsigned n) {
241         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
242         const char *interface = "";
243         int r;
244         DBusMessageIter iter, sub, sub2, sub3;
245         StatusInfo info = {};
246
247         assert(args);
248
249         r = bus_method_call_with_reply(
250                         bus,
251                         "org.freedesktop.timedate1",
252                         "/org/freedesktop/timedate1",
253                         "org.freedesktop.DBus.Properties",
254                         "GetAll",
255                         &reply,
256                         NULL,
257                         DBUS_TYPE_STRING, &interface,
258                         DBUS_TYPE_INVALID);
259         if (r < 0)
260                 return r;
261
262         if (!dbus_message_iter_init(reply, &iter) ||
263             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
264             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
265                 log_error("Failed to parse reply.");
266                 return -EIO;
267         }
268
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 = arg_ask_password;
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 = arg_ask_password;
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 = arg_ask_password, 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 = arg_ask_password, 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
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_print(zones);
492
493         return 0;
494 }
495
496 static int help(void) {
497
498         printf("%s [OPTIONS...] COMMAND ...\n\n"
499                "Query or change system time and date settings.\n\n"
500                "  -h --help              Show this help\n"
501                "     --version           Show package version\n"
502                "     --adjust-system-clock\n"
503                "                         Adjust system clock when changing local RTC mode\n"
504                "     --no-pager          Do not pipe output into a pager\n"
505                "  -P --privileged        Acquire privileges before execution\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(SYSTEMD_FEATURES);
556                         return 0;
557
558                 case 'P':
559                         arg_transport = TRANSPORT_POLKIT;
560                         break;
561
562                 case 'H':
563                         arg_transport = TRANSPORT_SSH;
564                         parse_user_at_host(optarg, &arg_user, &arg_host);
565                         break;
566
567                 case ARG_NO_ASK_PASSWORD:
568                         arg_ask_password = false;
569                         break;
570
571                 case ARG_ADJUST_SYSTEM_CLOCK:
572                         arg_adjust_system_clock = true;
573                         break;
574
575                 case ARG_NO_PAGER:
576                         arg_no_pager = true;
577                         break;
578
579                 case '?':
580                         return -EINVAL;
581
582                 default:
583                         log_error("Unknown option code %c", c);
584                         return -EINVAL;
585                 }
586         }
587
588         return 1;
589 }
590
591 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
592
593         static const struct {
594                 const char* verb;
595                 const enum {
596                         MORE,
597                         LESS,
598                         EQUAL
599                 } argc_cmp;
600                 const int argc;
601                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
602         } verbs[] = {
603                 { "status",                LESS,   1, show_status      },
604                 { "set-time",              EQUAL,  2, set_time         },
605                 { "set-timezone",          EQUAL,  2, set_timezone     },
606                 { "list-timezones",        EQUAL,  1, list_timezones   },
607                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
608                 { "set-ntp",               EQUAL,  2, set_ntp,         },
609         };
610
611         int left;
612         unsigned i;
613
614         assert(argc >= 0);
615         assert(argv);
616         assert(error);
617
618         left = argc - optind;
619
620         if (left <= 0)
621                 /* Special rule: no arguments means "status" */
622                 i = 0;
623         else {
624                 if (streq(argv[optind], "help")) {
625                         help();
626                         return 0;
627                 }
628
629                 for (i = 0; i < ELEMENTSOF(verbs); i++)
630                         if (streq(argv[optind], verbs[i].verb))
631                                 break;
632
633                 if (i >= ELEMENTSOF(verbs)) {
634                         log_error("Unknown operation %s", argv[optind]);
635                         return -EINVAL;
636                 }
637         }
638
639         switch (verbs[i].argc_cmp) {
640
641         case EQUAL:
642                 if (left != verbs[i].argc) {
643                         log_error("Invalid number of arguments.");
644                         return -EINVAL;
645                 }
646
647                 break;
648
649         case MORE:
650                 if (left < verbs[i].argc) {
651                         log_error("Too few arguments.");
652                         return -EINVAL;
653                 }
654
655                 break;
656
657         case LESS:
658                 if (left > verbs[i].argc) {
659                         log_error("Too many arguments.");
660                         return -EINVAL;
661                 }
662
663                 break;
664
665         default:
666                 assert_not_reached("Unknown comparison operator.");
667         }
668
669         if (!bus) {
670                 log_error("Failed to get D-Bus connection: %s", error->message);
671                 return -EIO;
672         }
673
674         return verbs[i].dispatch(bus, argv + optind, left);
675 }
676
677 int main(int argc, char *argv[]) {
678         int r, retval = EXIT_FAILURE;
679         DBusConnection *bus = NULL;
680         DBusError error;
681
682         dbus_error_init(&error);
683
684         setlocale(LC_ALL, "");
685         log_parse_environment();
686         log_open();
687
688         r = parse_argv(argc, argv);
689         if (r < 0)
690                 goto finish;
691         else if (r == 0) {
692                 retval = EXIT_SUCCESS;
693                 goto finish;
694         }
695
696         if (arg_transport == TRANSPORT_NORMAL)
697                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
698         else if (arg_transport == TRANSPORT_POLKIT)
699                 bus_connect_system_polkit(&bus, &error);
700         else if (arg_transport == TRANSPORT_SSH)
701                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
702         else
703                 assert_not_reached("Uh, invalid transport...");
704
705         r = timedatectl_main(bus, argc, argv, &error);
706         retval = r < 0 ? EXIT_FAILURE : r;
707
708 finish:
709         if (bus) {
710                 dbus_connection_flush(bus);
711                 dbus_connection_close(bus);
712                 dbus_connection_unref(bus);
713         }
714
715         dbus_error_free(&error);
716         dbus_shutdown();
717
718         pager_close();
719
720         return retval;
721 }