# # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Oxide Computer Company # .PARALLEL: $(SUBDIRS) SUBDIRS = doc tests include $(SRC)/test/Makefile.com # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Oxide Computer Company # DOCS = README ROOTOPTDIR = $(ROOT)/opt/tz-tests ROOTOPTDOCS = $(DOCS:%=$(ROOTOPTDIR)/%) include $(SRC)/Makefile.master $(ROOTOPTDOCS) : FILEMODE = 0444 .KEEP_STATE: all: $(DOCS) install: $(ROOTOPTDIR) .WAIT $(ROOTOPTDOCS) $(ROOTOPTDIR): $(INS.dir) $(ROOTOPTDIR)/%: % $(INS.file) clean clobber: # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Oxide Computer Company # TZ Code and Data Related Tests ------------------------------ This suite contains programs that are intended to help verify the behavior of code and data updates to time zone related information. The programs included here are not on their own sufficient to validate information, but should be useful when making updates and comparisons. The tzdata files that come from the upstream IANA source are kept in $SRC/data/zoneinfo. These data files are compiled by zic(8), a copy of which is kept in the tools build, and then installed into the proto area where it is consumed by: - libc's time implementation (e.g. localtime(3C)) - libzoneinfo - various third party software The data format is standardized in various RFCs. As of 2025, the latest RFC that documents the data format is RFC 9636. When using these tests, we assume the only thing that is changing is the tzcode files related to zic and zdump. Specifically, if you are changing the tzdata, then some bets are off. Most programs are designed to take an output directory and write out information on a per timezone or related structure basis. It is then up to the person using these tests to compare old and new versions of these. Because of this construction, there is no runfile that is provided. All of the resultant files are installed in /opt/tz-tests/tests. We generally provide 32-bit and 64-bit variants of a subset of the dumping programs to allow that to be chosen. When there is no specific need, we only provide a 64-bit program. Some of these programs embed implementation details of how the data is laid out privately in libc. This may mean that changes to libc will cause these programs to not work as expected. # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Oxide Computer Company # PROGS = \ tznames \ tzload \ zoneinfo_dump SCRIPTS = \ basic_tzs \ tz_dump \ tzname_17947 PROGS32 = $(PROGS:%=%.32) PROGS64 = $(PROGS:%=%.64) \ tzparams.64 \ tzlist.64 ROOTOPTDIR = $(ROOT)/opt/tz-tests ROOTOPTTESTS = $(ROOTOPTDIR)/tests ROOTOPTPROGS = $(PROGS32:%=$(ROOTOPTTESTS)/%) \ $(PROGS64:%=$(ROOTOPTTESTS)/%) \ $(SCRIPTS:%=$(ROOTOPTTESTS)/%) include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/Makefile.ctf CSTD = $(CSTD_GNU17) CTF_MODE = link CPPFLAGS += -D_REENTRANT zoneinfo_dump.32 : LDLIBS += -lzoneinfo zoneinfo_dump.64 : LDLIBS64 += -lzoneinfo tzlist.64 : LDLIBS64 += -lzoneinfo .KEEP_STATE: all: $(PROGS32) $(PROGS64) install: $(ROOTOPTTESTS) .WAIT $(ROOTOPTPROGS) $(ROOTOPTPROGS): $(PROGS32) $(PROGS64) $(ROOTOPTDIR) $(ROOTOPTDIR): $(INS.dir) $(ROOTOPTTESTS): $(ROOTOPTDIR) $(INS.dir) $(ROOTOPTTESTS)/%: % $(INS.file) $(ROOTOPTTESTS)/%: %.ksh $(INS.rename) %.64: %.c $(LINK64.c) -o $@ $< $(LDLIBS64) $(POST_PROCESS) %.32: %.c $(LINK.c) -o $@ $< $(LDLIBS) $(POST_PROCESS) clobber: $(SUBDIRS) $(RM) $(PROGS32) $(PROGS64) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: #!/usr/bin/ksh # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Oxide Computer Company # # # This takes a few historical transition points in US time zones and verifies # that we still see the expected date formats for a given POSIX time. In # particular we look at DST transitions before and after the Energy Policy Act # of 2005 which changed the range of time that DST was in effect for. # # In addition, we use a few different time zones with the same time_t value to # confirm that basic time zone functionality works. We also use some POSIX time # zone strings. Note, strings that begin with a ':' according to POSIX use an # implementation defined scheme, which for us is the same as the normal zoneinfo # database. # unalias -a set -o pipefail export LANG=C.UTF-8 # # Program paths that we allow someone to interpose on. # DATE=${DATE:-"/usr/bin/date"} btz_exit=0 typeset -A btz_tzs=( ["CHI (0)"]=(zone="America/Chicago" ts="834971400" res="Sun Jun 16 19:30:00 CDT 1996") ["CHI (1)"]=(zone="America/Chicago" ts="866253600" res="Fri Jun 13 21:00:00 CDT 1997") ["CHI (2)"]=(zone="America/Chicago" ts="897876000" res="Sun Jun 14 21:00:00 CDT 1998") ["CHI (3)"]=(zone="CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="834971400" res="Sun Jun 16 19:30:00 CDT 1996") ["CHI (4)"]=(zone="CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="866253600" res="Fri Jun 13 21:00:00 CDT 1997") ["CHI (5)"]=(zone="CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="866253600" res="Fri Jun 13 21:00:00 CDT 1997") ["PST (0)"]=(zone="US/Pacific" ts="1080738123" res="Wed Mar 31 05:02:03 PST 2004") ["PST (1)"]=(zone="US/Pacific" ts="1082728984" res="Fri Apr 23 07:03:04 PDT 2004") ["PST (2)"]=(zone="US/Pacific" ts="1680267723" res="Fri Mar 31 06:02:03 PDT 2023") ["PST (3)"]=(zone="US/Pacific" ts="1682258584" res="Sun Apr 23 07:03:04 PDT 2023") ["PST (4)"]=(zone="US/Pacific" ts="969650527" res="Fri Sep 22 12:22:07 PDT 2000") ["PST (5)"]=(zone="US/Pacific" ts="1095880927" res="Wed Sep 22 12:22:07 PDT 2004") # # These variants hard code the transition points which means that they # should differ from the PST examples above which follow Olson. Note the # way to read M3.2.0 is the first day (Sunday) of the second week of the # 3rd month (March). # ["PST/POSIX (0)"]=(zone="PST8PDT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="1080738123" res="Wed Mar 31 06:02:03 PDT 2004") ["PST/POSIX (1)"]=(zone="PST8PDT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="1079254984" res="Sun Mar 14 01:03:04 PST 2004") ["PST/POSIX (2)"]=(zone="PST8PDT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="1079258584" res="Sun Mar 14 03:03:04 PDT 2004") ["PST/POSIX (3)"]=(zone="PST8PDT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="941965199" res="Sun Nov 7 01:59:59 PDT 1999") ["PST/POSIX (4)"]=(zone="PST8PDT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="941968799" res="Sun Nov 7 01:59:59 PST 1999") ["World (Auckland)"]=(zone="Pacific/Auckland" ts="946684800" res="Sat Jan 1 13:00:00 NZDT 2000") ["World (Belize)"]=(zone="America/Belize" ts="946684800" res="Fri Dec 31 18:00:00 CST 1999") ["World (Brisbane)"]=(zone="Australia/Brisbane" ts="946684800" res="Sat Jan 1 10:00:00 AEST 2000") ["World (Brisbane, ':')"]=(zone=":Australia/Brisbane" ts="946684800" res="Sat Jan 1 10:00:00 AEST 2000") ["World (Casablanca)"]=(zone="Africa/Casablanca" ts="946684800" res="Sat Jan 1 00:00:00 +01 2000") ["World (Damascus)"]=(zone="Asia/Damascus" ts="946684800" res="Sat Jan 1 02:00:00 +03 2000") ["World (Egypt)"]=(zone="Egypt" ts="946684800" res="Sat Jan 1 02:00:00 EET 2000") ["World (GMT)"]=(zone="GMT" ts="946684800" res="Sat Jan 1 00:00:00 GMT 2000") ["World (Guam)"]=(zone="Pacific/Guam" ts="946684800" res="Sat Jan 1 10:00:00 ChST 2000") ["World (Hong Kong)"]=(zone="Hongkong" ts="946684800" res="Sat Jan 1 08:00:00 HKT 2000") ["World (Japan)"]=(zone="Japan" ts="946684800" res="Sat Jan 1 09:00:00 JST 2000") ["World (Japan/POSIX)"]=(zone="JST-9" ts="946684800" res="Sat Jan 1 09:00:00 JST 2000") ["World (London)"]=(zone="Europe/London" ts="946684800" res="Sat Jan 1 00:00:00 GMT 2000") ["World (London/POSIX)"]=(zone="BST0GMT,M3.2.0/2:00:00,M11.1.0/2:00:00" ts="946684800" res="Sat Jan 1 00:00:00 BST 2000") ["World (Longyearbyen)"]=(zone="Arctic/Longyearbyen" ts="946684800" res="Sat Jan 1 01:00:00 CET 2000") ["World (Manila)"]=(zone="Asia/Manila" ts="946684800" res="Sat Jan 1 08:00:00 PST 2000") ["World (McMurdo)"]=(zone="Antarctica/McMurdo" ts="946684800" res="Sat Jan 1 13:00:00 NZDT 2000") ["World (Rome)"]=(zone="Europe/Rome" ts="946684800" res="Sat Jan 1 01:00:00 CET 2000") ["World (Singapore)"]=(zone="Singapore" ts="946684800" res="Sat Jan 1 08:00:00 +08 2000") ["World (St. Thomas)"]=(zone="America/St_Thomas" ts="946684800" res="Fri Dec 31 20:00:00 AST 1999") ["World (Tunis)"]=(zone="Africa/Tunis" ts="946684800" res="Sat Jan 1 01:00:00 CET 2000") ["World (Tunis, ':')"]=(zone=":Africa/Tunis" ts="946684800" res="Sat Jan 1 01:00:00 CET 2000") ["World (UTC)"]=(zone="UTC" ts="946684800" res="Sat Jan 1 00:00:00 UTC 2000") ) test_one() { typeset key="$1" typeset out= typeset -n data=btz_tzs[$key] out=$(TZ=${data.zone} $DATE -r ${data.ts}) if [[ "$out" != "${data.res}" ]]; then printf "TEST FAILED: %s: expected %s, found %s\n" "$key" \ "${data.res}" "$out" >&2 btz_exit=1 else printf "TEST PASSED: %s: %s\n" "$key" "$out" fi } for i in "${!btz_tzs[@]}"; do test_one "$i" done if (( btz_exit == 0 )); then printf "All tests passed successfully!\n" fi exit $btz_exit #!/usr/bin/ksh # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Oxide Computer Company # # # The purpose of this program is to go through and dump the internal libc state # from every time zone that we encounter so that we can go back and do a before # an after comparison of this information. # unalias -a set -o pipefail export LANG=C.UTF-8 lc_arg0=$(basename $0) lc_dir=$(dirname $0) lc_print="*lclzonep::print -tL struct state" lc_prev= lc_out= lc_libc=0 lc_zdump=0 lc_prog=${ZDUMP:-/usr/sbin/zdump} # # These will be filled in with the variable size that we detect via a program so # we can survive some changes in libc without as much pain. # lc_chars= lc_times= # # List of timezones # lc_zones= usage() { typeset msg="$*" [[ -z "$msg" ]] || echo "$msg" >&2 cat <&2 Usage: $lc_arg0 [-c] [-z] -o dir Dump information about all timezones to a directory. -c dump state from inside of libc -o directory dump data to output directory -z dump zone with zdump(8) USAGE exit 2 } fatal() { typeset msg="$*" [[ -z "$msg" ]] && msg="failed" echo "lcx_arg0: $msg" >&2 exit 1 } get_tzparams() { eval $($lc_dir/tzparams.64); [[ -z "$TZ_MAX_TIMES" ]] && "failed to get TZ_MAX_TIMES" [[ -z "$TZ_MAX_CHARS" ]] && "failed to get TZ_MAX_CHARS" lc_chars="$TZ_MAX_CHARS" lc_times="$TZ_MAX_TIMES" for i in {0..$((lc_times - 1))}; do lc_prev+="$lc_print prev[0t$i].std[] prev[0t$i].alt[]\n" done lc_zones=$($lc_dir/tzlist.64) [[ -z "$lc_zones" ]] && fatal "failed to get time zones" } dump_one() { typeset tz="$1" typeset dir="$lc_out/$tz" if ! mkdir -p $dir; then fatal "failed to make output directory $dir" fi printf "Dumping %s\n" "$tz" if (( lc_libc != 0 )); then TZ=$tz mdb $lc_dir/tzload.32 > "$dir/libc-out.32" \ 2>"$dir/libc-err.32" < "$dir/libc-out.64" \ 2>"$dir/libc-err.64" <&1 >"$dir/zdump" fi } while getopts ":co:z" c $@; do case "$c" in c) lc_libc=1 ;; o) [[ ! -d "$OPTARG" ]] && fatal "$OPTARG is not a directory" lc_out="$OPTARG" ;; z) lc_zdump=1 ;; :) usage "option requires an argument -- $OPTARG" ;; *) usage "invalid option -- $OPTARG" ;; esac done [[ -z "$lc_out" ]] && usage "missing required output directory" (( lc_libc == 0 && lc_zdump == 0 )) && usage \ "at least one of -c or -z is required" get_tzparams for z in $lc_zones; do dump_one "$z" done exit 0 /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Use libzoneinfo to print a list of timezones for additional inspection. This * is paired with some of the DTrace script wrappers. */ #include #include #include #include #include int main(void) { struct tz_continent *conts; if (get_tz_continents(&conts) < 0) { err(EXIT_FAILURE, "failed to get continent list"); } for (struct tz_continent *ctnt = conts; ctnt != NULL; ctnt = ctnt->ctnt_next) { struct tz_country *countries; if (get_tz_countries(&countries, ctnt) < 0) { err(EXIT_FAILURE, "failed to get countries for %s", ctnt->ctnt_name); } for (struct tz_country *ctry = countries; ctry != NULL; ctry = ctry->ctry_next) { struct tz_timezone *zones; if (get_timezones_by_country(&zones, ctry) < 0) { err(EXIT_FAILURE, "failed to get timezones for " "%s/%s", ctnt->ctnt_name, ctry->ctry_code); } for (struct tz_timezone *tz = zones; tz != NULL; tz = tz->tz_next) { (void) printf("%s\n", tz->tz_name); } VERIFY0(free_timezones(zones)); } VERIFY0(free_tz_countries(countries)); } VERIFY0(free_tz_continents(conts)); return (0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * The purpose of this program is to force a given timezone to be loaded so the * internal libc state can be printed with mdb. */ #include #include #pragma weak mdb_hook void mdb_hook(void) { } int main(void) { tzset(); mdb_hook(); return (0); } #!/usr/bin/ksh # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Oxide Computer Company # # # This test serves as a basic regression test for #17947 where we were # incorrectly determining the value of tzname[] for various POSIX-style # timezones due to how we had updated the data and incorrect assumptions made # around indexes that were thrown off with the addition of LMT. # unalias -a set -o pipefail export LANG=C.UTF-8 tz_dir=$(dirname $0) tz_n32="$tz_dir/tznames.32" tz_n64="$tz_dir/tznames.64" tz_exit=0 # # When there is no DST style time zone, then the following empty string is used. # tz_none=" " test_one() { if ! $tz_n32 "$1" "$2" "$3"; then tz_exit=1 fi if ! $tz_n64 "$1" "$2" "$3"; then tz_exit=1 fi } test_one UTC UTC "$tz_none" test_one UTC0UTC UTC UTC test_one GMT0GMT GMT GMT test_one FOO0BAR FOO BAR test_one America/New_York EST EDT test_one CET0 CET "$tz_none" test_one CET0CET CET CET test_one CET0CEST CET CEST test_one Asia/Tokyo JST JDT test_one Europe/Rome CET CEST test_one Australia/Brisbane AEST AEDT if (( tz_exit == 0 )); then printf "All tests passed successfully!\n" fi exit $tz_exit /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * This file tests that the parsed tzname is equal to what was passed in as * args. We use the arguments as TZ=arg0, name0=arg1, name1=arg2. */ #include #include #include #include #include int main(int argc, char *argv[]) { int ret = EXIT_SUCCESS; if (argc != 4) { (void) fprintf(stderr, "Usage: tznames " "\n"); exit(EXIT_FAILURE); } if (setenv("TZ", argv[1], 1) != 0) { err(EXIT_FAILURE, "failed to set TZ to %s", argv[1]); } tzset(); if (strcmp(tzname[0], argv[2]) != 0) { warnx("TEST FAILED: TZ %s: found tzname[0] %s, expected %s", argv[1], tzname[0], argv[2]); ret = EXIT_FAILURE; } if (strcmp(tzname[1], argv[3]) != 0) { warnx("TEST FAILED: TZ %s: found tzname[1] %s, expected %s", argv[1], tzname[1], argv[3]); ret = EXIT_FAILURE; } return (ret); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * This program prints a few of the internal parameters that we're using for the * sizing of various Timezone related databases so that scripts don't have to * hardcode the constants. This outputs data in a shell-compatible way. */ #include #include int main(void) { (void) printf("TZ_MAX_TIMES=%u\n", TZ_MAX_TIMES); (void) printf("TZ_MAX_CHARS=%u\n", TZ_MAX_CHARS); return (0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Dump all of the information discovered by libzoneinfo in a way that's usable * for diffing. We use the following directory layout from the root: * * dir: * file: info * dir: * file: info * file: */ #include #include #include #include #include #include #include #include #include #include static void usage(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: zoneinfo_dump -d dir\n"); } static void dump_timezone(int dirfd, const struct tz_continent *cont, const struct tz_country *country, const struct tz_timezone *tz) { int fd; char *name; FILE *f; name = strdup(tz->tz_name); if (name == NULL) { err(EXIT_FAILURE, "failed to duplicate tz name %s", tz->tz_name); } for (size_t i = 0; i < strlen(name); i++) { if (name[i] == '/') name[i] = '-'; } if ((fd = openat(dirfd, name, O_RDWR| O_CREAT | O_TRUNC, 0644)) < 0) { err(EXIT_FAILURE, "failed to create tz file for %s/%s/%s", cont->ctnt_name, country->ctry_code, name); } if ((f = fdopen(fd, "w")) == NULL) { err(EXIT_FAILURE, "failed to create stdio stream for " "tz %s/%s/%s file", cont->ctnt_name, country->ctry_code, name); } (void) fprintf(f, "name: %s\n", tz->tz_name); (void) fprintf(f, "oname: %s\n", tz->tz_oname); (void) fprintf(f, "id: %s\n", tz->tz_id_desc); (void) fprintf(f, "desc: %s\n", tz->tz_display_desc); (void) fprintf(f, "lat: %d.%u.%u.%u\n", tz->tz_coord.lat_sign, tz->tz_coord.lat_degree, tz->tz_coord.lat_minute, tz->tz_coord.lat_second); (void) fprintf(f, "long: %d.%u.%u.%u\n", tz->tz_coord.long_sign, tz->tz_coord.long_degree, tz->tz_coord.long_minute, tz->tz_coord.long_second); VERIFY0(fflush(f)); VERIFY0(fclose(f)); free(name); } static void dump_country(int cfd, const struct tz_continent *cont, struct tz_country *country) { int dirfd, infofd, ret, found = 0; struct tz_timezone *zones; FILE *f; if (mkdirat(cfd, country->ctry_code, 0755) != 0 && errno != EEXIST) { err(EXIT_FAILURE, "failed to make country directory %s/%s", cont->ctnt_name, country->ctry_code); } if ((dirfd = openat(cfd, country->ctry_code, O_DIRECTORY)) < 0) { err(EXIT_FAILURE, "failed to open country %s/%s", cont->ctnt_name, country->ctry_code); } if ((infofd = openat(dirfd, "info", O_RDWR| O_CREAT | O_TRUNC, 0644)) < 0) { err(EXIT_FAILURE, "failed to create info file for country " "%s/%s", cont->ctnt_name, country->ctry_code); } if ((f = fdopen(infofd, "w")) == NULL) { err(EXIT_FAILURE, "failed to create stdio stream for " "country %s/%s info file", cont->ctnt_name, country->ctry_code); } (void) fprintf(f, "name: %s\n", country->ctry_code); (void) fprintf(f, "id: %s\n", country->ctry_id_desc); (void) fprintf(f, "desc: %s\n", country->ctry_display_desc); VERIFY0(country->ctry_status); VERIFY0(fflush(f)); VERIFY0(fclose(f)); ret = get_timezones_by_country(&zones, country); if (ret < 0) { err(EXIT_FAILURE, "failed to get timezones for country %s/%s", cont->ctnt_name, country->ctry_code); } for (struct tz_timezone *t = zones; t != NULL; t = t->tz_next, found++) { dump_timezone(dirfd, cont, country, t); } if (ret != found) { errx(EXIT_FAILURE, "zoneinfo said %u timezones should exist " "for country %s/%s, but found %u\n", ret, cont->ctnt_name, country->ctry_code, found); } VERIFY0(free_timezones(zones)); VERIFY0(close(dirfd)); } static void dump_continent(int root, struct tz_continent *cont) { int dirfd, infofd, ret, found = 0; struct tz_country *country; FILE *f; if (mkdirat(root, cont->ctnt_name, 0755) != 0 && errno != EEXIST) { err(EXIT_FAILURE, "failed to make continent %s", cont->ctnt_name); } if ((dirfd = openat(root, cont->ctnt_name, O_DIRECTORY)) < 0) { err(EXIT_FAILURE, "failed to open continent %s", cont->ctnt_name); } if ((infofd = openat(dirfd, "info", O_RDWR| O_CREAT | O_TRUNC, 0644)) < 0) { err(EXIT_FAILURE, "failed to create info file for continent " "%s", cont->ctnt_name); } if ((f = fdopen(infofd, "w")) == NULL) { err(EXIT_FAILURE, "failed to create stdio stream for " "continent %s info file", cont->ctnt_name); } (void) fprintf(f, "name: %s\n", cont->ctnt_name); (void) fprintf(f, "id: %s\n", cont->ctnt_id_desc); (void) fprintf(f, "desc: %s\n", cont->ctnt_display_desc); VERIFY0(fflush(f)); VERIFY0(fclose(f)); ret = get_tz_countries(&country, cont); if (ret < 0) { err(EXIT_FAILURE, "failed to get countries for continent %s", cont->ctnt_name); } for (struct tz_country *c = country; c != NULL; c = c->ctry_next, found++) { dump_country(dirfd, cont, c); } if (ret != found) { errx(EXIT_FAILURE, "zoneinfo said %u countries should exist " "for continent %s, but found %u\n", ret, cont->ctnt_name, found); } /* For each Country */ VERIFY0(free_tz_countries(country)); VERIFY0(close(dirfd)); } int main(int argc, char *argv[]) { int c, dirfd, ret, found = 0; const char *base = NULL; struct tz_continent *conts; while ((c = getopt(argc, argv, ":d:")) != -1) { switch (c) { case 'd': base = optarg; break; case '?': usage("option -%c requires an argument", optopt); exit(EXIT_FAILURE); case ':': usage("unknown option: -%c", optopt); exit(EXIT_FAILURE); } } if (base == NULL) { errx(EXIT_FAILURE, "missing required directory, please use " "the -d flag"); } if ((dirfd = open(base, O_RDONLY | O_DIRECTORY)) < 0) { err(EXIT_FAILURE, "failed to open directory %s", base); } ret = get_tz_continents(&conts); if (ret < 0) { err(EXIT_FAILURE, "failed to get continents"); } for (struct tz_continent *c = conts; c != NULL; c = c->ctnt_next, found++) { dump_continent(dirfd, c); } if (found != ret) { errx(EXIT_FAILURE, "zoneinfo said %u continents should exist, " "but found %u\n", ret, found); } VERIFY0(free_tz_continents(conts)); return (0); }