chiark / gitweb /
timedatectl: ensure n_zones is initialized
[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         struct timex txc;
73
74         zero(txc);
75         if (adjtimex(&txc) < 0)
76                 return false;
77
78         if (txc.status & STA_UNSYNC)
79                 return false;
80
81         return true;
82 }
83
84 static void print_status_info(StatusInfo *i) {
85         usec_t n;
86         char b[FORMAT_TIMESTAMP_MAX];
87         struct tm tm;
88         time_t sec;
89         int r;
90
91         assert(i);
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         zero(info);
195         dbus_message_iter_recurse(&iter, &sub);
196
197         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
198                 const char *name;
199
200                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
201                         log_error("Failed to parse reply.");
202                         return -EIO;
203                 }
204
205                 dbus_message_iter_recurse(&sub, &sub2);
206
207                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
208                         log_error("Failed to parse reply.");
209                         return -EIO;
210                 }
211
212                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
213                         log_error("Failed to parse reply.");
214                         return -EIO;
215                 }
216
217                 dbus_message_iter_recurse(&sub2, &sub3);
218
219                 r = status_property(name, &sub3, &info);
220                 if (r < 0) {
221                         log_error("Failed to parse reply.");
222                         return r;
223                 }
224
225                 dbus_message_iter_next(&sub);
226         }
227
228         print_status_info(&info);
229         return 0;
230 }
231
232 static int set_time(DBusConnection *bus, char **args, unsigned n) {
233         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
234         dbus_bool_t relative = false, interactive = true;
235         usec_t t;
236         dbus_int64_t u;
237         int r;
238
239         assert(args);
240         assert(n == 2);
241
242         polkit_agent_open_if_enabled();
243
244         r = parse_timestamp(args[1], &t);
245         if (r < 0) {
246                 log_error("Failed to parse time specification: %s", args[1]);
247                 return r;
248         }
249
250         u = (dbus_uint64_t) t;
251
252         return bus_method_call_with_reply(
253                         bus,
254                         "org.freedesktop.timedate1",
255                         "/org/freedesktop/timedate1",
256                         "org.freedesktop.timedate1",
257                         "SetTime",
258                         &reply,
259                         NULL,
260                         DBUS_TYPE_INT64, &u,
261                         DBUS_TYPE_BOOLEAN, &relative,
262                         DBUS_TYPE_BOOLEAN, &interactive,
263                         DBUS_TYPE_INVALID);
264 }
265
266 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
267         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
268         dbus_bool_t interactive = true;
269
270         assert(args);
271         assert(n == 2);
272
273         polkit_agent_open_if_enabled();
274
275         return bus_method_call_with_reply(
276                         bus,
277                         "org.freedesktop.timedate1",
278                         "/org/freedesktop/timedate1",
279                         "org.freedesktop.timedate1",
280                         "SetTimezone",
281                         &reply,
282                         NULL,
283                         DBUS_TYPE_STRING, &args[1],
284                         DBUS_TYPE_BOOLEAN, &interactive,
285                         DBUS_TYPE_INVALID);
286 }
287
288 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
289         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
290         dbus_bool_t interactive = true, b, q;
291         int r;
292
293         assert(args);
294         assert(n == 2);
295
296         polkit_agent_open_if_enabled();
297
298         r = parse_boolean(args[1]);
299         if (r < 0) {
300                 log_error("Failed to parse local RTC setting: %s", args[1]);
301                 return r;
302         }
303
304         b = r;
305         q = arg_fix_system;
306
307         return bus_method_call_with_reply(
308                         bus,
309                         "org.freedesktop.timedate1",
310                         "/org/freedesktop/timedate1",
311                         "org.freedesktop.timedate1",
312                         "SetLocalRTC",
313                         &reply,
314                         NULL,
315                         DBUS_TYPE_BOOLEAN, &b,
316                         DBUS_TYPE_BOOLEAN, &q,
317                         DBUS_TYPE_BOOLEAN, &interactive,
318                         DBUS_TYPE_INVALID);
319 }
320
321 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
322         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
323         dbus_bool_t interactive = true, b;
324         int r;
325
326         assert(args);
327         assert(n == 2);
328
329         polkit_agent_open_if_enabled();
330
331         r = parse_boolean(args[1]);
332         if (r < 0) {
333                 log_error("Failed to parse NTP setting: %s", args[1]);
334                 return r;
335         }
336
337         b = r;
338
339         return bus_method_call_with_reply(
340                         bus,
341                         "org.freedesktop.timedate1",
342                         "/org/freedesktop/timedate1",
343                         "org.freedesktop.timedate1",
344                         "SetNTP",
345                         &reply,
346                         NULL,
347                         DBUS_TYPE_BOOLEAN, &b,
348                         DBUS_TYPE_BOOLEAN, &interactive,
349                         DBUS_TYPE_INVALID);
350 }
351
352 static int zone_compare(const void *_a, const void *_b) {
353         const char **a = (const char**) _a, **b = (const char**) _b;
354
355         return strcmp(*a, *b);
356 }
357
358 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
359         _cleanup_fclose_ FILE *f = NULL;
360         _cleanup_strv_free_ char **zones = NULL;
361         size_t n_zones = 0;
362         char **i;
363
364         assert(args);
365         assert(n == 1);
366
367         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
368         if (!f) {
369                 log_error("Failed to open timezone database: %m");
370                 return -errno;
371         }
372
373         for (;;) {
374                 char l[LINE_MAX], *p, **z, *w;
375                 size_t k;
376
377                 if (!fgets(l, sizeof(l), f)) {
378                         if (feof(f))
379                                 break;
380
381                         log_error("Failed to read timezone database: %m");
382                         return -errno;
383                 }
384
385                 p = strstrip(l);
386
387                 if (isempty(p) || *p == '#')
388                         continue;
389
390
391                 /* Skip over country code */
392                 p += strcspn(p, WHITESPACE);
393                 p += strspn(p, WHITESPACE);
394
395                 /* Skip over coordinates */
396                 p += strcspn(p, WHITESPACE);
397                 p += strspn(p, WHITESPACE);
398
399                 /* Found timezone name */
400                 k = strcspn(p, WHITESPACE);
401                 if (k <= 0)
402                         continue;
403
404                 w = strndup(p, k);
405                 if (!w)
406                         return log_oom();
407
408                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
409                 if (!z) {
410                         free(w);
411                         return log_oom();
412                 }
413
414                 zones = z;
415                 zones[n_zones++] = w;
416         }
417
418         if (zones)
419                 zones[n_zones] = 0;
420
421         qsort(zones, n_zones, sizeof(char*), zone_compare);
422
423         pager_open_if_enabled();
424
425         STRV_FOREACH(i, zones)
426                 puts(*i);
427
428         return 0;
429 }
430
431 static int help(void) {
432
433         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
434                "Query or control system time and date settings.\n\n"
435                "  -h --help              Show this help\n"
436                "     --version           Show package version\n"
437                "     --fix-system        Adjust system clock when changing local RTC mode\n"
438                "     --no-pager          Do not pipe output into a pager\n"
439                "     --no-ask-password   Do not prompt for password\n"
440                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
441                "Commands:\n"
442                "  status                          Show current time settings\n"
443                "  set-time [TIME]                 Set system time\n"
444                "  set-timezone [ZONE]             Set system timezone\n"
445                "  list-timezones                  Show known timezones\n"
446                "  set-local-rtc [BOOL]            Control whether RTC is in local time\n"
447                "  set-ntp [BOOL]                  Control whether NTP is enabled\n",
448                program_invocation_short_name);
449
450         return 0;
451 }
452
453 static int parse_argv(int argc, char *argv[]) {
454
455         enum {
456                 ARG_VERSION = 0x100,
457                 ARG_NO_PAGER,
458                 ARG_FIX_SYSTEM,
459                 ARG_NO_ASK_PASSWORD
460         };
461
462         static const struct option options[] = {
463                 { "help",            no_argument,       NULL, 'h'                 },
464                 { "version",         no_argument,       NULL, ARG_VERSION         },
465                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
466                 { "host",            required_argument, NULL, 'H'                 },
467                 { "privileged",      no_argument,       NULL, 'P'                 },
468                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
469                 { "fix-system",      no_argument,       NULL, ARG_FIX_SYSTEM      },
470                 { NULL,              0,                 NULL, 0                   }
471         };
472
473         int c;
474
475         assert(argc >= 0);
476         assert(argv);
477
478         while ((c = getopt_long(argc, argv, "+hp:as:H:P", options, NULL)) >= 0) {
479
480                 switch (c) {
481
482                 case 'h':
483                         help();
484                         return 0;
485
486                 case ARG_VERSION:
487                         puts(PACKAGE_STRING);
488                         puts(DISTRIBUTION);
489                         puts(SYSTEMD_FEATURES);
490                         return 0;
491
492                 case 'P':
493                         arg_transport = TRANSPORT_POLKIT;
494                         break;
495
496                 case 'H':
497                         arg_transport = TRANSPORT_SSH;
498                         arg_host = optarg;
499                         break;
500
501                 case ARG_FIX_SYSTEM:
502                         arg_fix_system = true;
503                         break;
504
505                 case ARG_NO_PAGER:
506                         arg_no_pager = true;
507                         break;
508
509                 case '?':
510                         return -EINVAL;
511
512                 default:
513                         log_error("Unknown option code %c", c);
514                         return -EINVAL;
515                 }
516         }
517
518         return 1;
519 }
520
521 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
522
523         static const struct {
524                 const char* verb;
525                 const enum {
526                         MORE,
527                         LESS,
528                         EQUAL
529                 } argc_cmp;
530                 const int argc;
531                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
532         } verbs[] = {
533                 { "status",                LESS,   1, show_status      },
534                 { "set-time",              EQUAL,  2, set_time         },
535                 { "set-timezone",          EQUAL,  2, set_timezone     },
536                 { "list-timezones",        EQUAL,  1, list_timezones   },
537                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
538                 { "set-ntp",               EQUAL,  2, set_ntp,         },
539         };
540
541         int left;
542         unsigned i;
543
544         assert(argc >= 0);
545         assert(argv);
546         assert(error);
547
548         left = argc - optind;
549
550         if (left <= 0)
551                 /* Special rule: no arguments means "status" */
552                 i = 0;
553         else {
554                 if (streq(argv[optind], "help")) {
555                         help();
556                         return 0;
557                 }
558
559                 for (i = 0; i < ELEMENTSOF(verbs); i++)
560                         if (streq(argv[optind], verbs[i].verb))
561                                 break;
562
563                 if (i >= ELEMENTSOF(verbs)) {
564                         log_error("Unknown operation %s", argv[optind]);
565                         return -EINVAL;
566                 }
567         }
568
569         switch (verbs[i].argc_cmp) {
570
571         case EQUAL:
572                 if (left != verbs[i].argc) {
573                         log_error("Invalid number of arguments.");
574                         return -EINVAL;
575                 }
576
577                 break;
578
579         case MORE:
580                 if (left < verbs[i].argc) {
581                         log_error("Too few arguments.");
582                         return -EINVAL;
583                 }
584
585                 break;
586
587         case LESS:
588                 if (left > verbs[i].argc) {
589                         log_error("Too many arguments.");
590                         return -EINVAL;
591                 }
592
593                 break;
594
595         default:
596                 assert_not_reached("Unknown comparison operator.");
597         }
598
599         if (!bus) {
600                 log_error("Failed to get D-Bus connection: %s", error->message);
601                 return -EIO;
602         }
603
604         return verbs[i].dispatch(bus, argv + optind, left);
605 }
606
607 int main(int argc, char *argv[]) {
608         int r, retval = EXIT_FAILURE;
609         DBusConnection *bus = NULL;
610         DBusError error;
611
612         dbus_error_init(&error);
613
614         log_parse_environment();
615         log_open();
616
617         r = parse_argv(argc, argv);
618         if (r < 0)
619                 goto finish;
620         else if (r == 0) {
621                 retval = EXIT_SUCCESS;
622                 goto finish;
623         }
624
625         if (arg_transport == TRANSPORT_NORMAL)
626                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
627         else if (arg_transport == TRANSPORT_POLKIT)
628                 bus_connect_system_polkit(&bus, &error);
629         else if (arg_transport == TRANSPORT_SSH)
630                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
631         else
632                 assert_not_reached("Uh, invalid transport...");
633
634         r = timedatectl_main(bus, argc, argv, &error);
635         retval = r < 0 ? EXIT_FAILURE : r;
636
637 finish:
638         if (bus) {
639                 dbus_connection_flush(bus);
640                 dbus_connection_close(bus);
641                 dbus_connection_unref(bus);
642         }
643
644         dbus_error_free(&error);
645         dbus_shutdown();
646
647         pager_close();
648
649         return retval;
650 }