chiark / gitweb /
timedate: fix numerous grammar errors in comments and output/debug messages
[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   Copyright 2013 Kay Sievers
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <stdlib.h>
24 #include <stdbool.h>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <locale.h>
28 #include <string.h>
29 #include <sys/timex.h>
30
31 #include "sd-bus.h"
32 #include "bus-util.h"
33 #include "bus-error.h"
34 #include "util.h"
35 #include "spawn-polkit-agent.h"
36 #include "build.h"
37 #include "strv.h"
38 #include "pager.h"
39 #include "time-dst.h"
40
41 static bool arg_no_pager = false;
42 static bool arg_ask_password = true;
43 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
44 static char *arg_host = NULL;
45 static bool arg_adjust_system_clock = false;
46
47 static void pager_open_if_enabled(void) {
48
49         if (arg_no_pager)
50                 return;
51
52         pager_open(false);
53 }
54
55 static void polkit_agent_open_if_enabled(void) {
56
57         /* Open the polkit agent as a child process if necessary */
58         if (!arg_ask_password)
59                 return;
60
61         if (arg_transport != BUS_TRANSPORT_LOCAL)
62                 return;
63
64         polkit_agent_open();
65 }
66
67 typedef struct StatusInfo {
68         usec_t time;
69         char *timezone;
70
71         usec_t rtc_time;
72         bool rtc_local;
73
74         bool ntp_enabled;
75         bool ntp_capable;
76         bool ntp_synced;
77 } StatusInfo;
78
79 static const char *jump_str(int delta_minutes, char *s, size_t size) {
80         if (delta_minutes == 60)
81                 return "one hour forward";
82         if (delta_minutes == -60)
83                 return "one hour backwards";
84         if (delta_minutes < 0) {
85                 snprintf(s, size, "%i minutes backwards", -delta_minutes);
86                 return s;
87         }
88         if (delta_minutes > 0) {
89                 snprintf(s, size, "%i minutes forward", delta_minutes);
90                 return s;
91         }
92         return "";
93 }
94
95 static void print_status_info(const StatusInfo *i) {
96         char a[FORMAT_TIMESTAMP_MAX];
97         char b[FORMAT_TIMESTAMP_MAX];
98         char s[32];
99         struct tm tm;
100         time_t sec;
101         bool have_time = false;
102         _cleanup_free_ char *zc = NULL, *zn = NULL;
103         time_t t, tc, tn;
104         int dn = 0;
105         bool is_dstc = false, is_dstn = false;
106         int r;
107
108         assert(i);
109
110         /* Enforce the values of /etc/localtime */
111         if (getenv("TZ")) {
112                 fprintf(stderr, "Warning: Ignoring the TZ variable. Reading the system's time zone setting only.\n\n");
113                 unsetenv("TZ");
114         }
115
116         if (i->time != 0) {
117                 sec = (time_t) (i->time / USEC_PER_SEC);
118                 have_time = true;
119         } else if (arg_transport == BUS_TRANSPORT_LOCAL) {
120                 sec = time(NULL);
121                 have_time = true;
122         } else
123                 fprintf(stderr, "Warning: Could not get time from timedated and not operating locally.\n\n");
124
125         if (have_time) {
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         } else {
136                 printf("      Local time: %s\n", "n/a");
137                 printf("  Universal time: %s\n", "n/a");
138         }
139
140         if (i->rtc_time > 0) {
141                 time_t rtc_sec;
142
143                 rtc_sec = (time_t)(i->rtc_time / USEC_PER_SEC);
144                 zero(tm);
145                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)) > 0);
146                 char_array_0(a);
147                 printf("        RTC time: %s\n", a);
148         } else
149                 printf("        RTC time: %s\n", "n/a");
150
151         zero(tm);
152         assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
153         char_array_0(a);
154         printf("       Time zone: %s (%s)\n"
155                "     NTP enabled: %s\n"
156                "NTP synchronized: %s\n"
157                " RTC in local TZ: %s\n",
158                strna(i->timezone), a,
159                i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
160                yes_no(i->ntp_synced),
161                yes_no(i->rtc_local));
162
163         r = time_get_dst(sec, "/etc/localtime",
164                          &tc, &zc, &is_dstc,
165                          &tn, &dn, &zn, &is_dstn);
166         if (r < 0)
167                 printf("      DST active: %s\n", "n/a");
168         else if (have_time) {
169                 printf("      DST active: %s\n", yes_no(is_dstc));
170
171                 t = tc - 1;
172                 zero(tm);
173                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
174                 char_array_0(a);
175
176                 zero(tm);
177                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
178                 char_array_0(b);
179                 printf(" Last DST change: DST %s at\n"
180                        "                  %s\n"
181                        "                  %s\n",
182                        is_dstc ? "began" : "ended", a, b);
183
184                 t = tn - 1;
185                 zero(tm);
186                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
187                 char_array_0(a);
188
189                 zero(tm);
190                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
191                 char_array_0(b);
192                 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
193                        "                  %s\n"
194                        "                  %s\n",
195                        is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
196         } else
197                 printf("      DST active: %s\n", yes_no(is_dstc));
198
199         if (i->rtc_local)
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 time 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 show_status(sd_bus *bus, char **args, unsigned n) {
208         StatusInfo info = {};
209         static const struct bus_properties_map map[]  = {
210                 { "Timezone",        "s", NULL, offsetof(StatusInfo, timezone) },
211                 { "LocalRTC",        "b", NULL, offsetof(StatusInfo, rtc_local) },
212                 { "NTP",             "b", NULL, offsetof(StatusInfo, ntp_enabled) },
213                 { "CanNTP",          "b", NULL, offsetof(StatusInfo, ntp_capable) },
214                 { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) },
215                 { "TimeUSec",        "t", NULL, offsetof(StatusInfo, time) },
216                 { "RTCTimeUSec",     "t", NULL, offsetof(StatusInfo, rtc_time) },
217                 {}
218         };
219         int r;
220
221         assert(bus);
222
223         r = bus_map_all_properties(bus,
224                                    "org.freedesktop.timedate1",
225                                    "/org/freedesktop/timedate1",
226                                    map,
227                                    &info);
228         if (r < 0) {
229                 log_error("Failed to query server: %s", strerror(-r));
230                 goto fail;
231         }
232
233         print_status_info(&info);
234
235 fail:
236         free(info.timezone);
237         return r;
238 }
239
240 static int set_time(sd_bus *bus, char **args, unsigned n) {
241         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
242         bool relative = false, interactive = arg_ask_password;
243         usec_t t;
244         int r;
245
246         assert(args);
247         assert(n == 2);
248
249         polkit_agent_open_if_enabled();
250
251         r = parse_timestamp(args[1], &t);
252         if (r < 0) {
253                 log_error("Failed to parse time specification: %s", args[1]);
254                 return r;
255         }
256
257         r = sd_bus_call_method(bus,
258                                "org.freedesktop.timedate1",
259                                "/org/freedesktop/timedate1",
260                                "org.freedesktop.timedate1",
261                                "SetTime",
262                                &error,
263                                NULL,
264                                "xbb", (int64_t)t, relative, interactive);
265         if (r < 0)
266                 log_error("Failed to set time: %s", bus_error_message(&error, -r));
267
268         return r;
269 }
270
271 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
272         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
273         int r;
274
275         assert(args);
276         assert(n == 2);
277
278         polkit_agent_open_if_enabled();
279
280         r = sd_bus_call_method(bus,
281                                "org.freedesktop.timedate1",
282                                "/org/freedesktop/timedate1",
283                                "org.freedesktop.timedate1",
284                                "SetTimezone",
285                                &error,
286                                NULL,
287                                "sb", args[1], arg_ask_password);
288         if (r < 0)
289                 log_error("Failed to set time zone: %s", bus_error_message(&error, -r));
290
291         return r;
292 }
293
294 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
295         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
296         int r, b;
297
298         assert(args);
299         assert(n == 2);
300
301         polkit_agent_open_if_enabled();
302
303         b = parse_boolean(args[1]);
304         if (b < 0) {
305                 log_error("Failed to parse local RTC setting: %s", args[1]);
306                 return b;
307         }
308
309         r = sd_bus_call_method(bus,
310                                "org.freedesktop.timedate1",
311                                "/org/freedesktop/timedate1",
312                                "org.freedesktop.timedate1",
313                                "SetLocalRTC",
314                                &error,
315                                NULL,
316                                "bbb", b, arg_adjust_system_clock, arg_ask_password);
317         if (r < 0)
318                 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
319
320         return r;
321 }
322
323 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
324         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
325         int b, r;
326
327         assert(args);
328         assert(n == 2);
329
330         polkit_agent_open_if_enabled();
331
332         b = parse_boolean(args[1]);
333         if (b < 0) {
334                 log_error("Failed to parse NTP setting: %s", args[1]);
335                 return b;
336         }
337
338         r = sd_bus_call_method(bus,
339                                "org.freedesktop.timedate1",
340                                "/org/freedesktop/timedate1",
341                                "org.freedesktop.timedate1",
342                                "SetNTP",
343                                &error,
344                                NULL,
345                                "bb", b, arg_ask_password);
346         if (r < 0)
347                 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
348
349         return r;
350 }
351
352 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
353         _cleanup_fclose_ FILE *f = NULL;
354         _cleanup_strv_free_ char **zones = NULL;
355         size_t n_zones = 0;
356
357         assert(args);
358         assert(n == 1);
359
360         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
361         if (!f) {
362                 log_error("Failed to open time zone database: %m");
363                 return -errno;
364         }
365
366         for (;;) {
367                 char l[LINE_MAX], *p, **z, *w;
368                 size_t k;
369
370                 if (!fgets(l, sizeof(l), f)) {
371                         if (feof(f))
372                                 break;
373
374                         log_error("Failed to read time zone database: %m");
375                         return -errno;
376                 }
377
378                 p = strstrip(l);
379
380                 if (isempty(p) || *p == '#')
381                         continue;
382
383                 /* Skip over country code */
384                 p += strcspn(p, WHITESPACE);
385                 p += strspn(p, WHITESPACE);
386
387                 /* Skip over coordinates */
388                 p += strcspn(p, WHITESPACE);
389                 p += strspn(p, WHITESPACE);
390
391                 /* Found timezone name */
392                 k = strcspn(p, WHITESPACE);
393                 if (k <= 0)
394                         continue;
395
396                 w = strndup(p, k);
397                 if (!w)
398                         return log_oom();
399
400                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
401                 if (!z) {
402                         free(w);
403                         return log_oom();
404                 }
405
406                 zones = z;
407                 zones[n_zones++] = w;
408         }
409
410         if (zones)
411                 zones[n_zones] = NULL;
412
413         pager_open_if_enabled();
414
415         strv_sort(zones);
416         strv_print(zones);
417
418         return 0;
419 }
420
421 static int help(void) {
422
423         printf("%s [OPTIONS...] COMMAND ...\n\n"
424                "Query or change system time and date settings.\n\n"
425                "  -h --help                Show this help message\n"
426                "     --version             Show package version\n"
427                "     --no-pager            Do not pipe output into a pager\n"
428                "     --no-ask-password     Do not prompt for password\n"
429                "  -H --host=[USER@]HOST    Operate on remote host\n"
430                "  -M --machine=CONTAINER   Operate on local container\n"
431                "     --adjust-system-clock Adjust system clock when changing local RTC mode\n\n"
432                "Commands:\n"
433                "  status                   Show current time settings\n"
434                "  set-time TIME            Set system time\n"
435                "  set-timezone ZONE        Set system time zone\n"
436                "  list-timezones           Show known time zones\n"
437                "  set-local-rtc BOOL       Control whether RTC is in local time\n"
438                "  set-ntp BOOL             Control whether NTP is enabled\n",
439                program_invocation_short_name);
440
441         return 0;
442 }
443
444 static int parse_argv(int argc, char *argv[]) {
445
446         enum {
447                 ARG_VERSION = 0x100,
448                 ARG_NO_PAGER,
449                 ARG_ADJUST_SYSTEM_CLOCK,
450                 ARG_NO_ASK_PASSWORD
451         };
452
453         static const struct option options[] = {
454                 { "help",                no_argument,       NULL, 'h'                     },
455                 { "version",             no_argument,       NULL, ARG_VERSION             },
456                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
457                 { "host",                required_argument, NULL, 'H'                     },
458                 { "machine",             required_argument, NULL, 'M'                     },
459                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
460                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
461                 {}
462         };
463
464         int c;
465
466         assert(argc >= 0);
467         assert(argv);
468
469         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
470
471                 switch (c) {
472
473                 case 'h':
474                         return help();
475
476                 case ARG_VERSION:
477                         puts(PACKAGE_STRING);
478                         puts(SYSTEMD_FEATURES);
479                         return 0;
480
481                 case 'H':
482                         arg_transport = BUS_TRANSPORT_REMOTE;
483                         arg_host = optarg;
484                         break;
485
486                 case 'M':
487                         arg_transport = BUS_TRANSPORT_CONTAINER;
488                         arg_host = optarg;
489                         break;
490
491                 case ARG_NO_ASK_PASSWORD:
492                         arg_ask_password = false;
493                         break;
494
495                 case ARG_ADJUST_SYSTEM_CLOCK:
496                         arg_adjust_system_clock = true;
497                         break;
498
499                 case ARG_NO_PAGER:
500                         arg_no_pager = true;
501                         break;
502
503                 case '?':
504                         return -EINVAL;
505
506                 default:
507                         assert_not_reached("Unhandled option");
508                 }
509         }
510
511         return 1;
512 }
513
514 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
515
516         static const struct {
517                 const char* verb;
518                 const enum {
519                         MORE,
520                         LESS,
521                         EQUAL
522                 } argc_cmp;
523                 const int argc;
524                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
525         } verbs[] = {
526                 { "status",                LESS,   1, show_status      },
527                 { "set-time",              EQUAL,  2, set_time         },
528                 { "set-timezone",          EQUAL,  2, set_timezone     },
529                 { "list-timezones",        EQUAL,  1, list_timezones   },
530                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
531                 { "set-ntp",               EQUAL,  2, set_ntp,         },
532         };
533
534         int left;
535         unsigned i;
536
537         assert(argc >= 0);
538         assert(argv);
539
540         left = argc - optind;
541
542         if (left <= 0)
543                 /* Special rule: no arguments means "status" */
544                 i = 0;
545         else {
546                 if (streq(argv[optind], "help")) {
547                         help();
548                         return 0;
549                 }
550
551                 for (i = 0; i < ELEMENTSOF(verbs); i++)
552                         if (streq(argv[optind], verbs[i].verb))
553                                 break;
554
555                 if (i >= ELEMENTSOF(verbs)) {
556                         log_error("Unknown operation %s", argv[optind]);
557                         return -EINVAL;
558                 }
559         }
560
561         switch (verbs[i].argc_cmp) {
562
563         case EQUAL:
564                 if (left != verbs[i].argc) {
565                         log_error("Invalid number of arguments.");
566                         return -EINVAL;
567                 }
568
569                 break;
570
571         case MORE:
572                 if (left < verbs[i].argc) {
573                         log_error("Too few arguments.");
574                         return -EINVAL;
575                 }
576
577                 break;
578
579         case LESS:
580                 if (left > verbs[i].argc) {
581                         log_error("Too many arguments.");
582                         return -EINVAL;
583                 }
584
585                 break;
586
587         default:
588                 assert_not_reached("Unknown comparison operator.");
589         }
590
591         return verbs[i].dispatch(bus, argv + optind, left);
592 }
593
594 int main(int argc, char *argv[]) {
595         _cleanup_bus_unref_ sd_bus *bus = NULL;
596         int r;
597
598         setlocale(LC_ALL, "");
599         log_parse_environment();
600         log_open();
601
602         r = parse_argv(argc, argv);
603         if (r <= 0)
604                 goto finish;
605
606         r = bus_open_transport(arg_transport, arg_host, false, &bus);
607         if (r < 0) {
608                 log_error("Failed to create bus connection: %s", strerror(-r));
609                 goto finish;
610         }
611
612         r = timedatectl_main(bus, argc, argv);
613
614 finish:
615         pager_close();
616
617         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
618 }