about summary refs log tree commit diff
path: root/timezone/zic.c
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2024-04-06 23:39:53 -0700
committerPaul Eggert <eggert@cs.ucla.edu>2024-04-07 13:35:48 -0700
commit1f94147a79fcb7211f1421b87383cad93986797f (patch)
treee75fd16819071d7cf0b84adafa81e56fb91d0f99 /timezone/zic.c
parent57581acd9559217e859fdac693145ce6399f4d70 (diff)
downloadglibc-1f94147a79fcb7211f1421b87383cad93986797f.tar.gz
glibc-1f94147a79fcb7211f1421b87383cad93986797f.tar.xz
glibc-1f94147a79fcb7211f1421b87383cad93986797f.zip
timezone: sync to TZDB 2024a
Sync tzselect, zdump, zic to TZDB 2024a.
This patch incorporates the following TZDB source code changes,
listed roughly in descending order of importance.

  zic now supports links to links, needed for future tzdata
  zic now defaults to '-b slim'
  zic now updates output files atomically
  zic has new options -R, -l -, -p -
  zic -r now uses -00 for unspecified timestamps
  zdump now uses [lo,hi) for both -c and -t
  Fix several integer overflow bugs
  zic now checks input bytes more carefully
  Simplify and fix new TZDIR setup
  Default time_t to 64 bits on glibc 2.34+ 32-bit
  zic now generates TZ strings that conform to POSIX when all-year DST
  zic -v now shows extreme-int tm_year transitions
  Fix zic bug in last time type of Asia/Gaza etc.
  Fix zic bug with Palestine after 2075
  Fix bug uncovered by recent change to Iran history
  Fix 'zic -b fat' bug with Port Moresby 32-bit data
  Fix zic bug with -r @X where X is deduced from TZ
  Fix bug with zic -r cutoff before 1st transition
  Fix leap second expiry and truncation
  Fix zic bug on Linux 2.6.16 and 2.6.17
  Fix bug with 'zic -d /a/b/c' if /a is unwriteable
  Don't mistruncate TZif files at leap seconds
  Fix zdump undefined behavior if !USE_LTZ
  zdump -v reports localtime+gmtime failures better
  Fix zdump diagnostic for missing timezone
  Don't assume nonempty argv
  Port better to C23
  Do not assume negative >> behavior
  I18nize zdump a bit better
  Port zdump to right_only installations
  New tzselect menu option 'now'
  tzselect can now use current time to help choose
  Improve tzselect behavior for Turkey etc.
  tzselect: do not create temporary files
  tzselect: work around mawk bug with {2,}
  tzselect: Port to POSIX awk, which prohibits -v newlines
  Do not use empty RE in tzselect
  Don't set TZ in tzselect
  Avoid sed, expr in tzselect
  tzselect: Fix problems with spaces in TZDIR
  Improve tzselect diagnostics
  Remove zic workaround for Qt bug 53071
  Remove zic support for "min" in Rule lines
  Remove zic support for zic -y, Rule TYPEs, pacificnew
  Remove tzselect workaround for Bash 1.14.7 bug

* SHARED-FILES: Update to match current sync.
* config.h.in (HAVE_STRERROR): Remove; no longer needed.
* timezone/Makefile ($(objpfx)zic.o): Depend on tzdir.h.
($(objpfx)tzdir.h): New rule to build a placeholder.
* timezone/private.h, timezone/tzfile.h, timezone/version:
* timezone/zdump.c, timezone/zic.c: Copy verbatim from TZDB 2024a.
Diffstat (limited to 'timezone/zic.c')
-rw-r--r--timezone/zic.c2210
1 files changed, 1342 insertions, 868 deletions
diff --git a/timezone/zic.c b/timezone/zic.c
index 2875b5544c..00f00e307a 100644
--- a/timezone/zic.c
+++ b/timezone/zic.c
@@ -5,29 +5,42 @@
 ** 2006-07-17 by Arthur David Olson.
 */
 
+/* Use the system 'time' function, instead of any private replacement.
+   This avoids creating an unnecessary dependency on localtime.c.  */
+#undef EPOCH_LOCAL
+#undef EPOCH_OFFSET
+#undef RESERVE_STD_EXT_IDS
+#undef time_tz
+
 #include "version.h"
 #include "private.h"
+#include "tzdir.h"
 #include "tzfile.h"
 
 #include <fcntl.h>
 #include <locale.h>
+#include <signal.h>
 #include <stdarg.h>
-#include <stddef.h>
 #include <stdio.h>
 
-#define	ZIC_VERSION_PRE_2013 '2'
-#define	ZIC_VERSION	'3'
-
 typedef int_fast64_t	zic_t;
-#define ZIC_MIN INT_FAST64_MIN
-#define ZIC_MAX INT_FAST64_MAX
-#define PRIdZIC PRIdFAST64
+static zic_t const
+  ZIC_MIN = INT_FAST64_MIN,
+  ZIC_MAX = INT_FAST64_MAX,
+  ZIC32_MIN = -1 - (zic_t) 0x7fffffff,
+  ZIC32_MAX = 0x7fffffff;
 #define SCNdZIC SCNdFAST64
 
 #ifndef ZIC_MAX_ABBR_LEN_WO_WARN
-#define ZIC_MAX_ABBR_LEN_WO_WARN	6
+# define ZIC_MAX_ABBR_LEN_WO_WARN 6
 #endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
 
+/* Minimum and maximum years, assuming signed 32-bit time_t.  */
+enum { YEAR_32BIT_MIN = 1901, YEAR_32BIT_MAX = 2038 };
+
+/* An upper bound on how much a format might grow due to concatenation.  */
+enum { FORMAT_LEN_GROWTH_BOUND = 5 };
+
 #ifdef HAVE_DIRECT_H
 # include <direct.h>
 # include <io.h>
@@ -35,38 +48,40 @@ typedef int_fast64_t	zic_t;
 # define mkdir(name, mode) _mkdir(name)
 #endif
 
+#ifndef HAVE_GETRANDOM
+# ifdef __has_include
+#  if __has_include(<sys/random.h>)
+#   include <sys/random.h>
+#  endif
+# elif 2 < __GLIBC__ + (25 <= __GLIBC_MINOR__)
+#  include <sys/random.h>
+# endif
+# define HAVE_GETRANDOM GRND_RANDOM
+#elif HAVE_GETRANDOM
+# include <sys/random.h>
+#endif
+
 #if HAVE_SYS_STAT_H
-#include <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)
+# define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
 #else
-#define MKDIR_UMASK 0755
+# define MKDIR_UMASK 0755
 #endif
-/* Port to native MS-Windows and to ancient UNIX.  */
-#if !defined S_ISDIR && defined S_IFDIR && defined S_IFMT
-# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
-#endif
-
-#if HAVE_SYS_WAIT_H
-#include <sys/wait.h>	/* for WIFEXITED and WEXITSTATUS */
-#endif /* HAVE_SYS_WAIT_H */
-
-#ifndef WIFEXITED
-#define WIFEXITED(status)	(((status) & 0xff) == 0)
-#endif /* !defined WIFEXITED */
-#ifndef WEXITSTATUS
-#define WEXITSTATUS(status)	(((status) >> 8) & 0xff)
-#endif /* !defined WEXITSTATUS */
 
-/* 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));
+/* The minimum alignment of a type, for pre-C23 platforms.
+   The __SUNPRO_C test is because Oracle Developer Studio 12.6 lacks
+   <stdalign.h> even though __STDC_VERSION__ == 201112.  */
+#if __STDC_VERSION__ < 201112 || defined __SUNPRO_C
+# define alignof(type) offsetof(struct { char a; type b; }, b)
+#elif __STDC_VERSION__ < 202311
+# include <stdalign.h>
 #endif
 
-/* The minimum alignment of a type, for pre-C11 platforms.  */
-#if __STDC_VERSION__ < 201112
-# define _Alignof(type) offsetof(struct { char a; type b; }, b)
+/* The maximum length of a text line, including the trailing newline.  */
+#ifndef _POSIX2_LINE_MAX
+# define _POSIX2_LINE_MAX 2048
 #endif
 
 /* The type for line numbers.  Use PRIdMAX to format them; formerly
@@ -75,14 +90,12 @@ static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t));
 typedef intmax_t lineno;
 
 struct rule {
-	const char *	r_filename;
+	int		r_filenum;
 	lineno		r_linenum;
 	const char *	r_name;
 
 	zic_t		r_loyear;	/* for example, 1986 */
 	zic_t		r_hiyear;	/* for example, 1986 */
-	const char *	r_yrtype;
-	bool		r_lowasnum;
 	bool		r_hiwasnum;
 
 	int		r_month;	/* 0..11 */
@@ -103,15 +116,16 @@ struct rule {
 };
 
 /*
-**	r_dycode		r_dayofmonth	r_wday
+** r_dycode	r_dayofmonth	r_wday
 */
