chiark / gitweb /
set: introduce strv_sort()
[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_adjust_system_clock = 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_adjust_system_clock;
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 list_timezones(DBusConnection *bus, char **args, unsigned n) {
353         _cleanup_fclose_ FILE *f = NULL;
354         _cleanup_strv_free_ char **zones = NULL;
355         size_t n_zones = 0;
356         char **i;
357
358         assert(args);
359         assert(n == 1);
360
361         f = fopen("/usr/share/zoneinfo/zone.tab", "re");
362         if (!f) {
363                 log_error("Failed to open timezone database: %m");
364                 return -errno;
365         }
366
367         for (;;) {
368                 char l[LINE_MAX], *p, **z, *w;
369                 size_t k;
370
371                 if (!fgets(l, sizeof(l), f)) {
372                         if (feof(f))
373                                 break;
374
375                         log_error("Failed to read timezone database: %m");
376                         return -errno;
377                 }
378
379                 p = strstrip(l);
380
381                 if (isempty(p) || *p == '#')
382                         continue;
383
384
385                 /* Skip over country code */
386                 p += strcspn(p, WHITESPACE);
387                 p += strspn(p, WHITESPACE);
388
389                 /* Skip over coordinates */
390                 p += strcspn(p, WHITESPACE);
391                 p += strspn(p, WHITESPACE);
392
393                 /* Found timezone name */
394                 k = strcspn(p, WHITESPACE);
395                 if (k <= 0)
396                         continue;
397
398                 w = strndup(p, k);
399                 if (!w)
400                         return log_oom();
401
402                 z = realloc(zones, sizeof(char*) * (n_zones + 2));
403                 if (!z) {
404                         free(w);
405                         return log_oom();
406                 }
407
408                 zones = z;
409                 zones[n_zones++] = w;
410         }
411
412         if (zones)
413                 zones[n_zones] = NULL;
414
415         pager_open_if_enabled();
416
417         strv_sort(zones);
418         STRV_FOREACH(i, zones)
419                 puts(*i);
420
421         return 0;
422 }
423
424 static int help(void) {
425
426         printf("%s [OPTIONS...] COMMAND ...\n\n"
427                "Query or change system time and date settings.\n\n"
428                "  -h --help              Show this help\n"
429                "     --version           Show package version\n"
430                "     --adjust-system-clock\n"
431                "                         Adjust system clock when changing local RTC mode\n"
432                "     --no-pager          Do not pipe output into a pager\n"
433                "     --no-ask-password   Do not prompt for password\n"
434                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
435                "Commands:\n"
436                "  status                 Show current time settings\n"
437                "  set-time TIME          Set system time\n"
438                "  set-timezone ZONE      Set system timezone\n"
439                "  list-timezones         Show known timezones\n"
440                "  set-local-rtc BOOL     Control whether RTC is in local time\n"
441                "  set-ntp BOOL           Control whether NTP is enabled\n",
442                program_invocation_short_name);
443
444         return 0;
445 }
446
447 static int parse_argv(int argc, char *argv[]) {
448
449         enum {
450                 ARG_VERSION = 0x100,
451                 ARG_NO_PAGER,
452                 ARG_ADJUST_SYSTEM_CLOCK,
453                 ARG_NO_ASK_PASSWORD
454         };
455
456         static const struct option options[] = {
457                 { "help",                no_argument,       NULL, 'h'                     },
458                 { "version",             no_argument,       NULL, ARG_VERSION             },
459                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
460                 { "host",                required_argument, NULL, 'H'                     },
461                 { "privileged",          no_argument,       NULL, 'P'                     },
462                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
463                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
464                 { NULL,                  0,                 NULL, 0                       }
465         };
466
467         int c;
468
469         assert(argc >= 0);
470         assert(argv);
471
472         while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
473
474                 switch (c) {
475
476                 case 'h':
477                         help();
478                         return 0;
479
480                 case ARG_VERSION:
481                         puts(PACKAGE_STRING);
482                         puts(DISTRIBUTION);
483                         puts(SYSTEMD_FEATURES);
484                         return 0;
485
486                 case 'P':
487                         arg_transport = TRANSPORT_POLKIT;
488                         break;
489
490                 case 'H':
491                         arg_transport = TRANSPORT_SSH;
492                         arg_host = optarg;
493                         break;
494
495                 case ARG_ADJUST_SYSTEM_CLOCK:
496                         arg_adjust_system_clock = true;
497                         break;
498
499                 case ARG_NO_PAGER:
500                         arg_no_pager = true;
501                         break;
502
503                 case '?':
504                         return -EINVAL;
505
506                 default:
507                         log_error("Unknown option code %c", c);
508                         return -EINVAL;
509                 }
510         }
511
512         return 1;
513 }
514
515 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
516
517         static const struct {
518                 const char* verb;
519                 const enum {
520                         MORE,
521                         LESS,
522                         EQUAL
523                 } argc_cmp;
524                 const int argc;
525                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
526         } verbs[] = {
527                 { "status",                LESS,   1, show_status      },
528                 { "set-time",              EQUAL,  2, set_time         },
529                 { "set-timezone",          EQUAL,  2, set_timezone     },
530                 { "list-timezones",        EQUAL,  1, list_timezones   },
531                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
532                 { "set-ntp",               EQUAL,  2, set_ntp,         },
533         };
534
535         int left;
536         unsigned i;
537
538         assert(argc >= 0);
539         assert(argv);
540         assert(error);
541
542         left = argc - optind;
543
544         if (left <= 0)
545                 /* Special rule: no arguments means "status" */
546                 i = 0;
547         else {
548                 if (streq(argv[optind], "help")) {
549                         help();
550                         return 0;
551                 }
552
553                 for (i = 0; i < ELEMENTSOF(verbs); i++)
554                         if (streq(argv[optind], verbs[i].verb))
555                                 break;
556
557                 if (i >= ELEMENTSOF(verbs)) {
558                         log_error("Unknown operation %s", argv[optind]);
559                         return -EINVAL;
560                 }
561         }
562
563         switch (verbs[i].argc_cmp) {
564
565         case EQUAL:
566                 if (left != verbs[i].argc) {
567                         log_error("Invalid number of arguments.");
568                         return -EINVAL;
569                 }
570
571                 break;
572
573         case MORE:
574                 if (left < verbs[i].argc) {
575                         log_error("Too few arguments.");
576                         return -EINVAL;
577                 }
578
579                 break;
580
581         case LESS:
582                 if (left > verbs[i].argc) {
583                         log_error("Too many arguments.");
584                         return -EINVAL;
585                 }
586
587                 break;
588
589         default:
590                 assert_not_reached("Unknown comparison operator.");
591         }
592
593         if (!bus) {
594                 log_error("Failed to get D-Bus connection: %s", error->message);
595                 return -EIO;
596         }
597
598         return verbs[i].dispatch(bus, argv + optind, left);
599 }
600
601 int main(int argc, char *argv[]) {
602         int r, retval = EXIT_FAILURE;
603         DBusConnection *bus = NULL;
604         DBusError error;
605
606         dbus_error_init(&error);
607
608         log_parse_environment();
609         log_open();
610
611         r = parse_argv(argc, argv);
612         if (r < 0)
613                 goto finish;
614         else if (r == 0) {
615                 retval = EXIT_SUCCESS;
616                 goto finish;
617         }
618
619         if (arg_transport == TRANSPORT_NORMAL)
620                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
621         else if (arg_transport == TRANSPORT_POLKIT)
622                 bus_connect_system_polkit(&bus, &error);
623         else if (arg_transport == TRANSPORT_SSH)
624                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
625         else
626                 assert_not_reached("Uh, invalid transport...");
627
628         r = timedatectl_main(bus, argc, argv, &error);
629         retval = r < 0 ? EXIT_FAILURE : r;
630
631 finish:
632         if (bus) {
633                 dbus_connection_flush(bus);
634                 dbus_connection_close(bus);
635                 dbus_connection_unref(bus);
636         }
637
638         dbus_error_free(&error);
639         dbus_shutdown();
640
641         pager_close();
642
643         return retval;
644 }