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