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