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