-
-#define DC_DOM		0	/* 1..31 */	/* unused */
-#define DC_DOWGEQ	1	/* 1..31 */	/* 0..6 (Sun..Sat) */
-#define DC_DOWLEQ	2	/* 1..31 */	/* 0..6 (Sun..Sat) */
+enum {
+  DC_DOM,	/* 1..31 */	/* unused */
+  DC_DOWGEQ,	/* 1..31 */	/* 0..6 (Sun..Sat) */
+  DC_DOWLEQ	/* 1..31 */	/* 0..6 (Sun..Sat) */
+};
 
 struct zone {
-	const char *	z_filename;
+	int		z_filenum;
 	lineno		z_linenum;
 
 	const char *	z_name;
@@ -133,22 +147,28 @@ struct zone {
 #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 int	link(const char * target, const char * linkname);
 extern char *	optarg;
 extern int	optind;
 #endif
 
-#if ! HAVE_LINK
-# define link(from, to) (errno = ENOTSUP, -1)
-#endif
 #if ! HAVE_SYMLINK
-# define readlink(file, buf, size) (errno = ENOTSUP, -1)
-# define symlink(from, to) (errno = ENOTSUP, -1)
-# define S_ISLNK(m) 0
+static ssize_t
+readlink(char const *restrict file, char *restrict buf, size_t size)
+{
+  errno = ENOTSUP;
+  return -1;
+}
+static int
+symlink(char const *target, char const *linkname)
+{
+  errno = ENOTSUP;
+  return -1;
+}
 #endif
 #ifndef AT_SYMLINK_FOLLOW
-# define linkat(fromdir, from, todir, to, flag) \
-    (itssymlink(from) ? (errno = ENOTSUP, -1) : link(from, to))
+#  define linkat(targetdir, target, linknamedir, linkname, flag) \
+     (errno = ENOTSUP, -1)
 #endif
 
 static void	addtt(zic_t starttime, int type);
@@ -157,19 +177,18 @@ static void	leapadd(zic_t, int, int);
 static void	adjleap(void);
 static void	associate(void);
 static void	dolink(const char *, const char *, bool);
-static char **	getfields(char * buf);
+static int	getfields(char *, char **, int);
 static zic_t	gethms(const char * string, const char * errstring);
 static zic_t	getsave(char *, bool *);
 static void	inexpires(char **, int);
-static void	infile(const char * filename);
+static void	infile(int, char const *);
 static void	inleap(char ** fields, int nfields);
 static void	inlink(char ** fields, int nfields);
 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 bool	itsdir(char const *);
-static bool	itssymlink(char const *);
+static int	itssymlink(char const *, int *);
 static bool	is_alpha(char a);
 static char	lowerit(char);
 static void	mkdirs(char const *, bool);
@@ -177,29 +196,19 @@ static void	newabbr(const char * abbr);
 static zic_t	oadd(zic_t t1, zic_t t2);
 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,
+static bool	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(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
-   TZif files whose POSIX-TZ-style strings contain '<'; see
-   QTBUG-53071 <https://bugreports.qt.io/browse/QTBUG-53071>.  This
-   workaround will no longer be needed when Qt 5.6.1 and earlier are
-   obsolete, say in the year 2021.  */
-#ifndef WORK_AROUND_QTBUG_53071
-enum { WORK_AROUND_QTBUG_53071 = true };
-#endif
-
 static int		charcnt;
 static bool		errors;
 static bool		warnings;
-static const char *	filename;
+static int		filenum;
 static int		leapcnt;
 static bool		leapseen;
 static zic_t		leapminyear;
@@ -210,97 +219,121 @@ static int		max_format_len;
 static zic_t		max_year;
 static zic_t		min_year;
 static bool		noise;
-static const char *	rfilename;
+static int		rfilenum;
 static lineno		rlinenum;
 static const char *	progname;
+static char const *	leapsec;
+static char *const *	main_argv;
 static ptrdiff_t	timecnt;
 static ptrdiff_t	timecnt_alloc;
 static int		typecnt;
+static int		unspecifiedtype;
 
 /*
 ** Line codes.
 */
 
-#define LC_RULE		0
-#define LC_ZONE		1
-#define LC_LINK		2
-#define LC_LEAP		3
-#define LC_EXPIRES	4
+enum {
+  LC_RULE,
+  LC_ZONE,
+  LC_LINK,
+  LC_LEAP,
+  LC_EXPIRES
+};
 
 /*
 ** Which fields are which on a Zone line.
 */
 
-#define ZF_NAME		1
-#define ZF_STDOFF	2
-#define ZF_RULE		3
-#define ZF_FORMAT	4
-#define ZF_TILYEAR	5
-#define ZF_TILMONTH	6
-#define ZF_TILDAY	7
-#define ZF_TILTIME	8
-#define ZONE_MINFIELDS	5
-#define ZONE_MAXFIELDS	9
+enum {
+  ZF_NAME = 1,
+  ZF_STDOFF,
+  ZF_RULE,
+  ZF_FORMAT,
+  ZF_TILYEAR,
+  ZF_TILMONTH,
+  ZF_TILDAY,
+  ZF_TILTIME,
+  ZONE_MAXFIELDS,
+  ZONE_MINFIELDS = ZF_TILYEAR
+};
 
 /*
 ** Which fields are which on a Zone continuation line.
 */
 
-#define ZFC_STDOFF	0
-#define ZFC_RULE	1
-#define ZFC_FORMAT	2
-#define ZFC_TILYEAR	3
-#define ZFC_TILMONTH	4
-#define ZFC_TILDAY	5
-#define ZFC_TILTIME	6
-#define ZONEC_MINFIELDS	3
-#define ZONEC_MAXFIELDS	7
+enum {
+  ZFC_STDOFF,
+  ZFC_RULE,
+  ZFC_FORMAT,
+  ZFC_TILYEAR,
+  ZFC_TILMONTH,
+  ZFC_TILDAY,
+  ZFC_TILTIME,
+  ZONEC_MAXFIELDS,
+  ZONEC_MINFIELDS = ZFC_TILYEAR
+};
 
 /*
 ** Which files are which on a Rule line.
 */
 
-#define RF_NAME		1
-#define RF_LOYEAR	2
-#define RF_HIYEAR	3
-#define RF_COMMAND	4
-#define RF_MONTH	5
-#define RF_DAY		6
-#define RF_TOD		7
-#define RF_SAVE		8
-#define RF_ABBRVAR	9
-#define RULE_FIELDS	10
+enum {
+  RF_NAME = 1,
+  RF_LOYEAR,
+  RF_HIYEAR,
+  RF_COMMAND,
+  RF_MONTH,
+  RF_DAY,
+  RF_TOD,
+  RF_SAVE,
+  RF_ABBRVAR,
+  RULE_FIELDS
+};
 
 /*
 ** Which fields are which on a Link line.
 */
 
-#define LF_FROM		1
-#define LF_TO		2
-#define LINK_FIELDS	3
+enum {
+  LF_TARGET = 1,
+  LF_LINKNAME,
+  LINK_FIELDS
+};
 
 /*
 ** Which fields are which on a Leap line.
 */
 
-#define LP_YEAR		1
-#define LP_MONTH	2
-#define LP_DAY		3
-#define LP_TIME		4
-#define LP_CORR		5
-#define LP_ROLL		6
-#define LEAP_FIELDS	7
+enum {
+  LP_YEAR = 1,
+  LP_MONTH,
+  LP_DAY,
+  LP_TIME,
+  LP_CORR,
+  LP_ROLL,
+  LEAP_FIELDS,
+
+  /* Expires lines are like Leap lines, except without CORR and ROLL fields.  */
+  EXPIRES_FIELDS = LP_TIME + 1
+};
 
-/* Expires lines are like Leap lines, except without CORR and ROLL fields.  */
-#define EXPIRES_FIELDS	5
+/* The maximum number of fields on any of the above lines.
+   (The "+"s pacify gcc -Wenum-compare.)  */
+enum {
+  MAX_FIELDS = max(max(+RULE_FIELDS, +LINK_FIELDS),
+		   max(+LEAP_FIELDS, +EXPIRES_FIELDS))
+};
 
 /*
 ** Year synonyms.
 */
 
-#define YR_MINIMUM	0
-#define YR_MAXIMUM	1
-#define YR_ONLY		2
+enum {
+  YR_MINIMUM, /* "minimum" is for backward compatibility only */
+  YR_MAXIMUM,
+  YR_ONLY
+};
 
 static struct rule *	rules;
 static ptrdiff_t	nrules;	/* number of rules */
@@ -311,10 +344,10 @@ static ptrdiff_t	nzones;	/* number of zones */
 static ptrdiff_t	nzones_alloc;
 
 struct link {
-	const char *	l_filename;
+	int		l_filenum;
 	lineno		l_linenum;
-	const char *	l_from;
-	const char *	l_to;
+	const char *	l_target;
+	const char *	l_linkname;
 };
 
 static struct link *	links;
@@ -381,12 +414,10 @@ static struct lookup const	lasts[] = {
 
 static struct lookup const	begin_years[] = {
 	{ "minimum",	YR_MINIMUM },
-	{ "maximum",	YR_MAXIMUM },
 	{ NULL,		0 }
 };
 
 static struct lookup const	end_years[] = {
-	{ "minimum",	YR_MINIMUM },
 	{ "maximum",	YR_MAXIMUM },
 	{ "only",	YR_ONLY },
 	{ NULL,		0 }
@@ -426,29 +457,53 @@ static char		roll[TZ_MAX_LEAPS];
 ** Memory allocation.
 */
 
-static _Noreturn void
+ATTRIBUTE_NORETURN static void
 memory_exhausted(const char *msg)
 {
 	fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg);
 	exit(EXIT_FAILURE);
 }
 
-static ATTRIBUTE_PURE size_t
-size_product(size_t nitems, size_t itemsize)
+ATTRIBUTE_NORETURN static void
+size_overflow(void)
 {
-	if (SIZE_MAX / itemsize < nitems)
-		memory_exhausted(_("size overflow"));
-	return nitems * itemsize;
+  memory_exhausted(_("size overflow"));
 }
 
-static ATTRIBUTE_PURE size_t
-align_to(size_t size, size_t alignment)
+ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+size_sum(size_t a, size_t b)
 {
-  size_t aligned_size = size + alignment - 1;
-  aligned_size -= aligned_size % alignment;
-  if (aligned_size < size)
-    memory_exhausted(_("alignment overflow"));
-  return aligned_size;
+#ifdef ckd_add
+  ptrdiff_t sum;
+  if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
+    return sum;
+#else
+  if (a <= INDEX_MAX && b <= INDEX_MAX - a)
+    return a + b;
+#endif
+  size_overflow();
+}
+
+ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
+{
+#ifdef ckd_mul
+  ptrdiff_t product;
+  if (!ckd_mul(&product, nitems, itemsize) && product <= INDEX_MAX)
+    return product;
+#else
+  ptrdiff_t nitems_max = INDEX_MAX / itemsize;
+  if (nitems <= nitems_max)
+    return nitems * itemsize;
+#endif
+  size_overflow();
+}
+
+ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+align_to(ptrdiff_t size, ptrdiff_t alignment)
+{
+  ptrdiff_t lo_bits = alignment - 1, sum = size_sum(size, lo_bits);
+  return sum & ~lo_bits;
 }
 
 #if !HAVE_STRDUP
@@ -464,11 +519,11 @@ static void *
 memcheck(void *ptr)
 {
 	if (ptr == NULL)
-		memory_exhausted(strerror(errno));
+	  memory_exhausted(strerror(HAVE_MALLOC_ERRNO ? errno : ENOMEM));
 	return ptr;
 }
 
-static void * ATTRIBUTE_MALLOC
+ATTRIBUTE_MALLOC static void *
 emalloc(size_t size)
 {
   return memcheck(malloc(size));
@@ -480,47 +535,76 @@ erealloc(void *ptr, size_t size)
   return memcheck(realloc(ptr, size));
 }
 
-static char * ATTRIBUTE_MALLOC
-ecpyalloc (char const *str)
+ATTRIBUTE_MALLOC static char *
+estrdup(char const *str)
 {
   return memcheck(strdup(str));
 }
 
+static ptrdiff_t
+grow_nitems_alloc(ptrdiff_t *nitems_alloc, ptrdiff_t itemsize)
+{
+  ptrdiff_t addend = (*nitems_alloc >> 1) + 1;
+#if defined ckd_add && defined ckd_mul
+  ptrdiff_t product;
+  if (!ckd_add(nitems_alloc, *nitems_alloc, addend)
+      && !ckd_mul(&product, *nitems_alloc, itemsize) && product <= INDEX_MAX)
+    return product;
+#else
+  if (*nitems_alloc <= ((INDEX_MAX - 1) / 3 * 2) / itemsize) {
+    *nitems_alloc += addend;
+    return *nitems_alloc * itemsize;
+  }
+#endif
+  memory_exhausted(_("integer overflow"));
+}
+
 static void *
-growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc)
+growalloc(void *ptr, ptrdiff_t itemsize, ptrdiff_t nitems,
+	  ptrdiff_t *nitems_alloc)
 {
-	if (nitems < *nitems_alloc)
-		return ptr;
-	else {
-		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(_("integer overflow"));
-		*nitems_alloc += (*nitems_alloc >> 1) + 1;
-		return erealloc(ptr, size_product(*nitems_alloc, itemsize));
-	}
+  return (nitems < *nitems_alloc
+	  ? ptr
+	  : erealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
 }
 
 /*
 ** Error handling.
 */
 
+/* In most of the code, an input file name is represented by its index
+   into the main argument vector, except that LEAPSEC_FILENUM stands
+   for leapsec and COMMAND_LINE_FILENUM stands for the command line.  */
+enum { LEAPSEC_FILENUM = -2, COMMAND_LINE_FILENUM = -1 };
+
+/* Return the name of the Ith input file, for diagnostics.  */
+static char const *
+filename(int i)
+{
+  if (i == COMMAND_LINE_FILENUM)
+    return _("command line");
+  else {
+    char const *fname = i == LEAPSEC_FILENUM ? leapsec : main_argv[i];
+    return strcmp(fname, "-") == 0 ? _("standard input") : fname;
+  }
+}
+
 static void
-eats(char const *name, lineno num, char const *rname, lineno rnum)
+eats(int fnum, lineno num, int rfnum, lineno rnum)
 {
-	filename = name;
+	filenum = fnum;
 	linenum = num;
-	rfilename = rname;
+	rfilenum = rfnum;
 	rlinenum = rnum;
 }
 
 static void
-eat(char const *name, lineno num)
+eat(int fnum, lineno num)
 {
-	eats(name, num, NULL, -1);
+	eats(fnum, num, 0, -1);
 }
 
-static void ATTRIBUTE_FORMAT((printf, 1, 0))
+ATTRIBUTE_FORMAT((printf, 1, 0)) static void
 verror(const char *const string, va_list args)
 {
 	/*
@@ -528,16 +612,17 @@ verror(const char *const string, va_list args)
 	**	zic ... 2>&1 | error -t "*" -v
 	** on BSD systems.
 	*/
-	if (filename)
-	  fprintf(stderr, _("\"%s\", line %"PRIdMAX": "), filename, linenum);
+	if (filenum)
+	  fprintf(stderr, _("\"%s\", line %"PRIdMAX": "),
+		  filename(filenum), linenum);
 	vfprintf(stderr, string, args);
-	if (rfilename != NULL)
+	if (rfilenum)
 		fprintf(stderr, _(" (rule from \"%s\", line %"PRIdMAX")"),
-			rfilename, rlinenum);
+			filename(rfilenum), rlinenum);
 	fprintf(stderr, "\n");
 }
 
-static void ATTRIBUTE_FORMAT((printf, 1, 2))
+ATTRIBUTE_FORMAT((printf, 1, 2)) static void
 error(const char *const string, ...)
 {
 	va_list args;
@@ -547,7 +632,7 @@ error(const char *const string, ...)
 	errors = true;
 }
 
-static void ATTRIBUTE_FORMAT((printf, 1, 2))
+ATTRIBUTE_FORMAT((printf, 1, 2)) static void
 warning(const char *const string, ...)
 {
 	va_list args;
@@ -558,8 +643,11 @@ warning(const char *const string, ...)
 	warnings = true;
 }
 
+/* Close STREAM.  If it had an I/O error, report it against DIR/NAME,
+   remove TEMPNAME if nonnull, and then exit.  */
 static void
-close_file(FILE *stream, char const *dir, char const *name)
+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);
@@ -568,23 +656,26 @@ close_file(FILE *stream, char const *dir, char const *name)
 	    dir ? dir : "", dir ? "/" : "",
 	    name ? name : "", name ? ": " : "",
 	    e);
+    if (tempname)
+      remove(tempname);
     exit(EXIT_FAILURE);
   }
 }
 
-static _Noreturn void
+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]' ] [ -t localtime-link ] \\\n"
+	    "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -R '@hi' ] \\\n"
+	    "\t[ -t localtime-link ] \\\n"
 	    "\t[ filename ... ]\n\n"
 	    "Report bugs to %s.\n"),
 	  progname, progname, REPORT_BUGS_TO);
   if (status == EXIT_SUCCESS)
-    close_file(stream, NULL, NULL);
+    close_file(stream, NULL, NULL, NULL);
   exit(status);
 }
 
@@ -592,7 +683,7 @@ usage(FILE *stream, int status)
    ancestors.  After this is done, all files are accessed with names
    relative to DIR.  */
 static void
-change_directory (char const *dir)
+change_directory(char const *dir)
 {
   if (chdir(dir) != 0) {
     int chdir_errno = errno;
@@ -608,7 +699,194 @@ change_directory (char const *dir)
   }
 }
 
-#define TIME_T_BITS_IN_FILE 64
+/* Compare the two links A and B, for a stable sort by link name.  */
+static int
+qsort_linkcmp(void const *a, void const *b)
+{
+  struct link const *l = a;
+  struct link const *m = b;
+  int cmp = strcmp(l->l_linkname, m->l_linkname);
+  if (cmp)
+    return cmp;
+
+  /* The link names are the same.  Make the sort stable by comparing
+     file numbers (where subtraction cannot overflow) and possibly
+     line numbers (where it can).  */
+  cmp = l->l_filenum - m->l_filenum;
+  if (cmp)
+    return cmp;
+  return (l->l_linenum > m->l_linenum) - (l->l_linenum < m->l_linenum);
+}
+
+/* Compare the string KEY to the link B, for bsearch.  */
+static int
+bsearch_linkcmp(void const *key, void const *b)
+{
+  struct link const *m = b;
+  return strcmp(key, m->l_linkname);
+}
+
+/* Make the links specified by the Link lines.  */
+static void
+make_links(void)
+{
+  ptrdiff_t i, j, nalinks, pass_size;
+  if (1 < nlinks)
+    qsort(links, nlinks, sizeof *links, qsort_linkcmp);
+
+  /* Ignore each link superseded by a later link with the same name.  */
+  j = 0;
+  for (i = 0; i < nlinks; i++) {
+    while (i + 1 < nlinks
+	   && strcmp(links[i].l_linkname, links[i + 1].l_linkname) == 0)
+      i++;
+    links[j++] = links[i];
+  }
+  nlinks = pass_size = j;
+
+  /* Walk through the link array making links.  However,
+     if a link's target has not been made yet, append a copy to the
+     end of the array.  The end of the array will gradually fill
+     up with a small sorted subsequence of not-yet-made links.
+     nalinks counts all the links in the array, including copies.
+     When we reach the copied subsequence, it may still contain
+     a link to a not-yet-made link, so the process repeats.
+     At any given point in time, the link array consists of the
+     following subregions, where 0 <= i <= j <= nalinks and
+     0 <= nlinks <= nalinks:
+
+       0 .. (i - 1):
+	 links that either have been made, or have been copied to a
+	 later point point in the array (this later point can be in
+	 any of the three subregions)
+       i .. (j - 1):
+	 not-yet-made links for this pass
+       j .. (nalinks - 1):
+	 not-yet-made links that this pass has skipped because
+	 they were links to not-yet-made links
+
+     The first subregion might not be sorted if nlinks < i;
+     the other two subregions are sorted.  This algorithm does
+     not alter entries 0 .. (nlinks - 1), which remain sorted.
+
+     If there are L links, this algorithm is O(C*L*log(L)) where
+     C is the length of the longest link chain.  Usually C is
+     short (e.g., 3) though its worst-case value is L.  */
+
+  j = nalinks = nlinks;
+
+  for (i = 0; i < nalinks; i++) {
+    struct link *l;
+
+    eat(links[i].l_filenum, links[i].l_linenum);
+
+    /* If this pass examined all its links, start the next pass.  */
+    if (i == j) {
+      if (nalinks - i == pass_size) {
+	error(_("\"Link %s %s\" is part of a link cycle"),
+	      links[i].l_target, links[i].l_linkname);
+	break;
+      }
+      j = nalinks;
+      pass_size = nalinks - i;
+    }
+
+    /* Diagnose self links, which the cycle detection algorithm would not
+       otherwise catch.  */
+    if (strcmp(links[i].l_target, links[i].l_linkname) == 0) {
+      error(_("link %s targets itself"), links[i].l_target);
+      continue;
+    }
+
+    /* Make this link unless its target has not been made yet.  */
+    l = bsearch(links[i].l_target, &links[i + 1], j - (i + 1),
+		sizeof *links, bsearch_linkcmp);
+    if (!l)
+      l = bsearch(links[i].l_target, &links[j], nalinks - j,
+		  sizeof *links, bsearch_linkcmp);
+    if (!l)
+      dolink(links[i].l_target, links[i].l_linkname, false);
+    else {
+      /* The link target has not been made yet; copy the link to the end.  */
+      links = growalloc(links, sizeof *links, nalinks, &nlinks_alloc);
+      links[nalinks++] = links[i];
+    }
+
+    if (noise && i < nlinks) {
+      if (l)
+	warning(_("link %s targeting link %s mishandled by pre-2023 zic"),
+		links[i].l_linkname, links[i].l_target);
+      else if (bsearch(links[i].l_target, links, nlinks, sizeof *links,
+		       bsearch_linkcmp))
+	warning(_("link %s targeting link %s"),
+		links[i].l_linkname, links[i].l_target);
+    }
+  }
+}
+
+/* Simple signal handling: just set a flag that is checked
+   periodically outside critical sections.  To set up the handler,
+   prefer sigaction if available to close a signal race.  */
+
+static sig_atomic_t got_signal;
+
+static void
+signal_handler(int sig)
+{
+#ifndef SA_SIGINFO
+  signal(sig, signal_handler);
+#endif
+  got_signal = sig;
+}
+
+/* Arrange for SIGINT etc. to be caught by the handler.  */
+static void
+catch_signals(void)
+{
+  static int const signals[] = {
+#ifdef SIGHUP
+    SIGHUP,
+#endif
+    SIGINT,
+#ifdef SIGPIPE
+    SIGPIPE,
+#endif
+    SIGTERM
+  };
+  int i;
+  for (i = 0; i < sizeof signals / sizeof signals[0]; i++) {
+#ifdef SA_SIGINFO
+    struct sigaction act0, act;
+    act.sa_handler = signal_handler;
+    sigemptyset(&act.sa_mask);
+    act.sa_flags = 0;
+    if (sigaction(signals[i], &act, &act0) == 0
+	&& ! (act0.sa_flags & SA_SIGINFO) && act0.sa_handler == SIG_IGN) {
+      sigaction(signals[i], &act0, NULL);
+      got_signal = 0;
+    }
+#else
+    if (signal(signals[i], signal_handler) == SIG_IGN) {
+      signal(signals[i], SIG_IGN);
+      got_signal = 0;
+    }
+#endif
+  }
+}
+
+/* If a signal has arrived, terminate zic with appropriate status.  */
+static void
+check_for_signal(void)
+{
+  int sig = got_signal;
+  if (sig) {
+    signal(sig, SIG_DFL);
+    raise(sig);
+    abort(); /* A bug in 'raise'.  */
+  }
+}
+
+enum { TIME_T_BITS_IN_FILE = 64 };
 
 /* The minimum and maximum values representable in a TZif file.  */
 static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
