* Makefile, NEWS, zic.8: Mention this.
* zic.c (arg2num, mode_option, chmetadata): New functions.
(mode_t) [!HAVE_SYS_STAT_H]: New macro if not already defined.
(MODE_T_MAX, MODE_OPTION): New macros.
(HAVE_FCHMOD): New macro, defaulting to 1.
(fchmod): Default to returning 0.
(HAVE_SETMODE): New macro, defaulting to 1 on BSDish platforms.
(no_mode): New static constant.
(output_mode): New static var.
(close_file): Change metadata of temp files before closing,
and fflush before changing metadata as this may work around OS bugs.
(main): Support -m.
---
Makefile | 5 +++
NEWS | 2 +-
zic.8 | 13 ++++++++
zic.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
4 files changed, 110 insertions(+), 6 deletions(-)
diff --git a/Makefile b/Makefile
index 8f85fcfd..224f250a 100644
--- a/Makefile
+++ b/Makefile
@@ -244,6 +244,7 @@ LDLIBS=
# -DHAVE_DECL_ENVIRON if <unistd.h> declares 'environ'
# -DHAVE_DECL_TIMEGM=0 if <time.h> does not declare timegm
# -DHAVE_DIRECT_H if mkdir needs <direct.h> (MS-Windows)
+# -DHAVE_FCHMOD=0 if your system lacks the fchmod function
# -DHAVE__GENERIC=0 if _Generic does not work*
# -DHAVE_GETAUXVAL=1 if getauxval works, 0 otherwise (default is guessed)
# -DHAVE_GETEUID=0 if gete?[ug]id do not work
@@ -267,6 +268,8 @@ LDLIBS=
# -DHAVE_POSIX_DECLS=0 if your system's include files do not declare
# variables like 'tzname' required by POSIX
# -DHAVE_SETENV=0 if your system lacks the setenv function
+# -DHAVE_SETMODE=[01] if your system lacks or has the setmode and getmode
+# functions (default is guessed)
# -DHAVE_SNPRINTF=0 if your system lacks the snprintf function+
# -DHAVE_STDCKDINT_H=0 if neither <stdckdint.h> nor substitutes like
# __builtin_add_overflow work*
@@ -279,6 +282,8 @@ LDLIBS=
# -DHAVE_STRUCT_TIMESPEC=0 if your system lacks struct timespec+
# -DHAVE_SYMLINK=0 if your system lacks the symlink function
# -DHAVE_SYS_STAT_H=0 if <sys/stat.h> does not work*
+# The following additional option may be needed:
+# -Dmode_t=T to define mode_t to be type T (default int)
# -DHAVE_TZSET=0 if your system lacks a tzset function
# -DHAVE_UNISTD_H=0 if <unistd.h> does not work*
# -DHAVE_UTMPX_H=0 if <utmpx.h> does not work*
diff --git a/NEWS b/NEWS
index e777c6a7..8f48a7bd 100644
--- a/NEWS
+++ b/NEWS
@@ -105,7 +105,7 @@ Unreleased, experimental changes
exceedingly long TZ strings no longer fail merely because they
exceed an arbitrary file name length limit imposed by tzcode.
- zic has a new option -D, inspired by FreeBSD.
+ zic has new options -D and -m, inspired by FreeBSD.
zic now uses the fdopen function, which was standardized by
POSIX.1-1988 and is now safe to use in portable code.
diff --git a/zic.8 b/zic.8
index 45318020..761b204c 100644
--- a/zic.8
+++ b/zic.8
@@ -137,6 +137,19 @@ if
.IR timezone 's
transitions are at standard time or Universal Time (UT) instead of local time.
.TP
+.BI "\-m " mode
+Create TZif files with the given file mode bits.
+By default the files are created with mode 644 as modified by the umask.
+With this option they are created with the given mode instead.
+For portability the mode should be an unsigned octal integer,
+typically 644 or 444;
+some platforms also support
+.BR chmod (1)-style
+symbolic modes.
+This option does not affect created ancestor directories,
+which have mode 755 as modified by the umask.
+The option is ignored on platforms lacking the notion of file mode bits.
+.TP
.BR "\-r " "[\fB@\fP\fIlo\fP][\fB/@\fP\fIhi\fP]"
Limit the applicability of output files
to timestamps in the range from
diff --git a/zic.c b/zic.c
index d8408abf..fc03c32d 100644
--- a/zic.c
+++ b/zic.c
@@ -67,6 +67,10 @@ enum { FORMAT_LEN_GROWTH_BOUND = 5 };
#if HAVE_SYS_STAT_H
# include <sys/stat.h>
+#else
+# ifndef mode_t
+# define mode_t int
+# endif
#endif
#ifndef S_IRWXU
@@ -672,14 +676,91 @@ warning(const char *const string, ...)
warnings = true;
}
-/* Close STREAM. If it had an I/O error, report it against DIR/NAME,
- remove TEMPNAME if nonnull, and then exit. */
+/* Convert ARG, a string in base BASE, to an unsigned long value no
+ greater than MAXVAL. On failure, diagnose with MSGID and exit. */
+static unsigned long
+arg2num(char const *arg, int base, unsigned long maxval, char const *msgid)
+{
+ unsigned long n;
+ char *ep;
+ errno = 0;
+ n = strtoul(arg, &ep, base);
+ if (ep == arg || *ep || maxval < n || errno) {
+ fprintf(stderr, _(msgid), progname, arg);
+ exit(EXIT_FAILURE);
+ }
+ return n;
+}
+
+#ifndef MODE_T_MAX
+# define MODE_T_MAX_NO_PADDING MAXVAL(mode_t, TYPE_BIT(mode_t))
+# if HAVE__GENERIC
+# define MODE_T_MAX \
+ (TYPE_SIGNED(mode_t) \
+ ? _Generic((mode_t) 0, \
+ signed char: SCHAR_MAX, short: SHRT_MAX, \
+ int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \
+ default: MODE_T_MAX_NO_PADDING) \
+ : (mode_t) -1)
+# else
+# define MODE_T_MAX MODE_T_MAX_NO_PADDING
+# endif
+#endif
+
+#ifndef HAVE_FCHMOD
+# define HAVE_FCHMOD 1
+#endif
+#if !HAVE_FCHMOD
+# define fchmod(fd, mode) 0
+#endif
+
+#ifndef HAVE_SETMODE
+# if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+ || (defined __APPLE__ && defined __MACH__))
+# define HAVE_SETMODE 1
+# else
+# define HAVE_SETMODE 0
+# endif
+#endif
+
+static mode_t const no_mode = -1;
+static mode_t output_mode = -1;
+
+static mode_t
+mode_option(char const *arg)
+{
+#if HAVE_SETMODE
+ void *set = setmode(arg);
+ if (set) {
+ mode_t mode = getmode(set, CREAT_PERMS);
+ free(set);
+ return mode;
+ }
+#endif
+ return arg2num(arg, 8, min(MODE_T_MAX, ULONG_MAX),
+ "%s: -m '%s': invalid mode\n");
+}
+
+static int
+chmetadata(FILE *stream)
+{
+ return output_mode == no_mode ? 0 : fchmod(fileno(stream), output_mode);
+}
+
+/* Close STREAM.
+ If it had an I/O error, report it against DIR/NAME,
+ remove TEMPNAME if nonnull, and then exit.
+ If TEMPNAME is nonnull, and if requested,
+ change the stream's metadata before closing. */
static void
close_file(FILE *stream, char const *dir, char const *name,
char const *tempname)
{
char const *e = (ferror(stream) ? _("I/O error")
- : fclose(stream) != 0 ? strerror(errno) : NULL);
+ : (fflush(stream) < 0
+ || (tempname && chmetadata(stream) < 0)
+ || fclose(stream) < 0)
+ ? strerror(errno) : NULL);
if (e) {
if (name && *name == '/')
dir = NULL;
@@ -706,7 +787,7 @@ usage(FILE *stream, int status)
fprintf(stream,
_("%s: usage is %s [ --version ] [ --help ] [ -v ] \\\n"
"\t[ -b {slim|fat} ] [ -d directory ] [ -D ] \\\n"
- "\t[ -l localtime ] [ -L leapseconds ] \\\n"
+ "\t[ -l localtime ] [ -L leapseconds ] [ -m mode ] \\\n"
"\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -R @hi ] \\\n"
"\t[ -t localtime-link ] \\\n"
"\t[ filename ... ]\n\n"
@@ -1038,7 +1119,7 @@ main(int argc, char **argv)
} else if (strcmp(argv[k], "--help") == 0) {
usage(stdout, EXIT_SUCCESS);
}
- while ((c = getopt(argc, argv, "b:d:Dl:L:p:r:R:st:vy:")) != -1)
+ while ((c = getopt(argc, argv, "b:d:Dl:L:m:p:r:R:st:vy:")) != -1)
switch (c) {
default:
usage(stderr, EXIT_FAILURE);
@@ -1067,6 +1148,11 @@ main(int argc, char **argv)
duplicate_options("-l");
lcltime = optarg;
break;
+ case 'm':
+ if (output_mode != no_mode)
+ duplicate_options("-m");
+ output_mode = mode_option(optarg);
+ break;
case 'p':
if (psxrules)
duplicate_options("-p");
--
2.48.1