chiark / gitweb /
timedatectl: introduce new command line client for timedated
[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
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <string.h>
27 #include <sys/timex.h>
28
29 #include "dbus-common.h"
30 #include "util.h"
31 #include "spawn-polkit-agent.h"
32 #include "build.h"
33 #include "hwclock.h"
34 #include "strv.h"
35 #include "pager.h"
36
37 static bool arg_fix_system = false;
38 static bool arg_no_pager = false;
39 static enum transport {
40         TRANSPORT_NORMAL,
41         TRANSPORT_SSH,
42         TRANSPORT_POLKIT
43 } arg_transport = TRANSPORT_NORMAL;
44 static bool arg_ask_password = true;
45 static const char *arg_host = NULL;
46
47 static void pager_open_if_enabled(void) {
48
49         if (arg_no_pager)
50                 return;
51
52         pager_open();
53 }
54
55 static void polkit_agent_open_if_enabled(void) {
56
57         /* Open the polkit agent as a child process if necessary */
58
59         if (!arg_ask_password)
60                 return;
61
62         polkit_agent_open();
63 }
64
65 typedef struct StatusInfo {
66         const char *timezone;
67         bool local_rtc;
68         bool ntp;
69 } StatusInfo;
70
71 static bool ntp_synced(void) {
72
73         struct timex txc;
74
75         zero(txc);
76
77         if (adjtimex(&txc) < 0)
78                 return false;
79
80         if (txc.status & STA_UNSYNC)
81                 return false;
82
83         return true;
84 }
85
86 static void print_status_info(StatusInfo *i) {
87         usec_t n;
88         char b[FORMAT_TIMESTAMP_MAX];
89         struct tm tm;
90         time_t sec;
91         int r;
92
93         n = now(CLOCK_REALTIME);
94         sec = (time_t) (n / USEC_PER_SEC);
95
96         zero(tm);
97         assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
98         char_array_0(b);
99         printf("      Local time: %s\n", b);
100
101         zero(tm);
102         assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
103         char_array_0(b);
104         printf("  Universal time: %s\n", b);
105
106         zero(tm);
107         r = hwclock_get_time(&tm);
108         if (r >= 0) {
109                 /* Calculcate the week-day */
110                 mktime(&tm);
111
112                 assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S", &tm) > 0);
113                 char_array_0(b);
114                 printf("        RTC time: %s\n", b);
115         }
116
117         printf("        Timezone: %s\n"
118                "     NTP enabled: %s\n"
119                "NTP synchronized: %s\n"
120                " RTC in local TZ: %s\n",
121                strna(i->timezone),
122                yes_no(i->ntp),
123                yes_no(ntp_synced()),
124                yes_no(i->local_rtc));
125
126         if (i->local_rtc)
127                 fputs("\n" ANSI_HIGHLIGHT_ON
128                       "Warning: The RTC is configured to maintain time in the local time zone. This\n"
129                       "         mode is not fully supported and will create various problems with time\n"
130                       "         zone changes and daylight saving adjustments. If at all possible use\n"
131                       "         RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
132 }
133
134 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
135         assert(name);
136         assert(iter);
137
138         switch (dbus_message_iter_get_arg_type(iter)) {
139
140         case DBUS_TYPE_STRING: {
141                 const char *s;
142
143                 dbus_message_iter_get_basic(iter, &s);
144                 if (!isempty(s)) {
145                         if (streq(name, "Timezone"))
146                                 i->timezone = s;
147                 }
148                 break;
149         }
150
151         case DBUS_TYPE_BOOLEAN: {
152                 dbus_bool_t b;
153
154                 dbus_message_iter_get_basic(iter, &b);
155                 if (streq(name, "LocalRTC"))
156                         i->local_rtc = b;
157                 else if (streq(name, "NTP"))
158                         i->ntp = b;
159         }
160         }
161
162         return 0;
163 }
164
165 static int show_status(DBusConnection *bus, char **args, unsigned n) {
166         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
167         const char *interface = "";
168         int r;
169         DBusMessageIter iter, sub, sub2, sub3;
170         StatusInfo info;
171
172         assert(args);
173
174         r = bus_method_call_with_reply(
175                         bus,
176                         "org.freedesktop.timedate1",
177                         "/org/freedesktop/timedate1",
178                         "org.freedesktop.DBus.Properties",
179                         "GetAll",
180                         &reply,
181                         NULL,
182                         DBUS_TYPE_STRING, &interface,
183                         DBUS_TYPE_INVALID);
184         if (r < 0)
185                 return r;
186
187         if (!dbus_message_iter_init(reply, &iter) ||
188             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
189             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
190                 log_error("Failed to parse reply.");
191                 return -EIO;
192         }
193
194         dbus_message_iter_recurse(&iter, &sub);
195
196         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
197                 const char *name;
198
199                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
200                         log_error("Failed to parse reply.");
201                         return -EIO;
202                 }
203
204                 dbus_message_iter_recurse(&sub, &sub2);
205
206                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
207                         log_error("Failed to parse reply.");
208                         return -EIO;
209                 }
210
211                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
212                         log_error("Failed to parse reply.");
213                         return -EIO;
214                 }
215
216                 dbus_message_iter_recurse(&sub2, &sub3);
217
218                 r = status_property(name, &sub3, &info);
219                 if (r < 0) {
220                         log_error("Failed to parse reply.");
221                         return r;
222                 }
223
224                 dbus_message_iter_next(&sub);
225         }
226
227         print_status_info(&info);
228         return 0;
229 }
230
231 static int set_time(DBusConnection *bus, char **args, unsigned n) {
232         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
233         dbus_bool_t relative = false, interactive = true;
234         usec_t t;
235         dbus_int64_t u;
236         int r;
237
238         assert(args);
239         assert(n == 2);
240
241         polkit_agent_open_if_enabled();
242
243         r = parse_timestamp(args[1], &t);
244         if (r < 0) {
245                 log_error("Failed to parse time specification: %s", args[1]);
246                 return r;
247         }
248
249         u = (dbus_uint64_t) t;
250
251         return bus_method_call_with_reply(
252                         bus,
253                         "org.freedesktop.timedate1",
254                         "/org/freedesktop/timedate1",
255                         "org.freedesktop.timedate1",
256                         "SetTime",
257                         &reply,
258                         NULL,
259                         DBUS_TYPE_INT64, &u,
260                         DBUS_TYPE_BOOLEAN, &relative,
261                         DBUS_TYPE_BOOLEAN, &interactive,
262                         DBUS_TYPE_INVALID);
263 }
264
265 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
266         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
267         dbus_bool_t interactive = true;
268
269         assert(args);
270         assert(n == 2);
271
272         polkit_agent_open_if_enabled();
273
274         return bus_method_call_with_reply(
275                         bus,
276                         "org.freedesktop.timedate1",
277                         "/org/freedesktop/timedate1",
278                         "org.freedesktop.timedate1",
279                         "SetTimezone",
280                         &reply,
281                         NULL,
282                         DBUS_TYPE_STRING, &args[1],
283                         DBUS_TYPE_BOOLEAN, &interactive,
284                         DBUS_TYPE_INVALID);
285 }
286
287 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
288         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
289         dbus_bool_t interactive = true, b, q;
290         int r;
291
292         assert(args);
293         assert(n == 2);
294
295         polkit_agent_open_if_enabled();
296
297         r = parse_boolean(args[1]);
298         if (r < 0) {
299                 log_error("Failed to parse local RTC setting: %s", args[1]);
300                 return r;
301         }
302
303         b = r;
304         q = arg_fix_system;
305
306         return bus_method_call_with_reply(
307                         bus,
308                         "org.freedesktop.timedate1",
309                         "/org/freedesktop/timedate1",
310                         "org.freedesktop.timedate1",
311                         "SetLocalRTC",
312                         &reply,
313                         NULL,
314                         DBUS_TYPE_BOOLEAN, &b,
315                         DBUS_TYPE_BOOLEAN, &q,
316                         DBUS_TYPE_BOOLEAN, &interactive,
317                         DBUS_TYPE_INVALID);
318 }
319
320 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
321         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
322         dbus_bool_t interactive = true, b;
323         int r;
324
325         assert(args);
326         assert(n == 2);
327
328         polkit_agent_open_if_enabled();
329
330         r = parse_boolean(args[1]);
331         if (r < 0) {
332                 log_error("Failed to parse NTP setting: %s", args[1]);
333                 return r;
334         }
335
336         b = r;
337
338         return bus_method_call_with_reply(
339                         bus,
340                         "org.freedesktop.timedate1",
341                         "/org/freedesktop/timedate1",
342                         "org.freedesktop.timedate1",
343                         "SetNTP",
344                         &reply,
345                         NULL,
346                         DBUS_TYPE_BOOLEAN, &b,
347                         DBUS_TYPE_BOOLEAN, &interactive,
348                         DBUS_TYPE_INVALID);
349 }
350
351 static int zone_compare(const void *_a, const void *_b) {
352         const char **a = (const char**) _a, **b = (const char**) _b;
353
354         return strcmp(*a, *b);
355 }
356
357 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
358         _cleanup_fclose_ FILE *f = NULL;
359         _cleanup_strv_free_ char **zones = NULL;
360         size_t n_zones;
361         char **i;
362
363         assert(args);
364         assert(n == 1);
365
366         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
367         if (!f) {
368                 log_error("Failed to open timezone database: %m");
369                 return -errno;
370         }
371
372         for (;;) {
373                 char l[LINE_MAX], *p, **z, *w;
374                 size_t k;
375
376                 if (!fgets(l, sizeof(l), f)) {
377                         if (feof(f))
378                                 break;
379
380                         log_error("Failed to read timezone database: %m");
381                         return -errno;
382                 }
383
384                 p = strstrip(l);
385
386                 if (isempty(p) || *p == '#')
387                         continue;
388
389
390                 /* Skip over country code */
391                 p += strcspn(p, WHITESPACE);
392                 p += strspn(p, WHITESPACE);
393
394                 /* Skip over coordinates */
395                 p += strcspn(p, WHITESPACE);
396                 p += strspn(p, WHITESPACE);
397
398                 /* Found timezone name */
399                 k = strcspn(p, WHITESPACE);
400                 if (k <= 0)
401                         continue;
402
403                 w = strndup(p, k);
404                 if (!w)
405                         return log_oom();
406
407                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
408                 if (!z) {
409                         free(w);
410                         return log_oom();
411                 }
412
413                 zones = z;
414                 zones[n_zones++] = w;
415         }
416
417         if (zones)
418                 zones[n_zones] = 0;
419
420         qsort(zones, n_zones, sizeof(char*), zone_compare);
421
422         pager_open_if_enabled();
423
424         STRV_FOREACH(i, zones)
425                 puts(*i);
426
427         return 0;
428 }
429
430 static int help(void) {
431
432         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
433                "Query or control system time and date settings.\n\n"
434                "  -h --help              Show this help\n"
435                "     --version           Show package version\n"
436                "     --fix-system        Adjust system clock when changing local RTC mode\n"
437                "     --no-pager          Do not pipe output into a pager\n"
438                "     --no-ask-password   Do not prompt for password\n"
439                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
440                "Commands:\n"
441                "  status                          Show current time settings\n"
442                "  set-time [TIME]                 Set system time\n"
443                "  set-timezone [ZONE]             Set system timezone\n"
444                "  list-timezones                  Show known timezones\n"
445                "  set-local-rtc [BOOL]            Control whether RTC is in local time\n"
446                "  set-ntp [BOOL]                  Control whether NTP is enabled\n",
447                program_invocation_short_name);
448
449         return 0;
450 }
451
452 static int parse_argv(int argc, char *argv[]) {
453
454         enum {
455                 ARG_VERSION = 0x100,
456                 ARG_NO_PAGER,
457                 ARG_FIX_SYSTEM,
458                 ARG_NO_ASK_PASSWORD
459         };
460
461         static const struct option options[] = {
462                 { "help",            no_argument,       NULL, 'h'                 },
463                 { "version",         no_argument,       NULL, ARG_VERSION         },
464                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
465                 { "host",            required_argument, NULL, 'H'                 },
466                 { "privileged",      no_argument,       NULL, 'P'                 },
467                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
468                 { "fix-system",      no_argument,       NULL, ARG_FIX_SYSTEM      },
469                 { NULL,              0,                 NULL, 0                   }
470         };
471
472         int c;
473
474         assert(argc >= 0);
475         assert(argv);
476
477         while ((c = getopt_long(argc, argv, "+hp:as:H:P", options, NULL)) >= 0) {
478
479                 switch (c) {
480
481                 case 'h':
482                         help();
483                         return 0;
484
485                 case ARG_VERSION:
486                         puts(PACKAGE_STRING);
487                         puts(DISTRIBUTION);
488                         puts(SYSTEMD_FEATURES);
489                         return 0;
490
491                 case 'P':
492                         arg_transport = TRANSPORT_POLKIT;
493                         break;
494
495                 case 'H':
496                         arg_transport = TRANSPORT_SSH;
497                         arg_host = optarg;
498                         break;
499
500                 case ARG_FIX_SYSTEM:
501                         arg_fix_system = true;
502                         break;
503
504                 case ARG_NO_PAGER:
505                         arg_no_pager = true;
506                         break;
507
508                 case '?':
509                         return -EINVAL;
510
511                 default:
512                         log_error("Unknown option code %c", c);
513                         return -EINVAL;
514                 }
515         }
516
517         return 1;
518 }
519
520 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
521
522         static const struct {
523                 const char* verb;
524                 const enum {
525                         MORE,
526                         LESS,
527                         EQUAL
528                 } argc_cmp;
529                 const int argc;
530                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
531         } verbs[] = {
532                 { "status",                LESS,   1, show_status      },
533                 { "set-time",              EQUAL,  2, set_time         },
534                 { "set-timezone",          EQUAL,  2, set_timezone     },
535                 { "list-timezones",        EQUAL,  1, list_timezones   },
536                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
537                 { "set-ntp",               EQUAL,  2, set_ntp,         },
538         };
539
540         int left;
541         unsigned i;
542
543         assert(argc >= 0);
544         assert(argv);
545         assert(error);
546
547         left = argc - optind;
548
549         if (left <= 0)
550                 /* Special rule: no arguments means "status" */
551                 i = 0;
552         else {
553                 if (streq(argv[optind], "help")) {
554                         help();
555                         return 0;
556                 }
557
558                 for (i = 0; i < ELEMENTSOF(verbs); i++)
559                         if (streq(argv[optind], verbs[i].verb))
560                                 break;
561
562                 if (i >= ELEMENTSOF(verbs)) {
563                         log_error("Unknown operation %s", argv[optind]);
564                         return -EINVAL;
565                 }
566         }
567
568         switch (verbs[i].argc_cmp) {
569
570         case EQUAL:
571                 if (left != verbs[i].argc) {
572                         log_error("Invalid number of arguments.");
573                         return -EINVAL;
574                 }
575
576                 break;
577
578         case MORE:
579                 if (left < verbs[i].argc) {
580                         log_error("Too few arguments.");
581                         return -EINVAL;
582                 }
583
584                 break;
585
586         case LESS:
587                 if (left > verbs[i].argc) {
588                         log_error("Too many arguments.");
589                         return -EINVAL;
590                 }
591
592                 break;
593
594         default:
595                 assert_not_reached("Unknown comparison operator.");
596         }
597
598         if (!bus) {
599                 log_error("Failed to get D-Bus connection: %s", error->message);
600                 return -EIO;
601         }
602
603         return verbs[i].dispatch(bus, argv + optind, left);
604 }
605
606 int main(int argc, char *argv[]) {
607         int r, retval = EXIT_FAILURE;
608         DBusConnection *bus = NULL;
609         DBusError error;
610
611         dbus_error_init(&error);
612
613         log_parse_environment();
614         log_open();
615
616         r = parse_argv(argc, argv);
617         if (r < 0)
618                 goto finish;
619         else if (r == 0) {
620                 retval = EXIT_SUCCESS;
621                 goto finish;
622         }
623
624         if (arg_transport == TRANSPORT_NORMAL)
625                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
626         else if (arg_transport == TRANSPORT_POLKIT)
627                 bus_connect_system_polkit(&bus, &error);
628         else if (arg_transport == TRANSPORT_SSH)
629                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
630         else
631                 assert_not_reached("Uh, invalid transport...");
632
633         r = timedatectl_main(bus, argc, argv, &error);
634         retval = r < 0 ? EXIT_FAILURE : r;
635
636 finish:
637         if (bus) {
638                 dbus_connection_flush(bus);
639                 dbus_connection_close(bus);
640                 dbus_connection_unref(bus);
641         }
642
643         dbus_error_free(&error);
644         dbus_shutdown();
645
646         pager_close();
647
648         return retval;
649 }