[Linuxptp-devel] [PATCH v4 11/11] Introduce a time zone helper program.
PTP IEEE 1588 stack for Linux
Brought to you by:
rcochran
|
From: Richard C. <ric...@gm...> - 2023-01-28 22:44:17
|
The ptp4l program supports up to four time zones via the
ALTERNATE_TIME_OFFSET_INDICATOR TLV. Introduce a helper program that
leverages the local time zone database to monitor for changes in
daylight savings time and publishing them.
Signed-off-by: Richard Cochran <ric...@gm...>
---
makefile | 7 +-
tztool.c | 377 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 382 insertions(+), 2 deletions(-)
create mode 100644 tztool.c
diff --git a/makefile b/makefile
index ba3fb38..f03939a 100644
--- a/makefile
+++ b/makefile
@@ -22,7 +22,7 @@ CC = $(CROSS_COMPILE)gcc
VER = -DVER=$(version)
CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS)
LDLIBS = -lm -lrt -pthread $(EXTRA_LDFLAGS)
-PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster ts2phc
+PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster ts2phc tztool
FILTERS = filter.o mave.o mmedian.o
SERVOS = linreg.o ntpshm.o nullf.o pi.o servo.o
TRANSP = raw.o transport.o udp.o udp6.o uds.o
@@ -35,7 +35,7 @@ OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \
unicast_fsm.o unicast_service.o util.o version.o
OBJECTS = $(OBJ) hwstamp_ctl.o nsm.o phc2sys.o phc_ctl.o pmc.o pmc_agent.o \
- pmc_common.o sysoff.o timemaster.o $(TS2PHC)
+ pmc_common.o sysoff.o timemaster.o $(TS2PHC) tztool.o
SRC = $(OBJECTS:.o=.c)
DEPEND = $(OBJECTS:.o=.d)
srcdir := $(dir $(lastword $(MAKEFILE_LIST)))
@@ -72,6 +72,9 @@ ts2phc: config.o clockadj.o hash.o interface.o msg.o phc.o pmc_agent.o \
pmc_common.o print.o $(SERVOS) sk.o $(TS2PHC) tlv.o transport.o raw.o \
udp.o udp6.o uds.o util.o version.o
+tztool: config.o hash.o interface.o lstab.o msg.o phc.o pmc_common.o print.o \
+ sk.o tlv.o $(TRANSP) tztool.o util.o version.o
+
version.o: .version version.sh $(filter-out version.d,$(DEPEND))
.version: force
diff --git a/tztool.c b/tztool.c
new file mode 100644
index 0000000..a7a2a69
--- /dev/null
+++ b/tztool.c
@@ -0,0 +1,377 @@
+/**
+ * @file tztool.c
+ * @note Copyright (C) 2021 Richard Cochran <ric...@gm...>
+ * @note SPDX-License-Identifier: GPL-2.0+
+ */
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "lstab.h"
+#include "pmc_common.h"
+#include "print.h"
+#include "version.h"
+#include "tz.h"
+
+#define DEFAULT_TZ "PST8PDT"
+#define DEFAULT_PERIOD 3600
+#define DEFAULT_WINDOW (3600 * 24 * 30 * 3)
+
+static int key_field, period = DEFAULT_PERIOD, window = DEFAULT_WINDOW;
+static char uds_local[MAX_IFNAME_SIZE + 1];
+static struct lstab *lstab;
+static struct config *cfg;
+
+struct tzinfo {
+ const char *name;
+ char display_name[MAX_TZ_DISPLAY_NAME + 1];
+ time_t timestamp;
+
+ /* Following fields populated by get_offsets. */
+
+ time_t local_utc_offset;
+ time_t local_tai_offset;
+ int tai_utc_offset;
+ enum lstab_result tai_result;
+};
+
+static int get_offsets(struct tzinfo *tz);
+
+static bool offsets_equal(struct tzinfo *a, struct tzinfo *b)
+{
+ return a->local_utc_offset == b->local_utc_offset;
+}
+
+static bool find_next_discontinuity(struct tzinfo *tz, struct tzinfo *next)
+{
+ time_t i, j, n;
+ bool gt;
+
+ next->timestamp = tz->timestamp + window;
+ get_offsets(next);
+ if (offsets_equal(tz, next)) {
+ return false;
+ }
+
+ i = 0;
+ j = window;
+
+ while (1) {
+ next->timestamp = tz->timestamp + i;
+ get_offsets(next);
+ gt = offsets_equal(tz, next);
+ if (gt) {
+ n = j - i - 1;
+ } else {
+ j = i;
+ i = 0;
+ n = j - i - 1;
+ }
+ if (!n) {
+ if (gt) {
+ next->timestamp++;
+ get_offsets(next);
+ }
+ break;
+ }
+ i += (n + 1) / 2;
+ }
+
+ return true;
+}
+
+static int get_offsets(struct tzinfo *tz)
+{
+ struct tm tm = {0};
+ time_t t2;
+
+ tz->tai_result = lstab_utc2tai(lstab, tz->timestamp,
+ &tz->tai_utc_offset);
+ if (tz->tai_result == LSTAB_UNKNOWN) {
+ pr_err("leap second table is stale");
+ return -1;
+ }
+
+ setenv("TZ", tz->name, 1);
+ tzset();
+ if (!localtime_r(&tz->timestamp, &tm)) {
+ return -1;
+ }
+
+ setenv("TZ", "UTC", 1);
+ tzset();
+ t2 = mktime(&tm);
+ tz->local_utc_offset = t2 - tz->timestamp;
+ tz->local_tai_offset = tz->local_utc_offset - tz->tai_utc_offset;
+
+ return 0;
+}
+
+static int get_unambiguous_time(struct tzinfo *tz)
+{
+ int err;
+
+ do {
+ tz->timestamp = time(NULL);
+ err = get_offsets(tz);
+ } while (tz->tai_result == LSTAB_AMBIGUOUS);
+
+ return err;
+}
+
+static void show_timezone_info(const char *label, struct tzinfo *tz)
+{
+ pr_debug("%s %s ts %ld local-utc %ld tai-utc %d local-tai %ld %s",
+ label,
+ tz->name,
+ tz->timestamp,
+ tz->local_utc_offset,
+ tz->tai_utc_offset,
+ tz->local_tai_offset,
+ tz->tai_result == LSTAB_OK ? "valid" : "invalid");
+}
+
+/* Returns true if display name was truncated. */
+static bool tz_set_name(struct tzinfo *tz, const char *name)
+{
+ const char *suffix;
+ int len;
+
+ memset(tz->display_name, 0, sizeof(tz->display_name));
+ tz->name = name;
+
+ len = strlen(name);
+ if (len <= MAX_TZ_DISPLAY_NAME) {
+ strncpy(tz->display_name, name, sizeof(tz->display_name) - 1);
+ return false;
+ }
+
+ /*
+ * The displayName field is limited to 10 characters, but
+ * there are many valid time zone names like "Europe/Vienna".
+ * Use the suffix if present.
+ */
+ suffix = strchr(name, '/');
+ if (suffix) {
+ suffix++;
+ len = strlen(suffix);
+ if (len > 0 && len <= MAX_TZ_DISPLAY_NAME) {
+ strncpy(tz->display_name, suffix,
+ sizeof(tz->display_name) - 1);
+ return true;
+ }
+ }
+
+ /* No nice suffix to be found, so just truncate. */
+ strncpy(tz->display_name, name, sizeof(tz->display_name) - 1);
+
+ return true;
+}
+
+static int update_ptp_serivce(struct tzinfo *tz, struct tzinfo *next)
+{
+ struct alternate_time_offset_properties atop;
+ struct management_tlv_datum mtd;
+ struct pmc *pmc;
+ int err;
+
+ pmc = pmc_create(cfg, TRANS_UDS, uds_local, 0,
+ config_get_int(cfg, NULL, "domainNumber"),
+ config_get_int(cfg, NULL, "transportSpecific") << 4, 1);
+ if (!pmc) {
+ return -1;
+ }
+ err = pmc_send_set_aton(pmc, MID_ALTERNATE_TIME_OFFSET_NAME,
+ key_field, tz->display_name);
+ if (err) {
+ return err;
+ }
+ memset(&atop, 0, sizeof(atop));
+ atop.keyField = key_field;
+ atop.currentOffset = tz->local_tai_offset;
+ if (next) {
+ atop.jumpSeconds = next->local_tai_offset - tz->local_tai_offset;
+ atop.timeOfNextJump.seconds_lsb = next->timestamp;
+ }
+ err = pmc_send_set_action(pmc, MID_ALTERNATE_TIME_OFFSET_PROPERTIES,
+ &atop, sizeof(atop));
+ if (err) {
+ return err;
+ }
+ mtd.val = key_field;
+ mtd.reserved = 1; /*enable field*/
+ err = pmc_send_set_action(pmc, MID_ALTERNATE_TIME_OFFSET_ENABLE,
+ &mtd, sizeof(mtd));
+ if (err) {
+ return err;
+ }
+
+ pmc_destroy(pmc);
+ return 0;
+}
+
+static int do_tztool(const char *timezone)
+{
+ struct tzinfo nx, tz;
+ const char *leapfile;
+ bool pending;
+ char buf[64];
+ int err;
+
+ if (key_field > MAX_TIME_ZONES - 1) {
+ pr_err("key field %d exceeds maximum of %d", key_field,
+ MAX_TIME_ZONES - 1);
+ return -1;
+ }
+
+ tz_set_name(&nx, timezone);
+ if (tz_set_name(&tz, timezone)) {
+ pr_info("truncating time zone display name from %s to %s",
+ tz.name, tz.display_name);
+ }
+
+ leapfile = config_get_string(cfg, NULL, "leapfile");
+ if (!leapfile) {
+ pr_err("please specify leap second table with --leapfile");
+ return -1;
+ }
+
+ while (is_running()) {
+
+ /* Read the leap seconds file again as it may have changed. */
+ lstab = lstab_create(leapfile);
+ if (!lstab) {
+ pr_err("failed to create leap second table");
+ return -1;
+ }
+
+ err = get_unambiguous_time(&tz);
+ if (err) {
+ return err;
+ }
+ show_timezone_info("current time = ", &tz);
+
+ pending = find_next_discontinuity(&tz, &nx);
+ if (pending) {
+ setenv("TZ", nx.name, 1);
+ tzset();
+ if (ctime_r(&nx.timestamp, buf)) {
+ buf[strlen(buf) - 1] = 0;
+ }
+ show_timezone_info("discontinuity = ", &nx);
+ pr_info("next discontinuity %s %s", buf, nx.name);
+ } else {
+ pr_info("no discontinuity within %d second window", window);
+ }
+
+ lstab_destroy(lstab);
+ lstab = NULL;
+
+ err = update_ptp_serivce(&tz, pending ? &nx : NULL);
+ if (err) {
+ pr_err("failed to update PTP service");
+ return err;
+ }
+ puts("");
+ sleep(period);
+ }
+ return 0;
+}
+
+static void usage(char *progname)
+{
+ fprintf(stderr,
+ "\nusage: %s [options]\n\n"
+ " -f [file] read configuration from 'file'\n"
+ " -h prints this message and exits\n"
+ " -k [num] key field for the ALTERNATE_TIME_OFFSET_INDICATOR TLV\n"
+ " -p [num] period between updates in seconds, default %d\n"
+ " -v prints the software version and exits\n"
+ " -w [num] look ahead time window in seconds, default %d\n"
+ " -z zone Time zone string, default '%s'\n"
+ " See /usr/share/zoneinfo for valid strings\n"
+ "\n",
+ progname, DEFAULT_PERIOD, DEFAULT_WINDOW, DEFAULT_TZ);
+}
+
+int main(int argc, char *argv[])
+{
+ char *config = NULL, *progname, *timezone = DEFAULT_TZ;
+ int c, err = 0, index;
+ struct option *opts;
+
+ if (handle_term_signals()) {
+ return -1;
+ }
+ cfg = config_create();
+ if (!cfg) {
+ return -1;
+ }
+ opts = config_long_options(cfg);
+ print_set_verbose(1);
+ print_set_syslog(0);
+
+ /* Process the command line arguments. */
+ progname = strrchr(argv[0], '/');
+ progname = progname ? 1+progname : argv[0];
+ while (EOF != (c = getopt_long(argc, argv, "f:hk:p:vw:z:", opts, &index))) {
+ switch (c) {
+ case 0:
+ if (config_parse_option(cfg, opts[index].name, optarg)) {
+ config_destroy(cfg);
+ return -1;
+ }
+ break;
+ case 'f':
+ config = optarg;
+ break;
+ case 'k':
+ key_field = atoi(optarg);
+ break;
+ case 'p':
+ period = atoi(optarg);
+ break;
+ case 'v':
+ version_show(stdout);
+ config_destroy(cfg);
+ return 0;
+ case 'w':
+ window = atoi(optarg);
+ break;
+ case 'z':
+ timezone = optarg;
+ break;
+ case 'h':
+ usage(progname);
+ config_destroy(cfg);
+ return 0;
+ case '?':
+ default:
+ usage(progname);
+ config_destroy(cfg);
+ return -1;
+ }
+ }
+
+ print_set_syslog(0);
+ print_set_verbose(1);
+
+ if (config && (err = config_read(config, cfg))) {
+ goto out;
+ }
+
+ print_set_progname(progname);
+ print_set_tag(config_get_string(cfg, NULL, "message_tag"));
+ print_set_level(config_get_int(cfg, NULL, "logging_level"));
+ snprintf(uds_local, sizeof(uds_local), "/var/run/tztool.%d", getpid());
+
+ err = do_tztool(timezone);
+out:
+ config_destroy(cfg);
+ return err;
+}
--
2.30.2
|