chiark / gitweb /
clock-util: clock_[sg]et_time() -> clock_[sg]et_hwclock()
[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         if (have_time) {
152                 zero(tm);
153                 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
154                 char_array_0(a);
155         }
156
157         printf("       Time zone: %s (%s)\n"
158                "     NTP enabled: %s\n"
159                "NTP synchronized: %s\n"
160                " RTC in local TZ: %s\n",
161                strna(i->timezone), have_time ? a : "n/a",
162                i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
163                yes_no(i->ntp_synced),
164                yes_no(i->rtc_local));
165
166         if (have_time) {
167                 r = time_get_dst(sec, "/etc/localtime",
168                                  &tc, &zc, &is_dstc,
169                                  &tn, &dn, &zn, &is_dstn);
170                 if (r < 0)
171                         printf("      DST active: %s\n", "n/a");
172                 else {
173                         printf("      DST active: %s\n", yes_no(is_dstc));
174
175                         t = tc - 1;
176                         zero(tm);
177                         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
178                         char_array_0(a);
179
180                         zero(tm);
181                         assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
182                         char_array_0(b);
183                         printf(" Last DST change: DST %s at\n"
184                                "                  %s\n"
185                                "                  %s\n",
186                                is_dstc ? "began" : "ended", a, b);
187
188                         t = tn - 1;
189                         zero(tm);
190                         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
191                         char_array_0(a);
192
193                         zero(tm);
194                         assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
195                         char_array_0(b);
196                         printf(" Next DST change: DST %s (the clock jumps %s) at\n"
197                                "                  %s\n"
198                                "                  %s\n",
199                                is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
200                 }
201         } else
202                 printf("      DST active: %s\n", yes_no(is_dstc));
203
204         if (i->rtc_local)
205                 fputs("\n" ANSI_HIGHLIGHT_ON
206                       "Warning: The RTC is configured to maintain time in the local time zone. This\n"
207                       "         mode is not fully supported and will create various problems with time\n"
208                       "         zone changes and daylight saving time adjustments. If at all possible, use\n"
209                       "         RTC in UTC by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
210 }
211
212 static int show_status(sd_bus *bus, char **args, unsigned n) {
213         StatusInfo info = {};
214         static const struct bus_properties_map map[]  = {
215                 { "Timezone",        "s", NULL, offsetof(StatusInfo, timezone) },
216                 { "LocalRTC",        "b", NULL, offsetof(StatusInfo, rtc_local) },
217                 { "NTP",             "b", NULL, offsetof(StatusInfo, ntp_enabled) },
218                 { "CanNTP",          "b", NULL, offsetof(StatusInfo, ntp_capable) },
219                 { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) },
220                 { "TimeUSec",        "t", NULL, offsetof(StatusInfo, time) },
221                 { "RTCTimeUSec",     "t", NULL, offsetof(StatusInfo, rtc_time) },
222                 {}
223         };
224         int r;
225
226         assert(bus);
227
228         r = bus_map_all_properties(bus,
229                                    "org.freedesktop.timedate1",
230                                    "/org/freedesktop/timedate1",
231                                    map,
232                                    &info);
233         if (r < 0) {
234                 log_error("Failed to query server: %s", strerror(-r));
235                 goto fail;
236         }
237
238         print_status_info(&info);
239
240 fail:
241         free(info.timezone);
242         return r;
243 }
244
245 static int set_time(sd_bus *bus, char **args, unsigned n) {
246         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
247         bool relative = false, interactive = arg_ask_password;
248         usec_t t;
249         int r;
250
251         assert(args);
252         assert(n == 2);
253
254         polkit_agent_open_if_enabled();
255
256         r = parse_timestamp(args[1], &t);
257         if (r < 0) {
258                 log_error("Failed to parse time specification: %s", args[1]);
259                 return r;
260         }
261
262         r = sd_bus_call_method(bus,
263                                "org.freedesktop.timedate1",
264                                "/org/freedesktop/timedate1",
265                                "org.freedesktop.timedate1",
266                                "SetTime",
267                                &error,
268                                NULL,
269                                "xbb", (int64_t)t, relative, interactive);
270         if (r < 0)
271                 log_error("Failed to set time: %s", bus_error_message(&error, -r));
272
273         return r;
274 }
275
276 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
277         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
278         int r;
279
280         assert(args);
281         assert(n == 2);
282
283         polkit_agent_open_if_enabled();
284
285         r = sd_bus_call_method(bus,
286                                "org.freedesktop.timedate1",
287                                "/org/freedesktop/timedate1",
288                                "org.freedesktop.timedate1",
289                                "SetTimezone",
290                                &error,
291                                NULL,
292                                "sb", args[1], arg_ask_password);
293         if (r < 0)
294                 log_error("Failed to set time zone: %s", bus_error_message(&error, -r));
295
296         return r;
297 }
298
299 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
300         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
301         int r, b;
302
303         assert(args);
304         assert(n == 2);
305
306         polkit_agent_open_if_enabled();
307
308         b = parse_boolean(args[1]);
309         if (b < 0) {
310                 log_error("Failed to parse local RTC setting: %s", args[1]);
311                 return b;
312         }
313
314         r = sd_bus_call_method(bus,
315                                "org.freedesktop.timedate1",
316                                "/org/freedesktop/timedate1",
317                                "org.freedesktop.timedate1",
318                                "SetLocalRTC",
319                                &error,
320                                NULL,
321                                "bbb", b, arg_adjust_system_clock, arg_ask_password);
322         if (r < 0)
323                 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
324
325         return r;
326 }
327
328 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
329         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
330         int b, r;
331
332         assert(args);
333         assert(n == 2);
334
335         polkit_agent_open_if_enabled();
336
337         b = parse_boolean(args[1]);
338         if (b < 0) {
339                 log_error("Failed to parse NTP setting: %s", args[1]);
340                 return b;
341         }
342
343         r = sd_bus_call_method(bus,
344                                "org.freedesktop.timedate1",
345                                "/org/freedesktop/timedate1",
346                                "org.freedesktop.timedate1",
347                                "SetNTP",
348                                &error,
349                                NULL,
350                                "bb", b, arg_ask_password);
351         if (r < 0)
352                 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
353
354         return r;
355 }
356
357 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
358         _cleanup_fclose_ FILE *f = NULL;
359         _cleanup_strv_free_ char **zones = NULL;
360         size_t n_zones = 0;
361
362         assert(args);
363         assert(n == 1);
364
365         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
366         if (!f) {
367                 log_error("Failed to open time zone database: %m");
368                 return -errno;
369         }
370
371         for (;;) {
372                 char l[LINE_MAX], *p, **z, *w;
373                 size_t k;
374
375                 if (!fgets(l, sizeof(l), f)) {
376                         if (feof(f))
377                                 break;
378
379                         log_error("Failed to read time zone database: %m");
380                         return -errno;
381                 }
382
383                 p = strstrip(l);
384
385                 if (isempty(p) || *p == '#')
386                         continue;
387
388                 /* Skip over country code */
389                 p += strcspn(p, WHITESPACE);
390                 p += strspn(p, WHITESPACE);
391
392                 /* Skip over coordinates */
393                 p += strcspn(p, WHITESPACE);
394                 p += strspn(p, WHITESPACE);
395
396                 /* Found timezone name */
397                 k = strcspn(p, WHITESPACE);
398                 if (k <= 0)
399                         continue;
400
401                 w = strndup(p, k);
402                 if (!w)
403                         return log_oom();
404
405                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
406                 if (!z) {
407                         free(w);
408                         return log_oom();
409                 }
410
411                 zones = z;
412                 zones[n_zones++] = w;
413         }
414
415         if (zones)
416                 zones[n_zones] = NULL;
417
418         pager_open_if_enabled();
419
420         strv_sort(zones);
421         strv_print(zones);
422
423         return 0;
424 }
425
426 static int help(void) {
427
428         printf("%s [OPTIONS...] COMMAND ...\n\n"
429                "Query or change system time and date settings.\n\n"
430                "  -h --help                Show this help message\n"
431                "     --version             Show package version\n"
432                "     --no-pager            Do not pipe output into a pager\n"
433                "     --no-ask-password     Do not prompt for password\n"
434                "  -H --host=[USER@]HOST    Operate on remote host\n"
435                "  -M --machine=CONTAINER   Operate on local container\n"
436                "     --adjust-system-clock Adjust system clock when changing local RTC mode\n\n"
437                "Commands:\n"
438                "  status                   Show current time settings\n"
439                "  set-time TIME            Set system time\n"
440                "  set-timezone ZONE        Set system time zone\n"
441                "  list-timezones           Show known time zones\n"
442                "  set-local-rtc BOOL       Control whether RTC is in local time\n"
443                "  set-ntp BOOL             Control whether NTP is enabled\n",
444                program_invocation_short_name);
445
446         return 0;
447 }
448
449 static int parse_argv(int argc, char *argv[]) {
450
451         enum {
452                 ARG_VERSION = 0x100,
453                 ARG_NO_PAGER,
454                 ARG_ADJUST_SYSTEM_CLOCK,
455                 ARG_NO_ASK_PASSWORD
456         };
457
458         static const struct option options[] = {
459                 { "help",                no_argument,       NULL, 'h'                     },
460                 { "version",             no_argument,       NULL, ARG_VERSION             },
461                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
462                 { "host",                required_argument, NULL, 'H'                     },
463                 { "machine",             required_argument, NULL, 'M'                     },
464                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
465                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
466                 {}
467         };
468
469         int c;
470
471         assert(argc >= 0);
472         assert(argv);
473
474         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
475
476                 switch (c) {
477
478                 case 'h':
479                         return help();
480
481                 case ARG_VERSION:
482                         puts(PACKAGE_STRING);
483                         puts(SYSTEMD_FEATURES);
484                         return 0;
485
486                 case 'H':
487                         arg_transport = BUS_TRANSPORT_REMOTE;
488                         arg_host = optarg;
489                         break;
490
491                 case 'M':
492                         arg_transport = BUS_TRANSPORT_CONTAINER;
493                         arg_host = optarg;
494                         break;
495
496                 case ARG_NO_ASK_PASSWORD:
497                         arg_ask_password = false;
498                         break;
499
500                 case ARG_ADJUST_SYSTEM_CLOCK:
501                         arg_adjust_system_clock = true;
502                         break;
503
504                 case ARG_NO_PAGER:
505                         arg_no_pager = true;
506                         break;
507
508                 case '?':
509                         return -EINVAL;
510
511                 default:
512                         assert_not_reached("Unhandled option");
513                 }
514         }
515
516         return 1;
517 }
518
519 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
520
521         static const struct {
522                 const char* verb;
523                 const enum {
524                         MORE,
525                         LESS,
526                         EQUAL
527                 } argc_cmp;
528                 const int argc;
529                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
530         } verbs[] = {
531                 { "status",                LESS,   1, show_status      },
532                 { "set-time",              EQUAL,  2, set_time         },
533                 { "set-timezone",          EQUAL,  2, set_timezone     },
534                 { "list-timezones",        EQUAL,  1, list_timezones   },
535                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
536                 { "set-ntp",               EQUAL,  2, set_ntp,         },
537         };
538
539         int left;
540         unsigned i;
541
542         assert(argc >= 0);
543         assert(argv);
544
545         left = argc - optind;
546
547         if (left <= 0)
548                 /* Special rule: no arguments means "status" */
549                 i = 0;
550         else {
551                 if (streq(argv[optind], "help")) {
552                         help();
553                         return 0;
554                 }
555
556                 for (i = 0; i < ELEMENTSOF(verbs); i++)
557                         if (streq(argv[optind], verbs[i].verb))
558                                 break;
559
560                 if (i >= ELEMENTSOF(verbs)) {
561                         log_error("Unknown operation %s", argv[optind]);
562                         return -EINVAL;
563                 }
564         }
565
566         switch (verbs[i].argc_cmp) {
567
568         case EQUAL:
569                 if (left != verbs[i].argc) {
570                         log_error("Invalid number of arguments.");
571                         return -EINVAL;
572                 }
573
574                 break;
575
576         case MORE:
577                 if (left < verbs[i].argc) {
578                         log_error("Too few arguments.");
579                         return -EINVAL;
580                 }
581
582                 break;
583
584         case LESS:
585                 if (left > verbs[i].argc) {
586                         log_error("Too many arguments.");
587                         return -EINVAL;
588                 }
589
590                 break;
591
592         default:
593                 assert_not_reached("Unknown comparison operator.");
594         }
595
596         return verbs[i].dispatch(bus, argv + optind, left);
597 }
598
599 int main(int argc, char *argv[]) {
600         _cleanup_bus_unref_ sd_bus *bus = NULL;
601         int r;
602
603         setlocale(LC_ALL, "");
604         log_parse_environment();
605         log_open();
606
607         r = parse_argv(argc, argv);
608         if (r <= 0)
609                 goto finish;
610
611         r = bus_open_transport(arg_transport, arg_host, false, &bus);
612         if (r < 0) {
613                 log_error("Failed to create bus connection: %s", strerror(-r));
614                 goto finish;
615         }
616
617         r = timedatectl_main(bus, argc, argv);
618
619 finish:
620         pager_close();
621
622         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
623 }