* 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

Reply via email to