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