# 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
# 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
#endif
/* This string was in the Factory zone through version 2016f. */
-#define GRANDPARENTED "Local time zone must be set--see zic manual page"
+#ifndef GRANDPARENTED
+# define GRANDPARENTED "Local time zone must be set--see zic manual page"
+#endif
/*
** Defaults for preprocessor symbols.
# define HAVE__GENERIC (201112 <= __STDC_VERSION__)
#endif
+#ifndef HAVE_GETEUID
+# define HAVE_GETEUID 1
+#endif
+
+#ifndef HAVE_GETRESUID
+# define HAVE_GETRESUID 1
+#endif
+
#if !defined HAVE_GETTEXT && defined __has_include
# if __has_include(<libintl.h>)
-# define HAVE_GETTEXT true
+# define HAVE_GETTEXT 1
# endif
#endif
#ifndef HAVE_GETTEXT
-# define HAVE_GETTEXT false
+# define HAVE_GETTEXT 0
#endif
#ifndef HAVE_INCOMPATIBLE_CTIME_R
#if !defined HAVE_SYS_STAT_H && defined __has_include
# if !__has_include(<sys/stat.h>)
-# define HAVE_SYS_STAT_H false
+# define HAVE_SYS_STAT_H 0
# endif
#endif
#ifndef HAVE_SYS_STAT_H
-# define HAVE_SYS_STAT_H true
+# define HAVE_SYS_STAT_H 1
#endif
#if !defined HAVE_UNISTD_H && defined __has_include
# if !__has_include(<unistd.h>)
-# define HAVE_UNISTD_H false
+# define HAVE_UNISTD_H 0
# endif
#endif
#ifndef HAVE_UNISTD_H
-# define HAVE_UNISTD_H true
+# define HAVE_UNISTD_H 1
#endif
#ifndef NETBSD_INSPIRED
# define ctime_r _incompatible_ctime_r
#endif /* HAVE_INCOMPATIBLE_CTIME_R */
-/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems. */
-#define _GNU_SOURCE 1
-/* Fix asctime_r on Solaris 11. */
-#define _POSIX_PTHREAD_SEMANTICS 1
-/* Enable strtoimax on pre-C99 Solaris 11. */
-#define __EXTENSIONS__ 1
-
-/* On GNUish systems where time_t might be 32 or 64 bits, use 64.
- On these platforms _FILE_OFFSET_BITS must also be 64; otherwise
- setting _TIME_BITS to 64 does not work. The code does not
- otherwise rely on _FILE_OFFSET_BITS being 64, since it does not
- use off_t or functions like 'stat' that depend on off_t. */
-#ifndef _FILE_OFFSET_BITS
-# define _FILE_OFFSET_BITS 64
-#endif
-#if !defined _TIME_BITS && _FILE_OFFSET_BITS == 64
-# define _TIME_BITS 64
+#ifndef TZ_RUNTIME_LEAPS
+# define TZ_RUNTIME_LEAPS 1
#endif
/*
** Nested includes
*/
-/* Avoid clashes with NetBSD by renaming NetBSD's declarations.
- If defining the 'timezone' variable, avoid a clash with FreeBSD's
- 'timezone' function by renaming its declaration. */
-#define localtime_rz sys_localtime_rz
-#define mktime_z sys_mktime_z
-#define posix2time_z sys_posix2time_z
-#define time2posix_z sys_time2posix_z
-#if defined USG_COMPAT && USG_COMPAT == 2
+#include <stddef.h>
+
+/* If defining the 'timezone' variable a la POSIX, avoid clashing with the old
+ 'timezone' function of FreeBSD <= 14, by renaming the latter's declaration.
+ This hack can be removed after 2028-11-30, FreeBSD 14's expected EOL. */
+#if (defined __FreeBSD__ && __FreeBSD__ < 15 && defined __BSD_VISIBLE \
+ && defined USG_COMPAT && USG_COMPAT == 2)
# define timezone sys_timezone
+# define timezone_defined
#endif
-#define timezone_t sys_timezone_t
-#define tzalloc sys_tzalloc
-#define tzfree sys_tzfree
+
#include <time.h>
-#undef localtime_rz
-#undef mktime_z
-#undef posix2time_z
-#undef time2posix_z
-#if defined USG_COMPAT && USG_COMPAT == 2
+
+#ifdef timezone_defined
# undef timezone
+# undef timezone_defined
#endif
-#undef timezone_t
-#undef tzalloc
-#undef tzfree
-#include <stddef.h>
#include <string.h>
+#if defined HAVE_STRNLEN && !HAVE_STRNLEN
+static size_t
+strnlen (char const *s, size_t maxlen)
+{
+ size_t i;
+ for (i = 0; i < maxlen && s[i]; i++)
+ continue;
+ return i;
+}
+#endif
+
#if !PORT_TO_C89
# include <inttypes.h>
#endif
#ifndef ENOMEM
# define ENOMEM EINVAL
#endif
+#ifndef ENOTCAPABLE
+# define ENOTCAPABLE EINVAL
+#endif
#ifndef ENOTSUP
# define ENOTSUP EINVAL
#endif
#endif /* HAVE_GETTEXT */
#if HAVE_UNISTD_H
-# include <unistd.h> /* for R_OK, and other POSIX goodness */
+# include <unistd.h>
+#else
+/* Assume getopt.o or equivalent is linked via Makefile configuration. */
+int getopt(int, char *const[], char const *);
+extern char *optarg;
+extern int optind;
#endif /* HAVE_UNISTD_H */
/* SUPPORT_POSIX2008 means the tzcode library should support
# endif
#endif
+#ifndef HAVE_ISSETUGID
+# if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+ || (defined __linux__ && !defined __GLIBC__) /* Android, musl, etc. */ \
+ || (defined __APPLE__ && defined __MACH__) || defined __sun)
+# define HAVE_ISSETUGID 1
+# else
+# define HAVE_ISSETUGID 0
+# endif
+#endif
+
+#ifndef HAVE_SNPRINTF
+# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__)
+#endif
+
#ifndef HAVE_STRFTIME_L
# if _POSIX_VERSION < 200809
# define HAVE_STRFTIME_L 0
# endif
#endif
-#ifndef R_OK
-# define R_OK 4
-#endif /* !defined R_OK */
-
#if PORT_TO_C89
/*
** previously included files. glibc 2.1 and Solaris 10 and later have
** stdint.h, even with pre-C99 compilers.
*/
-#if !defined HAVE_STDINT_H && defined __has_include
-# define HAVE_STDINT_H true /* C23 __has_include implies C99 stdint.h. */
-#endif
-#ifndef HAVE_STDINT_H
-# define HAVE_STDINT_H \
- (199901 <= __STDC_VERSION__ \
- || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \
- || __CYGWIN__ || INTMAX_MAX)
-#endif /* !defined HAVE_STDINT_H */
-
-#if HAVE_STDINT_H
-# include <stdint.h>
-#endif /* !HAVE_STDINT_H */
-
-#ifndef HAVE_INTTYPES_H
-# define HAVE_INTTYPES_H HAVE_STDINT_H
-#endif
-#if HAVE_INTTYPES_H
-# include <inttypes.h>
-#endif
-
-/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */
-#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__
-# ifndef LLONG_MAX
-# define LLONG_MAX __LONG_LONG_MAX__
+# if !defined HAVE_STDINT_H && defined __has_include
+# define HAVE_STDINT_H 1 /* C23 __has_include implies C99 stdint.h. */
# endif
-# ifndef LLONG_MIN
-# define LLONG_MIN (-1 - LLONG_MAX)
+# ifndef HAVE_STDINT_H
+# define HAVE_STDINT_H \
+ (199901 <= __STDC_VERSION__ \
+ || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \
+ || __CYGWIN__ || INTMAX_MAX)
+# endif /* !defined HAVE_STDINT_H */
+
+# if HAVE_STDINT_H
+# include <stdint.h>
+# endif /* !HAVE_STDINT_H */
+
+# ifndef HAVE_INTTYPES_H
+# define HAVE_INTTYPES_H HAVE_STDINT_H
# endif
-# ifndef ULLONG_MAX
-# define ULLONG_MAX (LLONG_MAX * 2ull + 1)
+# if HAVE_INTTYPES_H
+# include <inttypes.h>
# endif
-#endif
-#ifndef INT_FAST64_MAX
-# if 1 <= LONG_MAX >> 31 >> 31
+/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */
+# if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__
+# ifndef LLONG_MAX
+# define LLONG_MAX __LONG_LONG_MAX__
+# endif
+# ifndef LLONG_MIN
+# define LLONG_MIN (-1 - LLONG_MAX)
+# endif
+# ifndef ULLONG_MAX
+# define ULLONG_MAX (LLONG_MAX * 2ull + 1)
+# endif
+# endif
+
+# ifndef INT_FAST64_MAX
+# if 1 <= LONG_MAX >> 31 >> 31
typedef long int_fast64_t;
-# define INT_FAST64_MIN LONG_MIN
-# define INT_FAST64_MAX LONG_MAX
-# else
+# define INT_FAST64_MIN LONG_MIN
+# define INT_FAST64_MAX LONG_MAX
+# else
/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */
typedef long long int_fast64_t;
-# define INT_FAST64_MIN LLONG_MIN
-# define INT_FAST64_MAX LLONG_MAX
+# define INT_FAST64_MIN LLONG_MIN
+# define INT_FAST64_MAX LLONG_MAX
+# endif
# endif
-#endif
-#ifndef PRIdFAST64
-# if INT_FAST64_MAX == LONG_MAX
-# define PRIdFAST64 "ld"
-# else
-# define PRIdFAST64 "lld"
+# ifndef PRIdFAST64
+# if INT_FAST64_MAX == LONG_MAX
+# define PRIdFAST64 "ld"
+# else
+# define PRIdFAST64 "lld"
+# endif
# endif
-#endif
-#ifndef SCNdFAST64
-# define SCNdFAST64 PRIdFAST64
-#endif
+# ifndef SCNdFAST64
+# define SCNdFAST64 PRIdFAST64
+# endif
-#ifndef INT_FAST32_MAX
-# if INT_MAX >> 31 == 0
+# ifndef INT_FAST32_MAX
typedef long int_fast32_t;
# define INT_FAST32_MAX LONG_MAX
# define INT_FAST32_MIN LONG_MIN
-# else
-typedef int int_fast32_t;
-# define INT_FAST32_MAX INT_MAX
-# define INT_FAST32_MIN INT_MIN
# endif
-#endif
-#ifndef INTMAX_MAX
-# ifdef LLONG_MAX
+# ifndef INT_LEAST32_MAX
+typedef int_fast32_t int_least32_t;
+# endif
+
+# ifndef INTMAX_MAX
+# ifdef LLONG_MAX
typedef long long intmax_t;
-# ifndef HAVE_STRTOLL
-# define HAVE_STRTOLL true
+# ifndef HAVE_STRTOLL
+# define HAVE_STRTOLL 1
+# endif
+# if HAVE_STRTOLL
+# define strtoimax strtoll
+# endif
+# define INTMAX_MAX LLONG_MAX
+# define INTMAX_MIN LLONG_MIN
+# else
+typedef long intmax_t;
+# define INTMAX_MAX LONG_MAX
+# define INTMAX_MIN LONG_MIN
# endif
-# if HAVE_STRTOLL
-# define strtoimax strtoll
+# ifndef strtoimax
+# define strtoimax strtol
# endif
-# define INTMAX_MAX LLONG_MAX
-# define INTMAX_MIN LLONG_MIN
-# else
-typedef long intmax_t;
-# define INTMAX_MAX LONG_MAX
-# define INTMAX_MIN LONG_MIN
-# endif
-# ifndef strtoimax
-# define strtoimax strtol
# endif
-#endif
-#ifndef PRIdMAX
-# if INTMAX_MAX == LLONG_MAX
-# define PRIdMAX "lld"
-# else
-# define PRIdMAX "ld"
+# ifndef PRIdMAX
+# if INTMAX_MAX == LLONG_MAX
+# define PRIdMAX "lld"
+# else
+# define PRIdMAX "ld"
+# endif
# endif
-#endif
-#ifndef PTRDIFF_MAX
-# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t))
-#endif
+# ifndef PTRDIFF_MAX
+# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t))
+# endif
-#ifndef UINT_FAST32_MAX
+# ifndef UINT_FAST32_MAX
typedef unsigned long uint_fast32_t;
-#endif
+# endif
-#ifndef UINT_FAST64_MAX
-# if 3 <= ULONG_MAX >> 31 >> 31
+# ifndef UINT_FAST64_MAX
+# if 3 <= ULONG_MAX >> 31 >> 31
typedef unsigned long uint_fast64_t;
-# define UINT_FAST64_MAX ULONG_MAX
-# else
+# define UINT_FAST64_MAX ULONG_MAX
+# else
/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */
typedef unsigned long long uint_fast64_t;
-# define UINT_FAST64_MAX ULLONG_MAX
+# define UINT_FAST64_MAX ULLONG_MAX
+# endif
# endif
-#endif
-#ifndef UINTMAX_MAX
-# ifdef ULLONG_MAX
+# ifndef UINTMAX_MAX
+# ifdef ULLONG_MAX
typedef unsigned long long uintmax_t;
-# define UINTMAX_MAX ULLONG_MAX
-# else
+# define UINTMAX_MAX ULLONG_MAX
+# else
typedef unsigned long uintmax_t;
-# define UINTMAX_MAX ULONG_MAX
+# define UINTMAX_MAX ULONG_MAX
+# endif
# endif
-#endif
-#ifndef PRIuMAX
-# ifdef ULLONG_MAX
-# define PRIuMAX "llu"
-# else
-# define PRIuMAX "lu"
+# ifndef PRIuMAX
+# ifdef ULLONG_MAX
+# define PRIuMAX "llu"
+# else
+# define PRIuMAX "lu"
+# endif
# endif
-#endif
-#ifndef SIZE_MAX
-# define SIZE_MAX ((size_t) -1)
-#endif
+# ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+# endif
#endif /* PORT_TO_C89 */
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
# 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]]
# 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
#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))
# 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'. */
** 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
# 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
# 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
|| 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
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
*/
#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
#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
#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. */
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) };
# 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
*/
#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,
#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>
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
#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
# 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.
# 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
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)
}
#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);
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,
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 };
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;
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;
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.
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
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
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);
}
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));
}
{
return (nitems < *nitems_alloc
? ptr
- : erealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
+ : xrealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
}
/*
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
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 ? ": " : "",
}
}
+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);
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. */
warning(_("link %s targeting link %s"),
links[i].l_linkname, links[i].l_target);
}
+ check_for_signal();
}
}
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. */
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
} 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);
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"),
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) {
/*
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) {
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;
}
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
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;
}
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];
}
}
+/* 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
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
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);
/* 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)
{
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] == '/')
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;
}
/* 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++)
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;
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);
}
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) {
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));
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"));
default: unreachable();
}
}
+ check_for_signal();
}
close_file(fp, NULL, filename(fnum), NULL);
if (wantcont)
&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 == '.';
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));
}
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);
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"));
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)
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)
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;
}
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)) {
** 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;
struct timerange {
int defaulttype;
ptrdiff_t base, count;
- int leapbase, leapcount;
+ ptrdiff_t leapbase, leapcount;
bool leapexpiry;
};
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--;
}
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--;
}
/* 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;
if (timecnt > 1)
qsort(attypes, timecnt, sizeof *attypes, atcomp);
/*
- ** Optimize.
+ ** Optimize and skip unwanted transitions.
*/
{
ptrdiff_t fromi, toi;
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]
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.
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;
}
}
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"),
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;
: 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;
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. */
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) {
} 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++)
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')
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)
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);
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;
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);
/*
startttisut);
if (usestart) {
addtt(starttime, type);
- if (useuntil && nonTZlimtime < starttime) {
+ if (nonTZlimtime < starttime) {
nonTZlimtime = starttime;
nonTZlimtype = type;
}
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;
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]
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;
** 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);
}
}
/* 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++))
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))
/* 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
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
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
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;
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)
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
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
** 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
*cp++ = '/';
}
free(name);
+ }
}