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