@@ -619,12 +897,13 @@ static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
 static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
 static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
 
+/* The time specified by the -R option, defaulting to MIN_TIME;
+   or lo_time, whichever is greater.  */
+static zic_t redundant_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
+
 /* The time specified by an Expires line, or negative if no such line.  */
 static zic_t leapexpires = -1;
 
-/* The time specified by an #expires comment, or negative if no such line.  */
-static zic_t comment_leapexpires = -1;
-
 /* Set the time range of the output to TIMERANGE.
    Return true if successful.  */
 static bool
@@ -634,35 +913,49 @@ timerange_option(char *timerange)
   char *lo_end = timerange, *hi_end;
   if (*timerange == '@') {
     errno = 0;
-    lo = strtoimax (timerange + 1, &lo_end, 10);
+    lo = strtoimax(timerange + 1, &lo_end, 10);
     if (lo_end == timerange + 1 || (lo == INTMAX_MAX && errno == ERANGE))
       return false;
   }
   hi_end = lo_end;
   if (lo_end[0] == '/' && lo_end[1] == '@') {
     errno = 0;
-    hi = strtoimax (lo_end + 2, &hi_end, 10);
+    hi = strtoimax(lo_end + 2, &hi_end, 10);
     if (hi_end == lo_end + 2 || hi == INTMAX_MIN)
       return false;
     hi -= ! (hi == INTMAX_MAX && errno == ERANGE);
   }
   if (*hi_end || hi < lo || max_time < lo || hi < min_time)
     return false;
-  lo_time = lo < min_time ? min_time : lo;
-  hi_time = max_time < hi ? max_time : hi;
+  lo_time = max(lo, min_time);
+  hi_time = min(hi, max_time);
   return true;
 }
 
+/* Generate redundant time stamps up to OPT.  Return true if successful.  */
+static bool
+redundant_time_option(char *opt)
+{
+  if (*opt == '@') {
+    intmax_t redundant;
+    char *opt_end;
+    redundant = strtoimax(opt + 1, &opt_end, 10);
+    if (opt_end != opt + 1 && !*opt_end) {
+      redundant_time = max(redundant_time, redundant);
+      return true;
+    }
+  }
+  return false;
+}
+
 static const char *	psxrules;
 static const char *	lcltime;
 static const char *	directory;
-static const char *	leapsec;
 static const char *	tzdefault;
-static const char *	yitcommand;
 
 /* -1 if the TZif output file should be slim, 0 if default, 1 if the
-   output should be fat for backward compatibility.  Currently the
-   default is fat, although this may change.  */
+   output should be fat for backward compatibility.  ZIC_BLOAT_DEFAULT
+   determines the default.  */
 static int bloat;
 
 static bool
@@ -672,7 +965,7 @@ want_bloat(void)
 }
 
 #ifndef ZIC_BLOAT_DEFAULT
-# define ZIC_BLOAT_DEFAULT "fat"
+# define ZIC_BLOAT_DEFAULT "slim"
 #endif
 
 int
@@ -687,12 +980,13 @@ main(int argc, char **argv)
 #endif
 #if HAVE_GETTEXT
 	setlocale(LC_ALL, "");
-#ifdef TZ_DOMAINDIR
+# ifdef TZ_DOMAINDIR
 	bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
-#endif /* defined TEXTDOMAINDIR */
+# endif /* defined TEXTDOMAINDIR */
 	textdomain(TZ_DOMAIN);
 #endif /* HAVE_GETTEXT */
-	progname = argv[0];
+	main_argv = argv;
+	progname = argv[0] ? argv[0] : "zic";
 	if (TYPE_BIT(zic_t) < 64) {
 		fprintf(stderr, "%s: %s\n", progname,
 			_("wild compilation-time specification of zic_t"));
@@ -701,12 +995,13 @@ main(int argc, char **argv)
 	for (k = 1; k < argc; k++)
 		if (strcmp(argv[k], "--version") == 0) {
 			printf("zic %s%s\n", PKGVERSION, TZVERSION);
-			close_file(stdout, NULL, NULL);
+			close_file(stdout, NULL, NULL, NULL);
 			return EXIT_SUCCESS;
 		} else if (strcmp(argv[k], "--help") == 0) {
 			usage(stdout, EXIT_SUCCESS);
 		}
-	while ((c = getopt(argc, argv, "b:d:l:L:p:r:st:vy:")) != EOF && c != -1)
+	while ((c = getopt(argc, argv, "b:d:l:L:p:r:R:st:vy:")) != EOF
+	       && c != -1)
 		switch (c) {
 			default:
 				usage(stderr, EXIT_FAILURE);
@@ -727,7 +1022,8 @@ main(int argc, char **argv)
 					directory = optarg;
 				else {
 					fprintf(stderr,
-_("%s: More than one -d option specified\n"),
+						_("%s: More than one -d option"
+						  " specified\n"),
 						progname);
 					return EXIT_FAILURE;
 				}
@@ -737,7 +1033,8 @@ _("%s: More than one -d option specified\n"),
 					lcltime = optarg;
 				else {
 					fprintf(stderr,
-_("%s: More than one -l option specified\n"),
+						_("%s: More than one -l option"
+						  " specified\n"),
 						progname);
 					return EXIT_FAILURE;
 				}
@@ -747,7 +1044,8 @@ _("%s: More than one -l option specified\n"),
 					psxrules = optarg;
 				else {
 					fprintf(stderr,
-_("%s: More than one -p option specified\n"),
+						_("%s: More than one -p option"
+						  " specified\n"),
 						progname);
 					return EXIT_FAILURE;
 				}
@@ -763,22 +1061,15 @@ _("%s: More than one -p option specified\n"),
 				tzdefault = optarg;
 				break;
 			case 'y':
-				if (yitcommand == NULL) {
-					warning(_("-y is obsolescent"));
-					yitcommand = optarg;
-				} else {
-					fprintf(stderr,
-_("%s: More than one -y option specified\n"),
-						progname);
-					return EXIT_FAILURE;
-				}
+				warning(_("-y ignored"));
 				break;
 			case 'L':
 				if (leapsec == NULL)
 					leapsec = optarg;
 				else {
 					fprintf(stderr,
-_("%s: More than one -L option specified\n"),
+						_("%s: More than one -L option"
+						  " specified\n"),
 						progname);
 					return EXIT_FAILURE;
 				}
@@ -789,44 +1080,64 @@ _("%s: More than one -L option specified\n"),
 			case 'r':
 				if (timerange_given) {
 				  fprintf(stderr,
-_("%s: More than one -r option specified\n"),
+					  _("%s: More than one -r option"
+					    " specified\n"),
 					  progname);
 				  return EXIT_FAILURE;
 				}
 				if (! timerange_option(optarg)) {
 				  fprintf(stderr,
-_("%s: invalid time range: %s\n"),
+					  _("%s: invalid time range: %s\n"),
 					  progname, optarg);
 				  return EXIT_FAILURE;
 				}
 				timerange_given = true;
 				break;
+			case 'R':
+				if (! redundant_time_option(optarg)) {
+				  fprintf(stderr, _("%s: invalid time: %s\n"),
+					  progname, optarg);
+				  return EXIT_FAILURE;
+				}
+				break;
 			case 's':
 				warning(_("-s ignored"));
 				break;
 		}
 	if (optind == argc - 1 && strcmp(argv[optind], "=") == 0)
 		usage(stderr, EXIT_FAILURE);	/* usage message by request */
-	if (bloat == 0)
-	  bloat = strcmp(ZIC_BLOAT_DEFAULT, "slim") == 0 ? -1 : 1;
+	if (hi_time + (hi_time < ZIC_MAX) < redundant_time) {
+	  fprintf(stderr, _("%s: -R time exceeds -r cutoff\n"), progname);
+	  return EXIT_FAILURE;
+	}
+	if (redundant_time < lo_time)
+	  redundant_time = lo_time;
+	if (bloat == 0) {
+	  static char const bloat_default[] = ZIC_BLOAT_DEFAULT;
+	  if (strcmp(bloat_default, "slim") == 0)
+	    bloat = -1;
+	  else if (strcmp(bloat_default, "fat") == 0)
+	    bloat = 1;
+	  else
+	    abort(); /* Configuration error.  */
+	}
 	if (directory == NULL)
 		directory = TZDIR;
 	if (tzdefault == NULL)
 		tzdefault = TZDEFAULT;
-	if (yitcommand == NULL)
-		yitcommand = "yearistype";
 
 	if (optind < argc && leapsec != NULL) {
-		infile(leapsec);
+		infile(LEAPSEC_FILENUM, leapsec);
 		adjleap();
 	}
 
 	for (k = optind; k < argc; k++)
-		infile(argv[k]);
+	  infile(k, argv[k]);
 	if (errors)
 		return EXIT_FAILURE;
 	associate();
 	change_directory(directory);
+	catch_signals();
 	for (i = 0; i < nzones; i = j) {
 		/*
 		** Find the next non-continuation zone entry.
@@ -835,24 +1146,13 @@ _("%s: invalid time range: %s\n"),
 			continue;
 		outzone(&zones[i], j - i);
 	}
-	/*
-	** Make links.
-	*/
-	for (i = 0; i < nlinks; ++i) {
-		eat(links[i].l_filename, links[i].l_linenum);
-		dolink(links[i].l_from, links[i].l_to, false);
-		if (noise)
-			for (j = 0; j < nlinks; ++j)
-				if (strcmp(links[i].l_to,
-					links[j].l_from) == 0)
-						warning(_("link to link"));
-	}
+	make_links();
 	if (lcltime != NULL) {
-		eat(_("command line"), 1);
+		eat(COMMAND_LINE_FILENUM, 1);
 		dolink(lcltime, tzdefault, true);
 	}
 	if (psxrules != NULL) {
-		eat(_("command line"), 1);
+		eat(COMMAND_LINE_FILENUM, 1);
 		dolink(psxrules, TZDEFRULES, true);
 	}
 	if (warnings && (ferror(stderr) || fclose(stderr) != 0))
@@ -868,9 +1168,9 @@ componentcheck(char const *name, char const *component,
 	ptrdiff_t component_len = component_end - component;
 	if (component_len == 0) {
 	  if (!*name)
-	    error (_("empty file name"));
+	    error(_("empty file name"));
 	  else
-	    error (_(component == name
+	    error(_(component == name
 		     ? "file name '%s' begins with '/'"
 		     : *component_end
 		     ? "file name '%s' contains '//'"
@@ -931,34 +1231,197 @@ 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
+/* Return a random uint_fast64_t.  */
+static uint_fast64_t
+get_rand_u64(void)
+{
+#if HAVE_GETRANDOM
+  static uint_fast64_t entropy_buffer[max(1, 256 / sizeof(uint_fast64_t))];
+  static int nwords;
+  if (!nwords) {
+    ssize_t s;
+    do
+      s = getrandom(entropy_buffer, sizeof entropy_buffer, 0);
+    while (s < 0 && errno == EINTR);
+
+    nwords = s < 0 ? -1 : s / sizeof *entropy_buffer;
+  }
+  if (0 < nwords)
+    return entropy_buffer[--nwords];
+#endif
+
+  /* getrandom didn't work, so fall back on portable code that is
+     not the best because the seed isn't cryptographically random and
+     'rand' might not be cryptographically secure.  */
+  {
+    static bool initialized;
+    if (!initialized) {
+      srand(time(NULL));
+      initialized = true;
+    }
+  }
+
+  /* Return a random number if rand() yields a random number and in
+     the typical case where RAND_MAX is one less than a power of two.
+     In other cases this code yields a sort-of-random number.  */
+  {
+    uint_fast64_t rand_max = RAND_MAX,
+      nrand = rand_max < UINT_FAST64_MAX ? rand_max + 1 : 0,
+      rmod = INT_MAX < UINT_FAST64_MAX ? 0 : UINT_FAST64_MAX / nrand + 1,
+      r = 0, rmax = 0;
+
+    do {
+      uint_fast64_t rmax1 = rmax;
+      if (rmod) {
+	/* Avoid signed integer overflow on theoretical platforms
+	   where uint_fast64_t promotes to int.  */
+	rmax1 %= rmod;
+	r %= rmod;
+      }
+      rmax1 = nrand * rmax1 + rand_max;
+      r = nrand * r + rand();
+      rmax = rmax < rmax1 ? rmax1 : UINT_FAST64_MAX;
+    } while (rmax < UINT_FAST64_MAX);
+
+    return r;
+  }
+}
+
+/* Generate a randomish name in the same directory as *NAME.  If
+   *NAMEALLOC, put the name into *NAMEALLOC which is assumed to be
+   that returned by a previous call and is thus already almost set up
+   and equal to *NAME; otherwise, allocate a new name and put its
+   address into both *NAMEALLOC and *NAME.  */
+static void
+random_dirent(char const **name, char **namealloc)
+{
+  char const *src = *name;
+  char *dst = *namealloc;
+  static char const prefix[] = ".zic";
+  static char const alphabet[] =
+    "abcdefghijklmnopqrstuvwxyz"
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    "0123456789";
+  enum { prefixlen = sizeof prefix - 1, alphabetlen = sizeof alphabet - 1 };
+  int suffixlen = 6;
+  char const *lastslash = strrchr(src, '/');
+  ptrdiff_t dirlen = lastslash ? lastslash + 1 - src : 0;
+  int i;
+  uint_fast64_t r;
+  uint_fast64_t base = alphabetlen;
+
+  /* BASE**6 */
+  uint_fast64_t base__6 = base * base * base * base * base * base;
+
+  /* The largest uintmax_t that is a multiple of BASE**6.  Any random
+     uintmax_t value that is this value or greater, yields a biased
+     remainder when divided by BASE**6.  UNFAIR_MIN equals the
+     mathematical value of ((UINTMAX_MAX + 1) - (UINTMAX_MAX + 1) % BASE**6)
+     computed without overflow.  */
+  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';
+    *name = *namealloc = dst;
+  }
+
+  do
+    r = get_rand_u64();
+  while (unfair_min <= r);
+
+  for (i = 0; i < suffixlen; i++) {
+    dst[dirlen + prefixlen + i] = alphabet[r % alphabetlen];
+    r /= alphabetlen;
+  }
+}
+
+/* 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
+   *TEMPNAME.  If *TEMPNAME is null, allocate the name of any such
+   temporary file; otherwise, reuse *TEMPNAME's storage, which is
+   already set up and only needs its trailing suffix updated.  */
+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) {
+      mkdirs(*outname, true);
+      dirs_made = true;
+    } else if (fopen_errno == EEXIST)
+      random_dirent(outname, tempname);
+    else {
+      fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
+	      progname, directory, *outname, strerror(fopen_errno));
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  return fp;
+}
+
+/* If TEMPNAME, the result is in the temporary file TEMPNAME even
+   though the user wanted it in NAME, so rename TEMPNAME to NAME.
+   Report an error and exit if there is trouble.  Also, free TEMPNAME.  */
+static void
+rename_dest(char *tempname, char const *name)
+{
+  if (tempname) {
+    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));
+      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.  */
 static char *
-relname(char const *from, char const *to)
+relname(char const *target, char const *linkname)
 {
-  size_t i, taillen, dotdotetcsize;
-  size_t dir_len = 0, dotdots = 0, linksize = SIZE_MAX;
-  char const *f = from;
+  size_t i, taillen, dir_len = 0, dotdots = 0;
+  ptrdiff_t dotdotetcsize, linksize = INDEX_MAX;
+  char const *f = target;
   char *result = NULL;
-  if (*to == '/') {
+  if (*linkname == '/') {
     /* Make F absolute too.  */
     size_t len = strlen(directory);
-    bool needslash = len && directory[len - 1] != '/';
-    linksize = len + needslash + strlen(from) + 1;
+    size_t lenslash = len + (len && directory[len - 1] != '/');
+    size_t targetsize = strlen(target) + 1;
+    linksize = size_sum(lenslash, targetsize);
     f = result = emalloc(linksize);
-    strcpy(result, directory);
+    memcpy(result, directory, len);
     result[len] = '/';
-    strcpy(result + len + needslash, from);
+    memcpy(result + lenslash, target, targetsize);
   }
-  for (i = 0; f[i] && f[i] == to[i]; i++)
+  for (i = 0; f[i] && f[i] == linkname[i]; i++)
     if (f[i] == '/')
       dir_len = i + 1;
-  for (; to[i]; i++)
-    dotdots += to[i] == '/' && to[i - 1] != '/';
+  for (; linkname[i]; i++)
+    dotdots += linkname[i] == '/' && linkname[i - 1] != '/';
   taillen = strlen(f + dir_len);
-  dotdotetcsize = 3 * dotdots + taillen + 1;
+  dotdotetcsize = size_sum(size_product(dotdots, 3), taillen + 1);
   if (dotdotetcsize <= linksize) {
     if (!result)
       result = emalloc(dotdotetcsize);
@@ -969,83 +1432,124 @@ relname(char const *from, char const *to)
   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)
+/* 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.  */
+static bool
+same_parent_dirs(char const *a, char const *b)
 {
-  int r = linkat(AT_FDCWD, from, AT_FDCWD, to, AT_SYMLINK_FOLLOW);
-  return r == 0 ? 0 : errno;
+  for (; *a == *b; a++, b++)
+    if (!*a)
+      return true;
+  return ! (strchr(a, '/') || strchr(b, '/'));
 }
 
 static void
-dolink(char const *fromfield, char const *tofield, bool staysymlink)
+dolink(char const *target, char const *linkname, bool staysymlink)
 {
-	bool todirs_made = false;
+	bool linkdirs_made = false;
 	int link_errno;
+	char *tempname = NULL;
+	char const *outname = linkname;
+	int targetissym = -2, linknameissym = -2;
 
-	/*
-	** We get to be careful here since
-	** there's a fair chance of root running us.
-	*/
-	if (itsdir(fromfield)) {
-		fprintf(stderr, _("%s: link from %s/%s failed: %s\n"),
-			progname, directory, fromfield, strerror(EPERM));
-		exit(EXIT_FAILURE);
-	}
-	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);
+	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);
+	    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);
+
+	while (true) {
+	  if (linkat(AT_FDCWD, target, AT_FDCWD, outname, AT_SYMLINK_FOLLOW)
+	      == 0) {
+	    link_errno = 0;
+	    break;
+	  }
+	  link_errno = errno;
+	  /* Linux 2.6.16 and 2.6.17 mishandle AT_SYMLINK_FOLLOW.  */
+	  if (link_errno == EINVAL)
+	    link_errno = ENOTSUP;
+#if HAVE_LINK
+	  /* If linkat is not supported, fall back on link(A, B).
+	     However, skip this if A is a relative symlink
+	     and A and B might not have the same parent directory.
+	     On some platforms link(A, B) does not follow a symlink A,
+	     and if A is relative it might misbehave elsewhere.  */
+	  if (link_errno == ENOTSUP
+	      && (same_parent_dirs(target, outname)
+		  || 0 <= itssymlink(target, &targetissym))) {
+	    if (link(target, outname) == 0) {
+	      link_errno = 0;
+	      break;
+	    }
+	    link_errno = errno;
+	  }
+#endif
+	  if (link_errno == EXDEV || link_errno == ENOTSUP)
+	    break;
+
+	  if (link_errno == EEXIST) {
+	    staysymlink &= !tempname;
+	    random_dirent(&outname, &tempname);
+	    if (staysymlink && itssymlink(linkname, &linknameissym))
+	      break;
+	  } else if (link_errno == ENOENT && !linkdirs_made) {
+	    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,
+		    strerror(link_errno));
+	    exit(EXIT_FAILURE);
+	  }
 	}
 	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 (!todirs_made
-	      && (symlink_errno == ENOENT || symlink_errno == ENOTSUP)) {
-	    mkdirs(tofield, true);
-	    if (symlink_errno == ENOENT)
-	      symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno;
+	  bool absolute = *target == '/';
+	  char *linkalloc = absolute ? NULL : relname(target, linkname);
+	  char const *contents = absolute ? target : linkalloc;
+	  int symlink_errno;
+
+	  while (true) {
+	    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;
 	  }
 	  free(linkalloc);
 	  if (symlink_errno == 0) {
-	    if (link_errno != ENOTSUP)
+	    if (link_errno != ENOTSUP && link_errno != EEXIST)
 	      warning(_("symbolic link used because hard link failed: %s"),
 		      strerror(link_errno));
 	  } else {
 	    FILE *fp, *tp;
 	    int c;
-	    fp = fopen(fromfield, "rb");
+	    fp = fopen(target, "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);
+		      progname, directory, target, e);
 	      exit(EXIT_FAILURE);
 	    }
+	    tp = open_outfile(&outname, &tempname);
 	    while ((c = getc(fp)) != EOF)
 	      putc(c, tp);
-	    close_file(fp, directory, fromfield);
-	    close_file(tp, directory, tofield);
+	    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));
@@ -1054,37 +1558,20 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink)
 		      strerror(symlink_errno));
 	  }
 	}
