From 4c6f92daead7aa989ae1b7c67760f81a3550f044 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Wed, 13 May 2026 11:08:34 -0700 Subject: [PATCH] timezone: sync to tzdb 2026b MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 vs getauxval df08e6a1 Port mode_t (and gid_t, uid_t) to MS-Windows 6127d375 New zic option -u, inspired by FreeBSD 813c9ee0 New zic option -m, inspired by FreeBSD 987ea89c New zic option -D, inspired by FreeBSD cc377b07 Simplify mkdir situation cd994a90 Simplify !HAVE_POSIX_DECLS situation 052ddf76 Minor gettext macro improvements d9018f1c Refactor duplicate duplicate-option code 8d65db97 Prefer fdopen to umask in zic d7edca6e Omit “'”s from zic usage message a09ba7a5 getopt returns -1 (not EOF) on failure e22d410c zic now uses is_digit f57cadda Always invoke umask at start 242a8338 Fix mode_t issues on MS-Windows 2fecd606 MKDIR_UMASK → MKDIR_PERMS refactoring 90ef088a Move static_assert to top level 41576478 Port better to platforms lacking mempcpy 90a08d3e * private.h: Include stddef.h early enough aa8b35fe Simplify port to NetBSD struct __state cd2fddf7 Port to -DHAVE_SYS_STAT_H=0 -DHAVE_POSIX_DECLS=0 8470e759 Pacify GCC 15 -Wunterminated-string-initialization 8817d42f Prefer mempcpy to doing it by hand 87abb113 Tighten security checks on TZ values c87f0918 Use strnlen 07f7f31a Fix preprocessor indenting 3adf4123 Add offtime_r à la FreeBSD and NetBSD b807a31e Don’t depend on ‘true’ for tzselect ddffc800 * zic.c: Fix misspelled comment (thanks to Jonathan Wakely). 7063d08c Fix bug with -d RELATIVE -t ABSOLUTE e8920e76 Rename emalloc to xmalloc. e8e1a3d2 NetBSD defines STD_INSPIRED functions 3411494c Define _CRT_DECLARE_NONSTDC_NAMES for MS-Windows 7c909166 Define NOMINMAX for MS-Windows 24a4d97f 'zdump -' now reads from stdin e6d6bc3e Pacify gcc -Wsuggest-attribute=format sans snprintf in zdump 99557862 TZNAME_MAXIMUM defaults to 254, not 255 fe5be99d Be more consistent about macro true/false vs 1/0 31f483a1 Remove dependency of asctime on strftime 7ef7ed06 Simplify timeoff redefinition 1bd67a4b Move MKTIME_MIGHT_OVERFLOW definition 67f7e8ab Pacify GCC 15ish -Wzero-as-null-pointer-constant 535a4e8b Pacify GCC 15ish -Wleading-whitespace=blanks 0706ef0b Move iinntt definition ea814e99 strftime %s no longer is limited to time_t range 41e5344e Fix bug near the year 2**31 - 1 - 1900 4e1de249 Pacify gcc -Wsuggest-attribute=const ebd2ed92 Don’t define _FILE_OFFSET_BITS if _TIME_BITS 26a649a1 Improve zdump overflow checking 9c8221d7 * private.h: Fix timeoff comment. 9db906a0 Switch from RFC 8536 to 9636 for documentation af54a9e8 Port better to glibc when used internally there Checked on x86_64-linux-gnu. Reviewed-by: Adhemerval Zanella --- 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 #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() -# 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() -# 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() -# 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 + +/* 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 -#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 #include +#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 #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 /* for R_OK, and other POSIX goodness */ +# include +#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 -#endif /* !HAVE_STDINT_H */ - -#ifndef HAVE_INTTYPES_H -# define HAVE_INTTYPES_H HAVE_STDINT_H -#endif -#if HAVE_INTTYPES_H -# include -#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 +# 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 # 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() -# 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 -#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 - -/* 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 +#ifndef O_BINARY +# define O_BINARY 0 /* MS-Windows */ +#endif + #include #include #include @@ -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 # include -# 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 #endif + #if HAVE_SYS_STAT_H # include #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() && __has_include() +# 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 +# include #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 #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); + } } -- 2.47.3