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