From 92bd70fb85bce57ac47ba5d8af008736832c955a Mon Sep 17 00:00:00 2001 From: Joseph Myers Date: Fri, 16 Jun 2017 11:09:21 +0000 Subject: Update timezone code from tzcode 2017b. This patch updates files coming from tzcode to the versions in tzcode 2017b. A couple of changes to other glibc code are needed. time/tzset.c was using the SECSPERDAY macro from tzfile.h, which no longer defines that macro, so a local definition is added to tzset.c. Because timezone/private.h now defines the _ macro whenever HAVE_GETTEXT is true, even if it was previously defined, it is also necessary to avoid a conflict with the definition in include/libintl.h. Defining _ISOMAC is the obvious way to avoid such internal definitions being visible, together with defining TZ_DOMAIN so that zic and zdump continue to get the messages from the libc domain as desired. However, zic and zdump rely on PKGVERSION and REPORT_BUGS_TO from config.h, which is not included by default with _ISOMAC, so -include config.h needs adding to the options for these programs as well. Together those changes allow unmodified tzcode 2017b sources to work in glibc. Tested for x86_64. * timezone/private.h: Update from tzcode 2017b. * timezone/tzfile.h: Likewise. * timezone/tzselect.ksh: Likewise. * timezone/zdump.c: Likewise. * timezone/zic.c: Likewise. * timezone/Makefile (tz-cflags): Add -D_ISOMAC -DTZ_DOMAIN='"libc"' -include $(common-objpfx)config.h. * time/tzset.c (SECSPERDAY): New macro. --- timezone/Makefile | 3 +- timezone/private.h | 182 ++++++++++---- timezone/tzfile.h | 52 ---- timezone/tzselect.ksh | 17 +- timezone/zdump.c | 541 ++++++++++++++++++++++----------------- timezone/zic.c | 684 +++++++++++++++++++++++++++++++------------------- 6 files changed, 864 insertions(+), 615 deletions(-) (limited to 'timezone') diff --git a/timezone/Makefile b/timezone/Makefile index 35e6a95e6b..d6cc7ba357 100644 --- a/timezone/Makefile +++ b/timezone/Makefile @@ -59,7 +59,8 @@ tz-cflags = -DTZDIR='"$(zonedir)"' \ -DTZDEFAULT='"$(localtime-file)"' \ -DTZDEFRULES='"$(posixrules-file)"' \ -DTM_GMTOFF=tm_gmtoff -DTM_ZONE=tm_zone \ - -DHAVE_GETTEXT -DUSE_LTZ=0 -Wno-maybe-uninitialized + -DHAVE_GETTEXT -DUSE_LTZ=0 -D_ISOMAC -DTZ_DOMAIN='"libc"' \ + -include $(common-objpfx)config.h -Wno-maybe-uninitialized # The -Wno-unused-variable flag is used to prevent GCC 6 # from warning about time_t_min and time_t_max which are diff --git a/timezone/private.h b/timezone/private.h index 1c176e62bc..e2f23f5f40 100644 --- a/timezone/private.h +++ b/timezone/private.h @@ -15,6 +15,7 @@ ** Thank you! */ +/* This string was in the Factory zone through version 2016f. */ #define GRANDPARENTED "Local time zone must be set--see zic manual page" /* @@ -22,6 +23,10 @@ ** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'. */ +#ifndef HAVE_DECL_ASCTIME_R +#define HAVE_DECL_ASCTIME_R 1 +#endif + #ifndef HAVE_GETTEXT #define HAVE_GETTEXT 0 #endif /* !defined HAVE_GETTEXT */ @@ -34,6 +39,10 @@ #define HAVE_LINK 1 #endif /* !defined HAVE_LINK */ +#ifndef HAVE_POSIX_DECLS +#define HAVE_POSIX_DECLS 1 +#endif + #ifndef HAVE_STRDUP #define HAVE_STRDUP 1 #endif @@ -69,9 +78,9 @@ /* Enable tm_gmtoff and tm_zone on GNUish systems. */ #define _GNU_SOURCE 1 -/* Fix asctime_r on Solaris 10. */ +/* Fix asctime_r on Solaris 11. */ #define _POSIX_PTHREAD_SEMANTICS 1 -/* Enable strtoimax on Solaris 10. */ +/* Enable strtoimax on pre-C99 Solaris 11. */ #define __EXTENSIONS__ 1 /* @@ -95,23 +104,26 @@ #undef tzalloc #undef tzfree -#include "sys/types.h" /* for time_t */ -#include "stdio.h" -#include "string.h" -#include "limits.h" /* for CHAR_BIT et al. */ -#include "stdlib.h" +#include /* for time_t */ +#include +#include +#include /* for CHAR_BIT et al. */ +#include -#include "errno.h" +#include #ifndef ENAMETOOLONG # define ENAMETOOLONG EINVAL #endif +#ifndef ENOTSUP +# define ENOTSUP EINVAL +#endif #ifndef EOVERFLOW # define EOVERFLOW EINVAL #endif #if HAVE_GETTEXT -#include "libintl.h" +#include #endif /* HAVE_GETTEXT */ #if HAVE_SYS_WAIT_H @@ -126,7 +138,7 @@ #endif /* !defined WEXITSTATUS */ #if HAVE_UNISTD_H -#include "unistd.h" /* for F_OK, R_OK, and other POSIX goodness */ +#include /* for F_OK, R_OK, and other POSIX goodness */ #endif /* HAVE_UNISTD_H */ #ifndef HAVE_STRFTIME_L @@ -149,19 +161,19 @@ /* ** Define HAVE_STDINT_H's default value here, rather than at the -** start, since __GLIBC__'s value depends on previously-included -** files. -** (glibc 2.1 and later have stdint.h, even with pre-C99 compilers.) +** start, since __GLIBC__ and INTMAX_MAX's values depend on +** previously-included files. glibc 2.1 and Solaris 10 and later have +** stdint.h, even with pre-C99 compilers. */ #ifndef HAVE_STDINT_H #define HAVE_STDINT_H \ (199901 <= __STDC_VERSION__ \ || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ - || __CYGWIN__) + || __CYGWIN__ || INTMAX_MAX) #endif /* !defined HAVE_STDINT_H */ #if HAVE_STDINT_H -#include "stdint.h" +#include #endif /* !HAVE_STDINT_H */ #ifndef HAVE_INTTYPES_H @@ -197,14 +209,18 @@ typedef long int_fast64_t; # endif #endif -#ifndef SCNdFAST64 +#ifndef PRIdFAST64 # if INT_FAST64_MAX == LLONG_MAX -# define SCNdFAST64 "lld" +# define PRIdFAST64 "lld" # else -# define SCNdFAST64 "ld" +# define PRIdFAST64 "ld" # endif #endif +#ifndef SCNdFAST64 +# define SCNdFAST64 PRIdFAST64 +#endif + #ifndef INT_FAST32_MAX # if INT_MAX >> 31 == 0 typedef long int_fast32_t; @@ -304,6 +320,13 @@ typedef unsigned long uintmax_t; ** Workarounds for compilers/systems. */ +#ifndef EPOCH_LOCAL +# define EPOCH_LOCAL 0 +#endif +#ifndef EPOCH_OFFSET +# define EPOCH_OFFSET 0 +#endif + /* ** Compile with -Dtime_tz=T to build the tz package with a private ** time_t type equivalent to T rather than the system-supplied time_t. @@ -311,7 +334,7 @@ typedef unsigned long uintmax_t; ** (e.g., time_t wider than 'long', or unsigned time_t) even on ** typical platforms. */ -#ifdef time_tz +#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0 # ifdef LOCALTIME_IMPLEMENTATION static time_t sys_time(time_t *x) { return time(x); } # endif @@ -379,25 +402,21 @@ time_t time(time_t *); void tzset(void); #endif -/* -** Some time.h implementations don't declare asctime_r. -** Others might define it as a macro. -** Fix the former without affecting the latter. -** Similarly for timezone, daylight, and altzone. -*/ - -#ifndef asctime_r -extern char * asctime_r(struct tm const *restrict, char *restrict); +#if !HAVE_DECL_ASCTIME_R && !defined asctime_r +extern char *asctime_r(struct tm const *restrict, char *restrict); #endif -#ifdef USG_COMPAT -# ifndef timezone +#if !HAVE_POSIX_DECLS +# ifdef USG_COMPAT +# ifndef timezone extern long timezone; -# endif -# ifndef daylight +# endif +# ifndef daylight extern int daylight; +# endif # endif #endif + #if defined ALTZONE && !defined altzone extern long altzone; #endif @@ -481,14 +500,8 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE; # include #endif -#ifndef TYPE_BIT #define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) -#endif /* !defined TYPE_BIT */ - -#ifndef TYPE_SIGNED #define TYPE_SIGNED(type) (((type) -1) < 0) -#endif /* !defined TYPE_SIGNED */ - #define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) /* Max and min values of the integer type T, of which only the bottom @@ -500,11 +513,29 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE; #define MINVAL(t, b) \ ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0)) -/* The minimum and maximum finite time values. This assumes no padding. */ +/* The minimum and maximum finite time values. This implementation + assumes no padding if time_t is signed and either the compiler is + pre-C11 or time_t is not one of the standard signed integer types. */ +#if 201112 <= __STDC_VERSION__ +static time_t const time_t_min + = (TYPE_SIGNED(time_t) + ? _Generic((time_t) 0, + signed char: SCHAR_MIN, short: SHRT_MIN, + int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN, + default: MINVAL(time_t, TYPE_BIT(time_t))) + : 0); +static time_t const time_t_max + = (TYPE_SIGNED(time_t) + ? _Generic((time_t) 0, + signed char: SCHAR_MAX, short: SHRT_MAX, + int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, + default: MAXVAL(time_t, TYPE_BIT(time_t))) + : -1); +#else static time_t const time_t_min = MINVAL(time_t, TYPE_BIT(time_t)); static time_t const time_t_max = MAXVAL(time_t, TYPE_BIT(time_t)); +#endif -#ifndef INT_STRLEN_MAXIMUM /* ** 302 / 1000 is log10(2.0) rounded up. ** Subtract one for the sign bit if the type is signed; @@ -514,7 +545,6 @@ static time_t const time_t_max = MAXVAL(time_t, TYPE_BIT(time_t)); #define INT_STRLEN_MAXIMUM(type) \ ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ 1 + TYPE_SIGNED(type)) -#endif /* !defined INT_STRLEN_MAXIMUM */ /* ** INITIALIZE(x) @@ -536,13 +566,11 @@ static time_t const time_t_max = MAXVAL(time_t, TYPE_BIT(time_t)); ** The default is to use gettext if available, and use MSGID otherwise. */ -#ifndef _ #if HAVE_GETTEXT #define _(msgid) gettext(msgid) #else /* !HAVE_GETTEXT */ #define _(msgid) msgid #endif /* !HAVE_GETTEXT */ -#endif /* !defined _ */ #if !defined TZ_DOMAIN && defined HAVE_GETTEXT # define TZ_DOMAIN "tz" @@ -555,24 +583,70 @@ char *asctime_r(struct tm const *, char *); char *ctime_r(time_t const *, char *); #endif /* HAVE_INCOMPATIBLE_CTIME_R */ -#ifndef YEARSPERREPEAT +/* Handy macros that are independent of tzfile implementation. */ + #define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ -#endif /* !defined YEARSPERREPEAT */ + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_JANUARY 0 +#define TM_FEBRUARY 1 +#define TM_MARCH 2 +#define TM_APRIL 3 +#define TM_MAY 4 +#define TM_JUNE 5 +#define TM_JULY 6 +#define TM_AUGUST 7 +#define TM_SEPTEMBER 8 +#define TM_OCTOBER 9 +#define TM_NOVEMBER 10 +#define TM_DECEMBER 11 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) /* -** The Gregorian year averages 365.2425 days, which is 31556952 seconds. +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** and so +** isleap(a + b) == isleap((a + b) % 400) +** or +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not C99). +** We use this to avoid addition overflow problems. */ -#ifndef AVGSECSPERYEAR -#define AVGSECSPERYEAR 31556952L -#endif /* !defined AVGSECSPERYEAR */ +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + -#ifndef SECSPERREPEAT -#define SECSPERREPEAT ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR) -#endif /* !defined SECSPERREPEAT */ +/* +** The Gregorian year averages 365.2425 days, which is 31556952 seconds. +*/ -#ifndef SECSPERREPEAT_BITS +#define AVGSECSPERYEAR 31556952L +#define SECSPERREPEAT \ + ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR) #define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ -#endif /* !defined SECSPERREPEAT_BITS */ #endif /* !defined PRIVATE_H */ diff --git a/timezone/tzfile.h b/timezone/tzfile.h index ebecd68322..0e51dce1d7 100644 --- a/timezone/tzfile.h +++ b/timezone/tzfile.h @@ -114,56 +114,4 @@ struct tzhead { #define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ #endif /* !defined TZ_MAX_LEAPS */ -#define SECSPERMIN 60 -#define MINSPERHOUR 60 -#define HOURSPERDAY 24 -#define DAYSPERWEEK 7 -#define DAYSPERNYEAR 365 -#define DAYSPERLYEAR 366 -#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) -#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) -#define MONSPERYEAR 12 - -#define TM_SUNDAY 0 -#define TM_MONDAY 1 -#define TM_TUESDAY 2 -#define TM_WEDNESDAY 3 -#define TM_THURSDAY 4 -#define TM_FRIDAY 5 -#define TM_SATURDAY 6 - -#define TM_JANUARY 0 -#define TM_FEBRUARY 1 -#define TM_MARCH 2 -#define TM_APRIL 3 -#define TM_MAY 4 -#define TM_JUNE 5 -#define TM_JULY 6 -#define TM_AUGUST 7 -#define TM_SEPTEMBER 8 -#define TM_OCTOBER 9 -#define TM_NOVEMBER 10 -#define TM_DECEMBER 11 - -#define TM_YEAR_BASE 1900 - -#define EPOCH_YEAR 1970 -#define EPOCH_WDAY TM_THURSDAY - -#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) - -/* -** Since everything in isleap is modulo 400 (or a factor of 400), we know that -** isleap(y) == isleap(y % 400) -** and so -** isleap(a + b) == isleap((a + b) % 400) -** or -** isleap(a + b) == isleap(a % 400 + b % 400) -** This is true even if % means modulo rather than Fortran remainder -** (which is allowed by C89 but not C99). -** We use this to avoid addition overflow problems. -*/ - -#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) - #endif /* !defined TZFILE_H */ diff --git a/timezone/tzselect.ksh b/timezone/tzselect.ksh index 2c3b2f4438..d2c3a6d1dd 100755 --- a/timezone/tzselect.ksh +++ b/timezone/tzselect.ksh @@ -7,7 +7,7 @@ REPORT_BUGS_TO=tz@iana.org # Ask the user about the time zone, and output the resulting TZ value to stdout. # Interact with the user via stderr and stdin. -# Contributed by Paul Eggert. +# Contributed by Paul Eggert. This file is in the public domain. # Porting notes: # @@ -346,11 +346,14 @@ while 'that is 10 hours ahead (east) of UTC.' read TZ $AWK -v TZ="$TZ" 'BEGIN { - tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+" - time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?" + tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})" + time = "(2[0-4]|[0-1]?[0-9])" \ + "(:[0-5][0-9](:[0-5][0-9])?)?" offset = "[-+]?" time - date = "(J?[0-9]+|M[0-9]+\\.[0-9]+\\.[0-9]+)" - datetime = "," date "(/" time ")?" + mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]" + jdate = "((J[1-9]|[0-9]|J?[1-9][0-9]" \ + "|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])" + datetime = ",(" mdate "|" jdate ")(/" time ")?" tzpattern = "^(:.*|" tzname offset "(" tzname \ "(" offset ")?(" datetime datetime ")?)?)$" if (TZ ~ tzpattern) exit 1 @@ -509,7 +512,7 @@ while case $TZsec in $UTsec) extra_info=" -Local time is now: $TZdate. +Selected time is now: $TZdate. Universal Time is now: $UTdate." break esac @@ -545,7 +548,7 @@ case $SHELL in *) file=.profile line="TZ='$TZ'; export TZ" esac -say >&2 " +test -t 1 && say >&2 " You can make this change permanent for yourself by appending the line $line to the file '$file' in your home directory; then log out and log in again. diff --git a/timezone/zdump.c b/timezone/zdump.c index 063a2635ec..bf75800101 100644 --- a/timezone/zdump.c +++ b/timezone/zdump.c @@ -19,89 +19,7 @@ # define USE_LTZ 1 #endif -#if USE_LTZ -# include "private.h" -#endif - -/* Enable tm_gmtoff and tm_zone on GNUish systems. */ -#define _GNU_SOURCE 1 -/* Enable strtoimax on Solaris 10. */ -#define __EXTENSIONS__ 1 - -#include "stdio.h" /* for stdout, stderr, perror */ -#include "string.h" /* for strcpy */ -#include "sys/types.h" /* for time_t */ -#include "time.h" /* for struct tm */ -#include "stdlib.h" /* for exit, malloc, atoi */ -#include "limits.h" /* for CHAR_BIT, LLONG_MAX */ -#include - -/* -** Substitutes for pre-C99 compilers. -** Much of this section of code is stolen from private.h. -*/ - -#ifndef HAVE_STDINT_H -# define HAVE_STDINT_H \ - (199901 <= __STDC_VERSION__ \ - || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ - || __CYGWIN__) -#endif -#if HAVE_STDINT_H -# include "stdint.h" -#endif -#ifndef HAVE_INTTYPES_H -# define HAVE_INTTYPES_H HAVE_STDINT_H -#endif -#if HAVE_INTTYPES_H -# include -#endif - -#ifndef INT_FAST32_MAX -# if INT_MAX >> 31 == 0 -typedef long int_fast32_t; -# else -typedef int int_fast32_t; -# endif -#endif - -/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ -#if !defined LLONG_MAX && defined __LONG_LONG_MAX__ -# define LLONG_MAX __LONG_LONG_MAX__ -#endif - -#ifndef INTMAX_MAX -# ifdef LLONG_MAX -typedef long long intmax_t; -# define strtoimax strtoll -# define INTMAX_MAX LLONG_MAX -# else -typedef long intmax_t; -# define strtoimax strtol -# define INTMAX_MAX LONG_MAX -# endif -#endif - -#ifndef PRIdMAX -# if INTMAX_MAX == LLONG_MAX -# define PRIdMAX "lld" -# else -# define PRIdMAX "ld" -# endif -#endif - -/* Infer TM_ZONE on systems where this information is known, but suppress - guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */ -#if (defined __GLIBC__ \ - || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ - || (defined __APPLE__ && defined __MACH__)) -# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF -# define TM_GMTOFF tm_gmtoff -# endif -# if !defined TM_ZONE && !defined NO_TM_ZONE -# define TM_ZONE tm_zone -# endif -#endif +#include "private.h" #ifndef HAVE_LOCALTIME_R # define HAVE_LOCALTIME_R 1 @@ -131,62 +49,6 @@ typedef long intmax_t; #define MAX_STRING_LENGTH 1024 #endif /* !defined MAX_STRING_LENGTH */ -#if __STDC_VERSION__ < 199901 -# define true 1 -# define false 0 -# define bool int -#else -# include -#endif - -#ifndef EXIT_SUCCESS -#define EXIT_SUCCESS 0 -#endif /* !defined EXIT_SUCCESS */ - -#ifndef EXIT_FAILURE -#define EXIT_FAILURE 1 -#endif /* !defined EXIT_FAILURE */ - -#ifndef SECSPERMIN -#define SECSPERMIN 60 -#endif /* !defined SECSPERMIN */ - -#ifndef MINSPERHOUR -#define MINSPERHOUR 60 -#endif /* !defined MINSPERHOUR */ - -#ifndef SECSPERHOUR -#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) -#endif /* !defined SECSPERHOUR */ - -#ifndef HOURSPERDAY -#define HOURSPERDAY 24 -#endif /* !defined HOURSPERDAY */ - -#ifndef EPOCH_YEAR -#define EPOCH_YEAR 1970 -#endif /* !defined EPOCH_YEAR */ - -#ifndef TM_YEAR_BASE -#define TM_YEAR_BASE 1900 -#endif /* !defined TM_YEAR_BASE */ - -#ifndef DAYSPERNYEAR -#define DAYSPERNYEAR 365 -#endif /* !defined DAYSPERNYEAR */ - -#ifndef isleap -#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) -#endif /* !defined isleap */ - -#ifndef isleap_sum -/* -** See tzfile.h for details on isleap_sum. -*/ -#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) -#endif /* !defined isleap_sum */ - -#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR) #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY) #define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \ @@ -201,49 +63,24 @@ typedef long intmax_t; */ enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 }; -#ifndef HAVE_GETTEXT -#define HAVE_GETTEXT 0 -#endif #if HAVE_GETTEXT -#include "locale.h" /* for setlocale */ -#include "libintl.h" +#include /* for setlocale */ #endif /* HAVE_GETTEXT */ -#if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__) -# define ATTRIBUTE_PURE __attribute__ ((__pure__)) -#else -# define ATTRIBUTE_PURE /* empty */ -#endif - -/* -** For the benefit of GNU folk... -** '_(MSGID)' uses the current locale's message library string for MSGID. -** The default is to use gettext if available, and use MSGID otherwise. -*/ - -#ifndef _ -#if HAVE_GETTEXT -#define _(msgid) gettext(msgid) -#else /* !HAVE_GETTEXT */ -#define _(msgid) msgid -#endif /* !HAVE_GETTEXT */ -#endif /* !defined _ */ - -#if !defined TZ_DOMAIN && defined HAVE_GETTEXT -# define TZ_DOMAIN "tz" -#endif - #if ! HAVE_LOCALTIME_RZ # undef timezone_t # define timezone_t char ** #endif extern char ** environ; + +#if !HAVE_POSIX_DECLS extern int getopt(int argc, char * const argv[], const char * options); extern char * optarg; extern int optind; -extern char * tzname[2]; +extern char * tzname[]; +#endif /* The minimum and maximum finite time values. */ enum { atime_shift = CHAR_BIT * sizeof (time_t) - 2 }; @@ -266,6 +103,8 @@ static intmax_t delta(struct tm *, struct tm *) ATTRIBUTE_PURE; static void dumptime(struct tm const *); static time_t hunt(timezone_t, char *, time_t, time_t); static void show(timezone_t, char *, time_t, bool); +static void showtrans(char const *, struct tm const *, time_t, char const *, + char const *); static const char *tformat(void); static time_t yeartot(intmax_t) ATTRIBUTE_PURE; @@ -303,6 +142,19 @@ sumsize(size_t a, size_t b) return sum; } +/* Return a pointer to a newly allocated buffer of size SIZE, exiting + on failure. SIZE should be nonzero. */ +static void * +xmalloc(size_t size) +{ + void *p = malloc(size); + if (!p) { + perror(progname); + exit(EXIT_FAILURE); + } + return p; +} + #if ! HAVE_TZSET # undef tzset # define tzset zdump_tzset @@ -390,21 +242,13 @@ tzalloc(char const *val) while (*e++) continue; - env = malloc(sumsize(sizeof *environ, - (e - environ) * sizeof *environ)); - if (! env) { - perror(progname); - exit(EXIT_FAILURE); - } + env = xmalloc(sumsize(sizeof *environ, + (e - environ) * sizeof *environ)); to = 1; for (e = environ; (env[to] = *e); e++) to += strncmp(*e, "TZ=", 3) != 0; } - env0 = malloc(sumsize(sizeof "TZ=", strlen(val))); - if (! env0) { - perror(progname); - exit(EXIT_FAILURE); - } + env0 = xmalloc(sumsize(sizeof "TZ=", strlen(val))); env[0] = strcat(strcpy(env0, "TZ="), val); environ = fakeenv = env; tzset(); @@ -525,11 +369,7 @@ saveabbr(char **buf, size_t *bufalloc, struct tm const *tmp) to avoid O(N**2) behavior on repeated calls. */ *bufalloc = sumsize(*bufalloc, ablen + 1); - *buf = malloc(*bufalloc); - if (! *buf) { - perror(progname); - exit(EXIT_FAILURE); - } + *buf = xmalloc(*bufalloc); } return strcpy(*buf, ab); } @@ -550,10 +390,18 @@ static void usage(FILE * const stream, const int status) { fprintf(stream, -_("%s: usage: %s [--version] [--help] [-{vV}] [-{ct} [lo,]hi] zonename ...\n" +_("%s: usage: %s OPTIONS ZONENAME ...\n" + "Options include:\n" + " -c [L,]U Start at year L (default -500), end before year U (default 2500)\n" + " -t [L,]U Start at time L, end before time U (in seconds since 1970)\n" + " -i List transitions briefly (format is experimental)\n" \ + " -v List transitions verbosely\n" + " -V List transitions a bit less verbosely\n" + " --help Output this help\n" + " --version Output version info\n" "\n" "Report bugs to %s.\n"), - progname, progname, REPORT_BUGS_TO); + progname, progname, REPORT_BUGS_TO); if (status == EXIT_SUCCESS) close_file(stream); exit(status); @@ -565,7 +413,6 @@ main(int argc, char *argv[]) /* These are static so that they're initially zero. */ static char * abbrev; static size_t abbrevsize; - static struct tm newtm; register int i; register bool vflag; @@ -575,11 +422,7 @@ main(int argc, char *argv[]) register time_t cutlotime; register time_t cuthitime; time_t now; - time_t t; - time_t newt; - struct tm tm; - register struct tm * tmp; - register struct tm * newtmp; + bool iflag = false; cutlotime = absolute_min_time; cuthitime = absolute_max_time; @@ -601,9 +444,10 @@ main(int argc, char *argv[]) vflag = Vflag = false; cutarg = cuttimes = NULL; for (;;) - switch (getopt(argc, argv, "c:t:vV")) { + switch (getopt(argc, argv, "c:it:vV")) { case 'c': cutarg = optarg; break; case 't': cuttimes = optarg; break; + case 'i': iflag = true; break; case 'v': vflag = true; break; case 'V': Vflag = true; break; case -1: @@ -615,7 +459,7 @@ main(int argc, char *argv[]) } arg_processing_done:; - if (vflag | Vflag) { + if (iflag | vflag | Vflag) { intmax_t lo; intmax_t hi; char *loend, *hiend; @@ -672,7 +516,9 @@ main(int argc, char *argv[]) } } gmtzinit(); - now = time(NULL); + INITIALIZE (now); + if (! (iflag | vflag | Vflag)) + now = time(NULL); longest = 0; for (i = optind; i < argc; i++) { size_t arglen = strlen(argv[i]); @@ -683,48 +529,66 @@ main(int argc, char *argv[]) for (i = optind; i < argc; ++i) { timezone_t tz = tzalloc(argv[i]); char const *ab; + time_t t; + struct tm tm, newtm; + bool tm_ok; if (!tz) { perror(argv[i]); return EXIT_FAILURE; } - if (! (vflag | Vflag)) { + if (! (iflag | vflag | Vflag)) { show(tz, argv[i], now, false); tzfree(tz); continue; } warned = false; t = absolute_min_time; - if (!Vflag) { + if (! (iflag | Vflag)) { show(tz, argv[i], t, true); t += SECSPERDAY; show(tz, argv[i], t, true); } if (t < cutlotime) t = cutlotime; - tmp = my_localtime_rz(tz, &t, &tm); - if (tmp) + tm_ok = my_localtime_rz(tz, &t, &tm) != NULL; + if (tm_ok) { ab = saveabbr(&abbrev, &abbrevsize, &tm); + if (iflag) { + showtrans("\nTZ=%f", &tm, t, ab, argv[i]); + showtrans("-\t-\t%Q", &tm, t, ab, argv[i]); + } + } while (t < cuthitime) { - newt = ((t < absolute_max_time - SECSPERDAY / 2 - && t + SECSPERDAY / 2 < cuthitime) - ? t + SECSPERDAY / 2 - : cuthitime); - newtmp = localtime_rz(tz, &newt, &newtm); - if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) : - (delta(&newtm, &tm) != (newt - t) || - newtm.tm_isdst != tm.tm_isdst || - strcmp(abbr(&newtm), ab) != 0)) { - newt = hunt(tz, argv[i], t, newt); - newtmp = localtime_rz(tz, &newt, &newtm); - if (newtmp) - ab = saveabbr(&abbrev, &abbrevsize, - &newtm); - } - t = newt; - tm = newtm; - tmp = newtmp; + time_t newt = ((t < absolute_max_time - SECSPERDAY / 2 + && t + SECSPERDAY / 2 < cuthitime) + ? t + SECSPERDAY / 2 + : cuthitime); + struct tm *newtmp = localtime_rz(tz, &newt, &newtm); + bool newtm_ok = newtmp != NULL; + if (! (tm_ok & newtm_ok + ? (delta(&newtm, &tm) == newt - t + && newtm.tm_isdst == tm.tm_isdst + && strcmp(abbr(&newtm), ab) == 0) + : tm_ok == newtm_ok)) { + newt = hunt(tz, argv[i], t, newt); + newtmp = localtime_rz(tz, &newt, &newtm); + newtm_ok = newtmp != NULL; + if (iflag) + showtrans("%Y-%m-%d\t%L\t%Q", newtmp, newt, + newtm_ok ? abbr(&newtm) : NULL, argv[i]); + else { + show(tz, argv[i], newt - 1, true); + show(tz, argv[i], newt, true); + } + } + t = newt; + tm_ok = newtm_ok; + if (newtm_ok) { + ab = saveabbr(&abbrev, &abbrevsize, &newtm); + tm = newtm; + } } - if (!Vflag) { + if (! (iflag | Vflag)) { t = absolute_max_time; t -= SECSPERDAY; show(tz, argv[i], t, true); @@ -790,12 +654,11 @@ hunt(timezone_t tz, char *name, time_t lot, time_t hit) char const * ab; time_t t; struct tm lotm; - register struct tm * lotmp; struct tm tm; - register struct tm * tmp; + bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL; + bool tm_ok; - lotmp = my_localtime_rz(tz, &lot, &lotm); - if (lotmp) + if (lotm_ok) ab = saveabbr(&loab, &loabsize, &lotm); for ( ; ; ) { time_t diff = hit - lot; @@ -807,18 +670,17 @@ hunt(timezone_t tz, char *name, time_t lot, time_t hit) ++t; else if (t >= hit) --t; - tmp = my_localtime_rz(tz, &t, &tm); - if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) : - (delta(&tm, &lotm) == (t - lot) && - tm.tm_isdst == lotm.tm_isdst && - strcmp(abbr(&tm), ab) == 0)) { - lot = t; - lotm = tm; - lotmp = tmp; + tm_ok = my_localtime_rz(tz, &t, &tm) != NULL; + if (lotm_ok & tm_ok + ? (delta(&tm, &lotm) == t - lot + && tm.tm_isdst == lotm.tm_isdst + && strcmp(abbr(&tm), ab) == 0) + : lotm_ok == tm_ok) { + lot = t; + if (tm_ok) + lotm = tm; } else hit = t; } - show(tz, name, lot, true); - show(tz, name, hit, true); return hit; } @@ -862,13 +724,20 @@ adjusted_yday(struct tm const *a, struct tm const *b) /* If A is the broken-down local time and B the broken-down UTC for the same instant, return A's UTC offset in seconds, where positive - offsets are east of Greenwich. On failure, return LONG_MIN. */ + offsets are east of Greenwich. On failure, return LONG_MIN. + + If T is nonnull, *T is the timestamp that corresponds to A; call + my_gmtime_r and use its result instead of B. Otherwise, B is the + possibly nonnull result of an earlier call to my_gmtime_r. */ static long -gmtoff(struct tm const *a, struct tm const *b) +gmtoff(struct tm const *a, time_t *t, struct tm const *b) { #ifdef TM_GMTOFF return a->TM_GMTOFF; #else + struct tm tm; + if (t) + b = my_gmtime_r(t, &tm); if (! b) return LONG_MIN; else { @@ -907,7 +776,7 @@ show(timezone_t tz, char *zone, time_t t, bool v) if (*abbr(tmp) != '\0') printf(" %s", abbr(tmp)); if (v) { - long off = gmtoff(tmp, gmtmp); + long off = gmtoff(tmp, NULL, gmtmp); printf(" isdst=%d", tmp->tm_isdst); if (off != LONG_MIN) printf(" gmtoff=%ld", off); @@ -918,6 +787,206 @@ show(timezone_t tz, char *zone, time_t t, bool v) abbrok(abbr(tmp), zone); } +/* Store into BUF, of size SIZE, a formatted local time taken from *TM. + Use ISO 8601 format +HH:MM:SS. Omit :SS if SS is zero, and omit + :MM too if MM is also zero. + + Return the length of the resulting string. If the string does not + fit, return the length that the string would have been if it had + fit; do not overrun the output buffer. */ +static int +format_local_time(char *buf, size_t size, struct tm const *tm) +{ + int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour; + return (ss + ? snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss) + : mm + ? snprintf(buf, size, "%02d:%02d", hh, mm) + : snprintf(buf, size, "%02d", hh)); +} + +/* Store into BUF, of size SIZE, a formatted UTC offset for the + localtime *TM corresponding to time T. Use ISO 8601 format + +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the + format -00 for unknown UTC offsets. If the hour needs more than + two digits to represent, extend the length of HH as needed. + Otherwise, omit SS if SS is zero, and omit MM too if MM is also + zero. + + Return the length of the resulting string, or -1 if the result is + not representable as a string. If the string does not fit, return + the length that the string would have been if it had fit; do not + overrun the output buffer. */ +static int +format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t) +{ + long off = gmtoff(tm, &t, NULL); + char sign = ((off < 0 + || (off == 0 + && (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0))) + ? '-' : '+'); + long hh; + int mm, ss; + if (off < 0) + { + if (off == LONG_MIN) + return -1; + off = -off; + } + ss = off % 60; + mm = off / 60 % 60; + hh = off / 60 / 60; + return (ss || 100 <= hh + ? snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss) + : mm + ? 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. + If the representation's length is less than SIZE, return the + length; the representation is not null terminated. Otherwise + return SIZE, to indicate that BUF is too small. */ +static size_t +format_quoted_string(char *buf, size_t size, char const *p) +{ + char *b = buf; + size_t s = size; + if (!s) + return size; + *b++ = '"', s--; + for (;;) { + char c = *p++; + if (s <= 1) + return size; + switch (c) { + default: *b++ = c, s--; continue; + case '\0': *b++ = '"', s--; return size - s; + case '"': case '\\': break; + case ' ': c = 's'; break; + case '\f': c = 'f'; break; + case '\n': c = 'n'; break; + case '\r': c = 'r'; break; + case '\t': c = 't'; break; + case '\v': c = 'v'; break; + } + *b++ = '\\', *b++ = c, s -= 2; + } +} + +/* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT. + TM is the broken-down time, T the seconds count, AB the time zone + abbreviation, and ZONE_NAME the zone name. Return true if + successful, false if the output would require more than SIZE bytes. + TIME_FMT uses the same format that strftime uses, with these + additions: + + %f zone name + %L local time as per format_local_time + %Q like "U\t%Z\tD" where U is the UTC offset as for format_utc_offset + and D is the isdst flag; except omit D if it is zero, omit %Z if + it equals U, quote and escape %Z if it contains nonalphabetics, + and omit any trailing tabs. */ + +static bool +istrftime(char *buf, size_t size, char const *time_fmt, + struct tm const *tm, time_t t, char const *ab, char const *zone_name) +{ + char *b = buf; + size_t s = size; + char const *f = time_fmt, *p; + + for (p = f; ; p++) + if (*p == '%' && p[1] == '%') + p++; + else if (!*p + || (*p == '%' + && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) { + size_t formatted_len; + size_t f_prefix_len = p - f; + size_t f_prefix_copy_size = p - f + 2; + 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"); + formatted_len = strftime(b, s, f_prefix_copy, tm); + if (oversized) + free(f_prefix_copy); + if (formatted_len == 0) + return false; + formatted_len--; + b += formatted_len, s -= formatted_len; + if (!*p++) + break; + switch (*p) { + case 'f': + formatted_len = format_quoted_string(b, s, zone_name); + break; + case 'L': + formatted_len = format_local_time(b, s, tm); + break; + case 'Q': + { + bool show_abbr; + int offlen = format_utc_offset(b, s, tm, t); + if (! (0 <= offlen && offlen < s)) + return false; + show_abbr = strcmp(b, ab) != 0; + b += offlen, s -= offlen; + if (show_abbr) { + char const *abp; + size_t len; + if (s <= 1) + return false; + *b++ = '\t', s--; + for (abp = ab; is_alpha(*abp); abp++) + continue; + len = (!*abp && *ab + ? snprintf(b, s, "%s", ab) + : format_quoted_string(b, s, ab)); + if (s <= len) + return false; + b += len, s -= len; + } + formatted_len = (tm->tm_isdst + ? snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst) + : 0); + } + break; + } + if (s <= formatted_len) + return false; + b += formatted_len, s -= formatted_len; + f = p + 1; + } + *b = '\0'; + return true; +} + +/* Show a time transition. */ +static void +showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab, + char const *zone_name) +{ + if (!tm) { + printf(tformat(), t); + putchar('\n'); + } else { + char stackbuf[1000]; + size_t size = sizeof stackbuf; + char *buf = stackbuf; + char *bufalloc = NULL; + while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) { + size = sumsize(size, size); + free(bufalloc); + buf = bufalloc = xmalloc(size); + } + puts(buf); + free(bufalloc); + } +} + static char const * abbr(struct tm const *tmp) { diff --git a/timezone/zic.c b/timezone/zic.c index 78ab870941..068fb43318 100644 --- a/timezone/zic.c +++ b/timezone/zic.c @@ -5,10 +5,12 @@ #include "version.h" #include "private.h" -#include "locale.h" #include "tzfile.h" +#include +#include #include +#include #define ZIC_VERSION_PRE_2013 '2' #define ZIC_VERSION '3' @@ -16,12 +18,20 @@ typedef int_fast64_t zic_t; #define ZIC_MIN INT_FAST64_MIN #define ZIC_MAX INT_FAST64_MAX +#define PRIdZIC PRIdFAST64 #define SCNdZIC SCNdFAST64 #ifndef ZIC_MAX_ABBR_LEN_WO_WARN #define ZIC_MAX_ABBR_LEN_WO_WARN 6 #endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */ +#ifdef HAVE_DIRECT_H +# include +# include +# undef mkdir +# define mkdir(name, mode) _mkdir(name) +#endif + #if HAVE_SYS_STAT_H #include #endif @@ -31,9 +41,18 @@ typedef int_fast64_t zic_t; #define MKDIR_UMASK 0755 #endif +/* The maximum ptrdiff_t value, for pre-C99 platforms. */ +#ifndef PTRDIFF_MAX +static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)); +#endif + +/* The type and printf format for line numbers. */ +typedef intmax_t lineno; +#define PRIdLINENO PRIdMAX + struct rule { const char * r_filename; - int r_linenum; + lineno r_linenum; const char * r_name; zic_t r_loyear; /* for example, 1986 */ @@ -56,7 +75,7 @@ struct rule { zic_t r_stdoff; /* offset from standard time */ const char * r_abbrvar; /* variable part of abbreviation */ - int r_todo; /* a rule to do (used in outzone) */ + bool r_todo; /* a rule to do (used in outzone) */ zic_t r_temp; /* used in outzone */ }; @@ -70,7 +89,7 @@ struct rule { struct zone { const char * z_filename; - int z_linenum; + lineno z_linenum; const char * z_name; zic_t z_gmtoff; @@ -81,23 +100,31 @@ struct zone { zic_t z_stdoff; struct rule * z_rules; - int z_nrules; + ptrdiff_t z_nrules; struct rule z_untilrule; zic_t z_untiltime; }; +#if !HAVE_POSIX_DECLS extern int getopt(int argc, char * const argv[], const char * options); extern int link(const char * fromname, const char * toname); extern char * optarg; extern int optind; +#endif #if ! HAVE_LINK -# define link(from, to) (-1) +# define link(from, to) (errno = ENOTSUP, -1) #endif #if ! HAVE_SYMLINK -# define symlink(from, to) (-1) +# define readlink(file, buf, size) (errno = ENOTSUP, -1) +# define symlink(from, to) (errno = ENOTSUP, -1) +# define S_ISLNK(m) 0 +#endif +#ifndef AT_SYMLINK_FOLLOW +# define linkat(fromdir, from, todir, to, flag) \ + (itssymlink(from) ? (errno = ENOTSUP, -1) : link(from, to)) #endif static void addtt(zic_t starttime, int type); @@ -105,7 +132,7 @@ static int addtype(zic_t, char const *, bool, bool, bool); static void leapadd(zic_t, bool, int, int); static void adjleap(void); static void associate(void); -static void dolink(const char * fromfield, const char * tofield); +static void dolink(const char *, const char *, bool); static char ** getfields(char * buf); static zic_t gethms(const char * string, const char * errstring, bool); @@ -116,24 +143,32 @@ 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 itsdir(const char * name); +static bool itsdir(char const *); +static bool itssymlink(char const *); static bool is_alpha(char a); static char lowerit(char); -static bool mkdirs(char *); +static void mkdirs(char const *, bool); static void newabbr(const char * abbr); static zic_t oadd(zic_t t1, zic_t t2); -static void outzone(const struct zone * zp, int ntzones); +static void outzone(const struct zone * zp, ptrdiff_t ntzones); static zic_t rpytime(const struct rule * rp, zic_t wantedy); static void rulesub(struct rule * rp, const char * loyearp, const char * hiyearp, const char * typep, const char * monthp, const char * dayp, const char * timep); static zic_t tadd(zic_t t1, zic_t t2); -static bool yearistype(int year, const char * type); +static bool yearistype(zic_t year, const char * type); /* Bound on length of what %z can expand to. */ enum { PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1 }; +/* If true, work around a bug in Qt 5.6.1 and earlier, which mishandles + tz binary files whose POSIX-TZ-style strings contain '<'; see + QTBUG-53071 . This + workaround will no longer be needed when Qt 5.6.1 and earlier are + obsolete, say in the year 2021. */ +enum { WORK_AROUND_QTBUG_53071 = true }; + static int charcnt; static bool errors; static bool warnings; @@ -142,17 +177,17 @@ static int leapcnt; static bool leapseen; static zic_t leapminyear; static zic_t leapmaxyear; -static int linenum; +static lineno linenum; static int max_abbrvar_len = PERCENT_Z_LEN_BOUND; static int max_format_len; static zic_t max_year; static zic_t min_year; static bool noise; static const char * rfilename; -static int rlinenum; +static lineno rlinenum; static const char * progname; -static int timecnt; -static int timecnt_alloc; +static ptrdiff_t timecnt; +static ptrdiff_t timecnt_alloc; static int typecnt; /* @@ -237,23 +272,23 @@ static int typecnt; #define YR_ONLY 2 static struct rule * rules; -static int nrules; /* number of rules */ -static int nrules_alloc; +static ptrdiff_t nrules; /* number of rules */ +static ptrdiff_t nrules_alloc; static struct zone * zones; -static int nzones; /* number of zones */ -static int nzones_alloc; +static ptrdiff_t nzones; /* number of zones */ +static ptrdiff_t nzones_alloc; struct link { const char * l_filename; - int l_linenum; + lineno l_linenum; const char * l_from; const char * l_to; }; static struct link * links; -static int nlinks; -static int nlinks_alloc; +static ptrdiff_t nlinks; +static ptrdiff_t nlinks_alloc; struct lookup { const char * l_word; @@ -339,6 +374,7 @@ static const int len_years[2] = { static struct attype { zic_t at; + bool dontmerge; unsigned char type; } * attypes; static zic_t gmtoffs[TZ_MAX_TYPES]; @@ -406,15 +442,16 @@ ecpyalloc (char const *str) } static void * -growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc) +growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc) { if (nitems < *nitems_alloc) return ptr; else { - int amax = INT_MAX < SIZE_MAX ? INT_MAX : SIZE_MAX; + ptrdiff_t nitems_max = PTRDIFF_MAX - WORK_AROUND_QTBUG_53071; + ptrdiff_t amax = nitems_max < SIZE_MAX ? nitems_max : SIZE_MAX; if ((amax - 1) / 3 * 2 < *nitems_alloc) - memory_exhausted(_("int overflow")); - *nitems_alloc = *nitems_alloc + (*nitems_alloc >> 1) + 1; + memory_exhausted(_("integer overflow")); + *nitems_alloc += (*nitems_alloc >> 1) + 1; return erealloc(ptr, size_product(*nitems_alloc, itemsize)); } } @@ -424,8 +461,7 @@ growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc) */ static void -eats(const char *const name, const int num, const char *const rname, - const int rnum) +eats(char const *name, lineno num, char const *rname, lineno rnum) { filename = name; linenum = num; @@ -434,7 +470,7 @@ eats(const char *const name, const int num, const char *const rname, } static void -eat(const char *const name, const int num) +eat(char const *name, lineno num) { eats(name, num, NULL, -1); } @@ -448,10 +484,10 @@ verror(const char *const string, va_list args) ** on BSD systems. */ if (filename) - fprintf(stderr, _("\"%s\", line %d: "), filename, linenum); + fprintf(stderr, _("\"%s\", line %"PRIdLINENO": "), filename, linenum); vfprintf(stderr, string, args); if (rfilename != NULL) - fprintf(stderr, _(" (rule from \"%s\", line %d)"), + fprintf(stderr, _(" (rule from \"%s\", line %"PRIdLINENO")"), rfilename, rlinenum); fprintf(stderr, "\n"); } @@ -478,15 +514,15 @@ warning(const char *const string, ...) } static void -close_file(FILE *stream, char const *name) +close_file(FILE *stream, char const *dir, char const *name) { char const *e = (ferror(stream) ? _("I/O error") : fclose(stream) != 0 ? strerror(errno) : NULL); if (e) { - fprintf(stderr, "%s: ", progname); - if (name) - fprintf(stderr, "%s: ", name); - fprintf(stderr, "%s\n", e); + fprintf(stderr, "%s: %s%s%s%s%s\n", progname, + dir ? dir : "", dir ? "/" : "", + name ? name : "", name ? ": " : "", + e); exit(EXIT_FAILURE); } } @@ -501,10 +537,30 @@ usage(FILE *stream, int status) "Report bugs to %s.\n"), progname, progname, REPORT_BUGS_TO); if (status == EXIT_SUCCESS) - close_file(stream, NULL); + close_file(stream, NULL, NULL); exit(status); } +/* 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. */ +static void +change_directory (char const *dir) +{ + if (chdir(dir) != 0) { + int chdir_errno = errno; + if (chdir_errno == ENOENT) { + mkdirs(dir, false); + chdir_errno = chdir(dir) == 0 ? 0 : errno; + } + if (chdir_errno != 0) { + fprintf(stderr, _("%s: Can't chdir to %s: %s\n"), + progname, dir, strerror(chdir_errno)); + exit(EXIT_FAILURE); + } + } +} + static const char * psxrules; static const char * lcltime; static const char * directory; @@ -514,9 +570,8 @@ static const char * yitcommand; int main(int argc, char **argv) { - register int i; - register int j; - register int c; + register int c, k; + register ptrdiff_t i, j; #ifdef S_IWGRP umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); @@ -534,12 +589,12 @@ main(int argc, char **argv) _("wild compilation-time specification of zic_t")); return EXIT_FAILURE; } - for (i = 1; i < argc; ++i) - if (strcmp(argv[i], "--version") == 0) { + for (k = 1; k < argc; k++) + if (strcmp(argv[k], "--version") == 0) { printf("zic %s%s\n", PKGVERSION, TZVERSION); - close_file(stdout, NULL); + close_file(stdout, NULL, NULL); return EXIT_SUCCESS; - } else if (strcmp(argv[i], "--help") == 0) { + } else if (strcmp(argv[k], "--help") == 0) { usage(stdout, EXIT_SUCCESS); } while ((c = getopt(argc, argv, "d:l:p:L:vsy:")) != EOF && c != -1) @@ -615,11 +670,12 @@ _("%s: More than one -L option specified\n"), adjleap(); } - for (i = optind; i < argc; ++i) - infile(argv[i]); + for (k = optind; k < argc; k++) + infile(argv[k]); if (errors) return EXIT_FAILURE; associate(); + change_directory(directory); for (i = 0; i < nzones; i = j) { /* ** Find the next non-continuation zone entry. @@ -633,7 +689,7 @@ _("%s: More than one -L option specified\n"), */ for (i = 0; i < nlinks; ++i) { eat(links[i].l_filename, links[i].l_linenum); - dolink(links[i].l_from, links[i].l_to); + dolink(links[i].l_from, links[i].l_to, false); if (noise) for (j = 0; j < nlinks; ++j) if (strcmp(links[i].l_to, @@ -642,11 +698,11 @@ _("%s: More than one -L option specified\n"), } if (lcltime != NULL) { eat(_("command line"), 1); - dolink(lcltime, TZDEFAULT); + dolink(lcltime, TZDEFAULT, true); } if (psxrules != NULL) { eat(_("command line"), 1); - dolink(psxrules, TZDEFRULES); + dolink(psxrules, TZDEFRULES, true); } if (warnings && (ferror(stderr) || fclose(stderr) != 0)) return EXIT_FAILURE; @@ -658,7 +714,7 @@ componentcheck(char const *name, char const *component, char const *component_end) { enum { component_len_max = 14 }; - size_t component_len = component_end - component; + ptrdiff_t component_len = component_end - component; if (component_len == 0) { if (!*name) error (_("empty file name")); @@ -673,8 +729,9 @@ componentcheck(char const *name, char const *component, } if (0 < component_len && component_len <= 2 && component[0] == '.' && component_end[-1] == '.') { + int len = component_len; error(_("file name '%s' contains '%.*s' component"), - name, (int) component_len, component); + name, len, component); return false; } if (noise) { @@ -723,118 +780,138 @@ namecheck(const char *name) return componentcheck(name, component, cp); } +/* Create symlink contents suitable for symlinking FROM to TO, as a + freshly allocated string. FROM should be a relative file name, and + is relative to the global variable DIRECTORY. TO can be either + relative or absolute. */ static char * -relname(char const *dir, char const *base) -{ - if (*base == '/') - return ecpyalloc(base); - else { - size_t dir_len = strlen(dir); - bool needs_slash = dir_len && dir[dir_len - 1] != '/'; - char *result = emalloc(dir_len + needs_slash + strlen(base) + 1); - result[dir_len] = '/'; - strcpy(result + dir_len + needs_slash, base); - return memcpy(result, dir, dir_len); +relname(char const *from, char const *to) +{ + size_t i, taillen, dotdotetcsize; + size_t dir_len = 0, dotdots = 0, linksize = SIZE_MAX; + char const *f = from; + char *result = NULL; + if (*to == '/') { + /* Make F absolute too. */ + size_t len = strlen(directory); + bool needslash = len && directory[len - 1] != '/'; + linksize = len + needslash + strlen(from) + 1; + f = result = emalloc(linksize); + strcpy(result, directory); + result[len] = '/'; + strcpy(result + len + needslash, from); + } + for (i = 0; f[i] && f[i] == to[i]; i++) + if (f[i] == '/') + dir_len = i + 1; + for (; to[i]; i++) + dotdots += to[i] == '/' && to[i - 1] != '/'; + taillen = strlen(f + dir_len); + dotdotetcsize = 3 * dotdots + taillen + 1; + if (dotdotetcsize <= linksize) { + if (!result) + result = emalloc(dotdotetcsize); + for (i = 0; i < dotdots; i++) + memcpy(result + 3 * i, "../", 3); + memmove(result + 3 * dotdots, f + dir_len, taillen + 1); } + return result; +} + +/* Hard link FROM to TO, following any symbolic links. + Return 0 if successful, an error number otherwise. */ +static int +hardlinkerr(char const *from, char const *to) +{ + int r = linkat(AT_FDCWD, from, AT_FDCWD, to, AT_SYMLINK_FOLLOW); + return r == 0 ? 0 : errno; } static void -dolink(char const *fromfield, char const *tofield) +dolink(char const *fromfield, char const *tofield, bool staysymlink) { - register char * fromname; - register char * toname; - register int fromisdir; + bool todirs_made = false; + int link_errno; - fromname = relname(directory, fromfield); - toname = relname(directory, tofield); /* ** We get to be careful here since ** there's a fair chance of root running us. */ - fromisdir = itsdir(fromname); - if (fromisdir) { - char const *e = strerror(fromisdir < 0 ? errno : EPERM); - fprintf(stderr, _("%s: link from %s failed: %s"), - progname, fromname, e); + if (itsdir(fromfield)) { + fprintf(stderr, _("%s: link from %s/%s failed: %s\n"), + progname, directory, fromfield, strerror(EPERM)); exit(EXIT_FAILURE); } - if (itsdir(toname) <= 0) - remove(toname); - if (link(fromname, toname) != 0) { - int result; - - if (! mkdirs(toname)) - exit(EXIT_FAILURE); - - result = link(fromname, toname); - if (result != 0) { - const char *s = fromfield; - const char *t; - char *p; - size_t dotdots = 0; - register char * symlinkcontents = NULL; - - do - t = s; - while ((s = strchr(s, '/')) - && ! strncmp (fromfield, tofield, - ++s - fromfield)); - - for (s = tofield + (t - fromfield); *s; s++) - dotdots += *s == '/'; - symlinkcontents - = emalloc(3 * dotdots + strlen(t) + 1); - for (p = symlinkcontents; dotdots-- != 0; p += 3) - memcpy(p, "../", 3); - strcpy(p, t); - result = symlink(symlinkcontents, toname); - if (result == 0) -warning(_("hard link failed, symbolic link used")); - free(symlinkcontents); - } - if (result != 0) { - FILE *fp, *tp; - int c; - fp = fopen(fromname, "rb"); - if (!fp) { - const char *e = strerror(errno); - fprintf(stderr, - _("%s: Can't read %s: %s\n"), - progname, fromname, e); - exit(EXIT_FAILURE); - } - tp = fopen(toname, "wb"); - if (!tp) { - const char *e = strerror(errno); - fprintf(stderr, - _("%s: Can't create %s: %s\n"), - progname, toname, e); - exit(EXIT_FAILURE); - } - while ((c = getc(fp)) != EOF) - putc(c, tp); - close_file(fp, fromname); - close_file(tp, toname); - warning(_("link failed, copy used")); - } + if (staysymlink) + staysymlink = itssymlink(tofield); + if (remove(tofield) == 0) + todirs_made = true; + else if (errno != ENOENT) { + char const *e = strerror(errno); + fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"), + progname, directory, tofield, e); + exit(EXIT_FAILURE); + } + link_errno = staysymlink ? ENOTSUP : hardlinkerr(fromfield, tofield); + if (link_errno == ENOENT && !todirs_made) { + mkdirs(tofield, true); + todirs_made = true; + link_errno = hardlinkerr(fromfield, tofield); + } + if (link_errno != 0) { + bool absolute = *fromfield == '/'; + char *linkalloc = absolute ? NULL : relname(fromfield, tofield); + char const *contents = absolute ? fromfield : linkalloc; + int symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno; + if (symlink_errno == ENOENT && !todirs_made) { + mkdirs(tofield, true); + symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno; + } + free(linkalloc); + if (symlink_errno == 0) { + if (link_errno != ENOTSUP) + warning(_("symbolic link used because hard link failed: %s"), + strerror(link_errno)); + } else { + FILE *fp, *tp; + int c; + fp = fopen(fromfield, "rb"); + if (!fp) { + char const *e = strerror(errno); + fprintf(stderr, _("%s: Can't read %s/%s: %s\n"), + progname, directory, fromfield, e); + exit(EXIT_FAILURE); + } + tp = fopen(tofield, "wb"); + if (!tp) { + char const *e = strerror(errno); + fprintf(stderr, _("%s: Can't create %s/%s: %s\n"), + progname, directory, tofield, e); + exit(EXIT_FAILURE); + } + while ((c = getc(fp)) != EOF) + putc(c, tp); + close_file(fp, directory, fromfield); + close_file(tp, directory, tofield); + if (link_errno != ENOTSUP) + warning(_("copy used because hard link failed: %s"), + strerror(link_errno)); + else if (symlink_errno != ENOTSUP) + warning(_("copy used because symbolic link failed: %s"), + strerror(symlink_errno)); + } } - free(fromname); - free(toname); } #define TIME_T_BITS_IN_FILE 64 -static zic_t const min_time = MINVAL (zic_t, TIME_T_BITS_IN_FILE); -static zic_t const max_time = MAXVAL (zic_t, TIME_T_BITS_IN_FILE); +static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE); +static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); /* Estimated time of the Big Bang, in seconds since the POSIX epoch. rounded downward to the negation of a power of two that is comfortably outside the error bounds. - zic does not output time stamps before this, partly because they - are physically suspect, and partly because GNOME mishandles them; see - GNOME bug 730332 . - For the time of the Big Bang, see: Ade PAR, Aghanim N, Armitage-Caplan C et al. Planck 2013 results. @@ -855,26 +932,49 @@ static zic_t const max_time = MAXVAL (zic_t, TIME_T_BITS_IN_FILE); #define BIG_BANG (- (1LL << 59)) #endif -static const zic_t big_bang_time = BIG_BANG; +/* If true, work around GNOME bug 730332 + + by refusing to output time stamps before BIG_BANG. + Such time stamps are physically suspect anyway. -/* Return 1 if NAME is a directory, 0 if it's something else, -1 if trouble. */ -static int + The GNOME bug is scheduled to be fixed in GNOME 3.22, and if so + this workaround will no longer be needed when GNOME 3.21 and + earlier are obsolete, say in the year 2021. */ +enum { WORK_AROUND_GNOME_BUG_730332 = true }; + +static const zic_t early_time = (WORK_AROUND_GNOME_BUG_730332 + ? BIG_BANG + : MINVAL(zic_t, TIME_T_BITS_IN_FILE)); + +/* Return true if NAME is a directory. */ +static bool itsdir(char const *name) { struct stat st; int res = stat(name, &st); - if (res != 0) - return res; #ifdef S_ISDIR - return S_ISDIR(st.st_mode) != 0; -#else - { - char *nameslashdot = relname(name, "."); - res = stat(nameslashdot, &st); + if (res == 0) + return S_ISDIR(st.st_mode) != 0; +#endif + if (res == 0 || errno == EOVERFLOW) { + size_t n = strlen(name); + char *nameslashdot = emalloc(n + 3); + bool dir; + memcpy(nameslashdot, name, n); + strcpy(&nameslashdot[n], &"/."[! (n && name[n - 1] != '/')]); + dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW; free(nameslashdot); - return res == 0; + return dir; } -#endif + return false; +} + +/* Return true if NAME is a symbolic link. */ +static bool +itssymlink(char const *name) +{ + char c; + return 0 <= readlink(name, &c, 1); } /* @@ -897,8 +997,7 @@ associate(void) { register struct zone * zp; register struct rule * rp; - register int base, out; - register int i, j; + register ptrdiff_t i, j, base, out; if (nrules != 0) { qsort(rules, nrules, sizeof *rules, rcomp); @@ -976,7 +1075,7 @@ infile(const char *name) register const struct lookup * lp; register int nfields; register bool wantcont; - register int num; + register lineno num; char buf[BUFSIZ]; if (strcmp(name, "-") == 0) { @@ -1017,7 +1116,7 @@ infile(const char *name) lp = byword(fields[0], line_codes); if (lp == NULL) error(_("input line of unknown type")); - else switch ((int) (lp->l_value)) { + else switch (lp->l_value) { case LC_RULE: inrule(fields, nfields); wantcont = false; @@ -1046,7 +1145,7 @@ _("%s: panic: Invalid l_value %d\n"), } free(fields); } - close_file(fp, filename); + close_file(fp, NULL, filename); if (wantcont) error(_("expected continuation line not found")); } @@ -1129,7 +1228,7 @@ inrule(char **fields, int nfields) static bool inzone(char **fields, int nfields) { - register int i; + register ptrdiff_t i; if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) { error(_("wrong number of fields on Zone line")); @@ -1150,8 +1249,8 @@ _("\"Zone %s\" line and -p option are mutually exclusive"), for (i = 0; i < nzones; ++i) if (zones[i].z_name != NULL && strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) { - error( -_("duplicate zone name %s (file \"%s\", line %d)"), + error(_("duplicate zone name %s" + " (file \"%s\", line %"PRIdLINENO")"), fields[ZF_NAME], zones[i].z_filename, zones[i].z_linenum); @@ -1263,7 +1362,7 @@ inleap(char **fields, int nfields) { register const char * cp; register const struct lookup * lp; - register int i, j; + register zic_t i, j; zic_t year; int month, day; zic_t dayoff, tod; @@ -1355,7 +1454,7 @@ inleap(char **fields, int nfields) return; } t = tadd(t, tod); - if (t < big_bang_time) { + if (t < early_time) { error(_("leap second precedes Big Bang")); return; } @@ -1435,7 +1534,7 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, cp = loyearp; lp = byword(cp, begin_years); rp->r_lowasnum = lp == NULL; - if (!rp->r_lowasnum) switch ((int) lp->l_value) { + if (!rp->r_lowasnum) switch (lp->l_value) { case YR_MINIMUM: rp->r_loyear = ZIC_MIN; break; @@ -1454,7 +1553,7 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, cp = hiyearp; lp = byword(cp, end_years); rp->r_hiwasnum = lp == NULL; - if (!rp->r_hiwasnum) switch ((int) lp->l_value) { + if (!rp->r_hiwasnum) switch (lp->l_value) { case YR_MINIMUM: rp->r_hiyear = ZIC_MIN; break; @@ -1592,15 +1691,18 @@ static void writezone(const char *const name, const char *const string, char version) { register FILE * fp; - register int i, j; + register ptrdiff_t i, j; register int leapcnt32, leapi32; - register int timecnt32, timei32; + register ptrdiff_t timecnt32, timei32; register int pass; - char * fullname; static const struct tzhead tzh0; static struct tzhead tzh; - zic_t *ats = emalloc(size_product(timecnt, sizeof *ats + 1)); - void *typesptr = ats + timecnt; + bool dir_checked = false; + zic_t one = 1; + zic_t y2038_boundary = one << 31; + ptrdiff_t nats = timecnt + WORK_AROUND_QTBUG_53071; + zic_t *ats = emalloc(size_product(nats, sizeof *ats + 1)); + void *typesptr = ats + nats; unsigned char *types = typesptr; /* @@ -1612,12 +1714,11 @@ writezone(const char *const name, const char *const string, char version) ** Optimize. */ { - int fromi; - int toi; + ptrdiff_t fromi, toi; toi = 0; fromi = 0; - while (fromi < timecnt && attypes[fromi].at < big_bang_time) + while (fromi < timecnt && attypes[fromi].at < early_time) ++fromi; for ( ; fromi < timecnt; ++fromi) { if (toi > 1 && ((attypes[fromi].at + @@ -1628,15 +1729,23 @@ writezone(const char *const name, const char *const string, char version) attypes[fromi].type; continue; } - if (toi == 0 || - attypes[toi - 1].type != attypes[fromi].type) + if (toi == 0 + || attypes[fromi].dontmerge + || attypes[toi - 1].type != attypes[fromi].type) attypes[toi++] = attypes[fromi]; } timecnt = toi; } - if (noise && timecnt > 1200) + + if (noise && timecnt > 1200) { + if (timecnt > TZ_MAX_TIMES) + warning(_("reference clients mishandle" + " more than %d transition times"), + TZ_MAX_TIMES); + else warning(_("pre-2014 clients may mishandle" " more than 1200 transition times")); + } /* ** Transfer. */ @@ -1644,6 +1753,19 @@ writezone(const char *const name, const char *const string, char version) ats[i] = attypes[i].at; types[i] = attypes[i].type; } + + /* Work around QTBUG-53071 for time stamps less than y2038_boundary - 1, + by inserting a no-op transition at time y2038_boundary - 1. + This works only for timestamps before the boundary, which + should be good enough in practice as QTBUG-53071 should be + long-dead by 2038. */ + if (WORK_AROUND_QTBUG_53071 && timecnt != 0 + && ats[timecnt - 1] < y2038_boundary - 1 && strchr(string, '<')) { + ats[timecnt] = y2038_boundary - 1; + types[timecnt] = types[timecnt - 1]; + timecnt++; + } + /* ** Correct for leap seconds. */ @@ -1681,50 +1803,58 @@ writezone(const char *const name, const char *const string, char version) --leapcnt32; ++leapi32; } - fullname = relname(directory, name); /* ** Remove old file, if any, to snap links. */ - if (itsdir(fullname) <= 0 && remove(fullname) != 0 && errno != ENOENT) { + if (remove(name) == 0) + dir_checked = true; + else if (errno != ENOENT) { const char *e = strerror(errno); - fprintf(stderr, _("%s: Can't remove %s: %s\n"), - progname, fullname, e); + fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"), + progname, directory, name, e); exit(EXIT_FAILURE); } - if ((fp = fopen(fullname, "wb")) == NULL) { - if (! mkdirs(fullname)) - exit(EXIT_FAILURE); - if ((fp = fopen(fullname, "wb")) == NULL) { - const char *e = strerror(errno); - - fprintf(stderr, _("%s: Can't create %s: %s\n"), - progname, fullname, e); - exit(EXIT_FAILURE); - } + fp = fopen(name, "wb"); + if (!fp) { + int fopen_errno = errno; + if (fopen_errno == ENOENT && !dir_checked) { + mkdirs(name, true); + fp = fopen(name, "wb"); + fopen_errno = errno; + } + if (!fp) { + fprintf(stderr, _("%s: Can't create %s/%s: %s\n"), + progname, directory, name, strerror(fopen_errno)); + exit(EXIT_FAILURE); + } } for (pass = 1; pass <= 2; ++pass) { - register int thistimei, thistimecnt; - register int thisleapi, thisleapcnt; - register int thistimelim, thisleaplim; + register ptrdiff_t thistimei, thistimecnt, thistimelim; + register int thisleapi, thisleapcnt, thisleaplim; int writetype[TZ_MAX_TYPES]; int typemap[TZ_MAX_TYPES]; register int thistypecnt; char thischars[TZ_MAX_CHARS]; - char thischarcnt; + int thischarcnt; + bool toomanytimes; int indmap[TZ_MAX_CHARS]; if (pass == 1) { thistimei = timei32; thistimecnt = timecnt32; + toomanytimes = thistimecnt >> 31 >> 1 != 0; thisleapi = leapi32; thisleapcnt = leapcnt32; } else { thistimei = 0; thistimecnt = timecnt; + toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0; thisleapi = 0; thisleapcnt = leapcnt; } + if (toomanytimes) + error(_("too many transition times")); thistimelim = thistimei + thistimecnt; thisleaplim = thisleapi + thisleapcnt; for (i = 0; i < typecnt; ++i) @@ -1811,8 +1941,7 @@ writezone(const char *const name, const char *const string, char version) if (strcmp(&thischars[j], thisabbr) == 0) break; if (j == thischarcnt) { - strcpy(&thischars[(int) thischarcnt], - thisabbr); + strcpy(&thischars[thischarcnt], thisabbr); thischarcnt += strlen(thisabbr) + 1; } indmap[abbrinds[i]] = j; @@ -1894,9 +2023,8 @@ writezone(const char *const name, const char *const string, char version) putc(ttisgmts[i], fp); } fprintf(fp, "\n%s\n", string); - close_file(fp, fullname); + close_file(fp, directory, name); free(ats); - free(fullname); } static char const * @@ -2097,13 +2225,13 @@ rule_cmp(struct rule const *a, struct rule const *b) enum { YEAR_BY_YEAR_ZONE = 1 }; static int -stringzone(char *result, const struct zone *const zpfirst, const int zonecount) +stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) { register const struct zone * zp; register struct rule * rp; register struct rule * stdrp; register struct rule * dstrp; - register int i; + register ptrdiff_t i; register const char * abbrvar; register int compat = 0; register int c; @@ -2216,11 +2344,11 @@ stringzone(char *result, const struct zone *const zpfirst, const int zonecount) } static void -outzone(const struct zone *zpfirst, int zonecount) +outzone(const struct zone *zpfirst, ptrdiff_t zonecount) { register const struct zone * zp; register struct rule * rp; - register int i, j; + register ptrdiff_t i, j; register bool usestart, useuntil; register zic_t starttime, untiltime; register zic_t gmtoff; @@ -2239,6 +2367,10 @@ outzone(const struct zone *zpfirst, int zonecount) register int compat; register bool do_extend; register char version; + ptrdiff_t lastatmax = -1; + zic_t one = 1; + zic_t y2038_boundary = one << 31; + zic_t max_year0; max_abbr_len = 2 + max_format_len + max_abbrvar_len; max_envvar_len = 2 * max_abbr_len + 5 * 9; @@ -2334,21 +2466,22 @@ outzone(const struct zone *zpfirst, int zonecount) } /* ** For the benefit of older systems, - ** generate data from 1900 through 2037. + ** generate data from 1900 through 2038. */ if (min_year > 1900) min_year = 1900; - if (max_year < 2037) - max_year = 2037; + max_year0 = max_year; + if (max_year < 2038) + max_year = 2038; for (i = 0; i < zonecount; ++i) { /* ** A guess that may well be corrected later. */ stdoff = 0; zp = &zpfirst[i]; - usestart = i > 0 && (zp - 1)->z_untiltime > big_bang_time; + usestart = i > 0 && (zp - 1)->z_untiltime > early_time; useuntil = i < (zonecount - 1); - if (useuntil && zp->z_untiltime <= big_bang_time) + if (useuntil && zp->z_untiltime <= early_time) continue; gmtoff = zp->z_gmtoff; eat(zp->z_filename, zp->z_linenum); @@ -2363,7 +2496,7 @@ outzone(const struct zone *zpfirst, int zonecount) if (usestart) { addtt(starttime, type); usestart = false; - } else addtt(big_bang_time, type); + } else addtt(early_time, type); } else for (year = min_year; year <= max_year; ++year) { if (useuntil && year > zp->z_untilrule.r_hiyear) break; @@ -2378,11 +2511,15 @@ outzone(const struct zone *zpfirst, int zonecount) rp->r_todo = year >= rp->r_loyear && year <= rp->r_hiyear && yearistype(year, rp->r_yrtype); - if (rp->r_todo) + if (rp->r_todo) { rp->r_temp = rpytime(rp, year); + rp->r_todo + = (rp->r_temp < y2038_boundary + || year <= max_year0); + } } for ( ; ; ) { - register int k; + register ptrdiff_t k; register zic_t jtime, ktime; register zic_t offset; @@ -2471,6 +2608,10 @@ outzone(const struct zone *zpfirst, int zonecount) offset = oadd(zp->z_gmtoff, rp->r_stdoff); type = addtype(offset, ab, rp->r_stdoff != 0, rp->r_todisstd, rp->r_todisgmt); + if (rp->r_hiyear == ZIC_MAX + && ! (0 <= lastatmax + && ktime < attypes[lastatmax].at)) + lastatmax = timecnt; addtt(ktime, type); } } @@ -2502,6 +2643,8 @@ error(_("can't determine time zone abbreviation to use just after until time")); starttime = tadd(starttime, -gmtoff); } } + if (0 <= lastatmax) + attypes[lastatmax].dontmerge = true; if (do_extend) { /* ** If we're extending the explicitly listed observations @@ -2523,21 +2666,8 @@ error(_("can't determine time zone abbreviation to use just after until time")); if (attypes[i].at > lastat->at) lastat = &attypes[i]; if (lastat->at < rpytime(&xr, max_year - 1)) { - /* - ** Create new type code for the redundant entry, - ** to prevent it being optimized away. - */ - if (typecnt >= TZ_MAX_TYPES) { - error(_("too many local time types")); - exit(EXIT_FAILURE); - } - gmtoffs[typecnt] = gmtoffs[lastat->type]; - isdsts[typecnt] = isdsts[lastat->type]; - ttisstds[typecnt] = ttisstds[lastat->type]; - ttisgmts[typecnt] = ttisgmts[lastat->type]; - abbrinds[typecnt] = abbrinds[lastat->type]; - ++typecnt; addtt(rpytime(&xr, max_year + 1), typecnt-1); + attypes[timecnt - 1].dontmerge = true; } } writezone(zpfirst->z_name, envvar, version); @@ -2549,8 +2679,8 @@ error(_("can't determine time zone abbreviation to use just after until time")); static void addtt(zic_t starttime, int type) { - if (starttime <= big_bang_time || - (timecnt == 1 && attypes[0].at < big_bang_time)) { + if (starttime <= early_time + || (timecnt == 1 && attypes[0].at < early_time)) { gmtoffs[0] = gmtoffs[type]; isdsts[0] = isdsts[type]; ttisstds[0] = ttisstds[type]; @@ -2565,6 +2695,7 @@ addtt(zic_t starttime, int type) } attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc); attypes[timecnt].at = starttime; + attypes[timecnt].dontmerge = false; attypes[timecnt].type = type; ++timecnt; } @@ -2657,28 +2788,48 @@ adjleap(void) } } +static char * +shellquote(char *b, char const *s) +{ + *b++ = '\''; + while (*s) { + if (*s == '\'') + *b++ = '\'', *b++ = '\\', *b++ = '\''; + *b++ = *s++; + } + *b++ = '\''; + return b; +} + static bool -yearistype(int year, const char *type) +yearistype(zic_t year, const char *type) { - static char * buf; - int result; + char *buf; + char *b; + int result; if (type == NULL || *type == '\0') return true; - buf = erealloc(buf, 132 + strlen(yitcommand) + strlen(type)); - sprintf(buf, "%s %d %s", yitcommand, year, type); + buf = emalloc(1 + 4 * strlen(yitcommand) + 2 + + INT_STRLEN_MAXIMUM(zic_t) + 2 + 4 * strlen(type) + 2); + b = shellquote(buf, yitcommand); + *b++ = ' '; + b += sprintf(b, "%"PRIdZIC, year); + *b++ = ' '; + b = shellquote(b, type); + *b = '\0'; result = system(buf); - if (WIFEXITED(result)) switch (WEXITSTATUS(result)) { - case 0: - return true; - case 1: - return false; + if (WIFEXITED(result)) { + int status = WEXITSTATUS(result); + if (status <= 1) { + free(buf); + return status == 0; + } } error(_("Wild result from command execution")); fprintf(stderr, _("%s: command was '%s', result was %d\n"), progname, buf, result); - for ( ; ; ) - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } /* Is A a space character in the C locale? */ @@ -2806,10 +2957,8 @@ getfields(register char *cp) if (*dp != '\0') ++dp; else { - error(_( - "Odd number of quotation marks" - )); - exit(1); + error(_("Odd number of quotation marks")); + exit(EXIT_FAILURE); } } while (*cp && *cp != '#' && !is_space(*cp)); if (is_space(*cp)) @@ -2972,44 +3121,49 @@ mp = _("time zone abbreviation differs from POSIX standard"); charcnt += i; } -static bool -mkdirs(char *argname) +/* Ensure that the directories of ARGNAME exist, by making any missing + ones. If ANCESTORS, do this only for ARGNAME's ancestors; otherwise, + do it for ARGNAME too. Exit with failure if there is trouble. + Do not consider an existing non-directory to be trouble. */ +static void +mkdirs(char const *argname, bool ancestors) { register char * name; register char * cp; - if (argname == NULL || *argname == '\0') - return true; cp = name = ecpyalloc(argname); - while ((cp = strchr(cp + 1, '/')) != 0) { - *cp = '\0'; + + /* Do not mkdir a root directory, as it must exist. */ #ifdef HAVE_DOS_FILE_NAMES - /* - ** DOS drive specifier? - */ - if (is_alpha(name[0]) && name[1] == ':' && name[2] == '\0') { - *cp = '/'; - continue; - } + if (is_alpha(name[0]) && name[1] == ':') + cp += 2; #endif + while (*cp == '/') + cp++; + + while (cp && ((cp = strchr(cp, '/')) || !ancestors)) { + if (cp) + *cp = '\0'; /* ** Try to create it. It's OK if creation fails because ** the directory already exists, perhaps because some - ** other process just created it. + ** other process just created it. For simplicity do + ** not check first whether it already exists, as that + ** is checked anyway if the mkdir fails. */ if (mkdir(name, MKDIR_UMASK) != 0) { + /* For speed, skip itsdir if errno == EEXIST. Since + mkdirs is called only after open fails with ENOENT + on a subfile, EEXIST implies itsdir here. */ int err = errno; - if (itsdir(name) <= 0) { - char const *e = strerror(err); - warning(_("%s: Can't create directory" - " %s: %s"), - progname, name, e); - free(name); - return false; + if (err != EEXIST && !itsdir(name)) { + error(_("%s: Can't create directory %s: %s"), + progname, name, strerror(err)); + exit(EXIT_FAILURE); } } - *cp = '/'; + if (cp) + *cp++ = '/'; } free(name); - return true; } -- cgit 1.4.1