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 <richardcoch...@gmail.com>
---
 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 <richardcoch...@gmail.com>
+ * @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



_______________________________________________
Linuxptp-devel mailing list
Linuxptp-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxptp-devel

Reply via email to