+	rename_dest(tempname, linkname);
 }
 
-/* Return true if NAME is a directory.  */
-static bool
-itsdir(char const *name)
-{
-	struct stat st;
-	int res = stat(name, &st);
-#ifdef S_ISDIR
-	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 dir;
-	}
-	return false;
-}
-
-/* Return true if NAME is a symbolic link.  */
-static bool
-itssymlink(char const *name)
+/* Return 1 if NAME is an absolute symbolic link, -1 if it is relative,
+   0 if it is not a symbolic link.  If *CACHE is not -2, it is the
+   cached result of a previous call to this function with the same NAME.  */
+static int
+itssymlink(char const *name, int *cache)
 {
-  char c;
-  return 0 <= readlink(name, &c, 1);
+  if (*cache == -2) {
+    char c = '\0';
+    *cache = readlink(name, &c, 1) < 0 ? 0 : c == '/' ? 1 : -1;
+  }
+  return *cache;
 }
 
 /*
@@ -1098,8 +1585,8 @@ itssymlink(char const *name)
 static int
 rcomp(const void *cp1, const void *cp2)
 {
-	return strcmp(((const struct rule *) cp1)->r_name,
-		((const struct rule *) cp2)->r_name);
+  struct rule const *r1 = cp1, *r2 = cp2;
+  return strcmp(r1->r_name, r2->r_name);
 }
 
 static void
@@ -1109,28 +1596,26 @@ associate(void)
 	register struct rule *	rp;
 	register ptrdiff_t i, j, base, out;
 
-	if (nrules != 0) {
+	if (1 < nrules) {
 		qsort(rules, nrules, sizeof *rules, rcomp);
 		for (i = 0; i < nrules - 1; ++i) {
 			if (strcmp(rules[i].r_name,
 				rules[i + 1].r_name) != 0)
 					continue;
-			if (strcmp(rules[i].r_filename,
-				rules[i + 1].r_filename) == 0)
+			if (rules[i].r_filenum == rules[i + 1].r_filenum)
 					continue;
-			eat(rules[i].r_filename, rules[i].r_linenum);
+			eat(rules[i].r_filenum, rules[i].r_linenum);
 			warning(_("same rule name in multiple files"));
-			eat(rules[i + 1].r_filename, rules[i + 1].r_linenum);
+			eat(rules[i + 1].r_filenum, rules[i + 1].r_linenum);
 			warning(_("same rule name in multiple files"));
 			for (j = i + 2; j < nrules; ++j) {
 				if (strcmp(rules[i].r_name,
 					rules[j].r_name) != 0)
 						break;
-				if (strcmp(rules[i].r_filename,
-					rules[j].r_filename) == 0)
+				if (rules[i].r_filenum == rules[j].r_filenum)
 						continue;
-				if (strcmp(rules[i + 1].r_filename,
-					rules[j].r_filename) == 0)
+				if (rules[i + 1].r_filenum
+				    == rules[j].r_filenum)
 						continue;
 				break;
 			}
@@ -1161,7 +1646,7 @@ associate(void)
 			/*
 			** Maybe we have a local standard time offset.
 			*/
-			eat(zp->z_filename, zp->z_linenum);
+			eat(zp->z_filenum, zp->z_linenum);
 			zp->z_save = getsave(zp->z_rule, &zp->z_isdst);
 			/*
 			** Note, though, that if there's no rule,
@@ -1175,20 +1660,48 @@ associate(void)
 		exit(EXIT_FAILURE);
 }
 
+/* Read a text line from FP into BUF, which is of size BUFSIZE.
+   Terminate it with a NUL byte instead of a newline.
+   Return true if successful, false if EOF.
+   On error, report the error and exit.  */
+static bool
+inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
+{
+  ptrdiff_t linelen = 0, ch;
+  while ((ch = getc(fp)) != '\n') {
+    if (ch < 0) {
+      if (ferror(fp)) {
+	error(_("input error"));
+	exit(EXIT_FAILURE);
+      }
+      if (linelen == 0)
+	return false;
+      error(_("unterminated line"));
+      exit(EXIT_FAILURE);
+    }
+    if (!ch) {
+      error(_("NUL input byte"));
+      exit(EXIT_FAILURE);
+    }
+    buf[linelen++] = ch;
+    if (linelen == bufsize) {
+      error(_("line too long"));
+      exit(EXIT_FAILURE);
+    }
+  }
+  buf[linelen] = '\0';
+  return true;
+}
+
 static void
-infile(const char *name)
+infile(int fnum, char const *name)
 {
 	register FILE *			fp;
-	register char **		fields;
-	register char *			cp;
 	register const struct lookup *	lp;
-	register int			nfields;
 	register bool			wantcont;
 	register lineno			num;
-	char				buf[BUFSIZ];
 
 	if (strcmp(name, "-") == 0) {
-		name = _("standard input");
 		fp = stdin;
 	} else if ((fp = fopen(name, "r")) == NULL) {
 		const char *e = strerror(errno);
@@ -1199,32 +1712,23 @@ infile(const char *name)
 	}
 	wantcont = false;
 	for (num = 1; ; ++num) {
-		eat(name, num);
-		if (fgets(buf, sizeof buf, fp) != buf)
-			break;
-		cp = strchr(buf, '\n');
-		if (cp == NULL) {
-			error(_("line too long"));
-			exit(EXIT_FAILURE);
-		}
-		*cp = '\0';
-		fields = getfields(buf);
-		nfields = 0;
-		while (fields[nfields] != NULL) {
-			static char	nada;
-
-			if (strcmp(fields[nfields], "-") == 0)
-				fields[nfields] = &nada;
-			++nfields;
-		}
+		enum { bufsize_bound
+		  = (min(INT_MAX, INDEX_MAX) / FORMAT_LEN_GROWTH_BOUND) };
+		char buf[min(_POSIX2_LINE_MAX, bufsize_bound)];
+		int nfields;
+		char *fields[MAX_FIELDS];
+		eat(fnum, num);
+		if (!inputline(fp, buf, sizeof buf))
+		  break;
+		nfields = getfields(buf, fields,
+				    sizeof fields / sizeof *fields);
 		if (nfields == 0) {
-		  if (name == leapsec && *buf == '#')
-		    sscanf(buf, "#expires %"SCNdZIC, &comment_leapexpires);
+			/* nothing to do */
 		} else if (wantcont) {
 			wantcont = inzcont(fields, nfields);
 		} else {
 			struct lookup const *line_codes
-			  = name == leapsec ? leap_line_codes : zi_line_codes;
+			  = fnum < 0 ? leap_line_codes : zi_line_codes;
 			lp = byword(fields[0], line_codes);
 			if (lp == NULL)
 				error(_("input line of unknown type"));
@@ -1248,16 +1752,11 @@ infile(const char *name)
 					inexpires(fields, nfields);
 					wantcont = false;
 					break;
-				default:	/* "cannot happen" */
-					fprintf(stderr,
-_("%s: panic: Invalid l_value %d\n"),
-						progname, lp->l_value);
-					exit(EXIT_FAILURE);
+				default: unreachable();
 			}
 		}
-		free(fields);
 	}
-	close_file(fp, NULL, filename);
+	close_file(fp, NULL, filename(fnum), NULL);
 	if (wantcont)
 		error(_("expected continuation line not found"));
 }
@@ -1291,15 +1790,15 @@ gethms(char const *string, char const *errstring)
 	  default: ok = false; break;
 	  case 8:
 	    ok = '0' <= xr && xr <= '9';
-	    /* fallthrough */
+	    ATTRIBUTE_FALLTHROUGH;
 	  case 7:
 	    ok &= ssx == '.';
 	    if (ok && noise)
 	      warning(_("fractional seconds rejected by"
 			" pre-2018 versions of zic"));
