Module Name:    src
Committed By:   christos
Date:           Sun Dec 11 17:57:23 UTC 2022

Modified Files:
        src/lib/libc/time: Makefile NEWS ctime.3 localtime.c private.h
            strftime.c theory.html time2posix.3 tzfile.5 tzselect.8 tzset.3
            version zdump.8 zdump.c zic.8 zic.c

Log Message:
Merge in 2022g:

    Although tzcode still works with C89, bugs found in recent routine
    maintenance indicate that bitrot has set in and that in practice
    C89 is no longer used to build tzcode.  As it is a maintenance
    burden, support for C89 is planned to be removed soon.  Instead,
    please use compilers compatible with C99, C11, C17, or C23.

    timegm, which tzcode implemented in 1989, will finally be
    standardized 34 years later as part of C23, so timegm is now
    supported even if STD_INSPIRED is not defined.

    Fix bug in zdump's tzalloc emulation on hosts that lack tm_zone.
    (Problem reported by Đoàn Trần Công Danh.)

    Fix bug in zic on hosts where malloc(0) yields NULL on success.
    (Problem reported by Tim McBrayer for AIX 6.1.)

    Fix zic configuration to avoid linkage failures on some platforms.
    (Problems reported by Gilmore Davidson and Igor Ivanov.)

    Work around MS-Windows nmake incompatibility with POSIX.
    (Problem reported by Manuela Friedrich.)

    Port mktime and strftime to debugging platforms where accessing
    uninitialized data has undefined behavior (strftime problem
    reported by Robert Elz).

    Check more carefully for unlikely integer overflows, preferring
    C23 <stdckdint.h> to overflow checking by hand, as the latter has
    had obscure bugs.


To generate a diff of this commit:
cvs rdiff -u -r1.53 -r1.54 src/lib/libc/time/Makefile
cvs rdiff -u -r1.39 -r1.40 src/lib/libc/time/NEWS
cvs rdiff -u -r1.68 -r1.69 src/lib/libc/time/ctime.3
cvs rdiff -u -r1.135 -r1.136 src/lib/libc/time/localtime.c
cvs rdiff -u -r1.63 -r1.64 src/lib/libc/time/private.h
cvs rdiff -u -r1.50 -r1.51 src/lib/libc/time/strftime.c
cvs rdiff -u -r1.16 -r1.17 src/lib/libc/time/theory.html
cvs rdiff -u -r1.22 -r1.23 src/lib/libc/time/time2posix.3 \
    src/lib/libc/time/version
cvs rdiff -u -r1.32 -r1.33 src/lib/libc/time/tzfile.5
cvs rdiff -u -r1.12 -r1.13 src/lib/libc/time/tzselect.8
cvs rdiff -u -r1.44 -r1.45 src/lib/libc/time/tzset.3
cvs rdiff -u -r1.21 -r1.22 src/lib/libc/time/zdump.8
cvs rdiff -u -r1.58 -r1.59 src/lib/libc/time/zdump.c
cvs rdiff -u -r1.40 -r1.41 src/lib/libc/time/zic.8
cvs rdiff -u -r1.85 -r1.86 src/lib/libc/time/zic.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libc/time/Makefile
diff -u src/lib/libc/time/Makefile:1.53 src/lib/libc/time/Makefile:1.54
--- src/lib/libc/time/Makefile:1.53	Sat Oct 29 09:55:50 2022
+++ src/lib/libc/time/Makefile	Sun Dec 11 12:57:23 2022
@@ -196,6 +196,7 @@ PACKRATLIST=
 UTF8_LOCALE=	en_US.utf8
 
 # Non-default libraries needed to link.
+# On some hosts, this should have -lintl unless CFLAGS has -DHAVE_GETTEXT=0.
 LDLIBS=
 
 # Add the following to the end of the "CFLAGS=" line as needed to override
@@ -208,14 +209,18 @@ LDLIBS=
 #	For example, N is 252460800 on AmigaOS.
 #  -DHAVE_DECL_ASCTIME_R=0 if <time.h> does not declare asctime_r
 #  -DHAVE_DECL_ENVIRON if <unistd.h> declares 'environ'
+#  -DHAVE_DECL_TIMEGM=0 if <time.h> does not declare timegm
 #  -DHAVE_DIRECT_H if mkdir needs <direct.h> (MS-Windows)
-#  -DHAVE_GENERIC=0 if _Generic does not work
-#  -DHAVE_GETRANDOM if getgrandom works (e.g., GNU/Linux)*
-#  -DHAVE_GETTEXT if 'gettext' works (e.g., GNU/Linux, FreeBSD, Solaris)*
+#  -DHAVE_GENERIC=0 if _Generic does not work*
+#  -DHAVE_GETRANDOM if getrandom works (e.g., GNU/Linux),
+#	-DHAVE_GETRANDOM=0 to avoid using getrandom
+#  -DHAVE_GETTEXT if gettext works (e.g., GNU/Linux, FreeBSD, Solaris),
+#	where LDLIBS also needs to contain -lintl on some hosts;
+#	-DHAVE_GETTEXT=0 to avoid using gettext
 #  -DHAVE_INCOMPATIBLE_CTIME_R if your system's time.h declares
 #	ctime_r and asctime_r incompatibly with the POSIX standard
 #	(Solaris when _POSIX_PTHREAD_SEMANTICS is not defined).
-#  -DHAVE_INTTYPES_H if you have a non-C99 compiler with <inttypes.h>
+#  -DHAVE_INTTYPES_H=0 if <inttypes.h> does not work*
 #  -DHAVE_LINK=0 if your system lacks a link function
 #  -DHAVE_LOCALTIME_R=0 if your system lacks a localtime_r function
 #  -DHAVE_LOCALTIME_RZ=0 if you do not want zdump to use localtime_rz
@@ -225,15 +230,17 @@ LDLIBS=
 #	functions like 'link' or variables like 'tzname' required by POSIX
 #  -DHAVE_SETENV=0 if your system lacks the setenv function
 #  -DHAVE_SNPRINTF=0 if your system lacks the snprintf function
-#  -DHAVE_STDINT_H if you have a non-C99 compiler with <stdint.h>*
+#  -DHAVE_STDCKDINT_H=0 if neither <stdckdint.h> nor substitutes like
+#	__builtin_add_overflow work*
+#  -DHAVE_STDINT_H=0 if <stdint.h> does not work*
 #  -DHAVE_STRFTIME_L if <time.h> declares locale_t and strftime_l
 #  -DHAVE_STRDUP=0 if your system lacks the strdup function
 #  -DHAVE_STRTOLL=0 if your system lacks the strtoll function
 #  -DHAVE_SYMLINK=0 if your system lacks the symlink function
-#  -DHAVE_SYS_STAT_H=0 if your compiler lacks a <sys/stat.h>*
+#  -DHAVE_SYS_STAT_H=0 if <sys/stat.h> does not work*
 #  -DHAVE_TZSET=0 if your system lacks a tzset function
-#  -DHAVE_UNISTD_H=0 if your compiler lacks a <unistd.h>*
-#  -DHAVE_UTMPX_H=0 if your compiler lacks a <utmpx.h>*
+#  -DHAVE_UNISTD_H=0 if <unistd.h> does not work*
+#  -DHAVE_UTMPX_H=0 if <utmpx.h> does not work*
 #  -Dlocale_t=XXX if your system uses XXX instead of locale_t
 #  -DRESERVE_STD_EXT_IDS if your platform reserves standard identifiers
 #	with external linkage, e.g., applications cannot define 'localtime'.
@@ -280,7 +287,7 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 -fn
   -Wdeclaration-after-statement -Wdouble-promotion \
   -Wduplicated-branches -Wduplicated-cond \
   -Wformat=2 -Wformat-overflow=2 -Wformat-signedness -Wformat-truncation \
-  -Winit-self -Wlogical-op \
+  -Wimplicit-fallthrough=5 -Winit-self -Wlogical-op \
   -Wmissing-declarations -Wmissing-prototypes -Wnested-externs \
   -Wnull-dereference \
   -Wold-style-definition -Woverlength-strings -Wpointer-arith \
@@ -293,7 +300,7 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 -fn
   -Wtrampolines -Wundef -Wuninitialized -Wunused-macros -Wuse-after-free=3 \
   -Wvariadic-macros -Wvla -Wwrite-strings \
   -Wno-address -Wno-format-nonliteral -Wno-sign-compare \
-  -Wno-type-limits -Wno-unused-parameter
+  -Wno-type-limits
 #
 # If your system has a "GMT offset" field in its "struct tm"s
 # (or if you decide to add such a field in your system's "time.h" file),
@@ -340,14 +347,11 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 -fn
 # If you want functions that were inspired by early versions of X3J11's work,
 # add
 #	-DSTD_INSPIRED
-# to the end of the "CFLAGS=" line.  This arranges for the functions
-# "offtime", "timelocal", "timegm", "timeoff",
-# "posix2time", and "time2posix" to be added to the time conversion library.
+# to the end of the "CFLAGS=" line.  This arranges for the following
+# functions to be added to the time conversion library.
 # "offtime" is like "gmtime" except that it accepts a second (long) argument
 # that gives an offset to add to the time_t when converting it.
 # "timelocal" is equivalent to "mktime".
-# "timegm" is like "timelocal" except that it turns a struct tm into
-# a time_t using UT (rather than local time as "timelocal" does).
 # "timeoff" is like "timegm" except that it accepts a second (long) argument
 # that gives an offset to use when converting to a time_t.
 # "posix2time" and "time2posix" are described in an included manual page.
