chiark / gitweb /
journald.conf: remove MinSize= settings
[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 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                "     --adjust-system-clock\n"
438                "                         Adjust system clock when changing local RTC mode\n"
439                "     --no-pager          Do not pipe output into a pager\n"
440                "     --no-ask-password   Do not prompt for password\n"
441                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
442                "Commands:\n"
443                "  status                          Show current time settings\n"
444                "  set-time [TIME]                 Set system time\n"
445                "  set-timezone [ZONE]             Set system timezone\n"
446                "  list-timezones                  Show known timezones\n"
447                "  set-local-rtc [BOOL]            Control whether RTC is in local time\n"
448                "  set-ntp [BOOL]                  Control whether NTP is enabled\n",
449                program_invocation_short_name);
450
451         return 0;
452 }
453
454 static int parse_argv(int argc, char *argv[]) {
455
456         enum {
457                 ARG_VERSION = 0x100,
458                 ARG_NO_PAGER,
459                 ARG_ADJUST_SYSTEM_CLOCK,
460                 ARG_NO_ASK_PASSWORD
461         };
462
463         static const struct option options[] = {
464                 { "help",                no_argument,       NULL, 'h'                     },
465                 { "version",             no_argument,       NULL, ARG_VERSION             },
466                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
467                 { "host",                required_argument, NULL, 'H'                     },
468                 { "privileged",          no_argument,       NULL, 'P'                     },
469                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
470                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
471                 { NULL,                  0,                 NULL, 0                       }
472         };
473
474         int c;
475
476         assert(argc >= 0);
477         assert(argv);
478
479         while ((c = getopt_long(argc, argv, "+hp:as:H:P", options, NULL)) >= 0) {
480
481                 switch (c) {
482
483                 case 'h':
484                         help();
485                         return 0;
486
487                 case ARG_VERSION:
488                         puts(PACKAGE_STRING);
489                         puts(DISTRIBUTION);
490                         puts(SYSTEMD_FEATURES);
491                         return 0;
492
493                 case 'P':
494                         arg_transport = TRANSPORT_POLKIT;
495                         break;
496
497                 case 'H':
498                         arg_transport = TRANSPORT_SSH;
499                         arg_host = optarg;
500                         break;
501
502                 case ARG_ADJUST_SYSTEM_CLOCK:
503                         arg_adjust_system_clock = true;
504                         break;
505
506                 case ARG_NO_PAGER:
507                         arg_no_pager = true;
508                         break;
509
510                 case '?':
511                         return -EINVAL;
512
513                 default:
514                         log_error("Unknown option code %c", c);
515                         return -EINVAL;
516                 }
517         }
518
519         return 1;
520 }
521
522 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
523
524         static const struct {
525                 const char* verb;
526                 const enum {
527                         MORE,
528                         LESS,
529                         EQUAL
530                 } argc_cmp;
531                 const int argc;
532                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
533         } verbs[] = {
534                 { "status",                LESS,   1, show_status      },
535                 { "set-time",              EQUAL,  2, set_time         },
536                 { "set-timezone",          EQUAL,  2, set_timezone     },
537                 { "list-timezones",        EQUAL,  1, list_timezones   },
538                 { "set-local-rtc",         EQUAL,  2, set_local_rtc    },
539                 { "set-ntp",               EQUAL,  2, set_ntp,         },
540         };
541
542         int left;
543         unsigned i;
544
545         assert(argc >= 0);
546         assert(argv);
547         assert(error);
548
549         left = argc - optind;
550
551         if (left <= 0)
552                 /* Special rule: no arguments means "status" */
553                 i = 0;
554         else {
555                 if (streq(argv[optind], "help")) {
556                         help();
557                         return 0;
558                 }
559
560                 for (i = 0; i < ELEMENTSOF(verbs); i++)
561                         if (streq(argv[optind], verbs[i].verb))
562                                 break;
563
564                 if (i >= ELEMENTSOF(verbs)) {
565                         log_error("Unknown operation %s", argv[optind]);
566                         return -EINVAL;
567                 }
568         }
569
570         switch (verbs[i].argc_cmp) {
571
572         case EQUAL:
573                 if (left != verbs[i].argc) {
574                         log_error("Invalid number of arguments.");
575                         return -EINVAL;
576                 }
577
578                 break;
579
580         case MORE:
581                 if (left < verbs[i].argc) {
582                         log_error("Too few arguments.");
583                         return -EINVAL;
584                 }
585
586                 break;
587
588         case LESS:
589                 if (left > verbs[i].argc) {
590                         log_error("Too many arguments.");
591                         return -EINVAL;
592                 }
593
594                 break;
595
596         default:
597                 assert_not_reached("Unknown comparison operator.");
598         }
599
600         if (!bus) {
601                 log_error("Failed to get D-Bus connection: %s", error->message);
602                 return -EIO;
603         }
604
605         return verbs[i].dispatch(bus, argv + optind, left);
606 }
607
608 int main(int argc, char *argv[]) {
609         int r, retval = EXIT_FAILURE;
610         DBusConnection *bus = NULL;
611         DBusError error;
612
613         dbus_error_init(&error);
614
615         log_parse_environment();
616         log_open();
617
618         r = parse_argv(argc, argv);
619         if (r < 0)
620                 goto finish;
621         else if (r == 0) {
622                 retval = EXIT_SUCCESS;
623                 goto finish;
624         }
625
626         if (arg_transport == TRANSPORT_NORMAL)
627                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
628         else if (arg_transport == TRANSPORT_POLKIT)
629                 bus_connect_system_polkit(&bus, &error);
630         else if (arg_transport == TRANSPORT_SSH)
631                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
632         else
633                 assert_not_reached("Uh, invalid transport...");
634
635         r = timedatectl_main(bus, argc, argv, &error);
636         retval = r < 0 ? EXIT_FAILURE : r;
637
638 finish:
639         if (bus) {
640                 dbus_connection_flush(bus);
641                 dbus_connection_close(bus);
642                 dbus_connection_unref(bus);
643         }
644
645         dbus_error_free(&error);
646         dbus_shutdown();
647
648         pager_close();
649
650         return retval;
651 }