-	    /* fallthrough */
-	  case 5: ok &= mmx == ':'; /* fallthrough */
-	  case 3: ok &= hhx == ':'; /* fallthrough */
+	    ATTRIBUTE_FALLTHROUGH;
+	  case 5: ok &= mmx == ':'; ATTRIBUTE_FALLTHROUGH;
+	  case 3: ok &= hhx == ':'; ATTRIBUTE_FALLTHROUGH;
 	  case 1: break;
 	}
 	if (!ok) {
@@ -1329,7 +1828,7 @@ getsave(char *field, bool *isdst)
 {
   int dst = -1;
   zic_t save;
-  size_t fieldlen = strlen(field);
+  ptrdiff_t fieldlen = strlen(field);
   if (fieldlen != 0) {
     char *ep = field + fieldlen - 1;
     switch (*ep) {
@@ -1345,7 +1844,7 @@ getsave(char *field, bool *isdst)
 static void
 inrule(char **fields, int nfields)
 {
-	static struct rule	r;
+	struct rule r;
 
 	if (nfields != RULE_FIELDS) {
 		error(_("wrong number of fields on Rule line"));
@@ -1360,13 +1859,15 @@ inrule(char **fields, int nfields)
 		error(_("Invalid rule name \"%s\""), fields[RF_NAME]);
 		return;
 	}
-	r.r_filename = filename;
+	r.r_filenum = filenum;
 	r.r_linenum = linenum;
 	r.r_save = getsave(fields[RF_SAVE], &r.r_isdst);
-	rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND],
-		fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]);
-	r.r_name = ecpyalloc(fields[RF_NAME]);
-	r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]);
+	if (!rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR],
+		     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]);
 	if (max_abbrvar_len < strlen(r.r_abbrvar))
 		max_abbrvar_len = strlen(r.r_abbrvar);
 	rules = growalloc(rules, sizeof *rules, nrules, &nrules_alloc);
@@ -1383,25 +1884,23 @@ inzone(char **fields, int nfields)
 		return false;
 	}
 	if (lcltime != NULL && strcmp(fields[ZF_NAME], tzdefault) == 0) {
-		error(
-_("\"Zone %s\" line and -l option are mutually exclusive"),
-			tzdefault);
-		return false;
+	  error(_("\"Zone %s\" line and -l option are mutually exclusive"),
+		tzdefault);
+	  return false;
 	}
 	if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL) {
-		error(
-_("\"Zone %s\" line and -p option are mutually exclusive"),
-			TZDEFRULES);
-		return false;
+	  error(_("\"Zone %s\" line and -p option are mutually exclusive"),
+		TZDEFRULES);
+	  return false;
 	}
 	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 %"PRIdMAX")"),
-					fields[ZF_NAME],
-					zones[i].z_filename,
-					zones[i].z_linenum);
+				      fields[ZF_NAME],
+				      filename(zones[i].z_filenum),
+				      zones[i].z_linenum);
 				return false;
 		}
 	return inzsub(fields, nfields, false);
@@ -1422,7 +1921,8 @@ inzsub(char **fields, int nfields, bool iscont)
 {
 	register char *		cp;
 	char *			cp1;
-	static struct zone	z;
+	struct zone z;
+	int format_len;
 	register int		i_stdoff, i_rule, i_format;
 	register int		i_untilyear, i_untilmonth;
 	register int		i_untilday, i_untiltime;
@@ -1436,7 +1936,6 @@ inzsub(char **fields, int nfields, bool iscont)
 		i_untilmonth = ZFC_TILMONTH;
 		i_untilday = ZFC_TILDAY;
 		i_untiltime = ZFC_TILTIME;
-		z.z_name = NULL;
 	} else if (!namecheck(fields[ZF_NAME]))
 		return false;
 	else {
@@ -1447,9 +1946,8 @@ inzsub(char **fields, int nfields, bool iscont)
 		i_untilmonth = ZF_TILMONTH;
 		i_untilday = ZF_TILDAY;
 		i_untiltime = ZF_TILTIME;
-		z.z_name = ecpyalloc(fields[ZF_NAME]);
 	}
-	z.z_filename = filename;
+	z.z_filenum = filenum;
 	z.z_linenum = linenum;
 	z.z_stdoff = gethms(fields[i_stdoff], _("invalid UT offset"));
 	if ((cp = strchr(fields[i_format], '%')) != 0) {
@@ -1459,29 +1957,24 @@ inzsub(char **fields, int nfields, bool iscont)
 			return false;
 		}
 	}
-	z.z_rule = ecpyalloc(fields[i_rule]);
-	z.z_format = cp1 = ecpyalloc(fields[i_format]);
 	z.z_format_specifier = cp ? *cp : '\0';
-	if (z.z_format_specifier == 'z') {
-	  if (noise)
-	    warning(_("format '%s' not handled by pre-2015 versions of zic"),
-		    z.z_format);
-	  cp1[cp - fields[i_format]] = 's';
-	}
-	if (max_format_len < strlen(z.z_format))
-		max_format_len = strlen(z.z_format);
+	format_len = strlen(fields[i_format]);
+	if (max_format_len < format_len)
+	  max_format_len = format_len;
 	hasuntil = nfields > i_untilyear;
 	if (hasuntil) {
-		z.z_untilrule.r_filename = filename;
+		z.z_untilrule.r_filenum = filenum;
 		z.z_untilrule.r_linenum = linenum;
-		rulesub(&z.z_untilrule,
+		if (!rulesub(
+			&z.z_untilrule,
 			fields[i_untilyear],
 			"only",
 			"",
 			(nfields > i_untilmonth) ?
 			fields[i_untilmonth] : "Jan",
 			(nfields > i_untilday) ? fields[i_untilday] : "1",
-			(nfields > i_untiltime) ? fields[i_untiltime] : "0");
+			(nfields > i_untiltime) ? fields[i_untiltime] : "0"))
+		  return false;
 		z.z_untiltime = rpytime(&z.z_untilrule,
 			z.z_untilrule.r_loyear);
 		if (iscont && nzones > 0 &&
@@ -1490,12 +1983,20 @@ inzsub(char **fields, int nfields, bool iscont)
 			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;
+		  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]);
+	if (z.z_format_specifier == 'z') {
+	  cp1[cp - fields[i_format]] = 's';
+	  if (noise)
+	    warning(_("format '%s' not handled by pre-2015 versions of zic"),
+		    fields[i_format]);
+	}
 	zones = growalloc(zones, sizeof *zones, nzones, &nzones_alloc);
 	zones[nzones++] = z;
 	/*
@@ -1506,7 +2007,7 @@ inzsub(char **fields, int nfields, bool iscont)
 }
 
 static zic_t
-getleapdatetime(char **fields, int nfields, bool expire_line)
+getleapdatetime(char **fields, bool expire_line)
 {
 	register const char *		cp;
 	register const struct lookup *	lp;
@@ -1584,7 +2085,7 @@ inleap(char **fields, int nfields)
   if (nfields != LEAP_FIELDS)
     error(_("wrong number of fields on Leap line"));
   else {
-    zic_t t = getleapdatetime(fields, nfields, false);
+    zic_t t = getleapdatetime(fields, false);
     if (0 <= t) {
       struct lookup const *lp = byword(fields[LP_ROLL], leap_types);
       if (!lp)
@@ -1612,7 +2113,7 @@ inexpires(char **fields, int nfields)
   else if (0 <= leapexpires)
     error(_("multiple Expires lines"));
   else
-    leapexpires = getleapdatetime(fields, nfields, true);
+    leapexpires = getleapdatetime(fields, true);
 }
 
 static void
@@ -1624,21 +2125,21 @@ inlink(char **fields, int nfields)
 		error(_("wrong number of fields on Link line"));
 		return;
 	}
-	if (*fields[LF_FROM] == '\0') {
-		error(_("blank FROM field on Link line"));
+	if (*fields[LF_TARGET] == '\0') {
+		error(_("blank TARGET field on Link line"));
 		return;
 	}
-	if (! namecheck(fields[LF_TO]))
+	if (! namecheck(fields[LF_LINKNAME]))
 	  return;
-	l.l_filename = filename;
+	l.l_filenum = filenum;
 	l.l_linenum = linenum;
-	l.l_from = ecpyalloc(fields[LF_FROM]);
-	l.l_to = ecpyalloc(fields[LF_TO]);
+	l.l_target = estrdup(fields[LF_TARGET]);
+	l.l_linkname = estrdup(fields[LF_LINKNAME]);
 	links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc);
 	links[nlinks++] = l;
 }
 
-static void
+static bool
 rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	const char *typep, const char *monthp, const char *dayp,
 	const char *timep)
@@ -1651,12 +2152,12 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 
 	if ((lp = byword(monthp, mon_names)) == NULL) {
 		error(_("invalid month name"));
-		return;
+		return false;
 	}
 	rp->r_month = lp->l_value;
 	rp->r_todisstd = false;
 	rp->r_todisut = false;
-	dp = ecpyalloc(timep);
+	dp = estrdup(timep);
 	if (*dp != '\0') {
 		ep = dp + strlen(dp) - 1;
 		switch (lowerit(*ep)) {
@@ -1686,59 +2187,41 @@ 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 (lp->l_value) {
+	if (lp) switch (lp->l_value) {
 		case YR_MINIMUM:
-			rp->r_loyear = ZIC_MIN;
+			warning(_("FROM year \"%s\" is obsolete;"
+				  " treated as %d"),
+				cp, YEAR_32BIT_MIN - 1);
+			rp->r_loyear = YEAR_32BIT_MIN - 1;
 			break;
-		case YR_MAXIMUM:
-			rp->r_loyear = ZIC_MAX;
-			break;
-		default:	/* "cannot happen" */
-			fprintf(stderr,
-				_("%s: panic: Invalid l_value %d\n"),
-				progname, lp->l_value);
-			exit(EXIT_FAILURE);
+		default: unreachable();
 	} else if (sscanf(cp, "%"SCNdZIC"%c", &rp->r_loyear, &xs) != 1) {
 		error(_("invalid starting year"));
-		return;
+		return false;
 	}
 	cp = hiyearp;
 	lp = byword(cp, end_years);
 	rp->r_hiwasnum = lp == NULL;
 	if (!rp->r_hiwasnum) switch (lp->l_value) {
-		case YR_MINIMUM:
-			rp->r_hiyear = ZIC_MIN;
-			break;
 		case YR_MAXIMUM:
 			rp->r_hiyear = ZIC_MAX;
 			break;
 		case YR_ONLY:
 			rp->r_hiyear = rp->r_loyear;
 			break;
-		default:	/* "cannot happen" */
-			fprintf(stderr,
-				_("%s: panic: Invalid l_value %d\n"),
-				progname, lp->l_value);
-			exit(EXIT_FAILURE);
+		default: unreachable();
 	} else if (sscanf(cp, "%"SCNdZIC"%c", &rp->r_hiyear, &xs) != 1) {
 		error(_("invalid ending year"));
-		return;
+		return false;
 	}
 	if (rp->r_loyear > rp->r_hiyear) {
 		error(_("starting year greater than ending year"));
-		return;
+		return false;
 	}
-	if (*typep == '\0')
-		rp->r_yrtype = NULL;
-	else {
-		if (rp->r_loyear == rp->r_hiyear) {
-			error(_("typed single year"));
-			return;
-		}
-		warning(_("year type \"%s\" is obsolete; use \"-\" instead"),
+	if (*typep != '\0') {
+		error(_("year type \"%s\" is unsupported; use \"-\" instead"),
 			typep);
-		rp->r_yrtype = ecpyalloc(typep);
+		return false;
 	}
 	/*
 	** Day work.
@@ -1749,7 +2232,7 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	**	Sun<=20
 	**	Sun>=7
 	*/
-	dp = ecpyalloc(dayp);
+	dp = estrdup(dayp);
 	if ((lp = byword(dp, lasts)) != NULL) {
 		rp->r_dycode = DC_DOWLEQ;
 		rp->r_wday = lp->l_value;
@@ -1768,12 +2251,12 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 			if (*ep++ != '=') {
 				error(_("invalid day of month"));
 				free(dp);
-				return;
+				return false;
 			}
 			if ((lp = byword(dp, wday_names)) == NULL) {
 				error(_("invalid weekday name"));
 				free(dp);
-				return;
+				return false;
 			}
 			rp->r_wday = lp->l_value;
 		}
@@ -1782,36 +2265,37 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 			(rp->r_dayofmonth > len_months[1][rp->r_month])) {
 				error(_("invalid day of month"));
 				free(dp);
-				return;
+				return false;
 		}
 	}
 	free(dp);
+	return true;
 }
 
 static void
-convert(const int_fast32_t val, char *const buf)
+convert(uint_fast32_t val, char *buf)
 {
 	register int	i;
 	register int	shift;
 	unsigned char *const b = (unsigned char *) buf;
 
 	for (i = 0, shift = 24; i < 4; ++i, shift -= 8)
-		b[i] = val >> shift;
+	  b[i] = (val >> shift) & 0xff;
 }
 
 static void
-convert64(const zic_t val, char *const buf)
+convert64(uint_fast64_t val, char *buf)
 {
 	register int	i;
 	register int	shift;
 	unsigned char *const b = (unsigned char *) buf;
 
 	for (i = 0, shift = 56; i < 8; ++i, shift -= 8)
-		b[i] = val >> shift;
+	  b[i] = (val >> shift) & 0xff;
 }
 
 static void
-puttzcode(const int_fast32_t val, FILE *const fp)
+puttzcode(zic_t val, FILE *fp)
 {
 	char	buf[4];
 
@@ -1835,39 +2319,57 @@ puttzcodepass(zic_t val, FILE *fp, int pass)
 static int
 atcomp(const void *avp, const void *bvp)
 {
-	const zic_t	a = ((const struct attype *) avp)->at;
-	const zic_t	b = ((const struct attype *) bvp)->at;
-
-	return (a < b) ? -1 : (a > b);
+  struct attype const *ap = avp, *bp = bvp;
+  zic_t a = ap->at, b = bp->at;
+  return a < b ? -1 : a > b;
 }
 
 struct timerange {
   int defaulttype;
   ptrdiff_t base, count;
   int leapbase, leapcount;
+  bool leapexpiry;
 };
 
 static struct timerange
 limitrange(struct timerange r, zic_t lo, zic_t hi,
 	   zic_t const *ats, unsigned char const *types)
 {
+  /* Omit ordinary transitions < LO.  */
   while (0 < r.count && ats[r.base] < lo) {
     r.defaulttype = types[r.base];
     r.count--;
     r.base++;
   }
-  while (0 < r.leapcount && trans[r.leapbase] < lo) {
+
+  /* Omit as many initial leap seconds as possible, such that the
+     first leap second in the truncated list is <= LO, and is a
+     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) {
     r.leapcount--;
     r.leapbase++;
   }
+  while (0 < r.leapbase
+	 && ((corr[r.leapbase - 1] < corr[r.leapbase])
+	     != (0 < corr[r.leapbase]))) {
+    r.leapcount++;
+    r.leapbase--;
+  }
+
 
-  if (hi < ZIC_MAX) {
+  /* Omit ordinary and leap second transitions greater than HI + 1.  */
+  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])
       r.leapcount--;
   }
 
+  /* Determine whether to append an expiration to the leap second table.  */
+  r.leapexpiry = 0 <= leapexpires && leapexpires - 1 <= hi;
+
   return r;
 }
 
@@ -1878,20 +2380,18 @@ writezone(const char *const name, const char *const string, char version,
 	register FILE *			fp;
 	register ptrdiff_t		i, j;
 	register int			pass;
-	static const struct tzhead	tzh0;
-	static struct tzhead		tzh;
-	bool dir_checked = false;
-	zic_t one = 1;
-	zic_t y2038_boundary = one << 31;
-	ptrdiff_t nats = timecnt + WORK_AROUND_QTBUG_53071;
+	char *tempname = NULL;
+	char const *outname = name;
 
 	/* Allocate the ATS and TYPES arrays via a single malloc,
-	   as this is a bit faster.  */
-	zic_t *ats = emalloc(align_to(size_product(nats, sizeof *ats + 1),
-				      _Alignof(zic_t)));
-	void *typesptr = ats + nats;
+	   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,
+						   sizeof *ats + 1),
+				      alignof(zic_t)));
+	void *typesptr = ats + timecnt;
 	unsigned char *types = typesptr;
