[PATCH v2 1/2] timezone: sync to tzdb 2026b

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Fri May 15 15:49:03 GMT 2026



On 13/05/26 15:08, Paul Eggert wrote:
> Sync tzselect, zdump, zic to tzdb 2026b.
> 
> This fixes some buffer and integer overflows in zic,
> adds new zic options -D, -m and -u inspired by FreeBSD,
> and raises zic’s maximum number of abbreviation bytes
> per timezone from 50 to 256.
> 
> This patch incorporates the following tzdb source code changes:
> 
> f9d30685 Output a minimal time zone designation table
> 37a4d178 Fix zic overflow bug with too-large offsets
> 4392f2dc zic now checks for signals more often
> 99a08a66 Fix zic buffer overflow when computing TZ
> d63b9287 zic: keep needed last transition to new type
> d005045d Pacify clang -Wunterminated-string-initialization
> e67b08d3 Port to C23 strchr macro
> 3d4b4e46 Add zic.c overflow commentary
> d9101b88 zic now a bit safer for overflows near 2**63
> b23fa8e0 zic now allows more than 50 leap seconds
> 4ff518d2 Increase TZ_MAX_CHARS from 50 to 256
> 75d3b73b New -DTZ_RUNTIME_LEAPS=0 build-time option
> 87343c6e TZ_MAX_TIMES must be at least 310 now
> fc8f1b68 Simplify int_fast32_t definition on C89 platforms
> 24581465 Remove TZDEFRULES ("posixrules") from localtime.c
> fc708427 zic now warns about -p
> b09a3f23 Port TWOS_COMPLEMENT to signed-magnitude hosts
> 56b7a24a Make sure 2**31 - 1 is signed
> 9068ab78 zic no longer generates utoff == -2**31
> cb6f9b3b Omit unnecessary L suffixes
> c37fbc32 Clarify when ‘__attribute__((pure))’ is a hack
> 859690a7 Fix some unsequenced/reproducible commentary
> 9c772ca7 Port to POSIX.1-2001 fflush
> 10f93018 Omit no-op transitions when Rule+Zone cancel
> a0b09b52 Fix unlikely backslash bug in scripts
> 2cbd3a71 Allow builder to override GRANDPARENTED
> c7257626 not used at → used outside
> faed4bd3 Clarify <sys/auxv.h> vs getauxval
> df08e6a1 Port mode_t (and gid_t, uid_t) to MS-Windows
> 6127d375 New zic option -u, inspired by FreeBSD
> 813c9ee0 New zic option -m, inspired by FreeBSD
> 987ea89c New zic option -D, inspired by FreeBSD
> cc377b07 Simplify mkdir situation
> cd994a90 Simplify !HAVE_POSIX_DECLS situation
> 052ddf76 Minor gettext macro improvements
> d9018f1c Refactor duplicate duplicate-option code
> 8d65db97 Prefer fdopen to umask in zic
> d7edca6e Omit “'”s from zic usage message
> a09ba7a5 getopt returns -1 (not EOF) on failure
> e22d410c zic now uses is_digit
> f57cadda Always invoke umask at start
> 242a8338 Fix mode_t issues on MS-Windows
> 2fecd606 MKDIR_UMASK → MKDIR_PERMS refactoring
> 90ef088a Move static_assert to top level
> 41576478 Port better to platforms lacking mempcpy
> 90a08d3e * private.h: Include stddef.h early enough
> aa8b35fe Simplify port to NetBSD struct __state
> cd2fddf7 Port to -DHAVE_SYS_STAT_H=0 -DHAVE_POSIX_DECLS=0
> 8470e759 Pacify GCC 15 -Wunterminated-string-initialization
> 8817d42f Prefer mempcpy to doing it by hand
> 87abb113 Tighten security checks on TZ values
> c87f0918 Use strnlen
> 07f7f31a Fix preprocessor indenting
> 3adf4123 Add offtime_r à la FreeBSD and NetBSD
> b807a31e Don’t depend on ‘true’ for tzselect
> ddffc800 * zic.c: Fix misspelled comment (thanks to Jonathan Wakely).
> 7063d08c Fix bug with -d RELATIVE -t ABSOLUTE
> e8920e76 Rename emalloc to xmalloc.
> e8e1a3d2 NetBSD defines STD_INSPIRED functions
> 3411494c Define _CRT_DECLARE_NONSTDC_NAMES for MS-Windows
> 7c909166 Define NOMINMAX for MS-Windows
> 24a4d97f 'zdump -' now reads from stdin
> e6d6bc3e Pacify gcc -Wsuggest-attribute=format sans snprintf in zdump
> 99557862 TZNAME_MAXIMUM defaults to 254, not 255
> fe5be99d Be more consistent about macro true/false vs 1/0
> 31f483a1 Remove dependency of asctime on strftime
> 7ef7ed06 Simplify timeoff redefinition
> 1bd67a4b Move MKTIME_MIGHT_OVERFLOW definition
> 67f7e8ab Pacify GCC 15ish -Wzero-as-null-pointer-constant
> 535a4e8b Pacify GCC 15ish -Wleading-whitespace=blanks
> 0706ef0b Move iinntt definition
> ea814e99 strftime %s no longer is limited to time_t range
> 41e5344e Fix bug near the year 2**31 - 1 - 1900
> 4e1de249 Pacify gcc -Wsuggest-attribute=const
> ebd2ed92 Don’t define _FILE_OFFSET_BITS if _TIME_BITS
> 26a649a1 Improve zdump overflow checking
> 9c8221d7 * private.h: Fix timeoff comment.
> 9db906a0 Switch from RFC 8536 to 9636 for documentation
> af54a9e8 Port better to glibc when used internally there
> 
> Checked on x86_64-linux-gnu.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  SHARED-FILES          |   7 +-
>  timezone/private.h    | 478 +++++++++++++--------
>  timezone/tzfile.h     |  26 +-
>  timezone/tzselect.ksh |  15 +-
>  timezone/version      |   2 +-
>  timezone/zdump.c      |  83 ++--
>  timezone/zic.c        | 965 ++++++++++++++++++++++++++++--------------
>  7 files changed, 1015 insertions(+), 561 deletions(-)
> 
> diff --git a/SHARED-FILES b/SHARED-FILES
> index c0260d6615..866a0adc17 100644
> --- a/SHARED-FILES
> +++ b/SHARED-FILES
> @@ -212,8 +212,9 @@ unicode:
>  # The following files are shared with the upstream tzcode project and must be
>  # updated regularly to stay in sync with the upstream releases.
>  #
> -# Currently synced to TZDB 2024b, announced and distributed here:
> -#	https://github.com/eggert/tz/releases/tag/2024b
> +# Currently synced to TZDB 2026b, distributed at:
> +#	https://github.com/eggert/tz/releases/tag/2026b
> +#	https://data.iana.org/time-zones/releases/tzdb-2026b.tar.lz
>  tzcode:
>    timezone/private.h
>    timezone/tzfile.h
> @@ -258,7 +259,7 @@ tzdata:
>  # FLT_EVAL_METHOD equal to 2 (i386).  Additionally, extra optimizations
>  # are applied to share the internal data table across different
>  # implementations.
> -# 
> +#
>  # The project is distribute here:
>  #       https://gitlab.inria.fr/core-math/core-math/
>  core-math:
> diff --git a/timezone/private.h b/timezone/private.h
> index c33041049f..ee191b4ec3 100644
> --- a/timezone/private.h
> +++ b/timezone/private.h
> @@ -37,6 +37,38 @@
>  # define SUPPORT_C89 1
>  #endif
>  
> +
> +/* The following feature-test macros should be defined before
> +   any #include of a system header.  */
> +
> +/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems.  */
> +#define _GNU_SOURCE 1
> +/* Fix asctime_r on Solaris 11.  */
> +#define _POSIX_PTHREAD_SEMANTICS 1
> +/* Enable strtoimax on pre-C99 Solaris 11.  */
> +#define __EXTENSIONS__ 1
> +/* Cause MS-Windows headers to define POSIX names.  */
> +#define _CRT_DECLARE_NONSTDC_NAMES 1
> +/* Prevent MS-Windows headers from defining min and max.  */
> +#define NOMINMAX 1
> +
> +/* On GNUish systems where time_t might be 32 or 64 bits, use 64.
> +   On these platforms _FILE_OFFSET_BITS must also be 64; otherwise
> +   setting _TIME_BITS to 64 does not work.  The code does not
> +   otherwise rely on _FILE_OFFSET_BITS being 64, since it does not
> +   use off_t or functions like 'stat' that depend on off_t.  */
> +#ifndef _TIME_BITS
> +# ifndef _FILE_OFFSET_BITS
> +#  define _FILE_OFFSET_BITS 64
> +# endif
> +# if _FILE_OFFSET_BITS == 64
> +#  define _TIME_BITS 64
> +# endif
> +#endif
> +
> +/* End of feature-test macro definitions.  */
> +
> +
>  #ifndef __STDC_VERSION__
>  # define __STDC_VERSION__ 0
>  #endif
> @@ -50,7 +82,9 @@
>  # include <stdbool.h>
>  #endif
>  
> -#if __STDC_VERSION__ < 202311
> +/* For pre-C23 compilers, a substitute for static_assert.
> +   Some of these compilers may warn if it is used outside the top level.  */
> +#if __STDC_VERSION__ < 202311 && !defined static_assert
>  # define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1]
>  #endif
>  
> @@ -65,7 +99,9 @@
>  #endif
>  
>  /* This string was in the Factory zone through version 2016f.  */
> -#define GRANDPARENTED	"Local time zone must be set--see zic manual page"
> +#ifndef GRANDPARENTED
> +# define GRANDPARENTED	"Local time zone must be set--see zic manual page"
> +#endif
>  
>  /*
>  ** Defaults for preprocessor symbols.
> @@ -85,13 +121,21 @@
>  # define HAVE__GENERIC (201112 <= __STDC_VERSION__)
>  #endif
>  
> +#ifndef HAVE_GETEUID
> +# define HAVE_GETEUID 1
> +#endif
> +
> +#ifndef HAVE_GETRESUID
> +# define HAVE_GETRESUID 1
> +#endif
> +
>  #if !defined HAVE_GETTEXT && defined __has_include
>  # if __has_include(<libintl.h>)
> -#  define HAVE_GETTEXT true
> +#  define HAVE_GETTEXT 1
>  # endif
>  #endif
>  #ifndef HAVE_GETTEXT
> -# define HAVE_GETTEXT false
> +# define HAVE_GETTEXT 0
>  #endif
>  
>  #ifndef HAVE_INCOMPATIBLE_CTIME_R
> @@ -124,20 +168,20 @@
>  
>  #if !defined HAVE_SYS_STAT_H && defined __has_include
>  # if !__has_include(<sys/stat.h>)
> -#  define HAVE_SYS_STAT_H false
> +#  define HAVE_SYS_STAT_H 0
>  # endif
>  #endif
>  #ifndef HAVE_SYS_STAT_H
> -# define HAVE_SYS_STAT_H true
> +# define HAVE_SYS_STAT_H 1
>  #endif
>  
>  #if !defined HAVE_UNISTD_H && defined __has_include
>  # if !__has_include(<unistd.h>)
> -#  define HAVE_UNISTD_H false
> +#  define HAVE_UNISTD_H 0
>  # endif
>  #endif
>  #ifndef HAVE_UNISTD_H
> -# define HAVE_UNISTD_H true
> +# define HAVE_UNISTD_H 1
>  #endif
>  
>  #ifndef NETBSD_INSPIRED
> @@ -149,56 +193,44 @@
>  # define ctime_r _incompatible_ctime_r
>  #endif /* HAVE_INCOMPATIBLE_CTIME_R */
>  
> -/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems.  */
> -#define _GNU_SOURCE 1
> -/* Fix asctime_r on Solaris 11.  */
> -#define _POSIX_PTHREAD_SEMANTICS 1
> -/* Enable strtoimax on pre-C99 Solaris 11.  */
> -#define __EXTENSIONS__ 1
> -
> -/* On GNUish systems where time_t might be 32 or 64 bits, use 64.
> -   On these platforms _FILE_OFFSET_BITS must also be 64; otherwise
> -   setting _TIME_BITS to 64 does not work.  The code does not
> -   otherwise rely on _FILE_OFFSET_BITS being 64, since it does not
> -   use off_t or functions like 'stat' that depend on off_t.  */
> -#ifndef _FILE_OFFSET_BITS
> -# define _FILE_OFFSET_BITS 64
> -#endif
> -#if !defined _TIME_BITS && _FILE_OFFSET_BITS == 64
> -# define _TIME_BITS 64
> +#ifndef TZ_RUNTIME_LEAPS
> +# define TZ_RUNTIME_LEAPS 1
>  #endif
>  
>  /*
>  ** Nested includes
>  */
>  
> -/* Avoid clashes with NetBSD by renaming NetBSD's declarations.
> -   If defining the 'timezone' variable, avoid a clash with FreeBSD's
> -   'timezone' function by renaming its declaration.  */
> -#define localtime_rz sys_localtime_rz
> -#define mktime_z sys_mktime_z
> -#define posix2time_z sys_posix2time_z
> -#define time2posix_z sys_time2posix_z
> -#if defined USG_COMPAT && USG_COMPAT == 2
> +#include <stddef.h>
> +
> +/* If defining the 'timezone' variable a la POSIX, avoid clashing with the old
> +   'timezone' function of FreeBSD <= 14, by renaming the latter's declaration.
> +   This hack can be removed after 2028-11-30, FreeBSD 14's expected EOL.  */
> +#if (defined __FreeBSD__  && __FreeBSD__ < 15 && defined __BSD_VISIBLE \
> +     && defined USG_COMPAT  && USG_COMPAT == 2)
>  # define timezone sys_timezone
> +# define timezone_defined
>  #endif
> -#define timezone_t sys_timezone_t
> -#define tzalloc sys_tzalloc
> -#define tzfree sys_tzfree
> +
>  #include <time.h>
> -#undef localtime_rz
> -#undef mktime_z
> -#undef posix2time_z
> -#undef time2posix_z
> -#if defined USG_COMPAT && USG_COMPAT == 2
> +
> +#ifdef timezone_defined
>  # undef timezone
> +# undef timezone_defined
>  #endif
> -#undef timezone_t
> -#undef tzalloc
> -#undef tzfree
>  
> -#include <stddef.h>
>  #include <string.h>
> +#if defined HAVE_STRNLEN && !HAVE_STRNLEN
> +static size_t
> +strnlen (char const *s, size_t maxlen)
> +{
> +  size_t i;
> +  for (i = 0; i < maxlen && s[i]; i++)
> +    continue;
> +  return i;
> +}
> +#endif
> +
>  #if !PORT_TO_C89
>  # include <inttypes.h>
>  #endif
> @@ -220,6 +252,9 @@
>  #ifndef ENOMEM
>  # define ENOMEM EINVAL
>  #endif
> +#ifndef ENOTCAPABLE
> +# define ENOTCAPABLE EINVAL
> +#endif
>  #ifndef ENOTSUP
>  # define ENOTSUP EINVAL
>  #endif
> @@ -232,7 +267,12 @@
>  #endif /* HAVE_GETTEXT */
>  
>  #if HAVE_UNISTD_H
> -# include <unistd.h> /* for R_OK, and other POSIX goodness */
> +# include <unistd.h>
> +#else
> +/* Assume getopt.o or equivalent is linked via Makefile configuration.  */
> +int getopt(int, char *const[], char const *);
> +extern char *optarg;
> +extern int optind;
>  #endif /* HAVE_UNISTD_H */
>  
>  /* SUPPORT_POSIX2008 means the tzcode library should support
> @@ -260,6 +300,20 @@
>  # endif
>  #endif
>  
> +#ifndef HAVE_ISSETUGID
> +# if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
> +      || (defined __linux__ && !defined __GLIBC__) /* Android, musl, etc. */ \
> +      || (defined __APPLE__ && defined __MACH__) || defined __sun)
> +#  define HAVE_ISSETUGID 1
> +# else
> +#  define HAVE_ISSETUGID 0
> +# endif
> +#endif
> +
> +#ifndef HAVE_SNPRINTF
> +# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__)
> +#endif
> +
>  #ifndef HAVE_STRFTIME_L
>  # if _POSIX_VERSION < 200809
>  #  define HAVE_STRFTIME_L 0
> @@ -292,10 +346,6 @@
>  # endif
>  #endif
>  
> -#ifndef R_OK
> -# define R_OK 4
> -#endif /* !defined R_OK */
> -
>  #if PORT_TO_C89
>  
>  /*
> @@ -304,146 +354,144 @@
>  ** previously included files.  glibc 2.1 and Solaris 10 and later have
>  ** stdint.h, even with pre-C99 compilers.
>  */
> -#if !defined HAVE_STDINT_H && defined __has_include
> -# define HAVE_STDINT_H true /* C23 __has_include implies C99 stdint.h.  */
> -#endif
> -#ifndef HAVE_STDINT_H
> -# define HAVE_STDINT_H \
> -   (199901 <= __STDC_VERSION__ \
> -    || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \
> -    || __CYGWIN__ || INTMAX_MAX)
> -#endif /* !defined HAVE_STDINT_H */
> -
> -#if HAVE_STDINT_H
> -# include <stdint.h>
> -#endif /* !HAVE_STDINT_H */
> -
> -#ifndef HAVE_INTTYPES_H
> -# define HAVE_INTTYPES_H HAVE_STDINT_H
> -#endif
> -#if HAVE_INTTYPES_H
> -# include <inttypes.h>
> -#endif
> -
> -/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX.  */
> -#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__
> -# ifndef LLONG_MAX
> -#  define LLONG_MAX __LONG_LONG_MAX__
> +# if !defined HAVE_STDINT_H && defined __has_include
> +#  define HAVE_STDINT_H 1 /* C23 __has_include implies C99 stdint.h.  */
>  # endif
> -# ifndef LLONG_MIN
> -#  define LLONG_MIN (-1 - LLONG_MAX)
> +# ifndef HAVE_STDINT_H
> +#  define HAVE_STDINT_H \
> +    (199901 <= __STDC_VERSION__ \
> +     || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \
> +     || __CYGWIN__ || INTMAX_MAX)
> +# endif /* !defined HAVE_STDINT_H */
> +
> +# if HAVE_STDINT_H
> +#  include <stdint.h>
> +# endif /* !HAVE_STDINT_H */
> +
> +# ifndef HAVE_INTTYPES_H
> +#  define HAVE_INTTYPES_H HAVE_STDINT_H
>  # endif
> -# ifndef ULLONG_MAX
> -#  define ULLONG_MAX (LLONG_MAX * 2ull + 1)
> +# if HAVE_INTTYPES_H
> +#  include <inttypes.h>
>  # endif
> -#endif
>  
> -#ifndef INT_FAST64_MAX
> -# if 1 <= LONG_MAX >> 31 >> 31
> +/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_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
> +#  if 1 <= LONG_MAX >> 31 >> 31
>  typedef long int_fast64_t;
> -#  define INT_FAST64_MIN LONG_MIN
> -#  define INT_FAST64_MAX LONG_MAX
> -# else
> +#   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
> +#   define INT_FAST64_MIN LLONG_MIN
> +#   define INT_FAST64_MAX LLONG_MAX
> +#  endif
>  # endif
> -#endif
>  
> -#ifndef PRIdFAST64
> -# if INT_FAST64_MAX == LONG_MAX
> -#  define PRIdFAST64 "ld"
> -# else
> -#  define PRIdFAST64 "lld"
> +# ifndef PRIdFAST64
> +#  if INT_FAST64_MAX == LONG_MAX
> +#   define PRIdFAST64 "ld"
> +#  else
> +#   define PRIdFAST64 "lld"
> +#  endif
>  # endif
> -#endif
>  
> -#ifndef SCNdFAST64
> -# define SCNdFAST64 PRIdFAST64
> -#endif
> +# ifndef SCNdFAST64
> +#  define SCNdFAST64 PRIdFAST64
> +# endif
>  
> -#ifndef INT_FAST32_MAX
> -# if INT_MAX >> 31 == 0
> +# ifndef INT_FAST32_MAX
>  typedef long int_fast32_t;
>  #  define INT_FAST32_MAX LONG_MAX
>  #  define INT_FAST32_MIN LONG_MIN
> -# else
> -typedef int int_fast32_t;
> -#  define INT_FAST32_MAX INT_MAX
> -#  define INT_FAST32_MIN INT_MIN
>  # endif
> -#endif
>  
> -#ifndef INTMAX_MAX
> -# ifdef LLONG_MAX
> +# ifndef INT_LEAST32_MAX
> +typedef int_fast32_t int_least32_t;
> +# endif
> +
> +# ifndef INTMAX_MAX
> +#  ifdef LLONG_MAX
>  typedef long long intmax_t;
> -#  ifndef HAVE_STRTOLL
> -#   define HAVE_STRTOLL true
> +#   ifndef HAVE_STRTOLL
> +#    define HAVE_STRTOLL 1
> +#   endif
> +#   if HAVE_STRTOLL
> +#    define strtoimax strtoll
> +#   endif
> +#   define INTMAX_MAX LLONG_MAX
> +#   define INTMAX_MIN LLONG_MIN
> +#  else
> +typedef long intmax_t;
> +#   define INTMAX_MAX LONG_MAX
> +#   define INTMAX_MIN LONG_MIN
>  #  endif
> -#  if HAVE_STRTOLL
> -#   define strtoimax strtoll
> +#  ifndef strtoimax
> +#   define strtoimax strtol
>  #  endif
> -#  define INTMAX_MAX LLONG_MAX
> -#  define INTMAX_MIN LLONG_MIN
> -# else
> -typedef long intmax_t;
> -#  define INTMAX_MAX LONG_MAX
> -#  define INTMAX_MIN LONG_MIN
> -# endif
> -# ifndef strtoimax
> -#  define strtoimax strtol
>  # endif
> -#endif
>  
> -#ifndef PRIdMAX
> -# if INTMAX_MAX == LLONG_MAX
> -#  define PRIdMAX "lld"
> -# else
> -#  define PRIdMAX "ld"
> +# ifndef PRIdMAX
> +#  if INTMAX_MAX == LLONG_MAX
> +#   define PRIdMAX "lld"
> +#  else
> +#   define PRIdMAX "ld"
> +#  endif
>  # endif
> -#endif
>  
> -#ifndef PTRDIFF_MAX
> -# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t))
> -#endif
> +# ifndef PTRDIFF_MAX
> +#  define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t))
> +# endif
>  
> -#ifndef UINT_FAST32_MAX
> +# ifndef UINT_FAST32_MAX
>  typedef unsigned long uint_fast32_t;
> -#endif
> +# endif
>  
> -#ifndef UINT_FAST64_MAX
> -# if 3 <= ULONG_MAX >> 31 >> 31
> +# ifndef UINT_FAST64_MAX
> +#  if 3 <= ULONG_MAX >> 31 >> 31
>  typedef unsigned long uint_fast64_t;
> -#  define UINT_FAST64_MAX ULONG_MAX
> -# else
> +#   define UINT_FAST64_MAX ULONG_MAX
> +#  else
>  /* 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
> +#   define UINT_FAST64_MAX ULLONG_MAX
> +#  endif
>  # endif
> -#endif
>  
> -#ifndef UINTMAX_MAX
> -# ifdef ULLONG_MAX
> +# ifndef UINTMAX_MAX
> +#  ifdef ULLONG_MAX
>  typedef unsigned long long uintmax_t;
> -#  define UINTMAX_MAX ULLONG_MAX
> -# else
> +#   define UINTMAX_MAX ULLONG_MAX
> +#  else
>  typedef unsigned long uintmax_t;
> -#  define UINTMAX_MAX ULONG_MAX
> +#   define UINTMAX_MAX ULONG_MAX
> +#  endif
>  # endif
> -#endif
>  
> -#ifndef PRIuMAX
> -# ifdef ULLONG_MAX
> -#  define PRIuMAX "llu"
> -# else
> -#  define PRIuMAX "lu"
> +# ifndef PRIuMAX
> +#  ifdef ULLONG_MAX
> +#   define PRIuMAX "llu"
> +#  else
> +#   define PRIuMAX "lu"
> +#  endif
>  # endif
> -#endif
>  
> -#ifndef SIZE_MAX
> -# define SIZE_MAX ((size_t) -1)
> -#endif
> +# ifndef SIZE_MAX
> +#  define SIZE_MAX ((size_t) -1)
> +# endif
>  
>  #endif /* PORT_TO_C89 */
>  
> @@ -459,7 +507,7 @@ typedef unsigned long uintmax_t;
>     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
> +#  define HAVE_STDCKDINT_H 1
>  # endif
>  #endif
>  #ifdef HAVE_STDCKDINT_H
> @@ -491,6 +539,15 @@ typedef unsigned long uintmax_t;
>  # define HAVE___HAS_C_ATTRIBUTE false
>  #endif
>  
> +#ifdef __has_attribute
> +# if __has_attribute (nonstring)
> +#  define ATTRIBUTE_NONSTRING __attribute__((__nonstring__))
> +# endif
> +#endif
> +#ifndef ATTRIBUTE_NONSTRING
> +# define ATTRIBUTE_NONSTRING
> +#endif
> +
>  #if HAVE___HAS_C_ATTRIBUTE
>  # if __has_c_attribute(deprecated)
>  #  define ATTRIBUTE_DEPRECATED [[deprecated]]
> @@ -554,13 +611,26 @@ typedef unsigned long uintmax_t;
>  # define ATTRIBUTE_REPRODUCIBLE /* empty */
>  #endif
>  
> -/* GCC attributes that are useful in tzcode.
> -   __attribute__((pure)) is stricter than [[reproducible]],
> -   so the latter is an adequate substitute in non-GCC C23 platforms.  */
> +#if HAVE___HAS_C_ATTRIBUTE
> +# if __has_c_attribute(unsequenced)
> +#  define ATTRIBUTE_UNSEQUENCED [[unsequenced]]
> +# endif
> +#endif
> +#ifndef ATTRIBUTE_UNSEQUENCED
> +# define ATTRIBUTE_UNSEQUENCED /* empty */
> +#endif
> +
> +/* GNU C attributes that are useful in tzcode.
> +   Although neither __attribute__((const)) nor __attribute__((pure)) are
> +   stricter than their C23 counterparts [[unsequenced]] and [[reproducible]],
> +   the C23 attributes happen to work in each tzcode use of ATTRIBUTE_CONST
> +   and ATTRIBUTE_PURE.  (This might not work outside of tzcode!)  */
>  #if __GNUC__ < 3
> +# define ATTRIBUTE_CONST ATTRIBUTE_UNSEQUENCED
>  # define ATTRIBUTE_FORMAT(spec) /* empty */
>  # define ATTRIBUTE_PURE ATTRIBUTE_REPRODUCIBLE
>  #else
> +# define ATTRIBUTE_CONST __attribute__((const))
>  # define ATTRIBUTE_FORMAT(spec) __attribute__((format spec))
>  # define ATTRIBUTE_PURE __attribute__((pure))
>  #endif
> @@ -573,6 +643,12 @@ typedef unsigned long uintmax_t;
>  #else
>  # define ATTRIBUTE_PURE_114833 /* empty */
>  #endif
> +/* GCC_LINT hack to pacify GCC bug 114833 even though the attribute is
> +   not strictly correct, as the function might not return whereas pure
> +   functions are supposed to return exactly once.  This hack is not
> +   known to generate wrong code for tzcode on any platform.
> +   Remove this macro and its uses when the bug is fixed in a GCC release.  */
> +#define ATTRIBUTE_PURE_114833_HACK ATTRIBUTE_PURE_114833
>  
>  #if (__STDC_VERSION__ < 199901 && !defined restrict \
>       && (PORT_TO_C89 || defined _MSC_VER))
> @@ -593,6 +669,12 @@ typedef unsigned long uintmax_t;
>  # define RESERVE_STD_EXT_IDS 0
>  #endif
>  
> +#ifdef time_tz
> +# define defined_time_tz true
> +#else
> +# define defined_time_tz false
> +#endif
> +
>  /* If standard C identifiers with external linkage (e.g., localtime)
>     are reserved and are not already being renamed anyway, rename them
>     as if compiling with '-Dtime_tz=time_t'.  */
> @@ -608,9 +690,9 @@ typedef unsigned long uintmax_t;
>  ** typical platforms.
>  */
>  #if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0
> -# define TZ_TIME_T 1
> +# define TZ_TIME_T true
>  #else
> -# define TZ_TIME_T 0
> +# define TZ_TIME_T false
>  #endif
>  
>  #if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T
> @@ -643,6 +725,8 @@ typedef time_tz tz_time_t;
>  # define mktime_z tz_mktime_z
>  # undef  offtime
>  # define offtime tz_offtime
> +# undef  offtime_r
> +# define offtime_r tz_offtime_r
>  # undef  posix2time
>  # define posix2time tz_posix2time
>  # undef  posix2time_z
> @@ -701,11 +785,11 @@ typedef time_tz tz_time_t;
>  # endif
>  DEPRECATED_IN_C23 char *asctime(struct tm const *);
>  DEPRECATED_IN_C23 char *ctime(time_t const *);
> -#if SUPPORT_POSIX2008
> +# if SUPPORT_POSIX2008
>  char *asctime_r(struct tm const *restrict, char *restrict);
>  char *ctime_r(time_t const *, char *);
> -#endif
> -double difftime(time_t, time_t);
> +# endif
> +ATTRIBUTE_CONST double difftime(time_t, time_t);
>  size_t strftime(char *restrict, size_t, char const *restrict,
>  		struct tm const *restrict);
>  # if HAVE_STRFTIME_L
> @@ -727,9 +811,9 @@ void tzset(void);
>        || defined __GLIBC__ || defined __tm_zone /* musl */ \
>        || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
>        || (defined __APPLE__ && defined __MACH__))
> -#  define HAVE_DECL_TIMEGM true
> +#  define HAVE_DECL_TIMEGM 1
>  # else
> -#  define HAVE_DECL_TIMEGM false
> +#  define HAVE_DECL_TIMEGM 0
>  # endif
>  #endif
>  #if !HAVE_DECL_TIMEGM && !defined timegm
> @@ -752,6 +836,23 @@ extern char *asctime_r(struct tm const *restrict, char *restrict);
>  extern char **environ;
>  #endif
>  
> +#ifndef HAVE_MEMPCPY
> +# if (defined mempcpy \
> +      || defined __FreeBSD__ || defined __NetBSD__ || defined __linux__)
> +#  define HAVE_MEMPCPY 1
> +# else
> +#  define HAVE_MEMPCPY 0
> +# endif
> +#endif
> +#if !HAVE_MEMPCPY
> +static void *
> +mempcpy(void *restrict s1, void const *restrict s2, size_t n)
> +{
> +  char *p = memcpy(s1, s2, n);
> +  return p + n;
> +}
> +#endif
> +
>  #if 2 <= HAVE_TZNAME + (TZ_TIME_T || !HAVE_POSIX_DECLS)
>  extern char *tzname[];
>  #endif
> @@ -769,12 +870,19 @@ extern long altzone;
>  */
>  
>  #ifndef STD_INSPIRED
> -# define STD_INSPIRED 0
> +# ifdef __NetBSD__
> +#  define STD_INSPIRED 1
> +# else
> +#  define STD_INSPIRED 0
> +# endif
>  #endif
>  #if STD_INSPIRED
>  # if TZ_TIME_T || !defined offtime
>  struct tm *offtime(time_t const *, long);
>  # endif
> +# if TZ_TIME_T || !defined offtime_r
> +struct tm *offtime_r(time_t const *restrict, long, struct tm *restrict);
> +# endif
>  # if TZ_TIME_T || !defined timelocal
>  time_t timelocal(struct tm *);
>  # endif
> @@ -805,26 +913,32 @@ time_t posix2time(time_t);
>  #endif
>  
>  /*
> -** Define functions that are ABI compatible with NetBSD but have
> -** better prototypes.  NetBSD 6.1.4 defines a pointer type timezone_t
> -** and labors under the misconception that 'const timezone_t' is a
> -** pointer to a constant.  This use of 'const' is ineffective, so it
> -** is not done here.  What we call 'struct state' NetBSD calls
> +** Define functions that are ABI compatible with NetBSD.
> +** What we call 'struct state' NetBSD calls
>  ** 'struct __state', but this is a private name so it doesn't matter.
>  */
>  #if NETBSD_INSPIRED
> +# ifdef _NETBSD_SOURCE
> +#  define state __state
> +# else
>  typedef struct state *timezone_t;
> +# endif
>  struct tm *localtime_rz(timezone_t restrict, time_t const *restrict,
>  			struct tm *restrict);
>  time_t mktime_z(timezone_t restrict, struct tm *restrict);
>  timezone_t tzalloc(char const *);
>  void tzfree(timezone_t);
>  # if STD_INSPIRED
> +#  if TZ_RUNTIME_LEAPS
> +#   define ATTRIBUTE_POSIX2TIME ATTRIBUTE_PURE
> +#  else
> +#   define ATTRIBUTE_POSIX2TIME ATTRIBUTE_CONST
> +#  endif
>  #  if TZ_TIME_T || !defined posix2time_z
> -ATTRIBUTE_PURE time_t posix2time_z(timezone_t, time_t);
> +ATTRIBUTE_POSIX2TIME time_t posix2time_z(timezone_t, time_t);
>  #  endif
>  #  if TZ_TIME_T || !defined time2posix_z
> -ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t);
> +ATTRIBUTE_POSIX2TIME time_t time2posix_z(timezone_t, time_t);
>  #  endif
>  # endif
>  #endif
> @@ -835,7 +949,7 @@ ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t);
>  
>  #define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type))
>  #define TYPE_SIGNED(type) (((type) -1) < 0)
> -#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0)
> +#define TWOS_COMPLEMENT(type) (TYPE_SIGNED (type) && (! ~ (type) -1))
>  
>  /* Minimum and maximum of two values.  Use lower case to avoid
>     naming clashes with standard include files.  */
> @@ -875,7 +989,7 @@ ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t);
>  		default: TIME_T_MAX_NO_PADDING)			    \
>       : (time_t) -1)
>  enum { SIGNED_PADDING_CHECK_NEEDED
> -         = _Generic((time_t) 0,
> +	 = _Generic((time_t) 0,
>  		    signed char: false, short: false,
>  		    int: false, long: false, long long: false,
>  		    default: true) };
> @@ -922,8 +1036,8 @@ static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED
>  # define UNINIT_TRAP 0
>  #endif
>  
> -/* localtime.c sometimes needs access to timeoff if it is not already public.
> -   tz_private_timeoff should be used only by localtime.c.  */
> +/* strftime.c sometimes needs access to timeoff if it is not already public.
> +   tz_private_timeoff should be used only by localtime.c and strftime.c.  */
>  #if (!defined EXTERN_TIMEOFF \
>       && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP))
>  # ifndef timeoff
> @@ -958,23 +1072,27 @@ time_t timeoff(struct tm *, long);
>  */
>  
>  #if HAVE_GETTEXT
> -#define _(msgid) gettext(msgid)
> +# define _(msgid) gettext(msgid)
>  #else /* !HAVE_GETTEXT */
> -#define _(msgid) msgid
> +# define _(msgid) (msgid)
>  #endif /* !HAVE_GETTEXT */
> +#define N_(msgid) (msgid)
>  
>  #if !defined TZ_DOMAIN && defined HAVE_GETTEXT
>  # define TZ_DOMAIN "tz"
>  #endif
>  
>  #if HAVE_INCOMPATIBLE_CTIME_R
> -#undef asctime_r
> -#undef ctime_r
> +# undef asctime_r
> +# undef ctime_r
>  char *asctime_r(struct tm const *restrict, char *restrict);
>  char *ctime_r(time_t const *, char *);
>  #endif /* HAVE_INCOMPATIBLE_CTIME_R */
>  
> -/* Handy macros that are independent of tzfile implementation.  */
> +/* Handy constants that are independent of tzfile implementation.  */
> +
> +/* 2**31 - 1 as a signed integer, and usable in #if.  */
> +#define TWO_31_MINUS_1 2147483647
>  
>  enum {
>    SECSPERMIN = 60,
> diff --git a/timezone/tzfile.h b/timezone/tzfile.h
> index b154146654..1941bc3b76 100644
> --- a/timezone/tzfile.h
> +++ b/timezone/tzfile.h
> @@ -17,22 +17,14 @@
>  ** Thank you!
>  */
>  
> -/*
> -** Information about time zone files.
> -*/
> -
> -#ifndef TZDEFRULES
> -# define TZDEFRULES "posixrules"
> -#endif /* !defined TZDEFRULES */
> -
> -
> -/* See Internet RFC 8536 for more details about the following format.  */
> +/* Information about time zone files.
> +   See Internet RFC 9636 for more details about the following format.  */
>  
>  /*
>  ** Each file begins with. . .
>  */
>  
> -#define	TZ_MAGIC	"TZif"
> +#define TZ_MAGIC "TZif"
>  
>  struct tzhead {
>  	char	tzh_magic[4];		/* TZ_MAGIC */
> @@ -97,23 +89,25 @@ struct tzhead {
>  */
>  
>  #ifndef TZ_MAX_TIMES
> -/* This must be at least 242 for Europe/London with 'zic -b fat'.  */
> +/* The following limit applies to localtime.c; zic has no such limit.
> +   The limit must be at least 310 for Asia/Hebron with 'zic -b fat'.  */
>  # define TZ_MAX_TIMES 2000
>  #endif /* !defined TZ_MAX_TIMES */
>  
>  #ifndef TZ_MAX_TYPES
>  /* This must be at least 18 for Europe/Vilnius with 'zic -b fat'.  */
> -# define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
> +# define TZ_MAX_TYPES 256 /* Limited to 256 by Internet RFC 9636.  */
>  #endif /* !defined TZ_MAX_TYPES */
>  
>  #ifndef TZ_MAX_CHARS
>  /* This must be at least 40 for America/Anchorage.  */
> -# define TZ_MAX_CHARS 50	/* Maximum number of abbreviation characters */
> -				/* (limited by what unsigned chars can hold) */
> +# define TZ_MAX_CHARS 256	/* Maximum number of abbreviation characters */
> +				/* (limited to 256 by Internet RFC 9636) */
>  #endif /* !defined TZ_MAX_CHARS */
>  
>  #ifndef TZ_MAX_LEAPS
> -/* This must be at least 27 for leap seconds from 1972 through mid-2023.
> +/* The following limit applies to localtime.c; zic has no such limit.
> +   The limit must be at least 27 for leap seconds from 1972 through mid-2023.
>     There's a plan to discontinue leap seconds by 2035.  */
>  # define TZ_MAX_LEAPS 50	/* Maximum number of leap second corrections */
>  #endif /* !defined TZ_MAX_LEAPS */
> diff --git a/timezone/tzselect.ksh b/timezone/tzselect.ksh
> index ca3d82c6aa..85a8c670e0 100755
> --- a/timezone/tzselect.ksh
> +++ b/timezone/tzselect.ksh
> @@ -145,9 +145,11 @@ do
>    t*) # Undocumented option, used for developer testing.
>      zonetabtype=$OPTARG;;
>    -help)
> -    exec echo "$usage";;
> +    say "$usage"
> +    exit;;
>    -version)
> -    exec echo "tzselect $PKGVERSION$TZVERSION";;
> +    say "tzselect $PKGVERSION$TZVERSION"
> +    exit;;
>    -*)
>      say >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1;;
>    *)
> @@ -161,15 +163,15 @@ case $# in
>  *) say >&2 "$0: $1: unknown argument"; exit 1
>  esac
>  
> -# translit=true to try transliteration.
> +# translit=: to try transliteration.
>  # This is false if U+12345 CUNEIFORM SIGN URU TIMES KI has length 1
>  # which means the shell and (presumably) awk do not need transliteration.
> -# It is true if the byte string has some other length in characters, or
> +# It is ':' if the byte string has some other length in characters, or
>  # if this is a POSIX.1-2017 or earlier shell that does not support $'...'.
>  CUNEIFORM_SIGN_URU_TIMES_KI=$'\360\222\215\205'
>  if test ${#CUNEIFORM_SIGN_URU_TIMES_KI} = 1
>  then translit=false
> -else translit=true
> +else translit=:
>  fi
>  
>  # Read into shell variable $1 the contents of file $2.
> @@ -516,8 +518,7 @@ while
>  	' ="$distance_table"
>        )
>        echo >&2 'Please select one of the following timezones,'
> -      echo >&2 'listed roughly in increasing order' \
> -	"of distance from $coord".
> +      say >&2 "listed roughly in increasing order of distance from $coord."
>        doselect $regions
>        region=$select_result
>        tz=$(
> diff --git a/timezone/version b/timezone/version
> index 04fe674443..75d34ee389 100644
> --- a/timezone/version
> +++ b/timezone/version
> @@ -1 +1 @@
> -2024a
> +2026b
> diff --git a/timezone/zdump.c b/timezone/zdump.c
> index e817873337..c7812eae9d 100644
> --- a/timezone/zdump.c
> +++ b/timezone/zdump.c
> @@ -14,10 +14,6 @@
>  #include "private.h"
>  #include <stdio.h>
>  
> -#ifndef HAVE_SNPRINTF
> -# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__)
> -#endif
> -
>  #ifndef HAVE_LOCALTIME_R
>  # define HAVE_LOCALTIME_R 1
>  #endif
> @@ -65,13 +61,6 @@ enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
>  # define timezone_t char **
>  #endif
>  
> -#if !HAVE_POSIX_DECLS
> -extern int	getopt(int argc, char * const argv[],
> -			const char * options);
> -extern char *	optarg;
> -extern int	optind;
> -#endif
> -
>  /* The minimum and maximum finite time values.  */
>  enum { atime_shift = CHAR_BIT * sizeof(time_t) - 2 };
>  static time_t const absolute_min_time =
> @@ -134,7 +123,8 @@ size_overflow(void)
>  
>  /* Return A + B, exiting if the result would overflow either ptrdiff_t
>     or size_t.  A and B are both nonnegative.  */
> -ATTRIBUTE_PURE_114833 static ptrdiff_t
> +ATTRIBUTE_PURE_114833_HACK
> +static ptrdiff_t
>  sumsize(ptrdiff_t a, ptrdiff_t b)
>  {
>  #ifdef ckd_add
> @@ -148,17 +138,6 @@ sumsize(ptrdiff_t a, ptrdiff_t b)
>    size_overflow();
>  }
>  
> -/* Return the size of of the string STR, including its trailing NUL.
> -   Report an error and exit if this would exceed INDEX_MAX which means
> -   pointer subtraction wouldn't work.  */
> -static ptrdiff_t
> -xstrsize(char const *str)
> -{
> -  size_t len = strlen(str);
> -  if (len < INDEX_MAX)
> -    return len + 1;
> -  size_overflow();
> -}
>  
>  /* Return a pointer to a newly allocated buffer of size SIZE, exiting
>     on failure.  SIZE should be positive.  */
> @@ -266,7 +245,7 @@ tzalloc(char const *val)
>    static ptrdiff_t fakeenv0size;
>    void *freeable = NULL;
>    char **env = fakeenv, **initial_environ;
> -  ptrdiff_t valsize = xstrsize(val);
> +  ptrdiff_t valsize = strlen(val) + 1;
>    if (fakeenv0size < valsize) {
>      char **e = environ, **to;
>      ptrdiff_t initial_nenvptrs = 1;  /* Counting the trailing NULL pointer.  */
> @@ -425,7 +404,7 @@ saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
>    if (HAVE_LOCALTIME_RZ)
>      return ab;
>    else {
> -    ptrdiff_t absize = xstrsize(ab);
> +    ptrdiff_t absize = strlen(ab) + 1;
>      if (*bufalloc < absize) {
>        free(*buf);
>  
> @@ -487,6 +466,7 @@ main(int argc, char *argv[])
>  	register time_t		cuthitime;
>  	time_t			now;
>  	bool iflag = false;
> +	size_t arglenmax = 0;
>  
>  	cutlotime = absolute_min_time;
>  	cuthitime = absolute_max_time;
> @@ -586,15 +566,21 @@ main(int argc, char *argv[])
>  	  now = time(NULL);
>  	  now |= !now;
>  	}
> -	longest = 0;
>  	for (i = optind; i < argc; i++) {
>  	  size_t arglen = strlen(argv[i]);
> -	  if (longest < arglen)
> -	    longest = min(arglen, INT_MAX);
> +	  if (arglenmax < arglen)
> +	    arglenmax = arglen;
>  	}
> +	if (!HAVE_SETENV && INDEX_MAX <= arglenmax)
> +	  size_overflow();
> +	longest = min(arglenmax, INT_MAX - 2);
>  
>  	for (i = optind; i < argc; ++i) {
> -		timezone_t tz = tzalloc(argv[i]);
> +		/* Treat "-" as standard input on platforms with /dev/stdin.
> +		   It's not worth the bother of supporting "-" on other
> +		   platforms, as that would need temp files.  */
> +		timezone_t tz = tzalloc(strcmp(argv[i], "-") == 0
> +					? "/dev/stdin" : argv[i]);
>  		char const *ab;
>  		time_t t;
>  		struct tm tm, newtm;
> @@ -695,7 +681,7 @@ yeartot(intmax_t y)
>  				return absolute_max_time;
>  			seconds = diff400 * SECSPER400YEARS;
>  			years = diff400 * 400;
> -                } else {
> +		} else {
>  			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
>  			years = 1;
>  		}
> @@ -926,18 +912,16 @@ showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
>    }
>  }
>  
> -#if HAVE_SNPRINTF
> -# define my_snprintf snprintf
> -#else
> +/* On pre-C99 platforms, a snprintf substitute good enough for us.  */
> +#if !HAVE_SNPRINTF
>  # include <stdarg.h>
> -
> -/* A substitute for snprintf that is good enough for zdump.  */
> -static int
> +ATTRIBUTE_FORMAT((printf, 3, 4)) static int
>  my_snprintf(char *s, size_t size, char const *format, ...)
>  {
>    int n;
>    va_list args;
>    char const *arg;
> +  char *cp;
>    size_t arglen, slen;
>    char buf[1024];
>    va_start(args, format);
> @@ -954,12 +938,14 @@ my_snprintf(char *s, size_t size, char const *format, ...)
>      arglen = n;
>    }
>    slen = arglen < size ? arglen : size - 1;
> -  memcpy(s, arg, slen);
> -  s[slen] = '\0';
> +  cp = s;
> +  cp = mempcpy(cp, arg, slen);
> +  *cp = '\0';
>    n = arglen <= INT_MAX ? arglen : -1;
>    va_end(args);
>    return n;
>  }
> +# define snprintf my_snprintf
>  #endif
>  
>  /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
> @@ -974,10 +960,10 @@ 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
> -	  ? my_snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
> +	  ? snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
>  	  : mm
> -	  ? my_snprintf(buf, size, "%02d:%02d", hh, mm)
> -	  : my_snprintf(buf, size, "%02d", hh));
> +	  ? snprintf(buf, size, "%02d:%02d", hh, mm)
> +	  : snprintf(buf, size, "%02d", hh));
>  }
>  
>  /* Store into BUF, of size SIZE, a formatted UT offset for the
> @@ -1012,10 +998,10 @@ format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t)
>    mm = off / 60 % 60;
>    hh = off / 60 / 60;
>    return (ss || 100 <= hh
> -	  ? my_snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
> +	  ? snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
>  	  : mm
> -	  ? my_snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
> -	  : my_snprintf(buf, size, "%c%02ld", sign, hh));
> +	  ? snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
> +	  : snprintf(buf, size, "%c%02ld", sign, hh));
>  }
>  
>  /* Store into BUF (of size SIZE) a quoted string representation of P.
> @@ -1083,8 +1069,9 @@ istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
>        char fbuf[100];
>        bool oversized = 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");
> +      char *cp = f_prefix_copy;
> +      cp = mempcpy(cp, f, f_prefix_len);
> +      strcpy(cp, "X");
>        formatted_len = strftime(b, s, f_prefix_copy, tm);
>        if (oversized)
>  	free(f_prefix_copy);
> @@ -1118,7 +1105,7 @@ istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
>  	    for (abp = ab; is_alpha(*abp); abp++)
>  	      continue;
>  	    len = (!*abp && *ab
> -		   ? my_snprintf(b, s, "%s", ab)
> +		   ? snprintf(b, s, "%s", ab)
>  		   : format_quoted_string(b, s, ab));
>  	    if (s <= len)
>  	      return false;
> @@ -1126,7 +1113,7 @@ istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
>  	  }
>  	  formatted_len
>  	    = (tm->tm_isdst
> -	       ? my_snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
> +	       ? snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
>  	       : 0);
>  	}
>  	break;
> diff --git a/timezone/zic.c b/timezone/zic.c
> index d5d30163b0..7ecf3b9115 100644
> --- a/timezone/zic.c
> +++ b/timezone/zic.c
> @@ -18,6 +18,10 @@
>  #include "tzfile.h"
>  
>  #include <fcntl.h>
> +#ifndef O_BINARY
> +# define O_BINARY 0 /* MS-Windows */
> +#endif
> +
>  #include <locale.h>
>  #include <signal.h>
>  #include <stdarg.h>
> @@ -27,8 +31,8 @@ typedef int_fast64_t	zic_t;
>  static zic_t const
>    ZIC_MIN = INT_FAST64_MIN,
>    ZIC_MAX = INT_FAST64_MAX,
> -  ZIC32_MIN = -1 - (zic_t) 0x7fffffff,
> -  ZIC32_MAX = 0x7fffffff;
> +  ZIC32_MIN = -1 - (zic_t) TWO_31_MINUS_1,
> +  ZIC32_MAX = TWO_31_MINUS_1;
>  #define SCNdZIC SCNdFAST64
>  
>  #ifndef ZIC_MAX_ABBR_LEN_WO_WARN
> @@ -44,8 +48,8 @@ enum { FORMAT_LEN_GROWTH_BOUND = 5 };
>  #ifdef HAVE_DIRECT_H
>  # include <direct.h>
>  # include <io.h>
> -# undef mkdir
>  # define mkdir(name, mode) _mkdir(name)
> +typedef unsigned short gid_t, mode_t, uid_t;
>  #endif
>  
>  #ifndef HAVE_GETRANDOM
> @@ -61,13 +65,94 @@ enum { FORMAT_LEN_GROWTH_BOUND = 5 };
>  # include <sys/random.h>
>  #endif
>  
> +
>  #if HAVE_SYS_STAT_H
>  # include <sys/stat.h>
>  #endif
> -#ifdef S_IRUSR
> -# define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
> +
> +#ifndef S_IRWXU
> +# define S_IRUSR 0400
> +# define S_IWUSR 0200
> +# define S_IXUSR 0100
> +# define S_IRGRP 0040
> +# define S_IWGRP 0020
> +# define S_IXGRP 0010
> +# define S_IROTH 0004
> +# define S_IWOTH 0002
> +# define S_IXOTH 0001
> +# define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR)
> +# define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
> +# define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
> +#endif
> +
> +/* All file permission bits.  */
> +#define ALL_PERMS (S_IRWXU | S_IRWXG | S_IRWXO)
> +
> +/* Troublesome file permission bits.  */
> +#define TROUBLE_PERMS (S_IWGRP | S_IWOTH)
> +
> +/* File permission bits for making directories.
> +   The umask modifies these bits.  */
> +#define MKDIR_PERMS (ALL_PERMS & ~TROUBLE_PERMS)
> +
> +/* File permission bits for making regular files.
> +   The umask modifies these bits.  */
> +#define CREAT_PERMS (MKDIR_PERMS & ~(S_IXUSR | S_IXGRP | S_IXOTH))
> +static mode_t creat_perms = CREAT_PERMS;
> +
> +#ifndef HAVE_PWD_H
> +# ifdef __has_include
> +#  if __has_include(<pwd.h>) && __has_include(<grp.h>)
> +#   define HAVE_PWD_H 1
> +#  else
> +#   define HAVE_PWD_H 0
> +#  endif
> +# endif
> +#endif
> +#ifndef HAVE_PWD_H
> +# define HAVE_PWD_H 1
> +#endif
> +#if HAVE_PWD_H
> +# include <grp.h>
> +# include <pwd.h>
>  #else
> -# define MKDIR_UMASK 0755
> +struct group { gid_t gr_gid; };
> +struct passwd { uid_t pw_uid; };
> +# define getgrnam(arg) NULL
> +# define getpwnam(arg) NULL
> +# define fchown(fd, owner, group) ((fd) < 0 ? -1 : 0)
> +#endif
> +static gid_t const no_gid = -1;
> +static uid_t const no_uid = -1;
> +static gid_t output_group = -1;
> +static uid_t output_owner = -1;
> +#ifndef GID_T_MAX
> +# define GID_T_MAX_NO_PADDING MAXVAL(gid_t, TYPE_BIT(gid_t))
> +# if HAVE__GENERIC
> +#  define GID_T_MAX \
> +    (TYPE_SIGNED(gid_t) \
> +     ? _Generic((gid_t) 0, \
> +		signed char: SCHAR_MAX, short: SHRT_MAX, \
> +		int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \
> +		default: GID_T_MAX_NO_PADDING) \
> +     : (gid_t) -1)
> +# else
> +#  define GID_T_MAX GID_T_MAX_NO_PADDING
> +# endif
> +#endif
> +#ifndef UID_T_MAX
> +# define UID_T_MAX_NO_PADDING MAXVAL(uid_t, TYPE_BIT(uid_t))
> +# if HAVE__GENERIC
> +#  define UID_T_MAX \
> +    (TYPE_SIGNED(uid_t) \
> +     ? _Generic((uid_t) 0, \
> +		signed char: SCHAR_MAX, short: SHRT_MAX, \
> +		int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \
> +		default: UID_T_MAX_NO_PADDING) \
> +     : (uid_t) -1)
> +# else
> +#  define UID_T_MAX UID_T_MAX_NO_PADDING
> +# endif
>  #endif
>  
>  /* The minimum alignment of a type, for pre-C23 platforms.
> @@ -79,6 +164,11 @@ enum { FORMAT_LEN_GROWTH_BOUND = 5 };
>  # include <stdalign.h>
>  #endif
>  
> +/* The name used for the file implementing the obsolete -p option.  */
> +#ifndef TZDEFRULES
> +# define TZDEFRULES "posixrules"
> +#endif
> +
>  /* The maximum length of a text line, including the trailing newline.  */
>  #ifndef _POSIX2_LINE_MAX
>  # define _POSIX2_LINE_MAX 2048
> @@ -144,14 +234,6 @@ struct zone {
>  	zic_t		z_untiltime;
>  };
>  
> -#if !HAVE_POSIX_DECLS
> -extern int	getopt(int argc, char * const argv[],
> -			const char * options);
> -extern int	link(const char * target, const char * linkname);
> -extern char *	optarg;
> -extern int	optind;
> -#endif
> -
>  #if ! HAVE_SYMLINK
>  static ssize_t
>  readlink(char const *restrict file, char *restrict buf, size_t size)
> @@ -167,15 +249,17 @@ symlink(char const *target, char const *linkname)
>  }
>  #endif
>  #ifndef AT_SYMLINK_FOLLOW
> -#  define linkat(targetdir, target, linknamedir, linkname, flag) \
> -     (errno = ENOTSUP, -1)
> +# define linkat(targetdir, target, linknamedir, linkname, flag) \
> +   (errno = ENOTSUP, -1)
>  #endif
>  
> +static int	addabbr(char[TZ_MAX_CHARS], int *, char const *);
>  static void	addtt(zic_t starttime, int type);
>  static int	addtype(zic_t, char const *, bool, bool, bool);
> -static void	leapadd(zic_t, int, int);
>  static void	adjleap(void);
>  static void	associate(void);
> +static void	checkabbr(char const *);
> +static void	check_for_signal(void);
>  static void	dolink(const char *, const char *, bool);
>  static int	getfields(char *, char **, int);
>  static zic_t	gethms(const char * string, const char * errstring);
> @@ -188,12 +272,13 @@ static void	inrule(char ** fields, int nfields);
>  static bool	inzcont(char ** fields, int nfields);
>  static bool	inzone(char ** fields, int nfields);
>  static bool	inzsub(char **, int, bool);
> -static int	itssymlink(char const *, int *);
>  static bool	is_alpha(char a);
> +static int	itssymlink(char const *, int *);
> +static void	leapadd(zic_t, int, int);
>  static char	lowerit(char);
>  static void	mkdirs(char const *, bool);
> -static void	newabbr(const char * abbr);
>  static zic_t	oadd(zic_t t1, zic_t t2);
> +static zic_t	omul(zic_t, zic_t);
>  static void	outzone(const struct zone * zp, ptrdiff_t ntzones);
>  static zic_t	rpytime(const struct rule * rp, zic_t wantedy);
>  static bool	rulesub(struct rule * rp,
> @@ -202,6 +287,13 @@ static bool	rulesub(struct rule * rp,
>  			const char * dayp, const char * timep);
>  static zic_t	tadd(zic_t t1, zic_t t2);
>  
> +/* Is C an ASCII digit?  */
> +static bool
> +is_digit(char c)
> +{
> +  return '0' <= c && c <= '9';
> +}
> +
>  /* Bound on length of what %z can expand to.  */
>  enum { PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1 };
>  
> @@ -209,7 +301,8 @@ static int		charcnt;
>  static bool		errors;
>  static bool		warnings;
>  static int		filenum;
> -static int		leapcnt;
> +static ptrdiff_t	leapcnt;
> +static ptrdiff_t	leap_alloc;
>  static bool		leapseen;
>  static zic_t		leapminyear;
>  static zic_t		leapmaxyear;
> @@ -219,6 +312,7 @@ static int		max_format_len;
>  static zic_t		max_year;
>  static zic_t		min_year;
>  static bool		noise;
> +static bool		skip_mkdir;
>  static int		rfilenum;
>  static lineno		rlinenum;
>  static const char *	progname;
> @@ -449,9 +543,11 @@ static unsigned char	desigidx[TZ_MAX_TYPES];
>  static bool		ttisstds[TZ_MAX_TYPES];
>  static bool		ttisuts[TZ_MAX_TYPES];
>  static char		chars[TZ_MAX_CHARS];
> -static zic_t		trans[TZ_MAX_LEAPS];
> -static zic_t		corr[TZ_MAX_LEAPS];
> -static char		roll[TZ_MAX_LEAPS];
> +static struct {
> +  zic_t trans;
> +  zic_t corr;
> +  char roll;
> +} *leap;
>  
>  /*
>  ** Memory allocation.
> @@ -470,7 +566,8 @@ size_overflow(void)
>    memory_exhausted(_("size overflow"));
>  }
>  
> -ATTRIBUTE_PURE_114833 static ptrdiff_t
> +ATTRIBUTE_PURE_114833_HACK
> +static ptrdiff_t
>  size_sum(size_t a, size_t b)
>  {
>  #ifdef ckd_add
> @@ -484,7 +581,8 @@ size_sum(size_t a, size_t b)
>    size_overflow();
>  }
>  
> -ATTRIBUTE_PURE_114833 static ptrdiff_t
> +ATTRIBUTE_PURE_114833_HACK
> +static ptrdiff_t
>  size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
>  {
>  #ifdef ckd_mul
> @@ -499,7 +597,8 @@ size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
>    size_overflow();
>  }
>  
> -ATTRIBUTE_PURE_114833 static ptrdiff_t
> +ATTRIBUTE_PURE_114833_HACK
> +static ptrdiff_t
>  align_to(ptrdiff_t size, ptrdiff_t alignment)
>  {
>    ptrdiff_t lo_bits = alignment - 1, sum = size_sum(size, lo_bits);
> @@ -524,19 +623,19 @@ memcheck(void *ptr)
>  }
>  
>  static void *
> -emalloc(size_t size)
> +xmalloc(size_t size)
>  {
>    return memcheck(malloc(size));
>  }
>  
>  static void *
> -erealloc(void *ptr, size_t size)
> +xrealloc(void *ptr, size_t size)
>  {
>    return memcheck(realloc(ptr, size));
>  }
>  
>  static char *
> -estrdup(char const *str)
> +xstrdup(char const *str)
>  {
>    return memcheck(strdup(str));
>  }
> @@ -565,7 +664,7 @@ growalloc(void *ptr, ptrdiff_t itemsize, ptrdiff_t nitems,
>  {
>    return (nitems < *nitems_alloc
>  	  ? ptr
> -	  : erealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
> +	  : xrealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
>  }
>  
>  /*
> @@ -607,6 +706,7 @@ eat(int fnum, lineno num)
>  ATTRIBUTE_FORMAT((printf, 1, 0)) static void
>  verror(const char *const string, va_list args)
>  {
> +	check_for_signal();
>  	/*
>  	** Match the format of "cc" to allow sh users to
>  	**	zic ... 2>&1 | error -t "*" -v
> @@ -643,15 +743,99 @@ warning(const char *const string, ...)
>  	warnings = true;
>  }
>  
> -/* Close STREAM.  If it had an I/O error, report it against DIR/NAME,
> -   remove TEMPNAME if nonnull, and then exit.  */
> +/* Convert ARG, a string in base BASE, to an unsigned long value no
> +   greater than MAXVAL.  On failure, diagnose with MSGID and exit.  */
> +static unsigned long
> +arg2num(char const *arg, int base, unsigned long maxval, char const *msgid)
> +{
> +  unsigned long n;
> +  char *ep;
> +  errno = 0;
> +  n = strtoul(arg, &ep, base);
> +  if (ep == arg || *ep || maxval < n || errno) {
> +    fprintf(stderr, _(msgid), progname, arg);
> +    exit(EXIT_FAILURE);
> +  }
> +  return n;
> +}
> +
> +#ifndef MODE_T_MAX
> +# define MODE_T_MAX_NO_PADDING MAXVAL(mode_t, TYPE_BIT(mode_t))
> +# if HAVE__GENERIC
> +#  define MODE_T_MAX \
> +    (TYPE_SIGNED(mode_t) \
> +     ? _Generic((mode_t) 0, \
> +		signed char: SCHAR_MAX, short: SHRT_MAX, \
> +		int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \
> +		default: MODE_T_MAX_NO_PADDING) \
> +     : (mode_t) -1)
> +# else
> +#  define MODE_T_MAX MODE_T_MAX_NO_PADDING
> +# endif
> +#endif
> +
> +#ifndef HAVE_FCHMOD
> +# define HAVE_FCHMOD 1
> +#endif
> +#if !HAVE_FCHMOD
> +# define fchmod(fd, mode) 0
> +#endif
> +
> +#ifndef HAVE_SETMODE
> +# if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
> +      || (defined __APPLE__ && defined __MACH__))
> +#  define HAVE_SETMODE 1
> +# else
> +#  define HAVE_SETMODE 0
> +# endif
> +#endif
> +
> +static mode_t const no_mode = -1;
> +static mode_t output_mode = -1;
> +
> +static mode_t
> +mode_option(char const *arg)
> +{
> +#if HAVE_SETMODE
> +  void *set = setmode(arg);
> +  if (set) {
> +    mode_t mode = getmode(set, CREAT_PERMS);
> +    free(set);
> +    return mode;
> +  }
> +#endif
> +  return arg2num(arg, 8, min(MODE_T_MAX, ULONG_MAX),
> +		 N_("%s: -m '%s': invalid mode\n"));
> +}
> +
> +static int
> +chmetadata(FILE *stream)
> +{
> +  if (output_owner != no_uid || output_group != no_gid) {
> +    int r = fchown(fileno(stream), output_owner, output_group);
> +    if (r < 0)
> +      return r;
> +  }
> +  return output_mode == no_mode ? 0 : fchmod(fileno(stream), output_mode);
> +}
> +
> +/* Close STREAM.
> +   If it had an I/O error, report it against DIR/NAME,
> +   remove TEMPNAME if nonnull, and then exit.
> +   If TEMPNAME is nonnull, and if requested,
> +   change the stream's metadata before closing.  */
>  static void
>  close_file(FILE *stream, char const *dir, char const *name,
>  	   char const *tempname)
>  {
>    char const *e = (ferror(stream) ? _("I/O error")
> -		   : fclose(stream) != 0 ? strerror(errno) : NULL);
> +		   : ((tempname
> +		       && (fflush(stream) < 0 || chmetadata(stream) < 0))
> +		      || fclose(stream) < 0)
> +		   ? strerror(errno) : NULL);
>    if (e) {
> +    if (name && *name == '/')
> +      dir = NULL;
>      fprintf(stderr, "%s: %s%s%s%s%s\n", progname,
>  	    dir ? dir : "", dir ? "/" : "",
>  	    name ? name : "", name ? ": " : "",
> @@ -662,15 +846,22 @@ close_file(FILE *stream, char const *dir, char const *name,
>    }
>  }
>  
> +ATTRIBUTE_NORETURN static void
> +duplicate_options(char const *opt)
> +{
> +  fprintf(stderr, _("%s: More than one %s option specified\n"), progname, opt);
> +  exit(EXIT_FAILURE);
> +}
> +
>  ATTRIBUTE_NORETURN static void
>  usage(FILE *stream, int status)
>  {
>    fprintf(stream,
>  	  _("%s: usage is %s [ --version ] [ --help ] [ -v ] \\\n"
> -	    "\t[ -b {slim|fat} ] [ -d directory ] [ -l localtime ]"
> -	    " [ -L leapseconds ] \\\n"
> -	    "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -R '@hi' ] \\\n"
> -	    "\t[ -t localtime-link ] \\\n"
> +	    "\t[ -b {slim|fat} ] [ -d directory ] [ -D ] \\\n"
> +	    "\t[ -l localtime ] [ -L leapseconds ] [ -m mode ] \\\n"
> +	    "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -R @hi ] \\\n"
> +	    "\t[ -t localtime-link ] [ -u 'owner[:group]' ] \\\n"
>  	    "\t[ filename ... ]\n\n"
>  	    "Report bugs to %s.\n"),
>  	  progname, progname, REPORT_BUGS_TO);
> @@ -679,6 +870,71 @@ usage(FILE *stream, int status)
>    exit(status);
>  }
>  
> +static void
> +group_option(char const *arg)
> +{
> +  if (*arg) {
> +    if (output_group != no_gid) {
> +      fprintf(stderr, _("multiple groups specified"));
> +      exit(EXIT_FAILURE);
> +    } else {
> +      struct group *gr = getgrnam(arg);
> +      output_group = (gr ? gr->gr_gid
> +		      : arg2num(arg, 10, min(GID_T_MAX, ULONG_MAX),
> +				N_("%s: invalid group: %s\n")));
> +    }
> +  }
> +}
> +
> +static void
> +owner_option(char const *arg)
> +{
> +  if (*arg) {
> +    if (output_owner != no_uid) {
> +      fprintf(stderr, _("multiple owners specified"));
> +      exit(EXIT_FAILURE);
> +    } else {
> +      struct passwd *pw = getpwnam(arg);
> +      output_owner = (pw ? pw->pw_uid
> +		      : arg2num(arg, 10, min(UID_T_MAX, ULONG_MAX),
> +				N_("%s: invalid owner: %s\n")));
> +    }
> +  }
> +}
> +
> +/* If setting owner or group, use temp file permissions that avoid
> +   security races before the fchmod at the end.  */
> +static void
> +use_safe_temp_permissions(void)
> +{
> +  if (output_owner != no_uid || output_group != no_gid) {
> +
> +    /* The mode when done with the file.  */
> +    mode_t omode;
> +    if (output_mode == no_mode) {
> +      mode_t cmask = umask(0);
> +      umask(cmask);
> +      omode = CREAT_PERMS & ~cmask;
> +    } else
> +      omode = output_mode;
> +
> +    /* The mode passed to open+O_CREAT.  Do not bother with executable
> +       permissions, as they should not be used and this mode is merely
> +       a nicety (even a mode of 0 still work).  */
> +    creat_perms = ((((omode & (S_IRUSR | S_IRGRP | S_IROTH))
> +		     == (S_IRUSR | S_IRGRP | S_IROTH))
> +		    ? S_IRUSR | S_IRGRP | S_IROTH : 0)
> +		   | (((omode & (S_IWUSR | S_IWGRP | S_IWOTH))
> +		       == (S_IWUSR | S_IWGRP | S_IWOTH))
> +		      ? S_IWUSR | S_IWGRP | S_IWOTH : 0));
> +
> +    /* If creat_perms is not the final mode, arrange to run
> +       fchmod later, even if -m was not used.  */
> +    if (creat_perms != omode)
> +      output_mode = omode;
> +  }
> +}
> +
>  /* Change the working directory to DIR, possibly creating DIR and its
>     ancestors.  After this is done, all files are accessed with names
>     relative to DIR.  */
> @@ -821,6 +1077,7 @@ make_links(void)
>  	warning(_("link %s targeting link %s"),
>  		links[i].l_linkname, links[i].l_target);
>      }
> +    check_for_signal();
>    }
>  }
>  
> @@ -953,6 +1210,9 @@ static const char *	lcltime;
>  static const char *	directory;
>  static const char *	tzdefault;
>  
> +/* True if DIRECTORY ends in '/'.  */
> +static bool directory_ends_in_slash;
> +
>  /* -1 if the TZif output file should be slim, 0 if default, 1 if the
>     output should be fat for backward compatibility.  ZIC_BLOAT_DEFAULT
>     determines the default.  */
> @@ -975,9 +1235,6 @@ main(int argc, char **argv)
>  	register ptrdiff_t i, j;
>  	bool timerange_given = false;
>  
> -#ifdef S_IWGRP
> -	umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
> -#endif
>  #if HAVE_GETTEXT
>  	setlocale(LC_ALL, "");
>  # ifdef TZ_DOMAINDIR
> @@ -1000,8 +1257,7 @@ main(int argc, char **argv)
>  		} else if (strcmp(argv[k], "--help") == 0) {
>  			usage(stdout, EXIT_SUCCESS);
>  		}
> -	while ((c = getopt(argc, argv, "b:d:l:L:p:r:R:st:vy:")) != EOF
> -	       && c != -1)
> +	while ((c = getopt(argc, argv, "b:d:Dg:l:L:m:p:r:R:st:u:vy:")) != -1)
>  		switch (c) {
>  			default:
>  				usage(stderr, EXIT_FAILURE);
> @@ -1018,73 +1274,65 @@ main(int argc, char **argv)
>  				  error(_("invalid option: -b '%s'"), optarg);
>  				break;
>  			case 'd':
> -				if (directory == NULL)
> -					directory = optarg;
> -				else {
> -					fprintf(stderr,
> -						_("%s: More than one -d option"
> -						  " specified\n"),
> -						progname);
> -					return EXIT_FAILURE;
> -				}
> +				if (directory)
> +				  duplicate_options("-d");
> +				directory = optarg;
> +				break;
> +			case 'D':
> +				skip_mkdir = true;
> +				break;
> +			case 'g':
> +				/* This undocumented option is present for
> +				   compatibility with FreeBSD 14.  */
> +				group_option(optarg);
>  				break;
>  			case 'l':
> -				if (lcltime == NULL)
> -					lcltime = optarg;
> -				else {
> -					fprintf(stderr,
> -						_("%s: More than one -l option"
> -						  " specified\n"),
> -						progname);
> -					return EXIT_FAILURE;
> -				}
> +				if (lcltime)
> +				  duplicate_options("-l");
> +				lcltime = optarg;
> +				break;
> +			case 'm':
> +				if (output_mode != no_mode)
> +				  duplicate_options("-m");
> +				output_mode = mode_option(optarg);
>  				break;
>  			case 'p':
> -				if (psxrules == NULL)
> -					psxrules = optarg;
> -				else {
> -					fprintf(stderr,
> -						_("%s: More than one -p option"
> -						  " specified\n"),
> -						progname);
> -					return EXIT_FAILURE;
> -				}
> +				if (psxrules)
> +				  duplicate_options("-p");
> +				if (strcmp(optarg, "-") != 0)
> +				  warning(_("-p is obsolete"
> +					    " and likely ineffective"));
> +				psxrules = optarg;
>  				break;
>  			case 't':
> -				if (tzdefault != NULL) {
> -				  fprintf(stderr,
> -					  _("%s: More than one -t option"
> -					    " specified\n"),
> -					  progname);
> -				  return EXIT_FAILURE;
> -				}
> +				if (tzdefault)
> +				  duplicate_options("-t");
>  				tzdefault = optarg;
>  				break;
> +			case 'u':
> +				{
> +				  char *colon = strchr(optarg, ':');
> +				  if (colon)
> +				    *colon = '\0';
> +				  owner_option(optarg);
> +				  if (colon)
> +				    group_option(colon + 1);
> +				}
> +				break;
>  			case 'y':
>  				warning(_("-y ignored"));
>  				break;
>  			case 'L':
> -				if (leapsec == NULL)
> -					leapsec = optarg;
> -				else {
> -					fprintf(stderr,
> -						_("%s: More than one -L option"
> -						  " specified\n"),
> -						progname);
> -					return EXIT_FAILURE;
> -				}
> +				if (leapsec)
> +				  duplicate_options("-L");
> +				leapsec = optarg;
>  				break;
>  			case 'v':
>  				noise = true;
>  				break;
>  			case 'r':
> -				if (timerange_given) {
> -				  fprintf(stderr,
> -					  _("%s: More than one -r option"
> -					    " specified\n"),
> -					  progname);
> -				  return EXIT_FAILURE;
> -				}
> +				if (timerange_given)
> +				  duplicate_options("-r");
>  				if (! timerange_option(optarg)) {
>  				  fprintf(stderr,
>  					  _("%s: invalid time range: %s\n"),
> @@ -1136,7 +1384,9 @@ main(int argc, char **argv)
>  	if (errors)
>  		return EXIT_FAILURE;
>  	associate();
> +	use_safe_temp_permissions();
>  	change_directory(directory);
> +	directory_ends_in_slash = directory[strlen(directory) - 1] == '/';
>  	catch_signals();
>  	for (i = 0; i < nzones; i = j) {
>  		/*
> @@ -1145,6 +1395,7 @@ main(int argc, char **argv)
>  		for (j = i + 1; j < nzones && zones[j].z_name == NULL; ++j)
>  			continue;
>  		outzone(&zones[i], j - i);
> +		check_for_signal();
>  	}
>  	make_links();
>  	if (lcltime != NULL) {
> @@ -1240,9 +1491,11 @@ get_rand_u64(void)
>    static int nwords;
>    if (!nwords) {
>      ssize_t s;
> -    do
> +    for (;; check_for_signal()) {
>        s = getrandom(entropy_buffer, sizeof entropy_buffer, 0);
> -    while (s < 0 && errno == EINTR);
> +      if (! (s < 0 && errno == EINTR))
> +	break;
> +    }
>  
>      nwords = s < 0 ? -1 : s / sizeof *entropy_buffer;
>    }
> @@ -1270,7 +1523,7 @@ get_rand_u64(void)
>        rmod = INT_MAX < UINT_FAST64_MAX ? 0 : UINT_FAST64_MAX / nrand + 1,
>        r = 0, rmax = 0;
>  
> -    do {
> +    for (;; check_for_signal()) {
>        uint_fast64_t rmax1 = rmax;
>        if (rmod) {
>  	/* Avoid signed integer overflow on theoretical platforms
> @@ -1281,7 +1534,9 @@ get_rand_u64(void)
>        rmax1 = nrand * rmax1 + rand_max;
>        r = nrand * r + rand();
>        rmax = rmax < rmax1 ? rmax1 : UINT_FAST64_MAX;
> -    } while (rmax < UINT_FAST64_MAX);
> +      if (UINT_FAST64_MAX <= rmax)
> +	break;
> +    }
>  
>      return r;
>    }
> @@ -1321,16 +1576,18 @@ random_dirent(char const **name, char **namealloc)
>    uint_fast64_t unfair_min = - ((UINTMAX_MAX % base__6 + 1) % base__6);
>  
>    if (!dst) {
> -    dst = emalloc(size_sum(dirlen, prefixlen + suffixlen + 1));
> -    memcpy(dst, src, dirlen);
> -    memcpy(dst + dirlen, prefix, prefixlen);
> -    dst[dirlen + prefixlen + suffixlen] = '\0';
> +    char *cp = dst = xmalloc(size_sum(dirlen, prefixlen + suffixlen + 1));
> +    cp = mempcpy(cp, src, dirlen);
> +    cp = mempcpy(cp, prefix, prefixlen);
> +    cp[suffixlen] = '\0';
>      *name = *namealloc = dst;
>    }
>  
> -  do
> +  for (;; check_for_signal()) {
>      r = get_rand_u64();
> -  while (unfair_min <= r);
> +    if (r < unfair_min)
> +      break;
> +  }
>  
>    for (i = 0; i < suffixlen; i++) {
>      dst[dirlen + prefixlen + i] = alphabet[r % alphabetlen];
> @@ -1338,6 +1595,20 @@ random_dirent(char const **name, char **namealloc)
>    }
>  }
>  
> +/* For diagnostics the directory, and file name relative to that
> +   directory, respectively.  A diagnostic routine can name FILENAME by
> +   outputting diagdir(FILENAME), then diagslash(FILENAME), then FILENAME.  */
> +static char const *
> +diagdir(char const *filename)
> +{
> +  return *filename == '/' ? "" : directory;
> +}
> +static char const *
> +diagslash(char const *filename)
> +{
> +  return &"/"[*filename == '/' || directory_ends_in_slash];
> +}
> +
>  /* Prepare to write to the file *OUTNAME, using *TEMPNAME to store the
>     name of the temporary file that will eventually be renamed to
>     *OUTNAME.  Assign the temporary file's name to both *OUTNAME and
> @@ -1347,32 +1618,35 @@ random_dirent(char const **name, char **namealloc)
>  static FILE *
>  open_outfile(char const **outname, char **tempname)
>  {
> -#if __STDC_VERSION__ < 201112
> -  static char const fopen_mode[] = "wb";
> -#else
> -  static char const fopen_mode[] = "wbx";
> -#endif
> -
> -  FILE *fp;
>    bool dirs_made = false;
>    if (!*tempname)
>      random_dirent(outname, tempname);
>  
> -  while (! (fp = fopen(*outname, fopen_mode))) {
> -    int fopen_errno = errno;
> -    if (fopen_errno == ENOENT && !dirs_made) {
> +  for (;; check_for_signal()) {
> +    int oflags = O_WRONLY | O_BINARY | O_CREAT | O_EXCL;
> +    int fd = open(*outname, oflags, creat_perms);
> +    int err;
> +    if (fd < 0)
> +      err = errno;
> +    else {
> +      FILE *fp = fdopen(fd, "wb");
> +      if (fp)
> +	return fp;
> +      err = errno;
> +      close(fd);
> +    }
> +    if (err == ENOENT && !dirs_made) {
>        mkdirs(*outname, true);
>        dirs_made = true;
> -    } else if (fopen_errno == EEXIST)
> +    } else if (err == EEXIST)
>        random_dirent(outname, tempname);
>      else {
> -      fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
> -	      progname, directory, *outname, strerror(fopen_errno));
> +      fprintf(stderr, _("%s: Can't create %s%s%s: %s\n"),
> +	      progname, diagdir(*outname), diagslash(*outname), *outname,
> +	      strerror(err));
>        exit(EXIT_FAILURE);
>      }
>    }
> -
> -  return fp;
>  }
>  
>  /* If TEMPNAME, the result is in the temporary file TEMPNAME even
> @@ -1385,8 +1659,9 @@ rename_dest(char *tempname, char const *name)
>      if (rename(tempname, name) != 0) {
>        int rename_errno = errno;
>        remove(tempname);
> -      fprintf(stderr, _("%s: rename to %s/%s: %s\n"),
> -	      progname, directory, name, strerror(rename_errno));
> +      fprintf(stderr, _("%s: rename to %s%s%s: %s\n"),
> +	      progname, diagdir(name), diagslash(name), name,
> +	      strerror(rename_errno));
>        exit(EXIT_FAILURE);
>      }
>      free(tempname);
> @@ -1396,7 +1671,8 @@ rename_dest(char *tempname, char const *name)
>  /* Create symlink contents suitable for symlinking TARGET to LINKNAME, as a
>     freshly allocated string.  TARGET should be a relative file name, and
>     is relative to the global variable DIRECTORY.  LINKNAME can be either
> -   relative or absolute.  */
> +   relative or absolute.  Return a null pointer if the symlink contents
> +   was not computed because LINKNAME is absolute but DIRECTORY is not.  */
>  static char *
>  relname(char const *target, char const *linkname)
>  {
> @@ -1407,13 +1683,17 @@ relname(char const *target, char const *linkname)
>    if (*linkname == '/') {
>      /* Make F absolute too.  */
>      size_t len = strlen(directory);
> -    size_t lenslash = len + (len && directory[len - 1] != '/');
> +    bool needs_slash = len && directory[len - 1] != '/';
> +    size_t lenslash = len + needs_slash;
>      size_t targetsize = strlen(target) + 1;
> +    char *cp;
> +    if (*directory != '/')
> +      return NULL;
>      linksize = size_sum(lenslash, targetsize);
> -    f = result = emalloc(linksize);
> -    memcpy(result, directory, len);
> -    result[len] = '/';
> -    memcpy(result + lenslash, target, targetsize);
> +    f = cp = result = xmalloc(linksize);
> +    cp = mempcpy(cp, directory, len);
> +    *cp = '/';
> +    memcpy(cp + needs_slash, target, targetsize);
>    }
>    for (i = 0; f[i] && f[i] == linkname[i]; i++)
>      if (f[i] == '/')
> @@ -1423,11 +1703,13 @@ relname(char const *target, char const *linkname)
>    taillen = strlen(f + dir_len);
>    dotdotetcsize = size_sum(size_product(dotdots, 3), taillen + 1);
>    if (dotdotetcsize <= linksize) {
> +    char *cp;
>      if (!result)
> -      result = emalloc(dotdotetcsize);
> +      result = xmalloc(dotdotetcsize);
> +    cp = result;
>      for (i = 0; i < dotdots; i++)
> -      memcpy(result + 3 * i, "../", 3);
> -    memmove(result + 3 * dotdots, f + dir_len, taillen + 1);
> +      cp = mempcpy(cp, "../", 3);
> +    memmove(cp, f + dir_len, taillen + 1);
>    }
>    return result;
>  }
> @@ -1435,7 +1717,8 @@ relname(char const *target, char const *linkname)
>  /* Return true if A and B must have the same parent dir if A and B exist.
>     Return false if this is not necessarily true (though it might be true).
>     Keep it simple, and do not inspect the file system.  */
> -ATTRIBUTE_PURE_114833 static bool
> +ATTRIBUTE_PURE_114833
> +static bool
>  same_parent_dirs(char const *a, char const *b)
>  {
>    for (; *a == *b; a++, b++)
> @@ -1453,20 +1736,19 @@ dolink(char const *target, char const *linkname, bool staysymlink)
>  	char const *outname = linkname;
>  	int targetissym = -2, linknameissym = -2;
>  
> -	check_for_signal();
> -
>  	if (strcmp(target, "-") == 0) {
>  	  if (remove(linkname) == 0 || errno == ENOENT || errno == ENOTDIR)
>  	    return;
>  	  else {
>  	    char const *e = strerror(errno);
> -	    fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
> -		    progname, directory, linkname, e);
> +	    fprintf(stderr, _("%s: Can't remove %s%s%s: %s\n"),
> +		    progname, diagdir(linkname), diagslash(linkname), linkname,
> +		    e);
>  	    exit(EXIT_FAILURE);
>  	  }
>  	}
>  
> -	while (true) {
> +	for (;; check_for_signal()) {
>  	  if (linkat(AT_FDCWD, target, AT_FDCWD, outname, AT_SYMLINK_FOLLOW)
>  	      == 0) {
>  	    link_errno = 0;
> @@ -1504,8 +1786,9 @@ dolink(char const *target, char const *linkname, bool staysymlink)
>  	    mkdirs(linkname, true);
>  	    linkdirs_made = true;
>  	  } else {
> -	    fprintf(stderr, _("%s: Can't link %s/%s to %s/%s: %s\n"),
> -		    progname, directory, target, directory, outname,
> +	    fprintf(stderr, _("%s: Can't link %s%s%s to %s%s%s: %s\n"),
> +		    progname, diagdir(target), diagslash(target), target,
> +		    diagdir(outname), diagslash(outname), outname,
>  		    strerror(link_errno));
>  	    exit(EXIT_FAILURE);
>  	  }
> @@ -1514,21 +1797,23 @@ dolink(char const *target, char const *linkname, bool staysymlink)
>  	  bool absolute = *target == '/';
>  	  char *linkalloc = absolute ? NULL : relname(target, linkname);
>  	  char const *contents = absolute ? target : linkalloc;
> -	  int symlink_errno;
> +	  int symlink_errno = -1;
>  
> -	  while (true) {
> -	    if (symlink(contents, outname) == 0) {
> -	      symlink_errno = 0;
> -	      break;
> +	  if (contents) {
> +	    for (;; check_for_signal()) {
> +	      if (symlink(contents, outname) == 0) {
> +		symlink_errno = 0;
> +		break;
> +	      }
> +	      symlink_errno = errno;
> +	      if (symlink_errno == EEXIST)
> +		random_dirent(&outname, &tempname);
> +	      else if (symlink_errno == ENOENT && !linkdirs_made) {
> +		mkdirs(linkname, true);
> +		linkdirs_made = true;
> +	      } else
> +		break;
>  	    }
> -	    symlink_errno = errno;
> -	    if (symlink_errno == EEXIST)
> -	      random_dirent(&outname, &tempname);
> -	    else if (symlink_errno == ENOENT && !linkdirs_made) {
> -	      mkdirs(linkname, true);
> -	      linkdirs_made = true;
> -	    } else
> -	      break;
>  	  }
>  	  free(linkalloc);
>  	  if (symlink_errno == 0) {
> @@ -1541,18 +1826,20 @@ dolink(char const *target, char const *linkname, bool staysymlink)
>  	    fp = fopen(target, "rb");
>  	    if (!fp) {
>  	      char const *e = strerror(errno);
> -	      fprintf(stderr, _("%s: Can't read %s/%s: %s\n"),
> -		      progname, directory, target, e);
> +	      fprintf(stderr, _("%s: Can't read %s%s%s: %s\n"),
> +		      progname, diagdir(target), diagslash(target), target, e);
>  	      exit(EXIT_FAILURE);
>  	    }
>  	    tp = open_outfile(&outname, &tempname);
> -	    while ((c = getc(fp)) != EOF)
> +	    for (; (c = getc(fp)) != EOF; check_for_signal())
>  	      putc(c, tp);
>  	    close_file(tp, directory, linkname, tempname);
>  	    close_file(fp, directory, target, NULL);
>  	    if (link_errno != ENOTSUP)
>  	      warning(_("copy used because hard link failed: %s"),
>  		      strerror(link_errno));
> +	    else if (symlink_errno < 0)
> +	      warning(_("copy used because symbolic link not obvious"));
>  	    else if (symlink_errno != ENOTSUP)
>  	      warning(_("copy used because symbolic link failed: %s"),
>  		      strerror(symlink_errno));
> @@ -1668,7 +1955,7 @@ static bool
>  inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
>  {
>    ptrdiff_t linelen = 0, ch;
> -  while ((ch = getc(fp)) != '\n') {
> +  for (; (ch = getc(fp)) != '\n'; check_for_signal()) {
>      if (ch < 0) {
>        if (ferror(fp)) {
>  	error(_("input error"));
> @@ -1755,6 +2042,7 @@ infile(int fnum, char const *name)
>  				default: unreachable();
>  			}
>  		}
> +		check_for_signal();
>  	}
>  	close_file(fp, NULL, filename(fnum), NULL);
>  	if (wantcont)
> @@ -1789,7 +2077,7 @@ gethms(char const *string, char const *errstring)
>  		       &hh, &hhx, &mm, &mmx, &ss, &ssx, &tenths, &xr, &xs)) {
>  	  default: ok = false; break;
>  	  case 8:
> -	    ok = '0' <= xr && xr <= '9';
> +	    ok = is_digit(xr);
>  	    ATTRIBUTE_FALLTHROUGH;
>  	  case 7:
>  	    ok &= ssx == '.';
> @@ -1811,15 +2099,11 @@ gethms(char const *string, char const *errstring)
>  			error("%s", errstring);
>  			return 0;
>  	}
> -	if (ZIC_MAX / SECSPERHOUR < hh) {
> -		error(_("time overflow"));
> -		return 0;
> -	}
>  	ss += 5 + ((ss ^ 1) & (xr == '0')) <= tenths; /* Round to even.  */
>  	if (noise && (hh > HOURSPERDAY ||
>  		(hh == HOURSPERDAY && (mm != 0 || ss != 0))))
>  warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
> -	return oadd(sign * hh * SECSPERHOUR,
> +	return oadd(omul(hh, sign * SECSPERHOUR),
>  		    sign * (mm * SECSPERMIN + ss));
>  }
>  
> @@ -1866,8 +2150,8 @@ inrule(char **fields, int nfields)
>  		     fields[RF_COMMAND], fields[RF_MONTH], fields[RF_DAY],
>  		     fields[RF_TOD]))
>  	  return;
> -	r.r_name = estrdup(fields[RF_NAME]);
> -	r.r_abbrvar = estrdup(fields[RF_ABBRVAR]);
> +	r.r_name = xstrdup(fields[RF_NAME]);
> +	r.r_abbrvar = xstrdup(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);
> @@ -1950,7 +2234,8 @@ inzsub(char **fields, int nfields, bool iscont)
>  	z.z_filenum = filenum;
>  	z.z_linenum = linenum;
>  	z.z_stdoff = gethms(fields[i_stdoff], _("invalid UT offset"));
> -	if ((cp = strchr(fields[i_format], '%')) != NULL) {
> +	cp = strchr(fields[i_format], '%');
> +	if (cp) {
>  		if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')
>  		    || strchr(fields[i_format], '/')) {
>  			error(_("invalid abbreviation format"));
> @@ -1978,19 +2263,15 @@ inzsub(char **fields, int nfields, bool iscont)
>  		z.z_untiltime = rpytime(&z.z_untilrule,
>  			z.z_untilrule.r_loyear);
>  		if (iscont && nzones > 0 &&
> -			z.z_untiltime > min_time &&
> -			z.z_untiltime < max_time &&
> -			zones[nzones - 1].z_untiltime > min_time &&
> -			zones[nzones - 1].z_untiltime < max_time &&
>  			zones[nzones - 1].z_untiltime >= z.z_untiltime) {
>  		  error(_("Zone continuation line end time is"
>  			  " not after end time of previous line"));
>  		  return false;
>  		}
>  	}
> -	z.z_name = iscont ? NULL : estrdup(fields[ZF_NAME]);
> -	z.z_rule = estrdup(fields[i_rule]);
> -	z.z_format = cp1 = estrdup(fields[i_format]);
> +	z.z_name = iscont ? NULL : xstrdup(fields[ZF_NAME]);
> +	z.z_rule = xstrdup(fields[i_rule]);
> +	z.z_format = cp1 = xstrdup(fields[i_format]);
>  	if (z.z_format_specifier == 'z') {
>  	  cp1[cp - fields[i_format]] = 's';
>  	  if (noise)
> @@ -2063,15 +2344,7 @@ getleapdatetime(char **fields, bool expire_line)
>  			return -1;
>  	}
>  	dayoff = oadd(dayoff, day - 1);
> -	if (dayoff < min_time / SECSPERDAY) {
> -		error(_("time too small"));
> -		return -1;
> -	}
> -	if (dayoff > max_time / SECSPERDAY) {
> -		error(_("time too large"));
> -		return -1;
> -	}
> -	t = dayoff * SECSPERDAY;
> +	t = omul(dayoff, SECSPERDAY);
>  	tod = gethms(fields[LP_TIME], _("invalid time of day"));
>  	t = tadd(t, tod);
>  	if (t < 0)
> @@ -2133,8 +2406,8 @@ inlink(char **fields, int nfields)
>  	  return;
>  	l.l_filenum = filenum;
>  	l.l_linenum = linenum;
> -	l.l_target = estrdup(fields[LF_TARGET]);
> -	l.l_linkname = estrdup(fields[LF_LINKNAME]);
> +	l.l_target = xstrdup(fields[LF_TARGET]);
> +	l.l_linkname = xstrdup(fields[LF_LINKNAME]);
>  	links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc);
>  	links[nlinks++] = l;
>  }
> @@ -2157,7 +2430,7 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
>  	rp->r_month = lp->l_value;
>  	rp->r_todisstd = false;
>  	rp->r_todisut = false;
> -	dp = estrdup(timep);
> +	dp = xstrdup(timep);
>  	if (*dp != '\0') {
>  		ep = dp + strlen(dp) - 1;
>  		switch (lowerit(*ep)) {
> @@ -2232,19 +2505,23 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
>  	**	Sun<=20
>  	**	Sun>=7
>  	*/
> -	dp = estrdup(dayp);
> +	dp = xstrdup(dayp);
>  	if ((lp = byword(dp, lasts)) != NULL) {
>  		rp->r_dycode = DC_DOWLEQ;
>  		rp->r_wday = lp->l_value;
>  		rp->r_dayofmonth = len_months[1][rp->r_month];
>  	} else {
> -		if ((ep = strchr(dp, '<')) != NULL)
> -			rp->r_dycode = DC_DOWLEQ;
> -		else if ((ep = strchr(dp, '>')) != NULL)
> -			rp->r_dycode = DC_DOWGEQ;
> +		ep = strchr(dp, '<');
> +		if (ep)
> +		    rp->r_dycode = DC_DOWLEQ;
>  		else {
> +		    ep = strchr(dp, '>');
> +		    if (ep)
> +			rp->r_dycode = DC_DOWGEQ;
> +		    else {
>  			ep = dp;
>  			rp->r_dycode = DC_DOM;
> +		    }
>  		}
>  		if (rp->r_dycode != DC_DOM) {
>  			*ep++ = 0;
> @@ -2327,7 +2604,7 @@ atcomp(const void *avp, const void *bvp)
>  struct timerange {
>    int defaulttype;
>    ptrdiff_t base, count;
> -  int leapbase, leapcount;
> +  ptrdiff_t leapbase, leapcount;
>    bool leapexpiry;
>  };
>  
> @@ -2347,13 +2624,13 @@ limitrange(struct timerange r, zic_t lo, zic_t hi,
>       positive leap second if and only if it has a positive correction.
>       This supports common TZif readers that assume that the first leap
>       second is positive if and only if its correction is positive.  */
> -  while (1 < r.leapcount && trans[r.leapbase + 1] <= lo) {
> +  while (1 < r.leapcount && leap[r.leapbase + 1].trans <= lo) {
>      r.leapcount--;
>      r.leapbase++;
>    }
>    while (0 < r.leapbase
> -	 && ((corr[r.leapbase - 1] < corr[r.leapbase])
> -	     != (0 < corr[r.leapbase]))) {
> +	 && ((leap[r.leapbase - 1].corr < leap[r.leapbase].corr)
> +	     != (0 < leap[r.leapbase].corr))) {
>      r.leapcount++;
>      r.leapbase--;
>    }
> @@ -2363,7 +2640,7 @@ limitrange(struct timerange r, zic_t lo, zic_t hi,
>    if (hi < max_time) {
>      while (0 < r.count && hi + 1 < ats[r.base + r.count - 1])
>        r.count--;
> -    while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1])
> +    while (0 < r.leapcount && hi + 1 < leap[r.leapbase + r.leapcount - 1].trans)
>        r.leapcount--;
>    }
>  
> @@ -2386,7 +2663,7 @@ writezone(const char *const name, const char *const string, char version,
>  	/* Allocate the ATS and TYPES arrays via a single malloc,
>  	   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,
> +	zic_t *ats = xmalloc(align_to(size_product(timecnt + !timecnt,
>  						   sizeof *ats + 1),
>  				      alignof(zic_t)));
>  	void *typesptr = ats + timecnt;
> @@ -2399,7 +2676,7 @@ writezone(const char *const name, const char *const string, char version,
>  	if (timecnt > 1)
>  		qsort(attypes, timecnt, sizeof *attypes, atcomp);
>  	/*
> -	** Optimize.
> +	** Optimize and skip unwanted transitions.
>  	*/
>  	{
>  		ptrdiff_t fromi, toi;
> @@ -2407,16 +2684,28 @@ writezone(const char *const name, const char *const string, char version,
>  		toi = 0;
>  		fromi = 0;
>  		for ( ; fromi < timecnt; ++fromi) {
> -			if (toi != 0
> -			    && ((attypes[fromi].at
> +			if (toi != 0) {
> +			    /* Skip the previous transition if it is unwanted
> +			       because its local time is not earlier.
> +			       The UT offset additions can't overflow because
> +			       of how the times were calculated.  */
> +			    unsigned char type_2 =
> +			      toi == 1 ? 0 : attypes[toi - 2].type;
> +			    if ((attypes[fromi].at
>  				 + utoffs[attypes[toi - 1].type])
> -				<= (attypes[toi - 1].at
> -				    + utoffs[toi == 1 ? 0
> -					     : attypes[toi - 2].type]))) {
> +				<= attypes[toi - 1].at + utoffs[type_2]) {
> +				    if (attypes[fromi].type == type_2)
> +					toi--;
> +				    else
>  					attypes[toi - 1].type =
>  						attypes[fromi].type;
> -					continue;
> +				    continue;
> +			    }
>  			}
> +
> +			/* Use a transition if it is the first one,
> +			   or if it cannot be merged for other reasons,
> +			   or if it transitions to different timekeeping.  */
>  			if (toi == 0
>  			    || attypes[fromi].dontmerge
>  			    || (utoffs[attypes[toi - 1].type]
> @@ -2430,14 +2719,19 @@ writezone(const char *const name, const char *const string, char version,
>  		timecnt = toi;
>  	}
>  
> -	if (noise && timecnt > 1200) {
> -	  if (timecnt > TZ_MAX_TIMES)
> +	if (noise) {
> +	  if (1200 < timecnt) {
> +	    if (TZ_MAX_TIMES < timecnt)
>  		warning(_("reference clients mishandle"
>  			  " more than %d transition times"),
>  			TZ_MAX_TIMES);
> -	  else
> +	    else
>  		warning(_("pre-2014 clients may mishandle"
>  			  " more than 1200 transition times"));
> +	  }
> +	  if (TZ_MAX_LEAPS < leapcnt)
> +	    warning(_("reference clients mishandle more than %d leap seconds"),
> +		    TZ_MAX_LEAPS);
>  	}
>  	/*
>  	** Transfer.
> @@ -2453,8 +2747,8 @@ writezone(const char *const name, const char *const string, char version,
>  	for (i = 0; i < timecnt; ++i) {
>  		j = leapcnt;
>  		while (--j >= 0)
> -			if (ats[i] > trans[j] - corr[j]) {
> -				ats[i] = tadd(ats[i], corr[j]);
> +			if (leap[j].trans - leap[j].corr < ats[i]) {
> +				ats[i] = tadd(ats[i], leap[j].corr);
>  				break;
>  			}
>  	}
> @@ -2483,7 +2777,7 @@ writezone(const char *const name, const char *const string, char version,
>  	    version = '4';
>  	  }
>  	  if (0 < r->leapcount
> -	      && corr[r->leapbase] != 1 && corr[r->leapbase] != -1) {
> +	      && leap[r->leapbase].corr != 1 && leap[r->leapbase].corr != -1) {
>  	    if (noise)
>  	      warning(_("%s: pre-2021b clients may mishandle"
>  			" leap second table truncation"),
> @@ -2498,7 +2792,7 @@ writezone(const char *const name, const char *const string, char version,
>  
>  	for (pass = 1; pass <= 2; ++pass) {
>  		register ptrdiff_t thistimei, thistimecnt, thistimelim;
> -		register int	thisleapi, thisleapcnt, thisleaplim;
> +		register ptrdiff_t thisleapi, thisleapcnt, thisleaplim;
>  		struct tzhead tzh;
>  		int pretranstype = -1, thisdefaulttype;
>  		bool locut, hicut, thisleapexpiry;
> @@ -2643,30 +2937,27 @@ writezone(const char *const name, const char *const string, char version,
>  			    : i == thisdefaulttype ? old0 : i]
>  		      = thistypecnt++;
>  
> -		for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i)
> -			indmap[i] = -1;
>  		thischarcnt = stdcnt = utcnt = 0;
>  		for (i = old0; i < typecnt; i++) {
> -			register char *	thisabbr;
> -
>  			if (omittype[i])
>  				continue;
>  			if (ttisstds[i])
>  			  stdcnt = thistypecnt;
>  			if (ttisuts[i])
>  			  utcnt = thistypecnt;
> -			if (indmap[desigidx[i]] >= 0)
> -				continue;
> -			thisabbr = &chars[desigidx[i]];
> -			for (j = 0; j < thischarcnt; ++j)
> -				if (strcmp(&thischars[j], thisabbr) == 0)
> -					break;
> -			if (j == thischarcnt) {
> -				strcpy(&thischars[thischarcnt], thisabbr);
> -				thischarcnt += strlen(thisabbr) + 1;
> -			}
> -			indmap[desigidx[i]] = j;
> +			addabbr(thischars, &thischarcnt, &chars[desigidx[i]]);
>  		}
> +
> +		/* Now that all abbrevs have been added to THISCHARS,
> +		   it is safe to set INDMAP without worrying about
> +		   whether the abbrevs might move later.  */
> +		for (i = 0; i < TZ_MAX_CHARS; i++)
> +		  indmap[i] = -1;
> +		for (i = old0; i < typecnt; i++)
> +		  if (!omittype[i] && indmap[desigidx[i]] < 0)
> +		    indmap[desigidx[i]] = addabbr(thischars, &thischarcnt,
> +						  &chars[desigidx[i]]);
> +
>  		if (pass == 1 && !want_bloat()) {
>  		  hicut = thisleapexpiry = false;
>  		  pretranstype = -1;
> @@ -2703,6 +2994,11 @@ writezone(const char *const name, const char *const string, char version,
>  		  continue;
>  		}
>  
> +		if (pass == 2 && noise && 50 < thischarcnt)
> +		  warning(_("%s: pre-2026 reference clients mishandle"
> +			    " more than 50 bytes of abbreviations"),
> +			  name);
> +
>  		/* Output a LO_TIME transition if needed; see limitrange.
>  		   But do not go below the minimum representable value
>  		   for this pass.  */
> @@ -2738,8 +3034,8 @@ writezone(const char *const name, const char *const string, char version,
>  		for (i = thisleapi; i < thisleaplim; ++i) {
>  			register zic_t	todo;
>  
> -			if (roll[i]) {
> -				if (timecnt == 0 || trans[i] < ats[0]) {
> +			if (leap[i].roll) {
> +				if (timecnt == 0 || leap[i].trans < ats[0]) {
>  					j = 0;
>  					while (isdsts[j])
>  						if (++j >= typecnt) {
> @@ -2749,23 +3045,23 @@ writezone(const char *const name, const char *const string, char version,
>  				} else {
>  					j = 1;
>  					while (j < timecnt &&
> -						trans[i] >= ats[j])
> +						ats[j] <= leap[i].trans)
>  							++j;
>  					j = types[j - 1];
>  				}
> -				todo = tadd(trans[i], -utoffs[j]);
> -			} else	todo = trans[i];
> +				todo = tadd(leap[i].trans, -utoffs[j]);
> +			} else	todo = leap[i].trans;
>  			puttzcodepass(todo, fp, pass);
> -			puttzcode(corr[i], fp);
> +			puttzcode(leap[i].corr, fp);
>  		}
>  		if (thisleapexpiry) {
>  		  /* Append a no-op leap correction indicating when the leap
>  		     second table expires.  Although this does not conform to
> -		     Internet RFC 8536, most clients seem to accept this and
> +		     Internet RFC 9636, most clients seem to accept this and
>  		     the plan is to amend the RFC to allow this in version 4
>  		     TZif files.  */
>  		  puttzcodepass(leapexpires, fp, pass);
> -		  puttzcode(thisleaplim ? corr[thisleaplim - 1] : 0, fp);
> +		  puttzcode(thisleaplim ? leap[thisleaplim - 1].corr : 0, fp);
>  		}
>  		if (stdcnt != 0)
>  		  for (i = old0; i < typecnt; i++)
> @@ -2825,11 +3121,10 @@ doabbr(char *abbr, struct zone const *zp, char const *letters,
>         bool isdst, zic_t save, bool doquotes)
>  {
>  	register char *	cp;
> -	register char *	slashp;
>  	ptrdiff_t len;
>  	char const *format = zp->z_format;
> +	char const *slashp = strchr(format, '/');
>  
> -	slashp = strchr(format, '/');
>  	if (slashp == NULL) {
>  	  char letterbuf[PERCENT_Z_LEN_BOUND + 1];
>  	  if (zp->z_format_specifier == 'z')
> @@ -2839,11 +3134,11 @@ doabbr(char *abbr, struct zone const *zp, char const *letters,
>  	  else if (letters == disable_percent_s)
>  	    return 0;
>  	  sprintf(abbr, format, letters);
> -	} else if (isdst) {
> -		strcpy(abbr, slashp + 1);
> -	} else {
> -		memcpy(abbr, format, slashp - format);
> -		abbr[slashp - format] = '\0';
> +	} else if (isdst)
> +	  strcpy(abbr, slashp + 1);
> +	else {
> +	  char *abbrend = mempcpy(abbr, format, slashp - format);
> +	  *abbrend = '\0';
>  	}
>  	len = strlen(abbr);
>  	if (!doquotes)
> @@ -2885,11 +3180,11 @@ stringoffset(char *result, zic_t offset)
>  	offset /= SECSPERMIN;
>  	minutes = offset % MINSPERHOUR;
>  	offset /= MINSPERHOUR;
> -	hours = offset;
> -	if (hours >= HOURSPERDAY * DAYSPERWEEK) {
> +	if (offset >= HOURSPERDAY * DAYSPERWEEK) {
>  		result[0] = '\0';
>  		return 0;
>  	}
> +	hours = offset;
>  	len += sprintf(result + len, "%d", hours);
>  	if (minutes != 0 || seconds != 0) {
>  		len += sprintf(result + len, ":%02d", minutes);
> @@ -3007,7 +3302,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
>  
>  	result[0] = '\0';
>  
> -	/* Internet RFC 8536 section 5.1 says to use an empty TZ string if
> +	/* Internet RFC 9636 section 6.1 says to use an empty TZ string if
>  	   future timestamps are truncated.  */
>  	if (hi_time < max_time)
>  	  return -1;
> @@ -3128,16 +3423,20 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
>  	int nonTZlimtype = -1;
>  	zic_t max_year0;
>  	int defaulttype = -1;
> +	int max_stringoffset_len = sizeof "-167:59:59" - 1;
> +	int max_comma_stringrule_len = (sizeof ",M12.5.6/" - 1
> +					+ max_stringoffset_len);
>  
>  	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;
> +	max_envvar_len = 2 * (max_abbr_len + max_stringoffset_len
> +			      + max_comma_stringrule_len);
>  
> -	startbuf = emalloc(max_abbr_len + 1);
> -	ab = emalloc(max_abbr_len + 1);
> -	envvar = emalloc(max_envvar_len + 1);
> +	startbuf = xmalloc(max_abbr_len + 1);
> +	ab = xmalloc(max_abbr_len + 1);
> +	envvar = xmalloc(max_envvar_len + 1);
>  	INITIALIZE(untiltime);
>  	INITIALIZE(starttime);
>  	/*
> @@ -3234,7 +3533,7 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
>  				startttisut);
>  			if (usestart) {
>  				addtt(starttime, type);
> -				if (useuntil && nonTZlimtime < starttime) {
> +				if (nonTZlimtime < starttime) {
>  				  nonTZlimtime = starttime;
>  				  nonTZlimtype = type;
>  				}
> @@ -3303,9 +3602,6 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
>  					if (!r->r_todisstd)
>  						offset = oadd(offset, save);
>  					jtime = r->r_temp;
> -					if (jtime == min_time ||
> -						jtime == max_time)
> -							continue;
>  					jtime = tadd(jtime, -offset);
>  					if (k < 0 || jtime < ktime) {
>  						k = j;
> @@ -3484,20 +3780,28 @@ static int
>  addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
>  {
>  	register int	i, j;
> +	int charcnt0;
>  
> -	if (! (-1L - 2147483647L <= utoff && utoff <= 2147483647L)) {
> +	/* RFC 9636 section 3.2 specifies this range for utoff.  */
> +	if (! (-TWO_31_MINUS_1 <= utoff && utoff <= TWO_31_MINUS_1)) {
>  		error(_("UT offset out of range"));
>  		exit(EXIT_FAILURE);
>  	}
>  	if (!want_bloat())
>  	  ttisstd = ttisut = false;
>  
> -	for (j = 0; j < charcnt; ++j)
> -		if (strcmp(&chars[j], abbr) == 0)
> -			break;
> -	if (j == charcnt)
> -		newabbr(abbr);
> -	else {
> +	checkabbr(abbr);
> +
> +	charcnt0 = charcnt;
> +	j = addabbr(chars, &charcnt, abbr);
> +	if (charcnt0 < charcnt) {
> +	  /* If an abbreviation was inserted, increment indexes no
> +	     earlier than the insert by the size of the insertion,
> +	     so that they continue to point to the same contents.  */
> +	  for (i = 0; i < typecnt; i++)
> +	    if (j <= desigidx[i])
> +	      desigidx[i] += charcnt - charcnt0;
> +	} else {
>  	  /* If there's already an entry, return its index.  */
>  	  for (i = 0; i < typecnt; i++)
>  	    if (utoff == utoffs[i] && isdst == isdsts[i] && j == desigidx[i]
> @@ -3524,32 +3828,27 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
>  static void
>  leapadd(zic_t t, int correction, int rolling)
>  {
> -	register int i;
> +	register ptrdiff_t i;
>  
> -	if (TZ_MAX_LEAPS <= leapcnt) {
> -		error(_("too many leap seconds"));
> -		exit(EXIT_FAILURE);
> -	}
>  	if (rolling && (lo_time != min_time || hi_time != max_time)) {
>  	  error(_("Rolling leap seconds not supported with -r"));
>  	  exit(EXIT_FAILURE);
>  	}
> +	leap = growalloc(leap, sizeof *leap, leapcnt, &leap_alloc);
>  	for (i = 0; i < leapcnt; ++i)
> -		if (t <= trans[i])
> +		if (t <= leap[i].trans)
>  			break;
> -	memmove(&trans[i + 1], &trans[i], (leapcnt - i) * sizeof *trans);
> -	memmove(&corr[i + 1], &corr[i], (leapcnt - i) * sizeof *corr);
> -	memmove(&roll[i + 1], &roll[i], (leapcnt - i) * sizeof *roll);
> -	trans[i] = t;
> -	corr[i] = correction;
> -	roll[i] = rolling;
> +	memmove(&leap[i + 1], &leap[i], (leapcnt - i) * sizeof *leap);
> +	leap[i].trans = t;
> +	leap[i].corr = correction;
> +	leap[i].roll = rolling;
>  	++leapcnt;
>  }
>  
>  static void
>  adjleap(void)
>  {
> -	register int	i;
> +	register ptrdiff_t i;
>  	register zic_t	last = 0;
>  	register zic_t	prevtrans = 0;
>  
> @@ -3557,18 +3856,18 @@ adjleap(void)
>  	** propagate leap seconds forward
>  	*/
>  	for (i = 0; i < leapcnt; ++i) {
> -		if (trans[i] - prevtrans < 28 * SECSPERDAY) {
> +		if (leap[i].trans - prevtrans < 28 * SECSPERDAY) {
>  		  error(_("Leap seconds too close together"));
>  		  exit(EXIT_FAILURE);
>  		}
> -		prevtrans = trans[i];
> -		trans[i] = tadd(trans[i], last);
> -		last = corr[i] += last;
> +		prevtrans = leap[i].trans;
> +		leap[i].trans = tadd(prevtrans, last);
> +		last = leap[i].corr += last;
>  	}
>  
>  	if (0 <= leapexpires) {
>  	  leapexpires = oadd(leapexpires, last);
> -	  if (! (leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) {
> +	  if (! (leapcnt == 0 || (leap[leapcnt - 1].trans < leapexpires))) {
>  	    error(_("last Leap time does not precede Expires time"));
>  	    exit(EXIT_FAILURE);
>  	  }
> @@ -3626,7 +3925,8 @@ lowerit(char a)
>  }
>  
>  /* case-insensitive equality */
> -ATTRIBUTE_PURE_114833 static bool
> +ATTRIBUTE_PURE_114833
> +static bool
>  ciequal(register const char *ap, register const char *bp)
>  {
>  	while (lowerit(*ap) == lowerit(*bp++))
> @@ -3635,7 +3935,8 @@ ciequal(register const char *ap, register const char *bp)
>  	return false;
>  }
>  
> -ATTRIBUTE_PURE_114833 static bool
> +ATTRIBUTE_PURE_114833
> +static bool
>  itsabbr(register const char *abbr, register const char *word)
>  {
>  	if (lowerit(*abbr) != lowerit(*word))
> @@ -3651,7 +3952,8 @@ itsabbr(register const char *abbr, register const char *word)
>  
>  /* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case.  */
>  
> -ATTRIBUTE_PURE_114833 static bool
> +ATTRIBUTE_PURE_114833
> +static bool
>  ciprefix(char const *abbr, char const *word)
>  {
>    do
> @@ -3761,7 +4063,9 @@ time_overflow(void)
>    exit(EXIT_FAILURE);
>  }
>  
> -ATTRIBUTE_PURE_114833 static zic_t
> +/* Return T1 + T2, but diagnose any overflow and exit.  */
> +ATTRIBUTE_PURE_114833_HACK
> +static zic_t
>  oadd(zic_t t1, zic_t t2)
>  {
>  #ifdef ckd_add
> @@ -3775,25 +4079,41 @@ oadd(zic_t t1, zic_t t2)
>    time_overflow();
>  }
>  
> -ATTRIBUTE_PURE_114833 static zic_t
> +/* Return T1 + T2, but diagnose any overflow and exit.
> +   This is like oadd, except the result must fit in min_time..max_time range,
> +   which on oddball machines can be a smaller range than ZIC_MIN..ZIC_MAX.  */
> +ATTRIBUTE_PURE_114833_HACK
> +static zic_t
>  tadd(zic_t t1, zic_t t2)
>  {
> -#ifdef ckd_add
> -  zic_t sum;
> -  if (!ckd_add(&sum, t1, t2) && min_time <= sum && sum <= max_time)
> +  zic_t sum = oadd(t1, t2);
> +  if (min_time <= sum && sum <= max_time)
>      return sum;
> +  time_overflow();
> +}
> +
> +/* Return T1 * T2, but diagnose any overflow and exit.  */
> +ATTRIBUTE_PURE_114833_HACK
> +static zic_t
> +omul(zic_t t1, zic_t t2)
> +{
> +#ifdef ckd_mul
> +  zic_t product;
> +  if (!ckd_mul(&product, t1, t2))
> +    return product;
>  #else
> -  if (t1 < 0 ? min_time - t1 <= t2 : t2 <= max_time - t1)
> -    return t1 + t2;
> +  if (t2 < 0
> +      ? ZIC_MAX / t2 <= t1 && (t2 == -1 || t1 <= ZIC_MIN / t2)
> +      : t2 == 0 || (ZIC_MIN / t2 <= t1 && t1 <= ZIC_MAX / t2))
> +    return t1 * t2;
>  #endif
> -  if (t1 == min_time || t1 == max_time)
> -    return t1;
>    time_overflow();
>  }
>  
>  /*
>  ** Given a rule, and a year, compute the date (in seconds since January 1,
>  ** 1970, 00:00 LOCAL time) in that year that the rule refers to.
> +** Do not count leap seconds.  On error, diagnose and exit.
>  */
>  
>  static zic_t
> @@ -3804,10 +4124,6 @@ rpytime(const struct rule *rp, zic_t wantedy)
>  	register zic_t	t, y;
>  	int yrem;
>  
> -	if (wantedy == ZIC_MIN)
> -		return min_time;
> -	if (wantedy == ZIC_MAX)
> -		return max_time;
>  	m = TM_JANUARY;
>  	y = EPOCH_YEAR;
>  
> @@ -3865,26 +4181,20 @@ rpytime(const struct rule *rp, zic_t wantedy)
>  will not work with pre-2004 versions of zic"));
>  		}
>  	}
> -	if (dayoff < min_time / SECSPERDAY)
> -		return min_time;
> -	if (dayoff > max_time / SECSPERDAY)
> -		return max_time;
> -	t = (zic_t) dayoff * SECSPERDAY;
> +	t = omul(dayoff, SECSPERDAY);
>  	return tadd(t, rp->r_tod);
>  }
>  
>  static void
> -newabbr(const char *string)
> +checkabbr(char const *string)
>  {
> -	register int	i;
> -
>  	if (strcmp(string, GRANDPARENTED) != 0) {
>  		register const char *	cp;
>  		const char *		mp;
>  
>  		cp = string;
>  		mp = NULL;
> -		while (is_alpha(*cp) || ('0' <= *cp && *cp <= '9')
> +		while (is_alpha(*cp) || is_digit(*cp)
>  		       || *cp == '-' || *cp == '+')
>  				++cp;
>  		if (noise && cp - string < 3)
> @@ -3896,13 +4206,50 @@ mp = _("time zone abbreviation differs from POSIX standard");
>  		if (mp != NULL)
>  			warning("%s (%s)", mp, string);
>  	}
> -	i = strlen(string) + 1;
> -	if (charcnt + i > TZ_MAX_CHARS) {
> -		error(_("too many, or too long, time zone abbreviations"));
> -		exit(EXIT_FAILURE);
> -	}
> -	strcpy(&chars[charcnt], string);
> -	charcnt += i;
> +}
> +
> +/* Put into CHS, which currently contains *PNCHS bytes containing
> +   NUL-terminated abbreviations none of which are suffixes of another,
> +   the abbreviation ABBR including its trailing NUL.
> +   If ABBR does not already appear in CHS,
> +   possibly as a suffix of an existing abbreviation,
> +   add ABBR to CHS, remove from CHS any abbreviation
> +   that is a suffix of ABBR, and increment *PNCHS accordingly.
> +   Return the index of ABBR after any modifications to CHS are made.
> +
> +   If all abbreviations have already been added, this function
> +   lets the caller look up the index of an existing abbreviation.  */
> +static int
> +addabbr(char chs[TZ_MAX_CHARS], int *pnchs, char const *abbr)
> +{
> +  int nchs = *pnchs;
> +  int alen = strlen(abbr), nchs_incr = alen + 1;
> +  int i;
> +  for (i = 0; i < nchs; ) {
> +    int clen = strlen(&chs[i]);
> +    if (alen <= clen) {
> +      /* If ABBR is a suffix of an abbreviation in CHS,
> +	 return the index of ABBR in CHS.  */
> +      int isuff = i + (clen - alen);
> +      if (memcmp(&chs[isuff], abbr, alen) == 0)
> +	return isuff;
> +    } else if (memcmp(&chs[i], &abbr[alen - clen], clen) == 0) {
> +      /* An abbreviation in CHS is a substring of ABBR.
> +	 Replace it with ABBR, instead of the more-common
> +	 actions of appending ABBR or doing nothing.  */
> +      nchs_incr = alen - clen;
> +      break;
> +    }
> +    i += clen + 1;
> +  }
> +  if (TZ_MAX_CHARS < nchs + nchs_incr) {
> +    error(_("too many, or too long, time zone abbreviations"));
> +    exit(EXIT_FAILURE);
> +  }
> +  memmove(&chs[i + nchs_incr], &chs[i], nchs - i);
> +  memcpy(&chs[i], abbr, nchs_incr);
> +  *pnchs = nchs + nchs_incr;
> +  return i;
>  }
>  
>  /* Ensure that the directories of ARGNAME exist, by making any missing
> @@ -3912,7 +4259,12 @@ mp = _("time zone abbreviation differs from POSIX standard");
>  static void
>  mkdirs(char const *argname, bool ancestors)
>  {
> -	char *name = estrdup(argname);
> +    /* If -D was specified, do not create directories.
> +       If a file operation's parent directory is missing,
> +       the operation will fail and be diagnosed.  */
> +    if (!skip_mkdir) {
> +
> +	char *name = xstrdup(argname);
>  	char *cp = name;
>  
>  	/* On MS-Windows systems, do not worry about drive letters or
> @@ -3936,7 +4288,7 @@ mkdirs(char const *argname, bool ancestors)
>  		** not check first whether it already exists, as that
>  		** is checked anyway if the mkdir fails.
>  		*/
> -		if (mkdir(name, MKDIR_UMASK) != 0) {
> +		if (mkdir(name, MKDIR_PERMS) < 0) {
>  			/* Do not report an error if err == EEXIST, because
>  			   some other process might have made the directory
>  			   in the meantime.  Likewise for ENOSYS, because
> @@ -3958,4 +4310,5 @@ mkdirs(char const *argname, bool ancestors)
>  		  *cp++ = '/';
>  	}
>  	free(name);
> +    }
>  }



More information about the Libc-alpha mailing list