]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
timedatectl: add timesync-status and show-timesync commands
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 3 May 2018 09:07:43 +0000 (18:07 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 3 May 2018 09:07:43 +0000 (18:07 +0900)
Closes #1589.

meson.build
src/timedate/timedatectl.c

index 0086f774832df312b00ebc3b7a02c721fa3a59ff..3fa4f7f3271cd1aa97c365e4b7630c729f4d9407 100644 (file)
@@ -1237,6 +1237,13 @@ foreach term : ['utmp',
         m4_defines += have ? ['-D' + name] : []
 endforeach
 
+if get_option('timedated') or get_option('timesyncd')
+        conf.set10('ENABLE_TIMEDATECTL', true)
+        m4_defines += ['-DENABLE_TIMEDATECTL']
+else
+        conf.set10('ENABLE_TIMEDATECTL', false)
+endif
+
 want_tests = get_option('tests')
 install_tests = get_option('install-tests')
 slow_tests = get_option('slow-tests')
@@ -1863,12 +1870,15 @@ if conf.get('ENABLE_TIMEDATED') == 1
                    install_rpath : rootlibexecdir,
                    install : true,
                    install_dir : rootlibexecdir)
+endif
 
+if conf.get('ENABLE_TIMEDATECTL') == 1
         exe = executable('timedatectl',
                          'src/timedate/timedatectl.c',
                          include_directories : includes,
                          install_rpath : rootlibexecdir,
                          link_with : [libshared],
+                         dependencies : [libm],
                          install : true)
         public_programs += [exe]
 endif
index 9a803d122138f2454ecd2fe719d1025b478ef8a4..e52a64a9c50f2ebc5e3d3419319b10783591a7ed 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <getopt.h>
 #include <locale.h>
+#include <math.h>
 #include <stdbool.h>
 #include <stdlib.h>
 
 
 #include "bus-error.h"
 #include "bus-util.h"
+#include "in-addr-util.h"
 #include "pager.h"
 #include "parse-util.h"
 #include "spawn-polkit-agent.h"
+#include "sparse-endian.h"
+#include "string-table.h"
 #include "strv.h"
 #include "terminal-util.h"
 #include "util.h"
@@ -28,6 +32,10 @@ static bool arg_ask_password = true;
 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
 static char *arg_host = NULL;
 static bool arg_adjust_system_clock = false;
+static bool arg_monitor = false;
+static char **arg_property = NULL;
+static bool arg_value = false;
+static bool arg_all = false;
 
 typedef struct StatusInfo {
         usec_t time;
@@ -272,6 +280,405 @@ static int list_timezones(int argc, char **argv, void *userdata) {
         return 0;
 }
 
+typedef struct NTPStatusInfo {
+        const char *server_name;
+        char *server_address;
+        usec_t poll_interval, poll_max, poll_min;
+        usec_t root_distance_max;
+
+        uint32_t leap, version, mode, stratum;
+        int32_t precision;
+        usec_t root_delay, root_dispersion;
+        union {
+                char str[5];
+                uint32_t val;
+        } reference;
+        usec_t origin, recv, trans, dest;
+
+        bool spike;
+        uint64_t packet_count;
+        usec_t jitter;
+
+        int64_t freq;
+} NTPStatusInfo;
+
+static void ntp_status_info_clear(NTPStatusInfo *p) {
+        p->server_address = mfree(p->server_address);
+}
+
+static const char * const ntp_leap_table[4] = {
+        [0] = "normal",
+        [1] = "last minute of the day has 61 seconds",
+        [2] = "last minute of the day has 59 seconds",
+        [3] = "not synchronized",
+};
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(ntp_leap, uint32_t);
+#pragma GCC diagnostic pop
+
+static void print_ntp_status_info(NTPStatusInfo *i) {
+        char ts[FORMAT_TIMESPAN_MAX], tmin[FORMAT_TIMESPAN_MAX], tmax[FORMAT_TIMESPAN_MAX];
+        usec_t delay, t14, t23, offset, root_distance;
+        bool offset_sign;
+
+        assert(i);
+
+        /*
+         * "Timestamp Name          ID   When Generated
+         *  ------------------------------------------------------------
+         *  Originate Timestamp     T1   time request sent by client
+         *  Receive Timestamp       T2   time request received by server
+         *  Transmit Timestamp      T3   time reply sent by server
+         *  Destination Timestamp   T4   time reply received by client
+         *
+         *  The round-trip delay, d, and system clock offset, t, are defined as:
+         *  d = (T4 - T1) - (T3 - T2)     t = ((T2 - T1) + (T3 - T4)) / 2"
+         */
+
+        printf("       Server: %s (%s)\n",
+               i->server_address, i->server_name);
+        printf("Poll interval: %s (min: %s; max %s)\n",
+               format_timespan(ts, sizeof(ts), i->poll_interval, 0),
+               format_timespan(tmin, sizeof(tmin), i->poll_min, 0),
+               format_timespan(tmax, sizeof(tmax), i->poll_max, 0));
+
+        if (i->packet_count == 0) {
+                printf(" Packet count: 0\n");
+                return;
+        }
+
+        if (i->dest < i->origin || i->trans < i->recv || i->dest - i->origin < i->trans - i->recv) {
+                log_error("Invalid NTP response");
+                return;
+        }
+
+        delay = (i->dest - i->origin) - (i->trans - i->recv);
+
+        t14 = i->origin + i->dest;
+        t23 = i->recv + i->trans;
+        offset_sign = t14 < t23;
+        offset = (offset_sign ? t23 - t14 : t14 - t23) / 2;
+
+        root_distance = i->root_delay / 2 + i->root_dispersion;
+
+        printf("         Leap: %s\n"
+               "      Version: %" PRIu32 "\n"
+               "      Stratum: %" PRIu32 "\n",
+               ntp_leap_to_string(i->leap),
+               i->version,
+               i->stratum);
+        if (i->stratum <= 1)
+                printf("    Reference: %s\n", i->reference.str);
+        else
+                printf("    Reference: %" PRIX32 "\n", be32toh(i->reference.val));
+        printf("    Precision: %s (%" PRIi32 ")\n",
+               format_timespan(ts, sizeof(ts), DIV_ROUND_UP((nsec_t) (exp2(i->precision) * NSEC_PER_SEC), NSEC_PER_USEC), 0),
+               i->precision);
+        printf("Root distance: %s (max: %s)\n",
+               format_timespan(ts, sizeof(ts), root_distance, 0),
+               format_timespan(tmax, sizeof(tmax), i->root_distance_max, 0));
+        printf("       Offset: %s%s\n",
+               offset_sign ? "+" : "-",
+               format_timespan(ts, sizeof(ts), offset, 0));
+        printf("        Delay: %s\n",
+               format_timespan(ts, sizeof(ts), delay, 0));
+        printf("       Jitter: %s\n",
+               format_timespan(ts, sizeof(ts), i->jitter, 0));
+        printf(" Packet count: %" PRIu64 "\n", i->packet_count);
+
+        if (!i->spike)
+                printf("    Frequency: %+.3fppm\n",
+                       (double) i->freq / 0x10000);
+}
+
+static int map_server_address(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+        char **p = (char **) userdata;
+        const void *d;
+        int family, r;
+        size_t sz;
+
+        assert(p);
+
+        r = sd_bus_message_enter_container(m, 'r', "iay");
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read(m, "i", &family);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read_array(m, 'y', &d, &sz);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_exit_container(m);
+        if (r < 0)
+                return r;
+
+        if (sz == 0 && family == AF_UNSPEC) {
+                *p = mfree(*p);
+                return 0;
+        }
+
+        if (!IN_SET(family, AF_INET, AF_INET6)) {
+                log_error("Unknown address family %i", family);
+                return -EINVAL;
+        }
+
+        if (sz != FAMILY_ADDRESS_SIZE(family)) {
+                log_error("Invalid address size");
+                return -EINVAL;
+        }
+
+        r = in_addr_to_string(family, d, p);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int map_ntp_message(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+        NTPStatusInfo *p = userdata;
+        const void *d;
+        size_t sz;
+        int32_t b;
+        int r;
+
+        assert(p);
+
+        r = sd_bus_message_enter_container(m, 'r', "uuuuittayttttbtt");
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read(m, "uuuuitt",
+                                &p->leap, &p->version, &p->mode, &p->stratum, &p->precision,
+                                &p->root_delay, &p->root_dispersion);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read_array(m, 'y', &d, &sz);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read(m, "ttttbtt",
+                                &p->origin, &p->recv, &p->trans, &p->dest,
+                                &b, &p->packet_count, &p->jitter);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_exit_container(m);
+        if (r < 0)
+                return r;
+
+        if (sz != 4)
+                return -EINVAL;
+
+        memcpy(p->reference.str, d, sz);
+
+        p->spike = !!b;
+
+        return 0;
+}
+
+static int show_timesync_status_once(sd_bus *bus) {
+        static const struct bus_properties_map map_timesync[]  = {
+                { "ServerName",           "s",                  NULL,               offsetof(NTPStatusInfo, server_name)       },
+                { "ServerAddress",        "(iay)",              map_server_address, offsetof(NTPStatusInfo, server_address)    },
+                { "PollIntervalUSec",     "t",                  NULL,               offsetof(NTPStatusInfo, poll_interval)     },
+                { "PollIntervalMinUSec",  "t",                  NULL,               offsetof(NTPStatusInfo, poll_min)          },
+                { "PollIntervalMaxUSec",  "t",                  NULL,               offsetof(NTPStatusInfo, poll_max)          },
+                { "RootDistanceMaxUSec",  "t",                  NULL,               offsetof(NTPStatusInfo, root_distance_max) },
+                { "NTPMessage",           "(uuuuittayttttbtt)", map_ntp_message,    0                                          },
+                { "Frequency",            "x",                  NULL,               offsetof(NTPStatusInfo, freq)              },
+                {}
+        };
+        _cleanup_(ntp_status_info_clear) NTPStatusInfo info = {};
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+        int r;
+
+        assert(bus);
+
+        r = bus_map_all_properties(bus,
+                                   "org.freedesktop.timesync1",
+                                   "/org/freedesktop/timesync1",
+                                   map_timesync,
+                                   BUS_MAP_BOOLEAN_AS_BOOL,
+                                   &error,
+                                   &m,
+                                   &info);
+        if (r < 0)
+                return log_error_errno(r, "Failed to query server: %s", bus_error_message(&error, r));
+
+        if (arg_monitor && !terminal_is_dumb())
+                fputs(ANSI_HOME_CLEAR, stdout);
+
+        print_ntp_status_info(&info);
+
+        return 0;
+}
+
+static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+        const char *name;
+        int r;
+
+        assert(m);
+
+        r = sd_bus_message_read(m, "s", &name);
+        if (r < 0)
+                return log_error_errno(r, "Failed to read interface name: %m");
+
+        if (!streq_ptr(name, "org.freedesktop.timesync1.Manager"))
+                return 0;
+
+        return show_timesync_status_once(sd_bus_message_get_bus(m));
+}
+
+static int show_timesync_status(int argc, char **argv, void *userdata) {
+        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+        sd_bus *bus = userdata;
+        int r;
+
+        assert(bus);
+
+        r = show_timesync_status_once(bus);
+        if (r < 0)
+                return r;
+
+        if (!arg_monitor)
+                return 0;
+
+        r = sd_event_default(&event);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get event loop: %m");
+
+        r = sd_bus_match_signal(bus,
+                                NULL,
+                                "org.freedesktop.timesync1",
+                                "/org/freedesktop/timesync1",
+                                "org.freedesktop.DBus.Properties",
+                                "PropertiesChanged",
+                                on_properties_changed, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to request match for PropertiesChanged signal: %m");
+
+        r = sd_bus_attach_event(bus, event, SD_EVENT_PRIORITY_NORMAL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to attach bus to event loop: %m");
+
+        r = sd_event_loop(event);
+        if (r < 0)
+                return log_error_errno(r, "Failed to run event loop: %m");
+
+        return 0;
+}
+
+#define property(name, fmt, ...)                                        \
+        do {                                                            \
+                if (value)                                              \
+                        printf(fmt "\n", __VA_ARGS__);                  \
+                else                                                    \
+                        printf("%s=" fmt "\n", name, __VA_ARGS__);      \
+        } while (0)
+
+static int print_timesync_property(const char *name, sd_bus_message *m, bool value, bool all) {
+        char type;
+        const char *contents;
+        int r;
+
+        assert(name);
+        assert(m);
+
+        r = sd_bus_message_peek_type(m, &type, &contents);
+        if (r < 0)
+                return r;
+
+        switch (type) {
+
+        case SD_BUS_TYPE_STRUCT:
+                if (streq(name, "NTPMessage")) {
+                        _cleanup_(ntp_status_info_clear) NTPStatusInfo i = {};
+                        char ts[FORMAT_TIMESPAN_MAX], stamp[FORMAT_TIMESTAMP_MAX];
+
+                        r = map_ntp_message(NULL, NULL, m, NULL, &i);
+                        if (r < 0)
+                                return r;
+
+                        if (i.packet_count == 0)
+                                return 1;
+
+                        if (!value) {
+                                fputs(name, stdout);
+                                fputc('=', stdout);
+                        }
+
+                        printf("{ Leap=%u, Version=%u, Mode=%u, Stratum=%u, Precision=%i,",
+                               i.leap, i.version, i.mode, i.stratum, i.precision);
+                        printf(" RootDelay=%s,",
+                               format_timespan(ts, sizeof(ts), i.root_delay, 0));
+                        printf(" RootDispersion=%s,",
+                               format_timespan(ts, sizeof(ts), i.root_dispersion, 0));
+
+                        if (i.stratum == 1)
+                                printf(" Reference=%s,", i.reference.str);
+                        else
+                                printf(" Reference=%" PRIX32 ",", be32toh(i.reference.val));
+
+                        printf(" OriginateTimestamp=%s,",
+                               format_timestamp(stamp, sizeof(stamp), i.origin));
+                        printf(" ReceiveTimestamp=%s,",
+                               format_timestamp(stamp, sizeof(stamp), i.recv));
+                        printf(" TransmitTimestamp=%s,",
+                               format_timestamp(stamp, sizeof(stamp), i.trans));
+                        printf(" DestinationTimestamp=%s,",
+                               format_timestamp(stamp, sizeof(stamp), i.dest));
+                        printf(" Ignored=%s PacketCount=%" PRIu64 ",",
+                               yes_no(i.spike), i.packet_count);
+                        printf(" Jitter=%s }\n",
+                               format_timespan(ts, sizeof(ts), i.jitter, 0));
+
+                        return 1;
+
+                } else if (streq(name, "ServerAddress")) {
+                        _cleanup_free_ char *str = NULL;
+
+                        r = map_server_address(NULL, NULL, m, NULL, &str);
+                        if (r < 0)
+                                return r;
+
+                        if (arg_all || !isempty(str))
+                                property(name, "%s", str);
+
+                        return 1;
+                }
+                break;
+        }
+
+        return 0;
+}
+
+static int show_timesync(int argc, char **argv, void *userdata) {
+        sd_bus *bus = userdata;
+        int r;
+
+        assert(bus);
+
+        r = bus_print_all_properties(bus,
+                                     "org.freedesktop.timesync1",
+                                     "/org/freedesktop/timesync1",
+                                     print_timesync_property,
+                                     arg_property,
+                                     arg_value,
+                                     arg_all,
+                                     NULL);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        return 0;
+}
+
 static int help(void) {
         printf("%s [OPTIONS...] COMMAND ...\n\n"
                "Query or change system time and date settings.\n\n"
@@ -281,15 +688,24 @@ static int help(void) {
                "     --no-ask-password     Do not prompt for password\n"
                "  -H --host=[USER@]HOST    Operate on remote host\n"
                "  -M --machine=CONTAINER   Operate on local container\n"
-               "     --adjust-system-clock Adjust system clock when changing local RTC mode\n\n"
+               "     --adjust-system-clock Adjust system clock when changing local RTC mode\n"
+               "     --monitor             Monitor status of systemd-timesyncd\n"
+               "  -p --property=NAME       Show only properties by this name\n"
+               "  -a --all                 Show all properties, including empty ones\n"
+               "     --value               When showing properties, only print the value\n"
+               "\n"
                "Commands:\n"
                "  status                   Show current time settings\n"
                "  set-time TIME            Set system time\n"
                "  set-timezone ZONE        Set system time zone\n"
                "  list-timezones           Show known time zones\n"
                "  set-local-rtc BOOL       Control whether RTC is in local time\n"
-               "  set-ntp BOOL             Enable or disable network time synchronization\n",
-               program_invocation_short_name);
+               "  set-ntp BOOL             Enable or disable network time synchronization\n"
+               "\n"
+               "systemd-timesyncd Commands:\n"
+               "  timesync-status          Show status of systemd-timesyncd\n"
+               "  show-timesync            Show properties of systemd-timesyncd\n"
+               , program_invocation_short_name);
 
         return 0;
 }
@@ -304,7 +720,9 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VERSION = 0x100,
                 ARG_NO_PAGER,
                 ARG_ADJUST_SYSTEM_CLOCK,
-                ARG_NO_ASK_PASSWORD
+                ARG_NO_ASK_PASSWORD,
+                ARG_MONITOR,
+                ARG_VALUE,
         };
 
         static const struct option options[] = {
@@ -315,15 +733,19 @@ static int parse_argv(int argc, char *argv[]) {
                 { "machine",             required_argument, NULL, 'M'                     },
                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
                 { "adjust-system-clock", no_argument,       NULL, ARG_ADJUST_SYSTEM_CLOCK },
+                { "monitor",             no_argument,       NULL, ARG_MONITOR             },
+                { "property",            required_argument, NULL, 'p'                     },
+                { "all",                 no_argument,       NULL, 'a'                     },
+                { "value",               no_argument,       NULL, ARG_VALUE               },
                 {}
         };
 
-        int c;
+        int c, r;
 
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hH:M:p:a", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -355,6 +777,30 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_no_pager = true;
                         break;
 
+                case ARG_MONITOR:
+                        arg_monitor = true;
+                        break;
+
+                case 'p': {
+                        r = strv_extend(&arg_property, optarg);
+                        if (r < 0)
+                                return log_oom();
+
+                        /* If the user asked for a particular
+                         * property, show it to him, even if it is
+                         * empty. */
+                        arg_all = true;
+                        break;
+                }
+
+                case 'a':
+                        arg_all = true;
+                        break;
+
+                case ARG_VALUE:
+                        arg_value = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -368,13 +814,15 @@ static int parse_argv(int argc, char *argv[]) {
 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
 
         static const Verb verbs[] = {
-                { "status",         VERB_ANY, 1,        VERB_DEFAULT, show_status    },
-                { "set-time",       2,        2,        0,            set_time       },
-                { "set-timezone",   2,        2,        0,            set_timezone   },
-                { "list-timezones", VERB_ANY, 1,        0,            list_timezones },
-                { "set-local-rtc",  2,        2,        0,            set_local_rtc  },
-                { "set-ntp",        2,        2,        0,            set_ntp        },
-                { "help",           VERB_ANY, VERB_ANY, 0,            verb_help      }, /* Not documented, but supported since it is created. */
+                { "status",          VERB_ANY, 1,        VERB_DEFAULT, show_status          },
+                { "set-time",        2,        2,        0,            set_time             },
+                { "set-timezone",    2,        2,        0,            set_timezone         },
+                { "list-timezones",  VERB_ANY, 1,        0,            list_timezones       },
+                { "set-local-rtc",   2,        2,        0,            set_local_rtc        },
+                { "set-ntp",         2,        2,        0,            set_ntp              },
+                { "timesync-status", VERB_ANY, 1,        0,            show_timesync_status },
+                { "show-timesync",   VERB_ANY, 1,        0,            show_timesync        },
+                { "help",            VERB_ANY, VERB_ANY, 0,            verb_help            }, /* Not documented, but supported since it is created. */
                 {}
         };