@@ -495,6 +499,11 @@ TARFLAGS=	`if tar $(GNUTARFLAGS) --versi
 # Flags to give 'gzip' when making a distribution.
 GZIPFLAGS=	-9n
 
+# When comparing .tzs files, use GNU diff's -F'^TZ=' option if supported.
+# This makes it easier to see which Zone has been affected.
+DIFF_TZS=	 diff -u$$(! diff -u -F'^TZ=' - - <>/dev/null >&0 2>&1 \
+			   || echo ' -F^TZ=')
+
 ###############################################################################
 
 #MAKE=		make
@@ -773,7 +782,8 @@ tzselect:	tzselect.ksh version
 		chmod +x $@.out
 		mv $@.out $@
 
-check:		check_character_set check_white_space check_links \
+check: check_back check_mild
+check_mild:	check_character_set check_white_space check_links \
 		  check_name_lengths check_slashed_abbrs check_sorted \
 		  check_tables check_web check_ziguard check_zishrink check_tzs
 
@@ -824,16 +834,19 @@ check_slashed_abbrs: $(TDATA_TO_CHECK)
 CHECK_CC_LIST = { n = split($$1,a,/,/); for (i=2; i<=n; i++) print a[1], a[i]; }
 
 check_sorted: backward backzone iso3166.tab zone.tab zone1970.tab
-		$(AWK) '/^Link/ {printf "%.5d %s\n", g, $$3} /^$$/ {g++}' \
+		$(AWK) '/^Link/ {printf "%.5d %s\n", g, $$3} !/./ {g++}' \
 		  backward | LC_ALL=C sort -cu
 		$(AWK) '/^Zone/ {print $$2}' backzone | LC_ALL=C sort -cu
 		touch $@
 
-check_links:	checklinks.awk $(TDATA_TO_CHECK) tzdata.zi
+check_back:	checklinks.awk $(TDATA_TO_CHECK)
 		$(AWK) \
 		  -v DATAFORM=$(DATAFORM) \
 		  -v backcheck=backward \
 		  -f checklinks.awk $(TDATA_TO_CHECK)
+		touch $@
+
+check_links:	checklinks.awk tzdata.zi
 		$(AWK) \
 		  -v DATAFORM=$(DATAFORM) \
 		  -f checklinks.awk tzdata.zi
@@ -849,7 +862,7 @@ check_tables:	checktab.awk $(YDATA) back
 
 check_tzs:	$(TZS) $(TZS_NEW)
 		if test -s $(TZS); then \
-		  diff -u $(TZS) $(TZS_NEW); \
+		  $(DIFF_TZS) $(TZS) $(TZS_NEW); \
 		else \
 		  cp $(TZS_NEW) $(TZS); \
 		fi
@@ -1050,7 +1063,7 @@ $(TIME_T_ALTERNATIVES): $(VERSION_DEPS)
 		      TZS_YEAR="$$range" TZS_CUTOFF_FLAG="-t $$range" \
 			D=$$wd/$@.dir \
 		      to$$range.tzs) && \
-		  diff -u $(TIME_T_ALTERNATIVES_HEAD).dir/to$$range.tzs \
+		  $(DIFF_TZS) $(TIME_T_ALTERNATIVES_HEAD).dir/to$$range.tzs \
 			  $@.dir/to$$range.tzs && \
 		  if diff -q Makefile Makefile 2>/dev/null; then \
 		    quiet_option='-q'; \
@@ -1220,7 +1233,7 @@ zdump.o:	version.h
 zic.o:		private.h tzfile.h version.h
 
 .PHONY: ALL INSTALL all
-.PHONY: check check_time_t_alternatives
+.PHONY: check check_mild check_time_t_alternatives
 .PHONY: check_web check_zishrink
 .PHONY: clean clean_misc dummy.zd force_tzs
 .PHONY: install install_data maintainer-clean names

Index: src/lib/libc/time/NEWS
diff -u src/lib/libc/time/NEWS:1.39 src/lib/libc/time/NEWS:1.40
--- src/lib/libc/time/NEWS:1.39	Sat Oct 29 09:55:50 2022
+++ src/lib/libc/time/NEWS	Sun Dec 11 12:57:23 2022
@@ -1,5 +1,91 @@
 News for the tz database
 
+Release 2022g - 2022-11-29 08:58:31 -0800
+
+  Briefly:
+    The northern edge of Chihuahua changes to US timekeeping.
+    Much of Greenland stops changing clocks after March 2023.
+    Fix some pre-1996 timestamps in northern Canada.
+    C89 is now deprecated; please use C99 or later.
+    Portability fixes for AIX, libintl, MS-Windows, musl, z/OS
+    In C code, use more C23 features if available.
+    C23 timegm now supported by default
+    Fixes for unlikely integer overflows
+
+  Changes to future timestamps
+
+    In the Mexican state of Chihuahua, the border strip near the US
+    will change to agree with nearby US locations on 2022-11-30.
+    The strip's western part, represented by Ciudad Juárez, switches
+    from -06 all year to -07/-06 with US DST rules, like El Paso, TX.
+    The eastern part, represented by Ojinaga, will observe US DST next
+    year, like Presidio, TX.  (Thanks to Heitor David Pinto.)
+    A new Zone America/Ciudad_Juarez splits from America/Ojinaga.
+
+    Much of Greenland, represented by America/Nuuk, stops observing
+    winter time after March 2023, so its daylight saving time becomes
+    standard time.  (Thanks to Jonas Nyrup and Jürgen Appel.)
+
+  Changes to past timestamps
+
+    Changes for pre-1996 northern Canada (thanks to Chris Walton):
+
+      Merge America/Iqaluit and America/Pangnirtung into the former,
+      with a backward compatibility link for the latter name.
+      There is no good evidence the two locations differ since 1970.
+      This change affects pre-1996 America/Pangnirtung timestamps.
+
+      Cambridge Bay, Inuvik, Iqaluit, Rankin Inlet, Resolute and
+      Yellowknife did not observe DST in 1965, and did observe DST
+      from 1972 through 1979.
+
+      Whitehorse moved from -09 to -08 on 1966-02-27, not 1967-05-28.
+
+    Colombia's 1993 fallback was 02-06 24:00, not 04-04 00:00.
+    (Thanks to Alois Treindl.)
+
+    Singapore's 1981-12-31 change was at 16:00 UTC (23:30 local time),
+    not 24:00 local time.  (Thanks to Geoff Clare via Robert Elz.)
+
+  Changes to code
+
+    Although tzcode still works with C89, bugs found in recent routine
+    maintenance indicate that bitrot has set in and that in practice
+    C89 is no longer used to build tzcode.  As it is a maintenance
+    burden, support for C89 is planned to be removed soon.  Instead,
+    please use compilers compatible with C99, C11, C17, or C23.
+
+    timegm, which tzcode implemented in 1989, will finally be
+    standardized 34 years later as part of C23, so timegm is now
+    supported even if STD_INSPIRED is not defined.
+
+    Fix bug in zdump's tzalloc emulation on hosts that lack tm_zone.
+    (Problem reported by Đoàn Trần Công Danh.)
+
+    Fix bug in zic on hosts where malloc(0) yields NULL on success.
+    (Problem reported by Tim McBrayer for AIX 6.1.)
+
+    Fix zic configuration to avoid linkage failures on some platforms.
+    (Problems reported by Gilmore Davidson and Igor Ivanov.)
+
+    Work around MS-Windows nmake incompatibility with POSIX.
+    (Problem reported by Manuela Friedrich.)
+
+    Port mktime and strftime to debugging platforms where accessing
+    uninitialized data has undefined behavior (strftime problem
+    reported by Robert Elz).
+
+    Check more carefully for unlikely integer overflows, preferring
+    C23 <stdckdint.h> to overflow checking by hand, as the latter has
+    had obscure bugs.
+
+  Changes to build procedure
+
+    New Makefile rule check_mild that skips checking whether Link
+    lines are in the file 'backward'.  (Inspired by a suggestion from
+    Stephen Colebourne.)
+
+
 Release 2022f - 2022-10-28 18:04:57 -0700
 
   Briefly:
@@ -16,7 +102,7 @@ Release 2022f - 2022-10-28 18:04:57 -070
     In C code, use some C23 features if available.
     Remove no-longer-needed workaround for Qt bug 53071.
 
-  Changes to future timestamps.
+  Changes to future timestamps
 
     Mexico will no longer observe DST after 2022, except for areas
     near the US border that continue to observe US DST rules.
@@ -24,6 +110,7 @@ Release 2022f - 2022-10-28 18:04:57 -070
     from -07 (-06 with DST) to year-round -06, thus not changing
     its clocks that day.  The new law states that Chihuahua
     near the US border no longer observes US DST.
+    (Thanks to gera for the heads-up about Chihuahua.)
 
     Fiji will not observe DST in 2022/3.  (Thanks to Shalvin Narayan.)
     For now, assume DST is suspended indefinitely.

Index: src/lib/libc/time/ctime.3
diff -u src/lib/libc/time/ctime.3:1.68 src/lib/libc/time/ctime.3:1.69
--- src/lib/libc/time/ctime.3:1.68	Wed Oct 26 19:22:54 2022
+++ src/lib/libc/time/ctime.3	Sun Dec 11 12:57:23 2022
@@ -1,6 +1,7 @@
-.\" $NetBSD: ctime.3,v 1.68 2022/10/26 23:22:54 jschauma Exp $
+.\" $NetBSD: ctime.3,v 1.69 2022/12/11 17:57:23 christos Exp $
 .\"
-.\" XXX: License missing?
+.\" This file is in the public domain, so clarified as of
+.\" 2009-05-17 by Arthur David Olson.
 .\"
 .Dd October 22, 2022
 .Dt CTIME 3
@@ -603,6 +604,4 @@ restricted to years in the range 1900 th
 To avoid this portability mess, new programs should use
 .Fn strftime
 instead.
-.\" @(#)newctime.3	8.3
-.\" This file is in the public domain, so clarified as of
 .\" 2009-05-17 by Arthur David Olson.

Index: src/lib/libc/time/localtime.c
diff -u src/lib/libc/time/localtime.c:1.135 src/lib/libc/time/localtime.c:1.136
--- src/lib/libc/time/localtime.c:1.135	Sat Oct 29 09:55:50 2022
+++ src/lib/libc/time/localtime.c	Sun Dec 11 12:57:23 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: localtime.c,v 1.135 2022/10/29 13:55:50 christos Exp $	*/
+/*	$NetBSD: localtime.c,v 1.136 2022/12/11 17:57:23 christos Exp $	*/
 
 /* Convert timestamp from time_t to struct tm.  */
 
@@ -12,7 +12,7 @@
 #if 0
 static char	elsieid[] = "@(#)localtime.c	8.17";
 #else
-__RCSID("$NetBSD: localtime.c,v 1.135 2022/10/29 13:55:50 christos Exp $");
+__RCSID("$NetBSD: localtime.c,v 1.136 2022/12/11 17:57:23 christos Exp $");
 #endif
 #endif /* LIBC_SCCS and not lint */
 
@@ -485,8 +485,7 @@ tzloadbody(char const *name, struct stat
 #endif
 	if (!doaccess) {
 		char const *dot;
-		size_t namelen = strlen(name);
-		if (sizeof lsp->fullname - sizeof tzdirslash <= namelen)
+		if (sizeof lsp->fullname - sizeof tzdirslash <= strlen(name))
 		  return ENAMETOOLONG;
 
 		/* Create a string "TZDIR/NAME".  Using sprintf here
@@ -895,7 +894,7 @@ is_digit(char c)
 ** Return a pointer to that character.
 */
 
-static ATTRIBUTE_PURE const char *
+static ATTRIBUTE_REPRODUCIBLE const char *
 getzname(register const char *strp)
 {
 	register char	c;
@@ -916,7 +915,7 @@ getzname(register const char *strp)
 ** We don't do any checking here; checking is done later in common-case code.
 */
 
-static ATTRIBUTE_PURE const char *
+static ATTRIBUTE_REPRODUCIBLE const char *
 getqzname(register const char *strp, const int delim)
 {
 	register int	c;
@@ -1182,13 +1181,11 @@ tzparse(const char *name, struct state *
 {
 	const char *			stdname;
 	const char *			dstname;
-	size_t				stdlen;
-	size_t				dstlen;
-	size_t				charcnt;
 	int_fast32_t			stdoffset;
 	int_fast32_t			dstoffset;
 	register char *			cp;
 	register bool			load_ok;
+	ptrdiff_t stdlen, dstlen, charcnt;
 	time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN;
 
 	dstname = NULL; /* XXX gcc */
@@ -1211,7 +1208,7 @@ tzparse(const char *name, struct state *
 	if (name == NULL)
 	  return false;
 	charcnt = stdlen + 1;
-	if (sizeof sp->chars < charcnt)
+	if ((ptrdiff_t)sizeof sp->chars < charcnt)
 	  return false;
 	if (basep) {
 	  if (0 < basep->timecnt)
@@ -1242,7 +1239,7 @@ tzparse(const char *name, struct state *
 		if (!dstlen)
 		  return false;
 		charcnt += dstlen + 1;
-		if (sizeof sp->chars < charcnt)
+		if ((ptrdiff_t)sizeof sp->chars < charcnt)
 		  return false;
 		if (*name != '\0' && *name != ',' && *name != ';') {
 			name = getoffset(name, &dstoffset);
@@ -1647,6 +1644,14 @@ localsub(struct state const *sp, time_t 
 			}
 			result = localsub(sp, &newt, setname, tmp);
 			if (result) {
+#if defined ckd_add && defined ckd_sub
+				if (t < sp->ats[0]
+				    ? ckd_sub(&result->tm_year,
+					      result->tm_year, years)
+				    : ckd_add(&result->tm_year,
+					      result->tm_year, years))
+				  return NULL;
+#else
 				register int_fast64_t newy;
 
 				newy = result->tm_year;
@@ -1658,6 +1663,7 @@ localsub(struct state const *sp, time_t 
 					return NULL;
 				}
 				result->tm_year = (int)newy;
+#endif
 			}
 			return result;
 	}
@@ -1733,8 +1739,8 @@ localtime_r(const time_t * __restrict ti
 */
 
 static struct tm *
-gmtsub(struct state const *sp, time_t const *timep, int_fast32_t offset,
-       struct tm *tmp)
+gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep,
+       int_fast32_t offset, struct tm *tmp)
 {
 	register struct tm *	result;
 
@@ -1910,6 +1916,12 @@ timesub(const time_t *timep, int_fast32_
 		y = newy;
 	}
 
+#ifdef ckd_add
+	if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) {
+	  errno = EOVERFLOW;
+	  return NULL;
+	}
+#else
 	if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) {
 	  int signed_y = (int)y;
 	  tmp->tm_year = signed_y - TM_YEAR_BASE;
@@ -1920,6 +1932,7 @@ timesub(const time_t *timep, int_fast32_
 	  errno = EOVERFLOW;
 	  return NULL;
 	}
+#endif
 	tmp->tm_yday = idays;
 	/*
 	** The "extra" mods below avoid overflow problems.
@@ -2005,6 +2018,9 @@ ctime_rz(const timezone_t sp, const time
 static bool
 increment_overflow(int *ip, int j)
 {
+#ifdef ckd_add
+	return ckd_add(ip, *ip, j);
+#else
 	register int const	i = *ip;
 
 	/*
@@ -2017,22 +2033,30 @@ increment_overflow(int *ip, int j)
 		return true;
 	*ip += j;
 	return false;
+#endif
 }
 
 static bool
 increment_overflow32(int_fast32_t *const lp, int const m)
 {
+#ifdef ckd_add
+	return ckd_add(lp, *lp, m);
+#else
 	register int_fast32_t const	l = *lp;
 
 	if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l))
 		return true;
 	*lp += m;
 	return false;
+#endif
 }
 
 static bool
 increment_overflow_time(__time_t *tp, int_fast32_t j)
 {
+#ifdef ckd_add
+	return ckd_add(tp, *tp, j);
+#else
 	/*
 	** This is like
 	** 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...',
@@ -2044,6 +2068,7 @@ increment_overflow_time(__time_t *tp, in
 		return true;
 	*tp += j;
 	return false;
+#endif
 }
 
 static bool
@@ -2086,6 +2111,23 @@ tmcomp(register const struct tm *const a
 	return result;
 }
 
+/* Copy to *DEST from *SRC.  Copy only the members needed for mktime,
+   as other members might not be initialized.  */
+static void
+mktmcpy(struct tm *dest, struct tm const *src)
+{
+  dest->tm_sec = src->tm_sec;
+  dest->tm_min = src->tm_min;
+  dest->tm_hour = src->tm_hour;
+  dest->tm_mday = src->tm_mday;
+  dest->tm_mon = src->tm_mon;
+  dest->tm_year = src->tm_year;
+  dest->tm_isdst = src->tm_isdst;
+#if defined TM_GMTOFF && ! UNINIT_TRAP
+  dest->TM_GMTOFF = src->TM_GMTOFF;
+#endif
+}
+
 static time_t
 time2sub(struct tm *const tmp,
 	 struct tm *(*funcp)(struct state const *, time_t const *,
@@ -2110,7 +2152,8 @@ time2sub(struct tm *const tmp,
 	struct tm			yourtm, mytm;
 
 	*okayp = false;
-	yourtm = *tmp;
+	mktmcpy(&yourtm, tmp);
+
 #ifdef NO_ERROR_IN_DST_GAP
 again:
 #endif
@@ -2155,14 +2198,19 @@ again:
 				goto out_of_range;
 		}
 	}
+#ifdef ckd_add
+	if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE))
+	  return WRONG;
+#else
 	if (increment_overflow32(&y, -TM_YEAR_BASE))
 		goto out_of_range;
 	if (! (INT_MIN <= y && y <= INT_MAX))
 		goto out_of_range;
 	yourtm.tm_year = (int)y;
+#endif
 	if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN)
 		saved_seconds = 0;
-	else if (y + TM_YEAR_BASE < EPOCH_YEAR) {
+	else if (yourtm.tm_year + TM_YEAR_BASE < EPOCH_YEAR) {
 		/*
 		** We can't set tm_sec to 0, because that might push the
 		** time below the minimum representable time.
@@ -2455,7 +2503,6 @@ mktime(struct tm *tmp)
 }
 
 #ifdef STD_INSPIRED
-
 time_t
 timelocal_z(const timezone_t sp, struct tm *const tmp)
 {
@@ -2471,13 +2518,9 @@ timelocal(struct tm *tmp)
 		tmp->tm_isdst = -1;	/* in case it wasn't initialized */
 	return mktime(tmp);
 }
-
-time_t
-timegm(struct tm *tmp)
-{
-  return timeoff(tmp, 0);
-}
-
+#else
+static
+#endif
 time_t
 timeoff(struct tm *tmp, long offset)
 {
@@ -2487,7 +2530,18 @@ timeoff(struct tm *tmp, long offset)
   return time1(tmp, gmtsub, gmtptr, (int_fast32_t)offset);
 }
 
-#endif /* defined STD_INSPIRED */
+time_t
+timegm(struct tm *tmp)
+{
+  time_t t;
+  struct tm tmcpy;
+  mktmcpy(&tmcpy, tmp);
+  tmcpy.tm_wday = -1;
+  t = timeoff(&tmcpy, 0);
+  if (0 <= tmcpy.tm_wday)
+    *tmp = tmcpy;
+  return t;
+}
 
 static int_fast32_t
 leapcorr(struct state const *sp, time_t t)

Index: src/lib/libc/time/private.h
diff -u src/lib/libc/time/private.h:1.63 src/lib/libc/time/private.h:1.64
--- src/lib/libc/time/private.h:1.63	Thu Nov 17 12:35:25 2022
+++ src/lib/libc/time/private.h	Sun Dec 11 12:57:23 2022
@@ -1,6 +1,6 @@
 /* Private header for tzdb code.  */
 
-/*	$NetBSD: private.h,v 1.63 2022/11/17 17:35:25 jakllsch Exp $	*/
+/*	$NetBSD: private.h,v 1.64 2022/12/11 17:57:23 christos Exp $	*/
 
 #ifndef PRIVATE_H
 #define PRIVATE_H
@@ -29,6 +29,10 @@
 ** Thank you!
 */
 
+#ifndef __STDC_VERSION__
+# define __STDC_VERSION__ 0
+#endif
+
 /* Define true, false and bool if they don't work out of the box.  */
 #if __STDC_VERSION__ < 199901
 # define true 1
@@ -68,27 +72,13 @@
 # endif
 #endif
 /* _Generic is buggy in pre-4.9 GCC.  */
-#if !defined HAVE_GENERIC && defined __GNUC__
+#if !defined HAVE_GENERIC && defined __GNUC__ && !defined __STRICT_ANSI__
 # define HAVE_GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__))
 #endif
 #ifndef HAVE_GENERIC
 # define HAVE_GENERIC (201112 <= __STDC_VERSION__)
 #endif
 
-#if defined __APPLE__
-# define HAVE_GETRANDOM false
-#endif
-#if !defined HAVE_GETRANDOM && defined __has_include
-# if __has_include(<sys/random.h>)
-#  define HAVE_GETRANDOM true
-# else
-#  define HAVE_GETRANDOM false
-# endif
-#endif
-#ifndef HAVE_GETRANDOM
-# define HAVE_GETRANDOM (2 < __GLIBC__ + (25 <= __GLIBC_MINOR__))
-#endif
-
 #if !defined HAVE_GETTEXT && defined __has_include
 # if __has_include(<libintl.h>)
 #  define HAVE_GETTEXT true
@@ -308,36 +298,36 @@
 #endif
 
 /* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX.  */
-#ifdef __LONG_LONG_MAX__
+#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__
 # ifndef LLONG_MAX
 #  define LLONG_MAX __LONG_LONG_MAX__
 # endif
 # ifndef LLONG_MIN
 #  define LLONG_MIN (-1 - LLONG_MAX)
 # endif
+# ifndef ULLONG_MAX
+#  define ULLONG_MAX (LLONG_MAX * 2ull + 1)
+# endif
 #endif
 
 #ifndef INT_FAST64_MAX
-# ifdef LLONG_MAX
-typedef long long	int_fast64_t;
-#  define INT_FAST64_MIN LLONG_MIN
-#  define INT_FAST64_MAX LLONG_MAX
-# else
-#  if LONG_MAX >> 31 < 0xffffffff
-Please use a compiler that supports a 64-bit integer type (or wider);
-you may need to compile with "-DHAVE_STDINT_H".
-#  endif
-typedef long		int_fast64_t;
+# if 1 <= LONG_MAX >> 31 >> 31
+typedef long int_fast64_t;
 #  define INT_FAST64_MIN LONG_MIN
 #  define INT_FAST64_MAX LONG_MAX
+# else
+/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler.  */
+typedef long long int_fast64_t;
+#  define INT_FAST64_MIN LLONG_MIN
+#  define INT_FAST64_MAX LLONG_MAX
 # endif
 #endif
 
 #ifndef PRIdFAST64
-# if INT_FAST64_MAX == LLONG_MAX
-#  define PRIdFAST64 "lld"
-# else
+# if INT_FAST64_MAX == LONG_MAX
 #  define PRIdFAST64 "ld"
+# else
+#  define PRIdFAST64 "lld"
 # endif
 #endif
 
@@ -383,24 +373,27 @@ typedef long intmax_t;
 # endif
 #endif
 
+#ifndef PTRDIFF_MAX
+# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t))
+#endif
+
 #ifndef UINT_FAST32_MAX
 typedef unsigned long uint_fast32_t;
 #endif
 
 #ifndef UINT_FAST64_MAX
-# if defined ULLONG_MAX || defined __LONG_LONG_MAX__
-typedef unsigned long long uint_fast64_t;
+# if 3 <= ULONG_MAX >> 31 >> 31
+typedef unsigned long uint_fast64_t;
+#  define UINT_FAST64_MAX ULONG_MAX
 # else
-#  if ULONG_MAX >> 31 >> 1 < 0xffffffff
-Please use a compiler that supports a 64-bit integer type (or wider);
-you may need to compile with "-DHAVE_STDINT_H".
-#  endif
-typedef unsigned long	uint_fast64_t;
+/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler.  */
+typedef unsigned long long uint_fast64_t;
+#  define UINT_FAST64_MAX ULLONG_MAX
 # endif
 #endif
 
 #ifndef UINTMAX_MAX
-# if defined ULLONG_MAX || defined __LONG_LONG_MAX__
+# ifdef ULLONG_MAX
 typedef unsigned long long uintmax_t;
 # else
 typedef unsigned long uintmax_t;
@@ -408,7 +401,7 @@ typedef unsigned long uintmax_t;
 #endif
 
 #ifndef PRIuMAX
-# if defined ULLONG_MAX || defined __LONG_LONG_MAX__
+# ifdef ULLONG_MAX
 #  define PRIuMAX "llu"
 # else
 #  define PRIuMAX "lu"
@@ -419,23 +412,114 @@ typedef unsigned long uintmax_t;
 # define SIZE_MAX ((size_t) -1)
 #endif
 
+/* Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like
+   hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG.  */
+#if !defined HAVE_STDCKDINT_H && defined __has_include
+# if __has_include(<stdckdint.h>)
+#  define HAVE_STDCKDINT_H true
+# endif
+#endif
+#ifdef HAVE_STDCKDINT_H
+# if HAVE_STDCKDINT_H
+#  include <stdckdint.h>
+# endif
+#elif defined __EDG__
+/* Do nothing, to work around EDG bug <https://bugs.gnu.org/53256>.  */
+#elif defined __has_builtin
+# if __has_builtin(__builtin_add_overflow)
+#  define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
+# endif
+# if __has_builtin(__builtin_sub_overflow)
+#  define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
+# endif
+# if __has_builtin(__builtin_mul_overflow)
+#  define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
+# endif
+#elif 7 <= __GNUC__
+# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
+# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
+# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
+#endif
+
 #if 3 <= __GNUC__
-# define ATTRIBUTE_CONST __attribute__((__const__))
 # define ATTRIBUTE_MALLOC __attribute__((__malloc__))
-# define ATTRIBUTE_PURE __attribute__((__pure__))
 # define ATTRIBUTE_FORMAT(spec) __attribute__((__format__ spec))
 #else
-# define ATTRIBUTE_CONST /* empty */
 # define ATTRIBUTE_MALLOC /* empty */
-# define ATTRIBUTE_PURE /* empty */
 # define ATTRIBUTE_FORMAT(spec) /* empty */
 #endif
 
-#if !defined _Noreturn && __STDC_VERSION__ < 201112
-# if 2 < __GNUC__ + (8 <= __GNUC_MINOR__)
-#  define _Noreturn __attribute__((__noreturn__))
+#if (defined __has_c_attribute \
+     && (202311 <= __STDC_VERSION__ || !defined __STRICT_ANSI__))
+# define HAVE_HAS_C_ATTRIBUTE true
+#else
+# define HAVE_HAS_C_ATTRIBUTE false
+#endif
+
+#if HAVE_HAS_C_ATTRIBUTE
+# if __has_c_attribute(fallthrough)
+#  define ATTRIBUTE_FALLTHROUGH [[fallthrough]]
+# endif
+#endif
+#ifndef ATTRIBUTE_FALLTHROUGH
+# if 7 <= __GNUC__
+#  define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough))
+# else
+#  define ATTRIBUTE_FALLTHROUGH ((void) 0)
+# endif
+#endif
+
+#if HAVE_HAS_C_ATTRIBUTE
+# if __has_c_attribute(maybe_unused)
+#  define ATTRIBUTE_MAYBE_UNUSED [[maybe_unused]]
+# endif
+#endif
+#ifndef ATTRIBUTE_MAYBE_UNUSED
+# if 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+#  define ATTRIBUTE_MAYBE_UNUSED __attribute__((unused))
 # else
-#  define _Noreturn
+#  define ATTRIBUTE_MAYBE_UNUSED /* empty */
+# endif
+#endif
+
+#if HAVE_HAS_C_ATTRIBUTE
+# if __has_c_attribute(noreturn)
+#  define ATTRIBUTE_NORETURN [[noreturn]]
+# endif
+#endif
+#ifndef ATTRIBUTE_NORETURN
+# if 201112 <= __STDC_VERSION__
+#  define ATTRIBUTE_NORETURN _Noreturn
+# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__)
+#  define ATTRIBUTE_NORETURN __attribute__((noreturn))
+# else
+#  define ATTRIBUTE_NORETURN /* empty */
+# endif
+#endif
+
+#if HAVE_HAS_C_ATTRIBUTE
+# if __has_c_attribute(reproducible)
+#  define ATTRIBUTE_REPRODUCIBLE [[reproducible]]
+# endif
+#endif
+#ifndef ATTRIBUTE_REPRODUCIBLE
+# if 3 <= __GNUC__
+#  define ATTRIBUTE_REPRODUCIBLE __attribute__((pure))
+# else
+#  define ATTRIBUTE_REPRODUCIBLE /* empty */
+# endif
+#endif
+
+#if HAVE_HAS_C_ATTRIBUTE
+# if __has_c_attribute(unsequenced)
+#  define ATTRIBUTE_UNSEQUENCED [[unsequenced]]
+# endif
+#endif
+#ifndef ATTRIBUTE_UNSEQUENCED
+# if 3 <= __GNUC__
+#  define ATTRIBUTE_UNSEQUENCED __attribute__((const))
+# else
+#  define ATTRIBUTE_UNSEQUENCED /* empty */
 # endif
 #endif
 
@@ -562,7 +646,7 @@ char *asctime(struct tm const *);
 char *asctime_r(struct tm const *restrict, char *restrict);
 char *ctime(time_t const *);
 char *ctime_r(time_t const *, char *);
-double difftime(time_t, time_t) ATTRIBUTE_CONST;
+double difftime(time_t, time_t) ATTRIBUTE_UNSEQUENCED;
 size_t strftime(char *restrict, size_t, char const *restrict,
 		struct tm const *restrict);
 # if HAVE_STRFTIME_L
@@ -575,9 +659,24 @@ struct tm *localtime(time_t const *);
 struct tm *localtime_r(time_t const *restrict, struct tm *restrict);
 time_t mktime(struct tm *);
 time_t time(time_t *);
+time_t timegm(struct tm *);
 void tzset(void);
 #endif
 
+#ifndef HAVE_DECL_TIMEGM
+# if (202311 <= __STDC_VERSION__ \
+      || defined __GLIBC__ || defined __tm_zone /* musl */ \
+      || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+      || (defined __APPLE__ && defined __MACH__))
+#  define HAVE_DECL_TIMEGM true
+# else
+#  define HAVE_DECL_TIMEGM false
+# endif
+#endif
+#if !HAVE_DECL_TIMEGM && !defined timegm
+time_t timegm(struct tm *);
+#endif
+
 #if !HAVE_DECL_ASCTIME_R && !defined asctime_r
 extern char *asctime_r(struct tm const *restrict, char *restrict);
 #endif
@@ -617,9 +716,6 @@ void tzsetwall(void);
 # if TZ_TIME_T || !defined offtime
 struct tm *offtime(time_t const *, long);
 # endif
-# if TZ_TIME_T || !defined timegm
-time_t timegm(struct tm *);
-# endif
 # if TZ_TIME_T || !defined timelocal
 time_t timelocal(struct tm *);
 # endif
@@ -637,6 +733,7 @@ time_t posix2time(time_t);
 /* Infer TM_ZONE on systems where this information is known, but suppress
    guessing if NO_TM_ZONE is defined.  Similarly for TM_GMTOFF.  */
 #if (defined __GLIBC__ \
+     || defined __tm_zone /* musl */ \
      || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
      || (defined __APPLE__ && defined __MACH__))
 # if !defined TM_GMTOFF && !defined NO_TM_GMTOFF
@@ -665,10 +762,10 @@ timezone_t tzalloc(char const *);
 void tzfree(timezone_t);
 # ifdef STD_INSPIRED
 #  if TZ_TIME_T || !defined posix2time_z
-time_t posix2time_z(timezone_t __restrict, time_t) ATTRIBUTE_PURE;
+time_t posix2time_z(timezone_t __restrict, time_t) ATTRIBUTE_REPRODUCIBLE;
 #  endif
 #  if TZ_TIME_T || !defined time2posix_z
-time_t time2posix_z(timezone_t __restrict, time_t) ATTRIBUTE_PURE;
+time_t time2posix_z(timezone_t __restrict, time_t) ATTRIBUTE_REPRODUCIBLE;
 #  endif
 # endif
 #endif

Index: src/lib/libc/time/strftime.c
diff -u src/lib/libc/time/strftime.c:1.50 src/lib/libc/time/strftime.c:1.51
--- src/lib/libc/time/strftime.c:1.50	Tue Aug 16 06:56:21 2022
+++ src/lib/libc/time/strftime.c	Sun Dec 11 12:57:23 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: strftime.c,v 1.50 2022/08/16 10:56:21 christos Exp $	*/
+/*	$NetBSD: strftime.c,v 1.51 2022/12/11 17:57:23 christos Exp $	*/
 
 /* Convert a broken-down timestamp to a string.  */
 
@@ -35,7 +35,7 @@
 static char	elsieid[] = "@(#)strftime.c	7.64";
 static char	elsieid[] = "@(#)strftime.c	8.3";
 #else
-__RCSID("$NetBSD: strftime.c,v 1.50 2022/08/16 10:56:21 christos Exp $");
+__RCSID("$NetBSD: strftime.c,v 1.51 2022/12/11 17:57:23 christos Exp $");
 #endif
 #endif /* LIBC_SCCS and not lint */
 
@@ -135,7 +135,7 @@ strftime_z(const timezone_t sp, char * _
 #if HAVE_STRFTIME_L
 size_t
 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
-	   locale_t locale)
+	   ATTRIBUTE_MAYBE_UNUSED locale_t locale)
 {
   /* Just call strftime, as only the C locale is supported.  */
   return strftime(s, maxsize, format, t);
@@ -374,12 +374,21 @@ label:
 								time_t) + 1];
 					time_t		mkt;
 
-					tm = *t;
+					tm.tm_sec = t->tm_sec;
+					tm.tm_min = t->tm_min;
+					tm.tm_hour = t->tm_hour;
+					tm.tm_mday = t->tm_mday;
+					tm.tm_mon = t->tm_mon;
+					tm.tm_year = t->tm_year;
+					tm.tm_isdst = t->tm_isdst;
+#if defined TM_GMTOFF && ! UNINIT_TRAP
+					tm.TM_GMTOFF = t->TM_GMTOFF;
+#endif
 					mkt = mktime_z(sp, &tm);
-					/* There is no portable, definitive
-					   test for whether whether mktime
-					   succeeded, so treat (time_t) -1 as
-					   the success that it might be.  */
+					/* If mktime fails, %s expands to the
+					   value of (time_t) -1 as a failure
+					   marker; this is better in practice
+					   than strftime failing.  */
 					/* CONSTCOND */
 					if (TYPE_SIGNED(time_t)) {
 						intmax_t n = mkt;

Index: src/lib/libc/time/theory.html
diff -u src/lib/libc/time/theory.html:1.16 src/lib/libc/time/theory.html:1.17
--- src/lib/libc/time/theory.html:1.16	Sat Oct 29 09:55:50 2022
+++ src/lib/libc/time/theory.html	Sun Dec 11 12:57:23 2022
@@ -60,7 +60,6 @@ with current and future timestamps in th
 American mountain time zone can choose from the timezones
 <code>America/Denver</code> which observes US-style daylight saving
 time (<abbr>DST</abbr>),
-<code>America/Mazatlan</code> which observes Mexican-style <abbr>DST</abbr>,
 and <code>America/Phoenix</code> which does not observe <abbr>DST</abbr>.
 Applications that also deal with past timestamps in the mountain time
 zone can choose from over a dozen timezones, such as

Index: src/lib/libc/time/time2posix.3
diff -u src/lib/libc/time/time2posix.3:1.22 src/lib/libc/time/time2posix.3:1.23
--- src/lib/libc/time/time2posix.3:1.22	Sun Oct 29 02:07:48 2017
+++ src/lib/libc/time/time2posix.3	Sun Dec 11 12:57:23 2022
@@ -1,4 +1,7 @@
-.\"	$NetBSD: time2posix.3,v 1.22 2017/10/29 06:07:48 abhinav Exp $
+.\"	$NetBSD: time2posix.3,v 1.23 2022/12/11 17:57:23 christos Exp $
+.\" @(#)time2posix.3	7.7
+.\" This file is in the public domain, so clarified as of
+.\" 1996-06-05 by Arthur David Olson.
 .Dd October 6, 2014
 .Dt TIME2POSIX 3
 .Os
@@ -63,8 +66,8 @@ However, POSIX gives an arithmetic expre
 value from a given date/time, and the same relationship is assumed by
 some (usually older) applications.
 Any programs creating/dissecting
-.Va time_t Ns 's
-using such a relationship will typically not handle intervals over
+.Va time_t
+values using such a relationship will typically not handle intervals over
 leap seconds correctly.
 .Pp
 The
@@ -132,10 +135,10 @@ A leap second deletion would look like..
 [Note: posix2time(B+1) => A+0 or A+1]
 .Pp
 If leap-second support is not enabled, local
-.Va time_t Ns 's
+.Va time_t
 and POSIX
-.Va time_t Ns 's
-are equivalent, and both
+.Va time_t
+values are equivalent, and both
 .Fn time2posix
 and
 .Fn posix2time
@@ -149,6 +152,3 @@ degenerate to the identity function.
 .Xr mktime_z 3 ,
 .Xr time 3 ,
 .Xr tzalloc 3
-.\" @(#)time2posix.3	7.7
-.\" This file is in the public domain, so clarified as of
-.\" 1996-06-05 by Arthur David Olson.
Index: src/lib/libc/time/version
diff -u src/lib/libc/time/version:1.22 src/lib/libc/time/version:1.23
--- src/lib/libc/time/version:1.22	Sat Oct 29 09:55:50 2022
+++ src/lib/libc/time/version	Sun Dec 11 12:57:23 2022
@@ -1 +1 @@
-2022f
+2022g

Index: src/lib/libc/time/tzfile.5
diff -u src/lib/libc/time/tzfile.5:1.32 src/lib/libc/time/tzfile.5:1.33
--- src/lib/libc/time/tzfile.5:1.32	Tue Aug 16 07:07:40 2022
+++ src/lib/libc/time/tzfile.5	Sun Dec 11 12:57:23 2022
@@ -1,7 +1,8 @@
-.\"	$NetBSD: tzfile.5,v 1.32 2022/08/16 11:07:40 christos Exp $
+.\"	$NetBSD: tzfile.5,v 1.33 2022/12/11 17:57:23 christos Exp $
 .\"
+.\" @(#)tzfile.5	8.3
 .\" This file is in the public domain, so clarified as of
-.\" 1996-06-05 by Arthur David Olson (arthur_david_ol...@nih.gov).
+.\" 2009-05-17 by Arthur David Olson.
 .Dd August 16, 2022
 .Dt TZFILE 5
 .Os
@@ -514,6 +515,3 @@ Future changes to the format may append 
 .%U https://doi.org/10.17487/RFC8536
 .%R RFC 8536
 .Re
-.\" @(#)tzfile.5	8.3
-.\" This file is in the public domain, so clarified as of
-.\" 1996-06-05 by Arthur David Olson.

Index: src/lib/libc/time/tzselect.8
diff -u src/lib/libc/time/tzselect.8:1.12 src/lib/libc/time/tzselect.8:1.13
--- src/lib/libc/time/tzselect.8:1.12	Tue Aug 16 07:07:40 2022
+++ src/lib/libc/time/tzselect.8	Sun Dec 11 12:57:23 2022
@@ -1,10 +1,12 @@
-.\"	$NetBSD: tzselect.8,v 1.12 2022/08/16 11:07:40 christos Exp $
+.\"	$NetBSD: tzselect.8,v 1.13 2022/12/11 17:57:23 christos Exp $
 .\"
+.\" This file is in the public domain, so clarified as of
+.\" 2009-05-17 by Arthur David Olson.
 .TH TZSELECT 8
 .SH NAME
 tzselect \- select a timezone
 .SH SYNOPSIS
-.ie \n(.g .ds - \f(CW-\fP
+.ie \n(.g .ds - \f(CR-\fP
 .el .ds - \-
 .ds d " degrees
 .ds m " minutes
@@ -123,6 +125,4 @@ newctime(3), tzfile(5), zdump(8), zic(8)
 Applications should not assume that
 .BR tzselect 's
 output matches the user's political preferences.
-.\" @(#)tzselect.8	8.2
-.\" This file is in the public domain, so clarified as of
 .\" 2009-05-17 by Arthur David Olson.

Index: src/lib/libc/time/tzset.3
diff -u src/lib/libc/time/tzset.3:1.44 src/lib/libc/time/tzset.3:1.45
--- src/lib/libc/time/tzset.3:1.44	Sun Dec  4 06:25:09 2022
+++ src/lib/libc/time/tzset.3	Sun Dec 11 12:57:23 2022
@@ -1,4 +1,6 @@
-.\"	$NetBSD: tzset.3,v 1.44 2022/12/04 11:25:09 uwe Exp $
+.\"	$NetBSD: tzset.3,v 1.45 2022/12/11 17:57:23 christos Exp $
+.\" This file is in the public domain, so clarified as of
+.\" 2009-05-17 by Arthur David Olson.
 .Dd Auguset 23, 2021
 .Dt TZSET 3
 .Os
@@ -407,7 +409,7 @@ local timezone information directory
 .\" .It Pa /usr/share/zoneinfo/localtime
 .\" local timezone file
 .It Pa /usr/share/zoneinfo/posixrules
-used with POSIX-style TZ's
+used with POSIX-style TZ
 .It Pa /usr/share/zoneinfo/GMT
 for UTC leap seconds
 .El

Index: src/lib/libc/time/zdump.8
diff -u src/lib/libc/time/zdump.8:1.21 src/lib/libc/time/zdump.8:1.22
--- src/lib/libc/time/zdump.8:1.21	Fri Oct 22 10:26:04 2021
+++ src/lib/libc/time/zdump.8	Sun Dec 11 12:57:23 2022
@@ -1,4 +1,8 @@
-.\"	$NetBSD: zdump.8,v 1.21 2021/10/22 14:26:04 christos Exp $
+.\" $NetBSD: zdump.8,v 1.22 2022/12/11 17:57:23 christos Exp $
+.\" @(#)zdump.8	8.2
+.\" This file is in the public domain, so clarified as of
+.\" 2009-05-17 by Arthur David Olson.
+.TH zdump 8
 .Dd October 22, 2021
 .Dt ZDUMP 8
 .Os
@@ -217,6 +221,3 @@ introduction of UTC is problematic.
 .Xr localtime 3 ,
 .Xr tzfile 5 ,
 .Xr zic 8
-.\" @(#)zdump.8	8.2
-.\" This file is in the public domain, so clarified as of
-.\" 2009-05-17 by Arthur David Olson.

Index: src/lib/libc/time/zdump.c
diff -u src/lib/libc/time/zdump.c:1.58 src/lib/libc/time/zdump.c:1.59
--- src/lib/libc/time/zdump.c:1.58	Sat Oct 29 09:55:50 2022
+++ src/lib/libc/time/zdump.c	Sun Dec 11 12:57:23 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: zdump.c,v 1.58 2022/10/29 13:55:50 christos Exp $	*/
+/*	$NetBSD: zdump.c,v 1.59 2022/12/11 17:57:23 christos Exp $	*/
 /* Dump time zone data in a textual format.  */
 
 /*
@@ -8,7 +8,7 @@
 
 #include <sys/cdefs.h>
 #ifndef lint
-__RCSID("$NetBSD: zdump.c,v 1.58 2022/10/29 13:55:50 christos Exp $");
+__RCSID("$NetBSD: zdump.c,v 1.59 2022/12/11 17:57:23 christos Exp $");
 #endif /* !defined lint */
 
 #ifndef NETBSD_INSPIRED
@@ -89,20 +89,20 @@ static time_t	absolute_max_time =
     ? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
    : -1);
 static size_t	longest;
-static char *	progname;
+static char const *progname;
 static bool	warned;
 static bool	errout;
 
 static char const *abbr(struct tm const *);
-static intmax_t	delta(struct tm *, struct tm *) ATTRIBUTE_PURE;
+static intmax_t	delta(struct tm *, struct tm *) ATTRIBUTE_REPRODUCIBLE;
 static void dumptime(struct tm const *);
-static time_t hunt(timezone_t, char *, time_t, time_t, bool);
+static time_t hunt(timezone_t, time_t, time_t, bool);
 static void show(timezone_t, char *, time_t, bool);
 static void showextrema(timezone_t, char *, time_t, struct tm *, time_t);
 static void showtrans(char const *, struct tm const *, time_t, char const *,
 		      char const *);
 static const char *tformat(void);
-static time_t yeartot(intmax_t) ATTRIBUTE_PURE;
+static time_t yeartot(intmax_t) ATTRIBUTE_REPRODUCIBLE;
 
 /* Is C an ASCII digit?  */
 static bool
@@ -130,14 +130,28 @@ is_alpha(char a)
 	}
 }
 
-/* Return A + B, exiting if the result would overflow.  */
-static size_t
+static ATTRIBUTE_NORETURN void
+size_overflow(void)
+{
+  fprintf(stderr, _("%s: size overflow\n"), progname);
+  exit(EXIT_FAILURE);
+}
+
+/* Return A + B, exiting if the result would overflow either ptrdiff_t
+   or size_t.  */
+static ATTRIBUTE_REPRODUCIBLE ptrdiff_t
 sumsize(size_t a, size_t b)
 {
-	size_t sum = a + b;
-	if (sum < a)
-		errx(EXIT_FAILURE, _("size overflow"));
-	return sum;
+#ifdef ckd_add
+  ptrdiff_t sum;
+  if (!ckd_add(&sum, a, b) && sum <= PTRDIFF_MAX)
+    return sum;
+#else
+  ptrdiff_t sum_max = min(PTRDIFF_MAX, SIZE_MAX);
+  if (a <= sum_max && b <= sum_max - a)
+    return a + b;
+#endif
+  size_overflow();
 }
 
 /* Return a pointer to a newly allocated buffer of size SIZE, exiting
@@ -237,21 +251,29 @@ tzalloc(char const *val)
     exit(EXIT_FAILURE);
   }
   tzset();
-  return NULL;
+  return &optarg;  /* Any valid non-null char ** will do.  */
 # else
   enum { TZeqlen = 3 };
   static char const TZeq[TZeqlen] = "TZ=";
-  static size_t fakeenv0size;
+  static ptrdiff_t fakeenv0size;
   void *freeable = NULL;
   char **env = fakeenv, **initial_environ;
   size_t valsize = strlen(val) + 1;
   if (fakeenv0size < valsize) {
     char **e = environ, **to;
-    ptrdiff_t initial_nenvptrs;  /* Counting the trailing NULL pointer.  */
+    ptrdiff_t initial_nenvptrs = 1;  /* Counting the trailing NULL pointer.  */
 
-    while (*e++)
-      continue;
-    initial_nenvptrs = e - environ;
+    while (*e++) {
+#  ifdef ckd_add
+      if (ckd_add(&initial_nenvptrs, initial_envptrs, 1)
+	  || SIZE_MAX < initial_envptrs)
+	size_overflow();
+#  else
+      if (initial_nenvptrs == min(PTRDIFF_MAX, SIZE_MAX) / sizeof *environ)
+	size_overflow();
+      initial_nenvptrs++;
+#  endif
++    }
     fakeenv0size = sumsize(valsize, valsize);
     fakeenv0size = max(fakeenv0size, 64);
     freeable = env;
@@ -386,14 +408,14 @@ abbrok(const char *const abbrp, const ch
    return the abbreviation.  Get the abbreviation from TMP.
    Exit on memory allocation failure.  */
 static char const *
-saveabbr(char **buf, size_t *bufalloc, struct tm const *tmp)
+saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
 {
 	char const *ab = abbr(tmp);
 	if (HAVE_LOCALTIME_RZ)
 		return ab;
 	else {
 		size_t ablen = strlen(ab);
-		if (*bufalloc <= ablen) {
+		if (*bufalloc <= (ptrdiff_t)ablen) {
 			free(*buf);
 
 			/* Make the new buffer at least twice as long as the
@@ -441,7 +463,7 @@ main(int argc, char *argv[])
 {
 	/* These are static so that they're initially zero.  */
 	static char *		abbrev;
-	static size_t		abbrevsize;
+	static ptrdiff_t	abbrevsize;
 
 	int		i;
 	bool		vflag;
@@ -462,7 +484,7 @@ main(int argc, char *argv[])
 # endif /* defined TEXTDOMAINDIR */
 	(void) textdomain(TZ_DOMAIN);
 #endif /* HAVE_GETTEXT */
-	progname = argv[0];
+	progname = argv[0] ? argv[0] : "zdump";
 	for (i = 1; i < argc; ++i)
 		if (strcmp(argv[i], "--version") == 0) {
 			(void) printf("zdump %s%s\n", PKGVERSION, TZVERSION);
@@ -482,7 +504,7 @@ main(int argc, char *argv[])
 	  case -1:
 	    if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
 	      goto arg_processing_done;
-	    /* Fall through.  */
+	    ATTRIBUTE_FALLTHROUGH;
 	  default:
 	    usage(stderr, EXIT_FAILURE);
 	  }
@@ -606,7 +628,7 @@ main(int argc, char *argv[])
 			    || (ab && (delta(&newtm, &tm) != newt - t
 				       || newtm.tm_isdst != tm.tm_isdst
 				       || strcmp(abbr(&newtm), ab) != 0))) {
-				newt = hunt(tz, argv[i], t, newt, false);
+				newt = hunt(tz, t, newt, false);
 				newtmp = localtime_rz(tz, &newt, &newtm);
 				newtm_ok = newtmp != NULL;
 				if (iflag)
@@ -687,7 +709,7 @@ yeartot(intmax_t y)
 	return t;
 }
 
-/* Search for a discontinuity in timezone TZ with name NAME, in the
+/* Search for a discontinuity in timezone TZ, in the
    timestamps ranging from LOT through HIT.  LOT and HIT disagree
    about some aspect of timezone.  If ONLY_OK, search only for
    definedness changes, i.e., localtime succeeds on one side of the
@@ -695,10 +717,10 @@ yeartot(intmax_t y)
    before the transition from LOT's settings.  */
 
 static time_t
-hunt(timezone_t tz, char *name, time_t lot, time_t hit, bool only_ok)
+hunt(timezone_t tz, time_t lot, time_t hit, bool only_ok)
 {
 	static char *		loab;
-	static size_t		loabsize;
+	static ptrdiff_t	loabsize;
 	struct tm		lotm;
 	struct tm		tm;
 
@@ -787,7 +809,8 @@ adjusted_yday(struct tm const *a, struct
    my_gmtime_r and use its result instead of B.  Otherwise, B is the
    possibly nonnull result of an earlier call to my_gmtime_r.  */
 static long
-gmtoff(struct tm const *a, time_t *t, struct tm const *b)
+gmtoff(struct tm const *a, ATTRIBUTE_MAYBE_UNUSED time_t *t,
+       ATTRIBUTE_MAYBE_UNUSED struct tm const *b)
 {
 #ifdef TM_GMTOFF
 	return a->TM_GMTOFF;
@@ -858,7 +881,7 @@ static void
 showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
 {
   struct tm localtm[2], gmtm[2];
-  time_t t, boundary = hunt(tz, zone, lo, hi, true);
+  time_t t, boundary = hunt(tz, lo, hi, true);
   bool old = false;
   hi = (SECSPERDAY < hi - boundary
 	? boundary + SECSPERDAY
@@ -937,7 +960,7 @@ my_snprintf(char *s, size_t size, char c
    fit, return the length that the string would have been if it had
    fit; do not overrun the output buffer.  */
 static int
-format_local_time(char *buf, size_t size, struct tm const *tm)
+format_local_time(char *buf, ptrdiff_t size, struct tm const *tm)
 {
   int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
   return (ss
@@ -960,7 +983,7 @@ format_local_time(char *buf, size_t size
    the length that the string would have been if it had fit; do not
    overrun the output buffer.  */
 static int
-format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t)
+format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t)
 {
   long off = gmtoff(tm, &t, NULL);
   char sign = ((off < 0
@@ -989,11 +1012,11 @@ format_utc_offset(char *buf, size_t size
    If the representation's length is less than SIZE, return the
    length; the representation is not null terminated.  Otherwise
    return SIZE, to indicate that BUF is too small.  */
-static size_t
-format_quoted_string(char *buf, size_t size, char const *p)
+static ptrdiff_t
+format_quoted_string(char *buf, ptrdiff_t size, char const *p)
 {
   char *b = buf;
-  size_t s = size;
+  ptrdiff_t s = size;
   if (!s)
     return size;
   *b++ = '"', s--;
@@ -1031,11 +1054,11 @@ format_quoted_string(char *buf, size_t s
       and omit any trailing tabs.  */
 
 static bool
-istrftime(char *buf, size_t size, char const *time_fmt,
+istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
 	  struct tm const *tm, time_t t, char const *ab, char const *zone_name)
 {
   char *b = buf;
-  size_t s = size;
+  ptrdiff_t s = size;
   char const *f = time_fmt, *p;
 
   for (p = f; ; p++)
@@ -1044,11 +1067,11 @@ istrftime(char *buf, size_t size, char c
     else if (!*p
 	     || (*p == '%'
 		 && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
-      size_t formatted_len;
-      size_t f_prefix_len = p - f;
-      size_t f_prefix_copy_size = p - f + 2;
+      ptrdiff_t formatted_len;
+      ptrdiff_t f_prefix_len = p - f;
+      ptrdiff_t f_prefix_copy_size = sumsize(f_prefix_len, 2);
       char fbuf[100];
-      bool oversized = sizeof fbuf <= f_prefix_copy_size;
+      bool oversized = (ptrdiff_t)sizeof fbuf <= f_prefix_copy_size;
       char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
       memcpy(f_prefix_copy, f, f_prefix_len);
       strcpy(f_prefix_copy + f_prefix_len, "X");
@@ -1072,20 +1095,20 @@ istrftime(char *buf, size_t size, char c
 	{
 	  bool show_abbr;
 	  int offlen = format_utc_offset(b, s, tm, t);
-	  if (! (0 <= offlen && (size_t)offlen < s))
+	  if (! (0 <= offlen && offlen < s))
 	    return false;
 	  show_abbr = strcmp(b, ab) != 0;
 	  b += offlen, s -= offlen;
 	  if (show_abbr) {
 	    char const *abp;
-	    size_t len;
+	    ptrdiff_t len;
 	    if (s <= 1)
 	      return false;
 	    *b++ = '\t', s--;
 	    for (abp = ab; is_alpha(*abp); abp++)
 	      continue;
 	    len = (!*abp && *ab
-		   ? (size_t)my_snprintf(b, s, "%s", ab)
+		   ? my_snprintf(b, s, "%s", ab)
 		   : format_quoted_string(b, s, ab));
 	    if (s <= len)
 	      return false;
@@ -1117,7 +1140,7 @@ showtrans(char const *time_fmt, struct t
     putchar('\n');
   } else {
     char stackbuf[1000];
-    size_t size = sizeof stackbuf;
+    ptrdiff_t size = sizeof stackbuf;
     char *buf = stackbuf;
     char *bufalloc = NULL;
     while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {

Index: src/lib/libc/time/zic.8
diff -u src/lib/libc/time/zic.8:1.40 src/lib/libc/time/zic.8:1.41
--- src/lib/libc/time/zic.8:1.40	Sat Oct 29 09:55:50 2022
+++ src/lib/libc/time/zic.8	Sun Dec 11 12:57:23 2022
@@ -1,4 +1,8 @@
-.\"	$NetBSD: zic.8,v 1.40 2022/10/29 13:55:50 christos Exp $
+.\" $NetBSD: zic.8,v 1.41 2022/12/11 17:57:23 christos Exp $
+.\" @(#)zic.8	8.6
+.\" This file is in the public domain, so clarified as of
+.\" 2009-05-17 by Arthur David Olson.
+.TH zic 8
 .Dd August 24, 2022
 .Dt ZIC 8
 .Os
@@ -842,6 +846,3 @@ specifying transition instants using uni
 .Sh SEE ALSO
 .Xr tzfile 5 ,
 .Xr zdump 8
-.\" @(#)zic.8	8.6
-.\" This file is in the public domain, so clarified as of
-.\" 2009-05-17 by Arthur David Olson.

Index: src/lib/libc/time/zic.c
diff -u src/lib/libc/time/zic.c:1.85 src/lib/libc/time/zic.c:1.86
--- src/lib/libc/time/zic.c:1.85	Wed Nov  2 08:49:10 2022
+++ src/lib/libc/time/zic.c	Sun Dec 11 12:57:23 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: zic.c,v 1.85 2022/11/02 12:49:10 christos Exp $	*/
+/*	$NetBSD: zic.c,v 1.86 2022/12/11 17:57:23 christos Exp $	*/
 /*
 ** This file is in the public domain, so clarified as of
 ** 2006-07-17 by Arthur David Olson.
@@ -11,7 +11,7 @@
 
 #include <sys/cdefs.h>
 #ifndef lint
-__RCSID("$NetBSD: zic.c,v 1.85 2022/11/02 12:49:10 christos Exp $");
+__RCSID("$NetBSD: zic.c,v 1.86 2022/12/11 17:57:23 christos Exp $");
 #endif /* !defined lint */
 
 /* Use the system 'time' function, instead of any private replacement.
@@ -31,6 +31,9 @@ __RCSID("$NetBSD: zic.c,v 1.85 2022/11/0
 #include <stdio.h>
 #include <unistd.h>
 #include <util.h>
+#define emalloc zic_malloc
+#define erealloc zic_realloc
+#define estrdup zic_strdup
 
 typedef int_fast64_t	zic_t;
 static zic_t const
@@ -44,6 +47,9 @@ static zic_t const
 # define ZIC_MAX_ABBR_LEN_WO_WARN 6
 #endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
 
+/* An upper bound on how much a format might grow due to concatenation.  */
+enum { FORMAT_LEN_GROWTH_BOUND = 5 };
+
 #ifdef HAVE_DIRECT_H
 # include <direct.h>
 # include <io.h>
@@ -51,7 +57,16 @@ static zic_t const
 # define mkdir(name, mode) _mkdir(name)
 #endif
 
-#if HAVE_GETRANDOM
+#ifndef HAVE_GETRANDOM
+# ifdef __has_include
+#  if __has_include(<sys/random.h>)
+#   include <sys/random.h>
+#  endif
+# elif 2 < __GLIBC__ + (25 <= __GLIBC_MINOR__)
+#  include <sys/random.h>
+# endif
+# define HAVE_GETRANDOM GRND_RANDOM
+#elif HAVE_GETRANDOM
 # include <sys/random.h>
 #endif
 
@@ -64,11 +79,6 @@ static zic_t const
 # define MKDIR_UMASK 0755
 #endif
 
-/* The maximum ptrdiff_t value, for pre-C99 platforms.  */
-#ifndef PTRDIFF_MAX
-static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t));
-#endif
-
 /* The minimum alignment of a type, for pre-C23 platforms.  */
 #if __STDC_VERSION__ < 201112
 # define alignof(type) offsetof(struct { char a; type b; }, b)
@@ -462,29 +472,54 @@ static char		roll[TZ_MAX_LEAPS];
 ** Memory allocation.
 */
 
-static _Noreturn void
+static ATTRIBUTE_NORETURN void
 memory_exhausted(const char *msg)
 {
 	fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg);
 	exit(EXIT_FAILURE);
 }
 
-static ATTRIBUTE_PURE size_t
-size_product(size_t nitems, size_t itemsize)
+static ATTRIBUTE_NORETURN void
+size_overflow(void)
+{
+  memory_exhausted(_("size overflow"));
+}
+
+static ATTRIBUTE_REPRODUCIBLE ptrdiff_t
+size_sum(size_t a, size_t b)
+{
+#ifdef ckd_add
+  ptrdiff_t sum;
+  if (!ckd_add(&sum, a, b) && sum <= PTRDIFF_MAX)
+    return sum;
+#else
+  ptrdiff_t sum_max = min(PTRDIFF_MAX, SIZE_MAX);
+  if (a <= sum_max && b <= sum_max - a)
+    return a + b;
+#endif
+  size_overflow();
+}
+
+static ATTRIBUTE_REPRODUCIBLE ptrdiff_t
+size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
+{
+#ifdef ckd_mul
+  ptrdiff_t product;
+  if (!ckd_mul(&product, nitems, itemsize) && product <= PTRDIFF_MAX)
+    return product;
+#else
+  ptrdiff_t nitems_max = min(PTRDIFF_MAX, SIZE_MAX) / itemsize;
+  if (nitems <= nitems_max)
+    return nitems * itemsize;
+#endif
+  size_overflow();
+}
+
+static ATTRIBUTE_REPRODUCIBLE ptrdiff_t
+align_to(ptrdiff_t size, ptrdiff_t alignment)
 {
-	if (SIZE_MAX / itemsize < nitems)
-		memory_exhausted(_("size overflow"));
-	return nitems * itemsize;
-}
-
-static ATTRIBUTE_PURE size_t
-align_to(size_t size, size_t alignment)
-{
-  size_t aligned_size = size + alignment - 1;
-  aligned_size -= aligned_size % alignment;
-  if (aligned_size < size)
-    memory_exhausted(_("alignment overflow"));
-  return aligned_size;
+  ptrdiff_t lo_bits = alignment - 1, sum = size_sum(size, lo_bits);
+  return sum & ~lo_bits;
 }
 
 #if !HAVE_STRDUP
@@ -505,35 +540,49 @@ memcheck(void *ptr)
 }
 
 static void * ATTRIBUTE_MALLOC
-zic_malloc(size_t size)
+emalloc(size_t size)
 {
 	return memcheck(malloc(size));
 }
 
 static void *
-zic_realloc(void *ptr, size_t size)
+erealloc(void *ptr, size_t size)
 {
 	return memcheck(realloc(ptr, size));
 }
 
 static char * ATTRIBUTE_MALLOC
-ecpyalloc(char const *str)
+estrdup(char const *str)
 {
 	return memcheck(strdup(str));
 }
 
+static ptrdiff_t
+grow_nitems_alloc(ptrdiff_t *nitems_alloc, ptrdiff_t itemsize)
+{
+  ptrdiff_t addend = (*nitems_alloc >> 1) + 1;
+#if defined ckd_add && defined ckd_mul
+  ptrdiff_t product;
+  if (!ckd_add(nitems_alloc, *nitems_alloc, addend)
+      && !ckd_mul(&product, *nitems_alloc, itemsize) && product <= PTRDIFF_MAX)
+    return product;
+#else
+  ptrdiff_t amax = min(PTRDIFF_MAX, SIZE_MAX);
+  if (*nitems_alloc <= ((amax - 1) / 3 * 2) / itemsize) {
+    *nitems_alloc += addend;
+    return *nitems_alloc * itemsize;
+  }
+#endif
+  memory_exhausted(_("integer overflow"));
+}
+
 static void *
-growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc)
+growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems,
+	ptrdiff_t *nitems_alloc)
 {
-	if (nitems < *nitems_alloc)
-		return ptr;
-	else {
-		ptrdiff_t amax = min(PTRDIFF_MAX, SIZE_MAX);
-		if ((amax - 1) / 3 * 2 < *nitems_alloc)
-			memory_exhausted(_("integer overflow"));
-		*nitems_alloc += (*nitems_alloc >> 1) + 1;
-		return zic_realloc(ptr, size_product(*nitems_alloc, itemsize));
-	}
+  return (nitems < *nitems_alloc
+	  ? ptr
+	  : erealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
 }
 
 /*
@@ -630,7 +679,7 @@ close_file(FILE *stream, char const *dir
   }
 }
 
-static _Noreturn void
+static ATTRIBUTE_NORETURN void
 usage(FILE *stream, int status)
 {
   fprintf(stream,
@@ -953,7 +1002,7 @@ main(int argc, char **argv)
 	textdomain(TZ_DOMAIN);
 #endif /* HAVE_GETTEXT */
 	main_argv = argv;
-	progname = argv[0];
+	progname = argv[0] ? argv[0] : "zic";
 	if (TYPE_BIT(zic_t) < 64) {
 		fprintf(stderr, "%s: %s\n", progname,
 			_("wild compilation-time specification of zic_t"));
@@ -1214,21 +1263,12 @@ get_rand_u64(void)
 #endif
 
   /* getrandom didn't work, so fall back on portable code that is
-     not the best because the seed doesn't necessarily have enough bits,
-     the seed isn't cryptographically random on platforms lacking
-     getrandom, and 'rand' might not be cryptographically secure.  */
+     not the best because the seed isn't cryptographically random and
+     'rand' might not be cryptographically secure.  */
   {
     static bool initialized;
     if (!initialized) {
-      unsigned seed;
-#ifdef CLOCK_REALTIME
-      struct timespec now;
-      clock_gettime (CLOCK_REALTIME, &now);
-      seed = now.tv_sec ^ now.tv_nsec;
-#else
-      seed = time(NULL);
-#endif
-      srand(seed);
+      srand(time(NULL));
       initialized = true;
     }
   }
@@ -1237,13 +1277,21 @@ get_rand_u64(void)
      the typical case where RAND_MAX is one less than a power of two.
      In other cases this code yields a sort-of-random number.  */
   {
-    uint_fast64_t
-      rand_max = RAND_MAX,
-      multiplier = rand_max + 1, /* It's OK if this overflows to 0.  */
+    uint_fast64_t rand_max = RAND_MAX,
+      nrand = rand_max < UINT_FAST64_MAX ? rand_max + 1 : 0,
+      rmod = INT_MAX < UINT_FAST64_MAX ? 0 : UINT_FAST64_MAX / nrand + 1,
       r = 0, rmax = 0;
+
     do {
-      uint_fast64_t rmax1 = rmax * multiplier + rand_max;
-      r = r * multiplier + rand();
+      uint_fast64_t rmax1 = rmax;
+      if (rmod) {
+	/* Avoid signed integer overflow on theoretical platforms
+	   where uint_fast64_t promotes to int.  */
+	rmax1 %= rmod;
+	r %= rmod;
+      }
+      rmax1 = nrand * rmax1 + rand_max;
+      r = nrand * r + rand();
       rmax = rmax < rmax1 ? rmax1 : UINT_FAST64_MAX;
     } while (rmax < UINT_FAST64_MAX);
 
@@ -1285,7 +1333,7 @@ random_dirent(char const **name, char **
   uint_fast64_t unfair_min = - ((UINTMAX_MAX % base__6 + 1) % base__6);
 
   if (!dst) {
-    dst = emalloc(dirlen + prefixlen + suffixlen + 1);
+    dst = emalloc(size_sum(dirlen, prefixlen + suffixlen + 1));
     memcpy(dst, src, dirlen);
     memcpy(dst + dirlen, prefix, prefixlen);
     dst[dirlen + prefixlen + suffixlen] = '\0';
@@ -1364,19 +1412,20 @@ rename_dest(char *tempname, char const *
 static char *
 relname(char const *target, char const *linkname)
 {
-  size_t i, taillen, dotdotetcsize;
-  size_t dir_len = 0, dotdots = 0, linksize = SIZE_MAX;
+  size_t i, taillen, dir_len = 0, dotdots = 0;
+  ptrdiff_t dotdotetcsize, linksize = min(PTRDIFF_MAX, SIZE_MAX);
   char const *f = target;
   char *result = NULL;
   if (*linkname == '/') {
     /* Make F absolute too.  */
     size_t len = strlen(directory);
-    bool needslash = len && directory[len - 1] != '/';
-    linksize = len + needslash + strlen(target) + 1;
+    size_t lenslash = len + (len && directory[len - 1] != '/');
+    size_t targetsize = strlen(target) + 1;
+    linksize = size_sum(lenslash, targetsize);
     f = result = emalloc(linksize);
-    strcpy(result, directory);
+    memcpy(result, directory, len);
     result[len] = '/';
-    strcpy(result + len + needslash, target);
+    memcpy(result + lenslash, target, targetsize);
   }
   for (i = 0; f[i] && f[i] == linkname[i]; i++)
     if (f[i] == '/')
@@ -1384,7 +1433,7 @@ relname(char const *target, char const *
   for (; linkname[i]; i++)
     dotdots += linkname[i] == '/' && linkname[i - 1] != '/';
   taillen = strlen(f + dir_len);
-  dotdotetcsize = 3 * dotdots + taillen + 1;
+  dotdotetcsize = size_sum(size_product(dotdots, 3), taillen + 1);
   if (dotdotetcsize <= linksize) {
     if (!result)
       result = emalloc(dotdotetcsize);
@@ -1588,10 +1637,9 @@ associate(void)
 
 /* Read a text line from FP into BUF, which is of size BUFSIZE.
    Terminate it with a NUL byte instead of a newline.
-   Return the line's length, not counting the NUL byte.
-   On EOF, return a negative number.
+   Return true if successful, false if EOF.
    On error, report the error and exit.  */
-static ptrdiff_t
+static bool
 inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
 {
   ptrdiff_t linelen = 0, ch;
@@ -1602,7 +1650,7 @@ inputline(FILE *fp, char *buf, ptrdiff_t
 	exit(EXIT_FAILURE);
       }
       if (linelen == 0)
-	return -1;
+	return false;
       error(_("unterminated line"));
       exit(EXIT_FAILURE);
     }
@@ -1617,7 +1665,7 @@ inputline(FILE *fp, char *buf, ptrdiff_t
     }
   }
   buf[linelen] = '\0';
-  return linelen;
+  return true;
 }
 
 static void
@@ -1639,13 +1687,14 @@ infile(int fnum, char const *name)
 	}
 	wantcont = false;
 	for (num = 1; ; ++num) {
-		ptrdiff_t linelen;
-		char buf[_POSIX2_LINE_MAX];
+		enum { bufsize_bound
+		  = (min(INT_MAX, min(PTRDIFF_MAX, SIZE_MAX))
+		     / FORMAT_LEN_GROWTH_BOUND) };
+		char buf[min(_POSIX2_LINE_MAX, bufsize_bound)];
 		int nfields;
 		char *fields[MAX_FIELDS];
 		eat(fnum, num);
-		linelen = inputline(fp, buf, sizeof buf);
-		if (linelen < 0)
+		if (!inputline(fp, buf, sizeof buf))
 		  break;
 		nfields = getfields(buf, fields,
 				    sizeof fields / sizeof *fields);
@@ -1717,15 +1766,15 @@ gethms(char const *string, char const *e
 	  default: ok = false; break;
 	  case 8:
 	    ok = '0' <= xr && xr <= '9';
-	    /* fallthrough */
+	    ATTRIBUTE_FALLTHROUGH;
 	  case 7:
 	    ok &= ssx == '.';
 	    if (ok && noise)
 	      warning(_("fractional seconds rejected by"
 			" pre-2018 versions of zic"));
-	    /* fallthrough */
-	  case 5: ok &= mmx == ':'; /* fallthrough */
-	  case 3: ok &= hhx == ':'; /* fallthrough */
+	    ATTRIBUTE_FALLTHROUGH;
+	  case 5: ok &= mmx == ':'; ATTRIBUTE_FALLTHROUGH;
+	  case 3: ok &= hhx == ':'; ATTRIBUTE_FALLTHROUGH;
 	  case 1: break;
 	}
 	if (!ok) {
@@ -1755,7 +1804,7 @@ getsave(char *field, bool *isdst)
 {
   int dst = -1;
   zic_t save;
-  size_t fieldlen = strlen(field);
+  ptrdiff_t fieldlen = strlen(field);
   if (fieldlen != 0) {
     char *ep = field + fieldlen - 1;
     switch (*ep) {
@@ -1793,8 +1842,8 @@ inrule(char **fields, int nfields)
 		     fields[RF_COMMAND], fields[RF_MONTH], fields[RF_DAY],
 		     fields[RF_TOD]))
 	  return;
-	r.r_name = ecpyalloc(fields[RF_NAME]);
-	r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]);
+	r.r_name = estrdup(fields[RF_NAME]);
+	r.r_abbrvar = estrdup(fields[RF_ABBRVAR]);
 	if (max_abbrvar_len < strlen(r.r_abbrvar))
 		max_abbrvar_len = strlen(r.r_abbrvar);
 	rules = growalloc(rules, sizeof *rules, nrules, &nrules_alloc);
@@ -1851,7 +1900,7 @@ inzsub(char **fields, int nfields, bool 
 	char *		cp;
 	char *		cp1;
 	struct zone	z;
-	size_t format_len;
+	int format_len;
 	int		i_stdoff, i_rule, i_format;
 	int		i_untilyear, i_untilmonth;
 	int		i_untilday, i_untiltime;
@@ -1888,7 +1937,7 @@ inzsub(char **fields, int nfields, bool 
 	}
 	z.z_format_specifier = cp ? *cp : '\0';
 	format_len = strlen(fields[i_format]);
-	if (max_format_len < format_len)
+	if ((ptrdiff_t)max_format_len < format_len)
 	  max_format_len = format_len;
 	hasuntil = nfields > i_untilyear;
 	if (hasuntil) {
@@ -1918,9 +1967,9 @@ inzsub(char **fields, int nfields, bool 
 				return false;
 		}
 	}
-	z.z_name = iscont ? NULL : ecpyalloc(fields[ZF_NAME]);
-	z.z_rule = ecpyalloc(fields[i_rule]);
-	z.z_format = cp1 = ecpyalloc(fields[i_format]);
+	z.z_name = iscont ? NULL : estrdup(fields[ZF_NAME]);
+	z.z_rule = estrdup(fields[i_rule]);
+	z.z_format = cp1 = estrdup(fields[i_format]);
 	if (z.z_format_specifier == 'z') {
 	  cp1[cp - fields[i_format]] = 's';
 	  if (noise)
@@ -1937,7 +1986,7 @@ inzsub(char **fields, int nfields, bool 
 }
 
 static zic_t
-getleapdatetime(char **fields, int nfields, bool expire_line)
+getleapdatetime(char **fields, bool expire_line)
 {
 	const char *		cp;
 	const struct lookup *	lp;
@@ -2015,7 +2064,7 @@ inleap(char **fields, int nfields)
   if (nfields != LEAP_FIELDS)
     error(_("wrong number of fields on Leap line"));
   else {
-    zic_t t = getleapdatetime(fields, nfields, false);
+    zic_t t = getleapdatetime(fields, false);
     if (0 <= t) {
       struct lookup const *lp = byword(fields[LP_ROLL], leap_types);
       if (!lp)
@@ -2043,7 +2092,7 @@ inexpires(char **fields, int nfields)
   else if (0 <= leapexpires)
     error(_("multiple Expires lines"));
   else
-    leapexpires = getleapdatetime(fields, nfields, true);
+    leapexpires = getleapdatetime(fields, true);
 }
 
 static void
@@ -2063,8 +2112,8 @@ inlink(char **fields, int nfields)
 	  return;
 	l.l_filenum = filenum;
 	l.l_linenum = linenum;
-	l.l_target = ecpyalloc(fields[LF_TARGET]);
-	l.l_linkname = ecpyalloc(fields[LF_LINKNAME]);
+	l.l_target = estrdup(fields[LF_TARGET]);
+	l.l_linkname = estrdup(fields[LF_LINKNAME]);
 	links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc);
 	links[nlinks++] = l;
 }
@@ -2087,7 +2136,7 @@ rulesub(struct rule *rp, const char *loy
 	rp->r_month = lp->l_value;
 	rp->r_todisstd = false;
 	rp->r_todisut = false;
-	dp = ecpyalloc(timep);
+	dp = estrdup(timep);
 	if (*dp != '\0') {
 		ep = dp + strlen(dp) - 1;
 		switch (lowerit(*ep)) {
@@ -2166,7 +2215,7 @@ rulesub(struct rule *rp, const char *loy
 	**	Sun<=20
 	**	Sun>=7
 	*/
-	dp = ecpyalloc(dayp);
+	dp = estrdup(dayp);
 	if ((lp = byword(dp, lasts)) != NULL) {
 		rp->r_dycode = DC_DOWLEQ;
 		rp->r_wday = lp->l_value;
@@ -2229,7 +2278,7 @@ convert64(uint_fast64_t val, char *buf)
 }
 
 static void
-puttzcode(const int_fast32_t val, FILE *const fp)
+puttzcode(zic_t val, FILE *fp)
 {
 	char	buf[4];
 
@@ -2318,8 +2367,10 @@ writezone(const char *const name, const 
 	char const *outname = name;
 
 	/* Allocate the ATS and TYPES arrays via a single malloc,
-	   as this is a bit faster.  */
-	zic_t *ats = emalloc(align_to(size_product(timecnt, sizeof *ats + 1),
+	   as this is a bit faster.  Do not malloc(0) if !timecnt,
+	   as that might return NULL even on success.  */
+	zic_t *ats = emalloc(align_to(size_product(timecnt + !timecnt,
+						   sizeof *ats + 1),
 				      alignof(zic_t)));
 	void *typesptr = ats + timecnt;
 	unsigned char *types = typesptr;
@@ -2752,13 +2803,13 @@ abbroffset(char *buf, zic_t offset)
 
 static char const disable_percent_s[] = "";
 
-static size_t
+static ptrdiff_t
 doabbr(char *abbr, size_t abbrlen, struct zone const *zp, const char *letters,
     bool isdst, zic_t save, bool doquotes)
 {
 	char *	cp;
 	char *	slashp;
-	size_t	len;
+	ptrdiff_t	len;
 	char const *format = zp->z_format;
 
 	slashp = strchr(format, '/');
@@ -2928,9 +2979,9 @@ stringzone(char *result, int resultlen, 
 	ptrdiff_t	i;
 	int			compat = 0;
 	int			c;
-	size_t			len;
 	int			offsetlen;
 	struct rule		stdr, dstr;
+	ptrdiff_t len;
 	int dstcmp;
 	struct rule *lastrp[2] = { NULL, NULL };
 	struct zone zstr[2];
@@ -3064,8 +3115,10 @@ outzone(const struct zone *zpfirst, ptrd
 
 	check_for_signal();
 
+	/* This cannot overflow; see FORMAT_LEN_GROWTH_BOUND.  */
 	max_abbr_len = 2 + max_format_len + max_abbrvar_len;
 	max_envvar_len = 2 * max_abbr_len + 5 * 9;
+
 	startbuf = zic_malloc(max_abbr_len + 1);
 	ab = zic_malloc(max_abbr_len + 1);
 	envvar = zic_malloc(max_envvar_len + 1);
@@ -3572,7 +3625,7 @@ lowerit(char a)
 }
 
 /* case-insensitive equality */
-static ATTRIBUTE_PURE bool
+static ATTRIBUTE_REPRODUCIBLE bool
 ciequal(const char *ap, const char *bp)
 {
 	while (lowerit(*ap) == lowerit(*bp++))
@@ -3581,7 +3634,7 @@ ciequal(const char *ap, const char *bp)
 	return false;
 }
 
-static ATTRIBUTE_PURE bool
+static ATTRIBUTE_REPRODUCIBLE bool
 itsabbr(const char *abbr, const char *word)
 {
 	if (lowerit(*abbr) != lowerit(*word))
@@ -3597,7 +3650,7 @@ itsabbr(const char *abbr, const char *wo
 
 /* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case.  */
 
-static ATTRIBUTE_PURE bool
+static ATTRIBUTE_REPRODUCIBLE bool
 ciprefix(char const *abbr, char const *word)
 {
   do
@@ -3700,38 +3753,41 @@ getfields(char *cp, char **array, int ar
 	return nsubs;
 }
 
-static _Noreturn void
+static ATTRIBUTE_NORETURN void
 time_overflow(void)
 {
 	error(_("time overflow"));
 	exit(EXIT_FAILURE);
 }
 
-static ATTRIBUTE_PURE zic_t
+static ATTRIBUTE_REPRODUCIBLE zic_t
 oadd(zic_t t1, zic_t t2)
 {
-	if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2)
-		time_overflow();
-	return t1 + t2;
+#ifdef ckd_add
+  zic_t sum;
+  if (!ckd_add(&sum, t1, t2))
+    return sum;
+#else
+  if (t1 < 0 ? ZIC_MIN - t1 <= t2 : t2 <= ZIC_MAX - t1)
+    return t1 + t2;
+#endif
+  time_overflow();
 }
 
-static ATTRIBUTE_PURE zic_t
+static ATTRIBUTE_REPRODUCIBLE zic_t
 tadd(zic_t t1, zic_t t2)
 {
-	if (t1 < 0) {
-		if (t2 < min_time - t1) {
-			if (t1 != min_time)
-				time_overflow();
-			return min_time;
-		}
-	} else {
-		if (max_time - t1 < t2) {
-			if (t1 != max_time)
-				time_overflow();
-			return max_time;
-		}
-	}
-	return t1 + t2;
+#ifdef ckd_add
+  zic_t sum;
+  if (!ckd_add(&sum, t1, t2) && min_time <= sum && sum <= max_time)
+    return sum;
+#else
+  if (t1 < 0 ? min_time - t1 <= t2 : t2 <= max_time - t1)
+    return t1 + t2;
+#endif
+  if (t1 == min_time || t1 == max_time)
+    return t1;
+  time_overflow();
 }
 
 /*
@@ -3855,10 +3911,8 @@ mp = _("time zone abbreviation differs f
 static void
 mkdirs(char const *argname, bool ancestors)
 {
-	char *	name;
-	char *	cp;
-
-	cp = name = ecpyalloc(argname);
+	char *name = estrdup(argname);
+	char *cp = name;
 
 	/* On MS-Windows systems, do not worry about drive letters or
 	   backslashes, as this should suffice in practice.  Time zone

Reply via email to