chiark / gitweb /
polkit: don't spawn local client if we access a remote system
[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         char *zc, *zn;
102         time_t t, tc, tn;
103         int dn;
104         bool is_dstc, is_dstn;
105         int r;
106
107         assert(i);
108
109         /* Enforce the values of /etc/localtime */
110         if (getenv("TZ")) {
111                 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
112                 unsetenv("TZ");
113         }
114
115         sec = (time_t) (i->time / USEC_PER_SEC);
116
117         zero(tm);
118         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
119         char_array_0(a);
120         printf("      Local time: %s\n", a);
121
122         zero(tm);
123         assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
124         char_array_0(a);
125         printf("  Universal time: %s\n", a);
126
127         if (i->rtc_time > 0) {
128                 time_t rtc_sec;
129
130                 rtc_sec = (time_t)(i->rtc_time / USEC_PER_SEC);
131                 zero(tm);
132                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)) > 0);
133                 char_array_0(a);
134                 printf("        RTC time: %s\n", a);
135         } else
136                 printf("        RTC time: n/a\n");
137
138         zero(tm);
139         assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
140         char_array_0(a);
141         printf("        Timezone: %s (%s)\n"
142                "     NTP enabled: %s\n"
143                "NTP synchronized: %s\n"
144                " RTC in local TZ: %s\n",
145                strna(i->timezone), a,
146                i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
147                yes_no(i->ntp_synced),
148                yes_no(i->rtc_local));
149
150         r = time_get_dst(sec, "/etc/localtime",
151                          &tc, &zc, &is_dstc,
152                          &tn, &dn, &zn, &is_dstn);
153         if (r < 0)
154                 printf("      DST active: n/a\n");
155         else {
156                 printf("      DST active: %s\n", yes_no(is_dstc));
157
158                 t = tc - 1;
159                 zero(tm);
160                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
161                 char_array_0(a);
162
163                 zero(tm);
164                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
165                 char_array_0(b);
166                 printf(" Last DST change: DST %s at\n"
167                        "                  %s\n"
168                        "                  %s\n",
169                        is_dstc ? "began" : "ended", a, b);
170
171                 t = tn - 1;
172                 zero(tm);
173                 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
174                 char_array_0(a);
175
176                 zero(tm);
177                 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
178                 char_array_0(b);
179                 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
180                        "                  %s\n"
181                        "                  %s\n",
182                        is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
183
184                 free(zc);
185                 free(zn);
186         }
187
188         if (i->rtc_local)
189                 fputs("\n" ANSI_HIGHLIGHT_ON
190                       "Warning: The RTC is configured to maintain time in the local timezone. This\n"
191                       "         mode is not fully supported and will create various problems with time\n"
192                       "         zone changes and daylight saving adjustments. If at all possible use\n"
193                       "         RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
194 }
195
196 static int show_status(sd_bus *bus, char **args, unsigned n) {
197         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
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                 goto fail;
220
221         print_status_info(&info);
222
223 fail:
224         free(info.timezone);
225         return r;
226 }
227
228 static int set_time(sd_bus *bus, char **args, unsigned n) {
229         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
230         bool relative = false, interactive = arg_ask_password;
231         usec_t t;
232         int r;
233
234         assert(args);
235         assert(n == 2);
236
237         polkit_agent_open_if_enabled();
238
239         r = parse_timestamp(args[1], &t);
240         if (r < 0) {
241                 log_error("Failed to parse time specification: %s", args[1]);
242                 return r;
243         }
244
245         r = sd_bus_call_method(bus,
246                                "org.freedesktop.timedate1",
247                                "/org/freedesktop/timedate1",
248                                "org.freedesktop.timedate1",
249                                "SetTime",
250                                &error,
251                                NULL,
252                                "xbb", (int64_t)t, relative, interactive);
253         if (r < 0)
254                 log_error("Failed to set time: %s", bus_error_message(&error, -r));
255
256         return r;
257 }
258
259 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
260         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
261         int r;
262
263         assert(args);
264         assert(n == 2);
265
266         polkit_agent_open_if_enabled();
267
268         r = sd_bus_call_method(bus,
269                                "org.freedesktop.timedate1",
270                                "/org/freedesktop/timedate1",
271                                "org.freedesktop.timedate1",
272                                "SetTimezone",
273                                &error,
274                                NULL,
275                                "sb", args[1], arg_ask_password);
276         if (r < 0)
277                 log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
278
279         return r;
280 }
281
282 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
283         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
284         int r, b;
285
286         assert(args);
287         assert(n == 2);
288
289         polkit_agent_open_if_enabled();
290
291         b = parse_boolean(args[1]);
292         if (b < 0) {
293                 log_error("Failed to parse local RTC setting: %s", args[1]);
294                 return b;
295         }
296
297         r = sd_bus_call_method(bus,
298                                "org.freedesktop.timedate1",
299                                "/org/freedesktop/timedate1",
300                                "org.freedesktop.timedate1",
301                                "SetLocalRTC",
302                                &error,
303                                NULL,
304                                "bbb", b, arg_adjust_system_clock, arg_ask_password);
305         if (r < 0)
306                 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
307
308         return r;
309 }
310
311 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
312         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
313         int b, r;
314
315         assert(args);
316         assert(n == 2);
317
318         polkit_agent_open_if_enabled();
319
320         b = parse_boolean(args[1]);
321         if (b < 0) {
322                 log_error("Failed to parse NTP setting: %s", args[1]);
323                 return b;
324         }
325
326         r = sd_bus_call_method(bus,
327                                "org.freedesktop.timedate1",
328                                "/org/freedesktop/timedate1",
329                                "org.freedesktop.timedate1",
330                                "SetNTP",
331                                &error,
332                                NULL,
333                                "bb", b, arg_ask_password);
334         if (r < 0)
335                 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
336
337         return r;
338 }
339
340 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
341         _cleanup_fclose_ FILE *f = NULL;
342         _cleanup_strv_free_ char **zones = NULL;
343         size_t n_zones = 0;
344
345         assert(args);
346         assert(n == 1);
347
348         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
349         if (!f) {
350                 log_error("Failed to open timezone database: %m");
351                 return -errno;
352         }
353
354         for (;;) {
355                 char l[LINE_MAX], *p, **z, *w;
356                 size_t k;
357
358                 if (!fgets(l, sizeof(l), f)) {
359                         if (feof(f))
360                                 break;
361
362                         log_error("Failed to read timezone database: %m");
363                         return -errno;
364                 }
365
366                 p = strstrip(l);
367
368                 if (isempty(p) || *p == '#')
369                         continue;
370
371
372                 /* Skip over country code */
373                 p += strcspn(p, WHITESPACE);
374                 p += strspn(p, WHITESPACE);
375
376                 /* Skip over coordinates */
377                 p += strcspn(p, WHITESPACE);
378                 p += strspn(p, WHITESPACE);
379
380                 /* Found timezone name */
381                 k = strcspn(p, WHITESPACE);
382                 if (k <= 0)
383                         continue;
384
385                 w = strndup(p, k);
386                 if (!w)
387                         return log_oom();
388
389                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
390                 if (!z) {
391                         free(w);
392                         return log_oom();
393                 }
394
395                 zones = z;
396                 zones[n_zones++] = w;
397         }
398
399         if (zones)
400                 zones[n_zones] = NULL;
401
402         pager_open_if_enabled();
403
404         strv_sort(zones);
405         strv_print(zones);
406
407         return 0;
408 }
409
410 static int help(void) {
411
412         printf("%s [OPTIONS...] COMMAND ...\n\n"
413                "Query or change system time and date settings.\n\n"
414                "  -h --help              Show this help\n"
415                "     --version           Show package version\n"
416                "     --adjust-system-clock\n"
417                "                         Adjust system clock when changing local RTC mode\n"
418                "     --no-pager          Do not pipe output into a pager\n"
419                "     --no-ask-password   Do not prompt for password\n"
420                "  -H --host=[USER@]HOST  Operate on remote host\n"
421                "  -M --machine=CONTAINER Operate on local container\n\n"
422                "Commands:\n"
423                "  status                 Show current time settings\n"
424                "  set-time TIME          Set system time\n"
425                "  set-timezone ZONE      Set system timezone\n"
426                "  list-timezones         Show known timezones\n"
427                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
428                "  set-ntp BOOL           Control whether NTP is enabled\n",
429                program_invocation_short_name);
430
431         return 0;
432 }
433
434 static int parse_argv(int argc, char *argv[]) {
435
436         enum {
437                 ARG_VERSION = 0x100,
438                 ARG_NO_PAGER,
439                 ARG_ADJUST_SYSTEM_CLOCK,
440                 ARG_NO_ASK_PASSWORD
441         };
442
443         static const struct option options[] = {
444                 { "help",                no_argument,       NULL, 'h'                     },
445                 { "version",             no_argument,       NULL, ARG_VERSION             },
446                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
447                 { "host",                required_argument, NULL, 'H'                     },
448                 { "machine",             required_argument, NULL, 'M'                     },
449                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
450                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
451                 {}
452         };
453
454         int c;
455
456         assert(argc >= 0);
457         assert(argv);
458
459         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
460
461                 switch (c) {
462
463                 case 'h':
464                         return help();
465
466                 case ARG_VERSION:
467                         puts(PACKAGE_STRING);
468                         puts(SYSTEMD_FEATURES);
469                         return 0;
470
471                 case 'H':
472                         arg_transport = BUS_TRANSPORT_REMOTE;
473                         arg_host = optarg;
474                         break;
475
476                 case 'M':
477                         arg_transport = BUS_TRANSPORT_CONTAINER;
478                         arg_host = optarg;
479                         break;
480
481                 case ARG_NO_ASK_PASSWORD:
482                         arg_ask_password = false;
483                         break;
484
485                 case ARG_ADJUST_SYSTEM_CLOCK:
486                         arg_adjust_system_clock = true;
487                         break;
488
489                 case ARG_NO_PAGER:
490                         arg_no_pager = true;
491                         break;
492
493                 case '?':
494                         return -EINVAL;
495
496                 default:
497                         assert_not_reached("Unhandled option");
498                 }
499         }
500
501         return 1;
502 }
503
504 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
505
506         static const struct {
507                 const char* verb;
508                 const enum {
509                         MORE,
510                         LESS,
511                         EQUAL
512                 } argc_cmp;
513                 const int argc;
514                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
515         } verbs[] = {
516                 { "status",                LESS,   1, show_status      },
517                 { "set-time",              EQUAL,  2, set_time         },
518                 { "set-timezone",          EQUAL,  2, set_timezone     },
519                 { "list-timezones",        EQUAL,  1, list_timezones   },
520                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
521                 { "set-ntp",               EQUAL,  2, set_ntp,         },
522         };
523
524         int left;
525         unsigned i;
526
527         assert(argc >= 0);
528         assert(argv);
529
530         left = argc - optind;
531
532         if (left <= 0)
533                 /* Special rule: no arguments means "status" */
534                 i = 0;
535         else {
536                 if (streq(argv[optind], "help")) {
537                         help();
538                         return 0;
539                 }
540
541                 for (i = 0; i < ELEMENTSOF(verbs); i++)
542                         if (streq(argv[optind], verbs[i].verb))
543                                 break;
544
545                 if (i >= ELEMENTSOF(verbs)) {
546                         log_error("Unknown operation %s", argv[optind]);
547                         return -EINVAL;
548                 }
549         }
550
551         switch (verbs[i].argc_cmp) {
552
553         case EQUAL:
554                 if (left != verbs[i].argc) {
555                         log_error("Invalid number of arguments.");
556                         return -EINVAL;
557                 }
558
559                 break;
560
561         case MORE:
562                 if (left < verbs[i].argc) {
563                         log_error("Too few arguments.");
564                         return -EINVAL;
565                 }
566
567                 break;
568
569         case LESS:
570                 if (left > verbs[i].argc) {
571                         log_error("Too many arguments.");
572                         return -EINVAL;
573                 }
574
575                 break;
576
577         default:
578                 assert_not_reached("Unknown comparison operator.");
579         }
580
581         return verbs[i].dispatch(bus, argv + optind, left);
582 }
583
584 int main(int argc, char *argv[]) {
585         _cleanup_bus_unref_ sd_bus *bus = NULL;
586         int r;
587
588         setlocale(LC_ALL, "");
589         log_parse_environment();
590         log_open();
591
592         r = parse_argv(argc, argv);
593         if (r <= 0)
594                 goto finish;
595
596         r = bus_open_transport(arg_transport, arg_host, false, &bus);
597         if (r < 0) {
598                 log_error("Failed to create bus connection: %s", strerror(-r));
599                 goto finish;
600         }
601
602         r = timedatectl_main(bus, argc, argv);
603
604 finish:
605         pager_close();
606
607         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
608 }