-	struct timerange rangeall, range32, range64;
+	struct timerange rangeall = {0}, range32, range64;
 
 	/*
 	** Sort.
@@ -1959,60 +2459,50 @@ writezone(const char *const name, const char *const string, char version,
 			}
 	}
 
-	/* Work around QTBUG-53071 for timestamps 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.  Do this after correcting for leap
-	   seconds, as the idea is to insert a transition just before
-	   32-bit time_t rolls around, and this occurs at a slightly
-	   different moment if transitions are leap-second corrected.  */
-	if (WORK_AROUND_QTBUG_53071 && timecnt != 0 && want_bloat()
-	    && ats[timecnt - 1] < y2038_boundary - 1 && strchr(string, '<')) {
-	  ats[timecnt] = y2038_boundary - 1;
-	  types[timecnt] = types[timecnt - 1];
-	  timecnt++;
-	}
-
 	rangeall.defaulttype = defaulttype;
-	rangeall.base = rangeall.leapbase = 0;
 	rangeall.count = timecnt;
 	rangeall.leapcount = leapcnt;
-	range64 = limitrange(rangeall, lo_time, hi_time, ats, types);
-	range32 = limitrange(range64, INT32_MIN, INT32_MAX, ats, types);
-
-	/*
-	** Remove old file, if any, to snap links.
-	*/
-	if (remove(name) == 0)
-		dir_checked = true;
-	else if (errno != ENOENT) {
-		const char *e = strerror(errno);
-
-		fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
-			progname, directory, name, 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;
+	range64 = limitrange(rangeall, lo_time,
+			     max(hi_time,
+				 redundant_time - (ZIC_MIN < redundant_time)),
+			     ats, types);
+	range32 = limitrange(range64, ZIC32_MIN, ZIC32_MAX, ats, types);
+
+	/* TZif version 4 is needed if a no-op transition is appended to
+	   indicate the expiration of the leap second table, or if the first
+	   leap second transition is not to a +1 or -1 correction.  */
+	for (pass = 1; pass <= 2; pass++) {
+	  struct timerange const *r = pass == 1 ? &range32 : &range64;
+	  if (pass == 1 && !want_bloat())
+	    continue;
+	  if (r->leapexpiry) {
+	    if (noise)
+	      warning(_("%s: pre-2021b clients may mishandle"
+			" leap second expiry"),
+		      name);
+	    version = '4';
 	  }
-	  if (!fp) {
-	    fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
-		    progname, directory, name, strerror(fopen_errno));
-	    exit(EXIT_FAILURE);
+	  if (0 < r->leapcount
+	      && corr[r->leapbase] != 1 && corr[r->leapbase] != -1) {
+	    if (noise)
+	      warning(_("%s: pre-2021b clients may mishandle"
+			" leap second table truncation"),
+		      name);
+	    version = '4';
 	  }
+	  if (version == '4')
+	    break;
 	}
+
+	fp = open_outfile(&outname, &tempname);
+
 	for (pass = 1; pass <= 2; ++pass) {
 		register ptrdiff_t thistimei, thistimecnt, thistimelim;
 		register int	thisleapi, thisleapcnt, thisleaplim;
-		int currenttype, thisdefaulttype;
-		bool locut, hicut;
-		zic_t lo;
+		struct tzhead tzh;
+		int pretranstype = -1, thisdefaulttype;
+		bool locut, hicut, thisleapexpiry;
+		zic_t lo, thismin, thismax;
 		int old0;
 		char		omittype[TZ_MAX_TYPES];
 		int		typemap[TZ_MAX_TYPES];
@@ -2023,27 +2513,15 @@ writezone(const char *const name, const char *const string, char version,
 		int		indmap[TZ_MAX_CHARS];
 
 		if (pass == 1) {
-			/* Arguably the default time type in the 32-bit data
-			   should be range32.defaulttype, which is suited for
-			   timestamps just before INT32_MIN.  However, zic
-			   traditionally used the time type of the indefinite
-			   past instead.  Internet RFC 8532 says readers should
-			   ignore 32-bit data, so this discrepancy matters only
-			   to obsolete readers where the traditional type might
-			   be more appropriate even if it's "wrong".  So, use
-			   the historical zic value, unless -r specifies a low
-			   cutoff that excludes some 32-bit timestamps.  */
-			thisdefaulttype = (lo_time <= INT32_MIN
-					   ? range64.defaulttype
-					   : range32.defaulttype);
-
+			thisdefaulttype = range32.defaulttype;
 			thistimei = range32.base;
 			thistimecnt = range32.count;
 			toomanytimes = thistimecnt >> 31 >> 1 != 0;
 			thisleapi = range32.leapbase;
 			thisleapcnt = range32.leapcount;
-			locut = INT32_MIN < lo_time;
-			hicut = hi_time < INT32_MAX;
+			thisleapexpiry = range32.leapexpiry;
+			thismin = ZIC32_MIN;
+			thismax = ZIC32_MAX;
 		} else {
 			thisdefaulttype = range64.defaulttype;
 			thistimei = range64.base;
@@ -2051,37 +2529,49 @@ writezone(const char *const name, const char *const string, char version,
 			toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0;
 			thisleapi = range64.leapbase;
 			thisleapcnt = range64.leapcount;
-			locut = min_time < lo_time;
-			hicut = hi_time < max_time;
+			thisleapexpiry = range64.leapexpiry;
+			thismin = min_time;
+			thismax = max_time;
 		}
 		if (toomanytimes)
 		  error(_("too many transition times"));
 
-		/* Keep the last too-low transition if no transition is
-		   exactly at LO.  The kept transition will be output as
-		   a LO "transition"; see "Output a LO_TIME transition"
-		   below.  This is needed when the output is truncated at
-		   the start, and is also useful when catering to buggy
-		   32-bit clients that do not use time type 0 for
+		locut = thismin < lo_time && lo_time <= thismax;
+		hicut = thismin <= hi_time && hi_time < thismax;
+		thistimelim = thistimei + thistimecnt;
+		memset(omittype, true, typecnt);
+
+		/* Determine whether to output a transition before the first
+		   transition in range.  This is needed when the output is
+		   truncated at the start, and is also useful when catering to
+		   buggy 32-bit clients that do not use time type 0 for
 		   timestamps before the first transition.  */
-		if (0 < thistimei && ats[thistimei] != lo_time) {
-		  thistimei--;
-		  thistimecnt++;
-		  locut = false;
+		if ((locut || (pass == 1 && thistimei))
+		    && ! (thistimecnt && ats[thistimei] == lo_time)) {
+		  pretranstype = thisdefaulttype;
+		  omittype[pretranstype] = false;
 		}
 
-		thistimelim = thistimei + thistimecnt;
-		thisleaplim = thisleapi + thisleapcnt;
-		if (thistimecnt != 0) {
-		  if (ats[thistimei] == lo_time)
-		    locut = false;
-		  if (hi_time < ZIC_MAX && ats[thistimelim - 1] == hi_time + 1)
-		    hicut = false;
-		}
-		memset(omittype, true, typecnt);
+		/* Arguably the default time type in the 32-bit data
+		   should be range32.defaulttype, which is suited for
+		   timestamps just before ZIC32_MIN.  However, zic
+		   traditionally used the time type of the indefinite
+		   past instead.  Internet RFC 8532 says readers should
+		   ignore 32-bit data, so this discrepancy matters only
+		   to obsolete readers where the traditional type might
+		   be more appropriate even if it's "wrong".  So, use
+		   the historical zic value, unless -r specifies a low
+		   cutoff that excludes some 32-bit timestamps.  */
+		if (pass == 1 && lo_time <= thismin)
+		  thisdefaulttype = range64.defaulttype;
+
+		if (locut)
+		  thisdefaulttype = unspecifiedtype;
 		omittype[thisdefaulttype] = false;
 		for (i = thistimei; i < thistimelim; i++)
 		  omittype[types[i]] = false;
+		if (hicut)
+		  omittype[unspecifiedtype] = false;
 
 		/* Reorder types to make THISDEFAULTTYPE type 0.
 		   Use TYPEMAP to swap OLD0 and THISDEFAULTTYPE so that
@@ -2102,7 +2592,13 @@ writezone(const char *const name, const char *const string, char version,
 			register int	mrudst, mrustd, hidst, histd, type;
 
 			hidst = histd = mrudst = mrustd = -1;
-			for (i = thistimei; i < thistimelim; ++i)
+			if (0 <= pretranstype) {
+			  if (isdsts[pretranstype])
+			    mrudst = pretranstype;
+			  else
+			    mrustd = pretranstype;
+			}
+			for (i = thistimei; i < thistimelim; i++)
 				if (isdsts[types[i]])
 					mrudst = types[i];
 				else	mrustd = types[i];
@@ -2172,19 +2668,20 @@ writezone(const char *const name, const char *const string, char version,
 			indmap[desigidx[i]] = j;
 		}
 		if (pass == 1 && !want_bloat()) {
-		  utcnt = stdcnt = thisleapcnt = 0;
-		  thistimecnt = - (locut + hicut);
+		  hicut = thisleapexpiry = false;
+		  pretranstype = -1;
+		  thistimecnt = thisleapcnt = 0;
 		  thistypecnt = thischarcnt = 1;
-		  thistimelim = thistimei;
 		}
 #define DO(field)	fwrite(tzh.field, sizeof tzh.field, 1, fp)
-		tzh = tzh0;
+		memset(&tzh, 0, sizeof tzh);
 		memcpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic);
 		tzh.tzh_version[0] = version;
 		convert(utcnt, tzh.tzh_ttisutcnt);
 		convert(stdcnt, tzh.tzh_ttisstdcnt);
-		convert(thisleapcnt, tzh.tzh_leapcnt);
-		convert(locut + thistimecnt + hicut, tzh.tzh_timecnt);
+		convert(thisleapcnt + thisleapexpiry, tzh.tzh_leapcnt);
+		convert((0 <= pretranstype) + thistimecnt + hicut,
+			tzh.tzh_timecnt);
 		convert(thistypecnt, tzh.tzh_typecnt);
 		convert(thischarcnt, tzh.tzh_charcnt);
 		DO(tzh_magic);
@@ -2209,25 +2706,21 @@ writezone(const char *const name, const char *const string, char version,
 		/* Output a LO_TIME transition if needed; see limitrange.
 		   But do not go below the minimum representable value
 		   for this pass.  */
-		lo = pass == 1 && lo_time < INT32_MIN ? INT32_MIN : lo_time;
+		lo = pass == 1 && lo_time < ZIC32_MIN ? ZIC32_MIN : lo_time;
 
-		if (locut)
+		if (0 <= pretranstype)
 		  puttzcodepass(lo, fp, pass);
 		for (i = thistimei; i < thistimelim; ++i) {
-		  zic_t at = ats[i] < lo ? lo : ats[i];
-		  puttzcodepass(at, fp, pass);
+		  puttzcodepass(ats[i], fp, pass);
 		}
 		if (hicut)
 		  puttzcodepass(hi_time + 1, fp, pass);
-		currenttype = 0;
-		if (locut)
-		  putc(currenttype, fp);
-		for (i = thistimei; i < thistimelim; ++i) {
-		  currenttype = typemap[types[i]];
-		  putc(currenttype, fp);
-		}
+		if (0 <= pretranstype)
+		  putc(typemap[pretranstype], fp);
+		for (i = thistimei; i < thistimelim; i++)
+		  putc(typemap[types[i]], fp);
 		if (hicut)
-		  putc(currenttype, fp);
+		  putc(typemap[unspecifiedtype], fp);
 
 		for (i = old0; i < typecnt; i++) {
 		  int h = (i == old0 ? thisdefaulttype
@@ -2241,6 +2734,7 @@ writezone(const char *const name, const char *const string, char version,
 		if (thischarcnt != 0)
 			fwrite(thischars, sizeof thischars[0],
 				      thischarcnt, fp);
+		thisleaplim = thisleapi + thisleapcnt;
 		for (i = thisleapi; i < thisleaplim; ++i) {
 			register zic_t	todo;
 
@@ -2264,6 +2758,15 @@ writezone(const char *const name, const char *const string, char version,
 			puttzcodepass(todo, fp, pass);
 			puttzcode(corr[i], 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
+		     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);
+		}
 		if (stdcnt != 0)
 		  for (i = old0; i < typecnt; i++)
 			if (!omittype[i])
@@ -2274,7 +2777,8 @@ writezone(const char *const name, const char *const string, char version,
 				putc(ttisuts[i], fp);
 	}
 	fprintf(fp, "\n%s\n", string);
-	close_file(fp, directory, name);
+	close_file(fp, directory, name, tempname);
+	rename_dest(tempname, name);
 	free(ats);
 }
 
@@ -2314,13 +2818,15 @@ abbroffset(char *buf, zic_t offset)
   }
 }
 
-static size_t
+static char const disable_percent_s[] = "";
+
+static ptrdiff_t
 doabbr(char *abbr, struct zone const *zp, char const *letters,
        bool isdst, zic_t save, bool doquotes)
 {
 	register char *	cp;
 	register char *	slashp;
-	register size_t	len;
+	ptrdiff_t len;
 	char const *format = zp->z_format;
 
 	slashp = strchr(format, '/');
@@ -2330,6 +2836,8 @@ doabbr(char *abbr, struct zone const *zp, char const *letters,
 	    letters = abbroffset(letterbuf, zp->z_stdoff + save);
 	  else if (!letters)
 	    letters = "%s";
+	  else if (letters == disable_percent_s)
+	    return 0;
 	  sprintf(abbr, format, letters);
 	} else if (isdst) {
 		strcpy(abbr, slashp + 1);
@@ -2467,11 +2975,17 @@ rule_cmp(struct rule const *a, struct rule const *b)
 		return 1;
 	if (a->r_hiyear != b->r_hiyear)
 		return a->r_hiyear < b->r_hiyear ? -1 : 1;
+	if (a->r_hiyear == ZIC_MAX)
+		return 0;
 	if (a->r_month - b->r_month != 0)
 		return a->r_month - b->r_month;
 	return a->r_dayofmonth - b->r_dayofmonth;
 }
 
+/* Store into RESULT a POSIX.1-2017 TZ string that represent the future
+   predictions for the zone ZPFIRST with ZONECOUNT entries.  Return a
+   compatibility indicator (a TZDB release year) if successful, a
+   negative integer if no such TZ string exissts.  */
 static int
 stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 {
@@ -2480,12 +2994,16 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	register struct rule *		stdrp;
 	register struct rule *		dstrp;
 	register ptrdiff_t		i;
-	register const char *		abbrvar;
 	register int			compat = 0;
 	register int			c;
-	size_t				len;
 	int				offsetlen;
 	struct rule			stdr, dstr;
+	ptrdiff_t len;
+	int dstcmp;
+	struct rule *lastrp[2] = { NULL, NULL };
+	struct zone zstr[2];
+	struct zone const *stdzp;
+	struct zone const *dstzp;
 
 	result[0] = '\0';
 
@@ -2495,65 +3013,64 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	  return -1;
 
 	zp = zpfirst + zonecount - 1;
-	stdrp = dstrp = NULL;
 	for (i = 0; i < zp->z_nrules; ++i) {
+		struct rule **last;
+		int cmp;
 		rp = &zp->z_rules[i];
-		if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX)
-			continue;
-		if (rp->r_yrtype != NULL)
-			continue;
-		if (!rp->r_isdst) {
-			if (stdrp == NULL)
-				stdrp = rp;
-			else	return -1;
-		} else {
-			if (dstrp == NULL)
-				dstrp = rp;
-			else	return -1;
-		}
-	}
-	if (stdrp == NULL && dstrp == NULL) {
-		/*
-		** There are no rules running through "max".
-		** Find the latest std rule in stdabbrrp
-		** and latest rule of any type in stdrp.
-		*/
-		register struct rule *stdabbrrp = NULL;
-		for (i = 0; i < zp->z_nrules; ++i) {
-			rp = &zp->z_rules[i];
-			if (!rp->r_isdst && rule_cmp(stdabbrrp, rp) < 0)
-				stdabbrrp = rp;
-			if (rule_cmp(stdrp, rp) < 0)
-				stdrp = rp;
-		}
-		if (stdrp != NULL && stdrp->r_isdst) {
-			/* Perpetual DST.  */
-			dstr.r_month = TM_JANUARY;
-			dstr.r_dycode = DC_DOM;
-			dstr.r_dayofmonth = 1;
-			dstr.r_tod = 0;
-			dstr.r_todisstd = dstr.r_todisut = false;
-			dstr.r_isdst = stdrp->r_isdst;
-			dstr.r_save = stdrp->r_save;
-			dstr.r_abbrvar = stdrp->r_abbrvar;
-			stdr.r_month = TM_DECEMBER;
-			stdr.r_dycode = DC_DOM;
-			stdr.r_dayofmonth = 31;
-			stdr.r_tod = SECSPERDAY + stdrp->r_save;
-			stdr.r_todisstd = stdr.r_todisut = false;
-			stdr.r_isdst = false;
-			stdr.r_save = 0;
-			stdr.r_abbrvar
-			  = (stdabbrrp ? stdabbrrp->r_abbrvar : "");
-			dstrp = &dstr;
-			stdrp = &stdr;
-		}
-	}
-	if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_isdst))
-		return -1;
-	abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
-	len = doabbr(result, zp, abbrvar, false, 0, true);
-	offsetlen = stringoffset(result + len, - zp->z_stdoff);
+		last = &lastrp[rp->r_isdst];
+		cmp = rule_cmp(*last, rp);
+		if (cmp < 0)
+		  *last = rp;
+		else if (cmp == 0)
+		  return -1;
+	}
+	stdrp = lastrp[false];
+	dstrp = lastrp[true];
+	dstcmp = zp->z_nrules ? rule_cmp(dstrp, stdrp) : zp->z_isdst ? 1 : -1;
+	stdzp = dstzp = zp;
+
+	if (dstcmp < 0) {
+	  /* Standard time all year.  */
+	  dstrp = NULL;
+	} else if (0 < dstcmp) {
+	  /* DST all year.  Use an abbreviation like
+	     "XXX3EDT4,0/0,J365/23" for EDT (-04) all year.  */
+	  zic_t save = dstrp ? dstrp->r_save : zp->z_save;
+	  if (0 <= save)
+	    {
+	      /* Positive DST, the typical case for all-year DST.
+		 Fake a timezone with negative DST.  */
+	      stdzp = &zstr[0];
+	      dstzp = &zstr[1];
+	      zstr[0].z_stdoff = zp->z_stdoff + 2 * save;
+	      zstr[0].z_format = "XXX";  /* Any 3 letters will do.  */
+	      zstr[0].z_format_specifier = 0;
+	      zstr[1].z_stdoff = zstr[0].z_stdoff;
+	      zstr[1].z_format = zp->z_format;
+	      zstr[1].z_format_specifier = zp->z_format_specifier;
+	    }
+	  dstr.r_month = TM_JANUARY;
+	  dstr.r_dycode = DC_DOM;
+	  dstr.r_dayofmonth = 1;
+	  dstr.r_tod = 0;
+	  dstr.r_todisstd = dstr.r_todisut = false;
+	  dstr.r_isdst = true;
+	  dstr.r_save = save < 0 ? save : -save;
+	  dstr.r_abbrvar = dstrp ? dstrp->r_abbrvar : NULL;
+	  stdr.r_month = TM_DECEMBER;
+	  stdr.r_dycode = DC_DOM;
+	  stdr.r_dayofmonth = 31;
+	  stdr.r_tod = SECSPERDAY + dstr.r_save;
+	  stdr.r_todisstd = stdr.r_todisut = false;
+	  stdr.r_isdst = false;
+	  stdr.r_save = 0;
+	  stdr.r_abbrvar = save < 0 && stdrp ? stdrp->r_abbrvar : NULL;
+	  dstrp = &dstr;
+	  stdrp = &stdr;
+	}
+	len = doabbr(result, stdzp, stdrp ? stdrp->r_abbrvar : NULL,
+		     false, 0, true);
+	offsetlen = stringoffset(result + len, - stdzp->z_stdoff);
 	if (! offsetlen) {
 		result[0] = '\0';
 		return -1;
@@ -2561,11 +3078,11 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	len += offsetlen;
 	if (dstrp == NULL)
 		return compat;
-	len += doabbr(result + len, zp, dstrp->r_abbrvar,
+	len += doabbr(result + len, dstzp, dstrp->r_abbrvar,
 		      dstrp->r_isdst, dstrp->r_save, true);
 	if (dstrp->r_save != SECSPERMIN * MINSPERHOUR) {
 	  offsetlen = stringoffset(result + len,
-				   - (zp->z_stdoff + dstrp->r_save));
+				   - (dstzp->z_stdoff + dstrp->r_save));
 	  if (! offsetlen) {
 	    result[0] = '\0';
 	    return -1;
@@ -2573,7 +3090,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	  len += offsetlen;
 	}
 	result[len++] = ',';
-	c = stringrule(result + len, dstrp, dstrp->r_save, zp->z_stdoff);
+	c = stringrule(result + len, dstrp, dstrp->r_save, stdzp->z_stdoff);
 	if (c < 0) {
 		result[0] = '\0';
 		return -1;
@@ -2582,7 +3099,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 		compat = c;
 	len += strlen(result + len);
 	result[len++] = ',';
-	c = stringrule(result + len, stdrp, dstrp->r_save, zp->z_stdoff);
+	c = stringrule(result + len, stdrp, dstrp->r_save, stdzp->z_stdoff);
 	if (c < 0) {
 		result[0] = '\0';
 		return -1;
@@ -2595,35 +3112,29 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 static void
 outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 {
-	register const struct zone *	zp;
-	register struct rule *		rp;
 	register ptrdiff_t		i, j;
-	register bool			usestart, useuntil;
 	register zic_t			starttime, untiltime;
-	register zic_t			stdoff;
-	register zic_t			save;
-	register zic_t			year;
-	register zic_t			startoff;
 	register bool			startttisstd;
 	register bool			startttisut;
-	register int			type;
 	register char *			startbuf;
 	register char *			ab;
 	register char *			envvar;
 	register int			max_abbr_len;
 	register int			max_envvar_len;
-	register bool			prodstic; /* all rules are min to max */
 	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 nonTZlimtime = ZIC_MIN;
+	int nonTZlimtype = -1;
 	zic_t max_year0;
 	int defaulttype = -1;
 
+	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;
+
 	startbuf = emalloc(max_abbr_len + 1);
 	ab = emalloc(max_abbr_len + 1);
 	envvar = emalloc(max_envvar_len + 1);
@@ -2635,7 +3146,6 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	timecnt = 0;
 	typecnt = 0;
 	charcnt = 0;
-	prodstic = zonecount == 1;
 	/*
 	** Thanks to Earl Chew
 	** for noting the need to unconditionally initialize startttisstd.
@@ -2648,29 +3158,27 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 		updateminmax(leapmaxyear + (leapmaxyear < ZIC_MAX));
 	}
 	for (i = 0; i < zonecount; ++i) {
-		zp = &zpfirst[i];
+		struct zone const *zp = &zpfirst[i];
 		if (i < zonecount - 1)
 			updateminmax(zp->z_untilrule.r_loyear);
 		for (j = 0; j < zp->z_nrules; ++j) {
-			rp = &zp->z_rules[j];
-			if (rp->r_lowasnum)
-				updateminmax(rp->r_loyear);
+			struct rule *rp = &zp->z_rules[j];
+			updateminmax(rp->r_loyear);
 			if (rp->r_hiwasnum)
 				updateminmax(rp->r_hiyear);
-			if (rp->r_lowasnum || rp->r_hiwasnum)
-				prodstic = false;
 		}
 	}
 	/*
 	** Generate lots of data if a rule can't cover all future times.
 	*/
 	compat = stringzone(envvar, zpfirst, zonecount);
-	version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION;
+	version = compat < 2013 ? '2' : '3';
 	do_extend = compat < 0;
 	if (noise) {
 		if (!*envvar)
 			warning("%s %s",
-				_("no POSIX environment variable for zone"),
+				_("no POSIX.1-2017 environment variable"
+				  " for zone"),
 				zpfirst->z_name);
 		else if (compat != 0) {
 			/* Circa-COMPAT clients, and earlier clients, might
@@ -2682,64 +3190,44 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 		}
 	}
 	if (do_extend) {
-		/*
-		** Search through a couple of extra years past the obvious
-		** 400, to avoid edge cases.  For example, suppose a non-POSIX
-		** rule applies from 2012 onwards and has transitions in March
-		** and September, plus some one-off transitions in November
-		** 2013.  If zic looked only at the last 400 years, it would
-		** set max_year=2413, with the intent that the 400 years 2014
-		** through 2413 will be repeated.  The last transition listed
-		** in the tzfile would be in 2413-09, less than 400 years
-		** after the last one-off transition in 2013-11.  Two years
-		** might be overkill, but with the kind of edge cases
-		** available we're not sure that one year would suffice.
-		*/
-		enum { years_of_observations = YEARSPERREPEAT + 2 };
-
 		if (min_year >= ZIC_MIN + years_of_observations)
 			min_year -= years_of_observations;
 		else	min_year = ZIC_MIN;
 		if (max_year <= ZIC_MAX - years_of_observations)
 			max_year += years_of_observations;
 		else	max_year = ZIC_MAX;
-		/*
-		** Regardless of any of the above,
-		** for a "proDSTic" zone which specifies that its rules
-		** always have and always will be in effect,
-		** we only need one cycle to define the zone.
-		*/
-		if (prodstic) {
-			min_year = 1900;
-			max_year = min_year + years_of_observations;
-		}
 	}
+	max_year = max(max_year, (redundant_time / (SECSPERDAY * DAYSPERNYEAR)
+				  + EPOCH_YEAR + 1));
 	max_year0 = max_year;
 	if (want_bloat()) {
 	  /* For the benefit of older systems,
 	     generate data from 1900 through 2038.  */
-	  if (min_year > 1900)
-		min_year = 1900;
-	  if (max_year < 2038)
-		max_year = 2038;
+	  if (min_year > YEAR_32BIT_MIN - 1)
+		min_year = YEAR_32BIT_MIN - 1;
+	  if (max_year < YEAR_32BIT_MAX)
+		max_year = YEAR_32BIT_MAX;
 	}
 
+	if (min_time < lo_time || hi_time < max_time)
+	  unspecifiedtype = addtype(0, "-00", false, false, false);
+
 	for (i = 0; i < zonecount; ++i) {
-		struct rule *prevrp = NULL;
 		/*
 		** A guess that may well be corrected later.
 		*/
-		save = 0;
-		zp = &zpfirst[i];
-		usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
-		useuntil = i < (zonecount - 1);
+		zic_t save = 0;
+		struct zone const *zp = &zpfirst[i];
+		bool usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
+		bool useuntil = i < (zonecount - 1);
+		zic_t stdoff = zp->z_stdoff;
+		zic_t startoff = stdoff;
 		if (useuntil && zp->z_untiltime <= min_time)
 			continue;
-		stdoff = zp->z_stdoff;
-		eat(zp->z_filename, zp->z_linenum);
+		eat(zp->z_filenum, zp->z_linenum);
 		*startbuf = '\0';
-		startoff = zp->z_stdoff;
 		if (zp->z_nrules == 0) {
+			int type;
 			save = zp->z_save;
 			doabbr(startbuf, zp, NULL, zp->z_isdst, save, false);
 			type = addtype(oadd(zp->z_stdoff, save),
@@ -2747,23 +3235,31 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				startttisut);
 			if (usestart) {
 				addtt(starttime, type);
+				if (useuntil && nonTZlimtime < starttime) {
+				  nonTZlimtime = starttime;
+				  nonTZlimtype = type;
+				}
 				usestart = false;
 			} else
 				defaulttype = type;
-		} else for (year = min_year; year <= max_year; ++year) {
+		} else {
+		  zic_t year;
+		  for (year = min_year; year <= max_year; ++year) {
 			if (useuntil && year > zp->z_untilrule.r_hiyear)
 				break;
 			/*
 			** Mark which rules to do in the current year.
 			** For those to do, calculate rpytime(rp, year);
+			** The former TYPE field was also considered here.
 			*/
 			for (j = 0; j < zp->z_nrules; ++j) {
-				rp = &zp->z_rules[j];
-				eats(zp->z_filename, zp->z_linenum,
-					rp->r_filename, rp->r_linenum);
+				zic_t one = 1;
+				zic_t y2038_boundary = one << 31;
+				struct rule *rp = &zp->z_rules[j];
+				eats(zp->z_filenum, zp->z_linenum,
+				     rp->r_filenum, rp->r_linenum);
 				rp->r_todo = year >= rp->r_loyear &&
-						year <= rp->r_hiyear &&
-						yearistype(year, rp->r_yrtype);
+						year <= rp->r_hiyear;
 				if (rp->r_todo) {
 					rp->r_temp = rpytime(rp, year);
 					rp->r_todo
@@ -2775,6 +3271,8 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				register ptrdiff_t k;
 				register zic_t	jtime, ktime;
 				register zic_t	offset;
+				struct rule *rp;
+				int type;
 
 				INITIALIZE(ktime);
 				if (useuntil) {
@@ -2797,15 +3295,15 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				*/
 				k = -1;
 				for (j = 0; j < zp->z_nrules; ++j) {
-					rp = &zp->z_rules[j];
-					if (!rp->r_todo)
+					struct rule *r = &zp->z_rules[j];
+					if (!r->r_todo)
 						continue;
-					eats(zp->z_filename, zp->z_linenum,
-						rp->r_filename, rp->r_linenum);
-					offset = rp->r_todisut ? 0 : stdoff;
-					if (!rp->r_todisstd)
+					eats(zp->z_filenum, zp->z_linenum,
+					     r->r_filenum, r->r_linenum);
+					offset = r->r_todisut ? 0 : stdoff;
+					if (!r->r_todisstd)
 						offset = oadd(offset, save);
-					jtime = rp->r_temp;
+					jtime = r->r_temp;
 					if (jtime == min_time ||
 						jtime == max_time)
 							continue;
@@ -2816,12 +3314,12 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 					} else if (jtime == ktime) {
 					  char const *dup_rules_msg =
 					    _("two rules for same instant");
-					  eats(zp->z_filename, zp->z_linenum,
-					       rp->r_filename, rp->r_linenum);
+					  eats(zp->z_filenum, zp->z_linenum,
+					       r->r_filenum, r->r_linenum);
 					  warning("%s", dup_rules_msg);
-					  rp = &zp->z_rules[k];
-					  eats(zp->z_filename, zp->z_linenum,
-					       rp->r_filename, rp->r_linenum);
+					  r = &zp->z_rules[k];
+					  eats(zp->z_filenum, zp->z_linenum,
+					       r->r_filenum, r->r_linenum);
 					  error("%s", dup_rules_msg);
 					}
 				}
@@ -2829,8 +3327,15 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 					break;	/* go on to next year */
 				rp = &zp->z_rules[k];
 				rp->r_todo = false;
-				if (useuntil && ktime >= untiltime)
+				if (useuntil && ktime >= untiltime) {
+					if (!*startbuf
+					    && (oadd(zp->z_stdoff, rp->r_save)
+						== startoff))
+					  doabbr(startbuf, zp, rp->r_abbrvar,
+						 rp->r_isdst, rp->r_save,
+						 false);
 					break;
+				}
 				save = rp->r_save;
 				if (usestart && ktime == starttime)
 					usestart = false;
@@ -2856,41 +3361,36 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 								false);
 					}
 				}
-				eats(zp->z_filename, zp->z_linenum,
-					rp->r_filename, rp->r_linenum);
+				eats(zp->z_filenum, zp->z_linenum,
+				     rp->r_filenum, rp->r_linenum);
 				doabbr(ab, zp, rp->r_abbrvar,
 				       rp->r_isdst, rp->r_save, false);
 				offset = oadd(zp->z_stdoff, rp->r_save);
-				if (!want_bloat() && !useuntil && !do_extend
-				    && prevrp
-				    && rp->r_hiyear == ZIC_MAX
-				    && prevrp->r_hiyear == ZIC_MAX)
-				  break;
 				type = addtype(offset, ab, rp->r_isdst,
 					rp->r_todisstd, rp->r_todisut);
 				if (defaulttype < 0 && !rp->r_isdst)
 				  defaulttype = type;
-				if (rp->r_hiyear == ZIC_MAX
-				    && ! (0 <= lastatmax
-					  && ktime < attypes[lastatmax].at))
-				  lastatmax = timecnt;
 				addtt(ktime, type);
-				prevrp = rp;
+				if (nonTZlimtime < ktime
+				    && (useuntil || rp->r_hiyear != ZIC_MAX)) {
+				  nonTZlimtime = ktime;
+				  nonTZlimtype = type;
+				}
 			}
+		  }
 		}
 		if (usestart) {
-			if (*startbuf == '\0' &&
-				zp->z_format != NULL &&
-				strchr(zp->z_format, '%') == NULL &&
-				strchr(zp->z_format, '/') == NULL)
-					strcpy(startbuf, zp->z_format);
-			eat(zp->z_filename, zp->z_linenum);
+			bool isdst = startoff != zp->z_stdoff;
+			if (*startbuf == '\0' && zp->z_format)
+			  doabbr(startbuf, zp, disable_percent_s,
+				 isdst, save, false);
+			eat(zp->z_filenum, zp->z_linenum);
 			if (*startbuf == '\0')
-error(_("can't determine time zone abbreviation to use just after until time"));
+			  error(_("can't determine time zone abbreviation"
+				  " to use just after until time"));
 			else {
-			  bool isdst = startoff != zp->z_stdoff;
-			  type = addtype(startoff, startbuf, isdst,
-					 startttisstd, startttisut);
+			  int type = addtype(startoff, startbuf, isdst,
+					     startttisstd, startttisut);
 			  if (defaulttype < 0 && !isdst)
 			    defaulttype = type;
 			  addtt(starttime, type);
@@ -2911,12 +3411,38 @@ error(_("can't determine time zone abbreviation to use just after until time"));
 	}
 	if (defaulttype < 0)
 	  defaulttype = 0;
-	if (0 <= lastatmax)
-	  attypes[lastatmax].dontmerge = true;
+	if (!do_extend && !want_bloat()) {
+	  /* Keep trailing transitions that are no greater than this.  */
+	  zic_t keep_at_max;
+
+	  /* The earliest transition into a time governed by the TZ string.  */
+	  zic_t TZstarttime = ZIC_MAX;
+	  for (i = 0; i < timecnt; i++) {
+	    zic_t at = attypes[i].at;
+	    if (nonTZlimtime < at && at < TZstarttime)
+	      TZstarttime = at;
+	  }
+	  if (TZstarttime == ZIC_MAX)
+	    TZstarttime = nonTZlimtime;
+
+	  /* Omit trailing transitions deducible from the TZ string,
+	     and not needed for -r or -R.  */
+	  keep_at_max = max(TZstarttime, redundant_time);
+	  for (i = j = 0; i < timecnt; i++)
+	    if (attypes[i].at <= keep_at_max) {
+	      attypes[j].at = attypes[i].at;
+	      attypes[j].dontmerge = (attypes[i].at == TZstarttime
+				      && (nonTZlimtype != attypes[i].type
+					  || strchr(envvar, ',')));
+	      attypes[j].type = attypes[i].type;
+	      j++;
+	    }
+	  timecnt = j;
+	}
 	if (do_extend) {
 		/*
-		** If we're extending the explicitly listed observations
-		** for 400 years because we can't fill the POSIX-TZ field,
+		** If we're extending the explicitly listed observations for
+		** 400 years because we can't fill the POSIX.1-2017 TZ field,
 		** check whether we actually ended up explicitly listing
 		** observations through that period.  If there aren't any
 		** near the end of the 400-year period, add a redundant
@@ -3005,6 +3531,10 @@ leapadd(zic_t t, int correction, int rolling)
 		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);
+	}
 	for (i = 0; i < leapcnt; ++i)
 		if (t <= trans[i])
 			break;
@@ -3037,67 +3567,15 @@ adjleap(void)
 		last = corr[i] += last;
 	}
 
-	if (leapexpires < 0) {
-	  leapexpires = comment_leapexpires;
-	  if (0 <= leapexpires)
-	    warning(_("\"#expires\" is obsolescent; use \"Expires\""));
-	}
-
 	if (0 <= leapexpires) {
 	  leapexpires = oadd(leapexpires, last);
 	  if (! (leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) {
 	    error(_("last Leap time does not precede Expires time"));
 	    exit(EXIT_FAILURE);
 	  }
-	  if (leapexpires <= hi_time)
-	    hi_time = leapexpires - 1;
 	}
 }
 
-static char *
-shellquote(char *b, char const *s)
-{
-  *b++ = '\'';
-  while (*s) {
-    if (*s == '\'')
-      *b++ = '\'', *b++ = '\\', *b++ = '\'';
-    *b++ = *s++;
-  }
-  *b++ = '\'';
-  return b;
-}
-
-static bool
-yearistype(zic_t year, const char *type)
-{
-	char *buf;
-	char *b;
-	int result;
-
-	if (type == NULL || *type == '\0')
-		return true;
-	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)) {
-	  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);
-	exit(EXIT_FAILURE);
-}
-
 /* Is A a space character in the C locale?  */
 static bool
 is_space(char a)
@@ -3149,7 +3627,7 @@ lowerit(char a)
 }
 
 /* case-insensitive equality */
-static ATTRIBUTE_PURE bool
+ATTRIBUTE_REPRODUCIBLE static bool
 ciequal(register const char *ap, register const char *bp)
 {
 	while (lowerit(*ap) == lowerit(*bp++))
@@ -3158,7 +3636,7 @@ ciequal(register const char *ap, register const char *bp)
 	return false;
 }
 
-static ATTRIBUTE_PURE bool
+ATTRIBUTE_REPRODUCIBLE static bool
 itsabbr(register const char *abbr, register const char *word)
 {
 	if (lowerit(*abbr) != lowerit(*word))
@@ -3174,7 +3652,7 @@ itsabbr(register const char *abbr, register const char *word)
 
 /* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case.  */
 
-static ATTRIBUTE_PURE bool
+ATTRIBUTE_REPRODUCIBLE static bool
 ciprefix(char const *abbr, char const *word)
 {
   do
@@ -3240,23 +3718,20 @@ byword(const char *word, const struct lookup *table)
 	return foundlp;
 }
 
-static char **
-getfields(register char *cp)
+static int
+getfields(char *cp, char **array, int arrayelts)
 {
 	register char *		dp;
-	register char **	array;
 	register int		nsubs;
 
-	if (cp == NULL)
-		return NULL;
-	array = emalloc(size_product(strlen(cp) + 1, sizeof *array));
 	nsubs = 0;
 	for ( ; ; ) {
+		char *dstart;
 		while (is_space(*cp))
 				++cp;
 		if (*cp == '\0' || *cp == '#')
 			break;
-		array[nsubs++] = dp = cp;
+		dstart = dp = cp;
 		do {
 			if ((*dp = *cp++) != '"')
 				++dp;
@@ -3271,43 +3746,50 @@ getfields(register char *cp)
 		if (is_space(*cp))
 			++cp;
 		*dp = '\0';
+		if (nsubs == arrayelts) {
+		  error(_("Too many input fields"));
+		  exit(EXIT_FAILURE);
+		}
+		array[nsubs++] = dstart + (*dstart == '-' && dp == dstart + 1);
 	}
-	array[nsubs] = NULL;
-	return array;
+	return nsubs;
 }
 
-static _Noreturn void
+ATTRIBUTE_NORETURN static void
 time_overflow(void)
 {
   error(_("time overflow"));
   exit(EXIT_FAILURE);
 }
 
-static ATTRIBUTE_PURE zic_t
+ATTRIBUTE_REPRODUCIBLE static zic_t
 oadd(zic_t t1, zic_t t2)
 {
-	if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2)
-	  time_overflow();
-	return t1 + t2;
+#ifdef ckd_add
+  zic_t sum;
+  if (!ckd_add(&sum, t1, t2))
+    return sum;
+#else
+  if (t1 < 0 ? ZIC_MIN - t1 <= t2 : t2 <= ZIC_MAX - t1)
+    return t1 + t2;
+#endif
+  time_overflow();
 }
 
-static ATTRIBUTE_PURE zic_t
+ATTRIBUTE_REPRODUCIBLE static zic_t
 tadd(zic_t t1, zic_t t2)
 {
-  if (t1 < 0) {
-    if (t2 < min_time - t1) {
-      if (t1 != min_time)
-	time_overflow();
-      return min_time;
-    }
-  } else {
-    if (max_time - t1 < t2) {
-      if (t1 != max_time)
-	time_overflow();
-      return max_time;
-    }
-  }
-  return t1 + t2;
+#ifdef ckd_add
+  zic_t sum;
+  if (!ckd_add(&sum, t1, t2) && min_time <= sum && sum <= max_time)
+    return sum;
+#else
+  if (t1 < 0 ? min_time - t1 <= t2 : t2 <= max_time - t1)
+    return t1 + t2;
+#endif
+  if (t1 == min_time || t1 == max_time)
+    return t1;
+  time_overflow();
 }
 
 /*
@@ -3321,32 +3803,28 @@ rpytime(const struct rule *rp, zic_t wantedy)
 	register int	m, i;
 	register zic_t	dayoff;			/* with a nod to Margaret O. */
 	register zic_t	t, y;
+	int yrem;
 
 	if (wantedy == ZIC_MIN)
 		return min_time;
 	if (wantedy == ZIC_MAX)
 		return max_time;
-	dayoff = 0;
 	m = TM_JANUARY;
 	y = EPOCH_YEAR;
-	if (y < wantedy) {
-	  wantedy -= y;
-	  dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
-	  wantedy %= YEARSPERREPEAT;
-	  wantedy += y;
-	} else if (wantedy < 0) {
-	  dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
-	  wantedy %= YEARSPERREPEAT;
-	}
+
+	/* dayoff = floor((wantedy - y) / YEARSPERREPEAT) * DAYSPERREPEAT,
+	   sans overflow.  */
+	yrem = wantedy % YEARSPERREPEAT - y % YEARSPERREPEAT;
+	dayoff = ((wantedy / YEARSPERREPEAT - y / YEARSPERREPEAT
+		   + yrem / YEARSPERREPEAT - (yrem % YEARSPERREPEAT < 0))
+		  * DAYSPERREPEAT);
+	/* wantedy = y + ((wantedy - y) mod YEARSPERREPEAT), sans overflow.  */
+	wantedy = y + (yrem + 2 * YEARSPERREPEAT) % YEARSPERREPEAT;
+
 	while (wantedy != y) {
-		if (wantedy > y) {
-			i = len_years[isleap(y)];
-			++y;
-		} else {
-			--y;
-			i = -len_years[isleap(y)];
-		}
+		i = len_years[isleap(y)];
 		dayoff = oadd(dayoff, i);
+		y++;
 	}
 	while (m != rp->r_month) {
 		i = len_months[isleap(y)][m];
@@ -3365,30 +3843,21 @@ rpytime(const struct rule *rp, zic_t wantedy)
 	--i;
 	dayoff = oadd(dayoff, i);
 	if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) {
-		register zic_t	wday;
-
-#define LDAYSPERWEEK	((zic_t) DAYSPERWEEK)
-		wday = EPOCH_WDAY;
 		/*
 		** Don't trust mod of negative numbers.
 		*/
-		if (dayoff >= 0)
-			wday = (wday + dayoff) % LDAYSPERWEEK;
-		else {
-			wday -= ((-dayoff) % LDAYSPERWEEK);
-			if (wday < 0)
-				wday += LDAYSPERWEEK;
-		}
+		zic_t wday = ((EPOCH_WDAY + dayoff % DAYSPERWEEK + DAYSPERWEEK)
+			      % DAYSPERWEEK);
 		while (wday != rp->r_wday)
 			if (rp->r_dycode == DC_DOWGEQ) {
 				dayoff = oadd(dayoff, 1);
-				if (++wday >= LDAYSPERWEEK)
+				if (++wday >= DAYSPERWEEK)
 					wday = 0;
 				++i;
 			} else {
 				dayoff = oadd(dayoff, -1);
 				if (--wday < 0)
-					wday = LDAYSPERWEEK - 1;
+					wday = DAYSPERWEEK - 1;
 				--i;
 			}
 		if (i < 0 || i >= len_months[isleap(y)][m]) {
@@ -3440,14 +3909,12 @@ mp = _("time zone abbreviation differs from POSIX standard");
 /* 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.  */
+   Do not consider an existing file to be trouble.  */
 static void
 mkdirs(char const *argname, bool ancestors)
 {
-	register char *	name;
-	register char *	cp;
-
-	cp = name = ecpyalloc(argname);
+	char *name = estrdup(argname);
+	char *cp = name;
 
 	/* On MS-Windows systems, do not worry about drive letters or
 	   backslashes, as this should suffice in practice.  Time zone
@@ -3471,11 +3938,18 @@ mkdirs(char const *argname, bool ancestors)
 		** 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.  */
+			/* Do not report an error if err == EEXIST, because
+			   some other process might have made the directory
+			   in the meantime.  Likewise for ENOSYS, because
+			   Solaris 10 mkdir fails with ENOSYS if the
+			   directory is an automounted mount point.
+			   Likewise for EACCES, since mkdir can fail
+			   with EACCES merely because the parent directory
+			   is unwritable.  Likewise for most other error
+			   numbers.  */
 			int err = errno;
-			if (err != EEXIST && !itsdir(name)) {
+			if (err == ELOOP || err == ENAMETOOLONG
+			    || err == ENOENT || err == ENOTDIR) {
 				error(_("%s: Can't create directory %s: %s"),
 				      progname, name, strerror(err));
 				exit(EXIT_FAILURE);