]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
timezone: sync to tzdb 2026b
authorPaul Eggert <eggert@cs.ucla.edu>
Wed, 13 May 2026 18:08:34 +0000 (11:08 -0700)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Mon, 18 May 2026 16:31:00 +0000 (13:31 -0300)
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.

Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
SHARED-FILES
timezone/private.h
timezone/tzfile.h
timezone/tzselect.ksh
timezone/version
timezone/zdump.c
timezone/zic.c

index c0260d661537205330b48d4d59706031a7ded2b6..866a0adc1719df7b49129256a09f433bcf208807 100644 (file)
@@ -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:
index c33041049f4c8f40b874c23b6fe0556967069b2c..ee191b4ec33c7dcd4971e2fff2693b45633e3b2e 100644 (file)
 # 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.
 # 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
 
 #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
 # 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
 #ifndef ENOMEM
 # define ENOMEM EINVAL
 #endif
+#ifndef ENOTCAPABLE
+# define ENOTCAPABLE EINVAL
+#endif
 #ifndef ENOTSUP
 # define ENOTSUP EINVAL
 #endif
 #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
 # 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
 # endif
 #endif
 
-#ifndef R_OK
-# define R_OK 4
-#endif /* !defined R_OK */
-
 #if PORT_TO_C89
 
 /*
 ** 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,
index b15414665443d763227cb58e65260c6e25f1bff2..1941bc3b769321d74a3fc056f7a255fb5f43d4f1 100644 (file)
 ** 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 */
index ca3d82c6aab6d8017878c8a24025a86f279c0308..85a8c670e0da33bf2f94b63b7763b3b7655aeaf5 100755 (executable)
@@ -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=$(
index 04fe6744432fa4d7731292d1af55f4070f48a385..75d34ee389315ff741842b501f580b21ea118eb4 100644 (file)
@@ -1 +1 @@
-2024a
+2026b
index e817873337c0fe381adfa967209a42334520f704..c7812eae9d2b1a8d51118f5a456d52a9925b09ed 100644 (file)
 #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;
index d5d30163b098567f6426f341e3e756ec23fdec31..7ecf3b9115f3902a922042f868e34b9490368bdb 100644 (file)
 #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);
+    }
 }