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