* 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