[Linuxptp-devel] [PATCH RFC v3 10/10] Introduce a time zone helper program.
PTP IEEE 1588 stack for Linux
                
                Brought to you by:
                
                    rcochran
                    
                
            
            
        
        
        
    | 
      
      
      From: Richard C. <ric...@gm...> - 2021-11-09 20:12:16
      
     | 
| 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 33e7ca0..811468d 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)))
@@ -71,6 +71,9 @@ timemaster: phc.o print.o rtnl.o sk.o timemaster.o util.o version.o
 ts2phc: config.o clockadj.o hash.o interface.o phc.o print.o $(SERVOS) sk.o \
  $(TS2PHC) 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.20.1
 |