From 8a073b9a2b5586a0211e837085766c57fa7f7628 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Thu, 3 Nov 2016 14:41:51 -0700 Subject: [PROPOSED] Port zic to systems lacking hard links (Thanks to Tom Lane for reporting the problem.) Also, port better to systems where 'link' does not follow symlinks. * NEWS: Document this. * zic.c: Include fcntl.h, for linkat. (lstat) [!HAVE_SYMLINK]: Remove; no longer needed. (readlink) [!HAVE_SYMLINK]: New macro. (linkat) [!AT_SYMLINK_FOLLOW]: New macro. (hardlinkerr): New function, which uses linkat to work around portability problems with 'link' implementations that do not follow hard links. (dolink): Use it. (itsdir): Return bool, not int, and only check whether the arg is a directory. All callers changed. This lets us revert to the previous implementation, which avoids EOVERFLOW problems. (itssymlink): New function. All callers of itsdir changed to use itssymlink, if they care about symlinks instead of dirs. --- NEWS | 8 ++++++++ zic.c | 65 ++++++++++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index db9f03d..39a7126 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,14 @@ Unreleased, experimental changes Asia/Gaza and Asia/Hebron now use "EEST", not "EET", to denote summer time before 1948. The old use of "EET" was a typo. + Changes to code + + zic no longer mishandles file systems that lack hard links, fixing + a bug introduced in 2016g. (Problem reported by Tom Lane.) + Also, when the destination already contains symbolic links, zic + should now work better on systems where the 'link' system call + does not follow symbolic links. + Release 2016i - 2016-11-01 23:19:52 -0700 diff --git a/zic.c b/zic.c index f6baf10..ed4991d 100644 --- a/zic.c +++ b/zic.c @@ -8,6 +8,7 @@ #include "locale.h" #include "tzfile.h" +#include #include #include @@ -117,10 +118,14 @@ extern int optind; # define link(from, to) (errno = ENOTSUP, -1) #endif #if ! HAVE_SYMLINK -# define lstat(name, st) stat(name, st) +# define readlink(file, buf, size) (errno = ENOTSUP, -1) # define symlink(from, to) (errno = ENOTSUP, -1) # define S_ISLNK(m) 0 #endif +#ifndef AT_SYMLINK_FOLLOW +# define linkat(fromdir, from, todir, to, flag) \ + (itssymlink(from) ? (errno = ENOTSUP, -1) : link(from, to)) +#endif static void addtt(zic_t starttime, int type); static int addtype(zic_t, char const *, bool, bool, bool); @@ -138,7 +143,8 @@ static void inrule(char ** fields, int nfields); static bool inzcont(char ** fields, int nfields); static bool inzone(char ** fields, int nfields); static bool inzsub(char **, int, bool); -static int itsdir(const char * name); +static bool itsdir(char const *); +static bool itssymlink(char const *); static bool is_alpha(char a); static char lowerit(char); static void mkdirs(char const *, bool); @@ -812,10 +818,18 @@ 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) +{ + int r = linkat(AT_FDCWD, from, AT_FDCWD, to, AT_SYMLINK_FOLLOW); + return r == 0 ? 0 : errno; +} + static void dolink(char const *fromfield, char const *tofield, bool staysymlink) { - register int fromisdir; bool todirs_made = false; int link_errno; @@ -823,15 +837,13 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink) ** We get to be careful here since ** there's a fair chance of root running us. */ - fromisdir = itsdir(fromfield); - if (fromisdir) { - char const *e = strerror(fromisdir < 0 ? errno : EPERM); + if (itsdir(fromfield)) { fprintf(stderr, _("%s: link from %s/%s failed: %s\n"), - progname, directory, fromfield, e); + progname, directory, fromfield, strerror(EPERM)); exit(EXIT_FAILURE); } if (staysymlink) - staysymlink = itsdir(tofield) == 2; + staysymlink = itssymlink(tofield); if (remove(tofield) == 0) todirs_made = true; else if (errno != ENOENT) { @@ -840,12 +852,11 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink) progname, directory, tofield, e); exit(EXIT_FAILURE); } - link_errno = (staysymlink ? ENOTSUP - : link(fromfield, tofield) == 0 ? 0 : errno); + link_errno = staysymlink ? ENOTSUP : hardlinkerr(fromfield, tofield); if (link_errno == ENOENT && !todirs_made) { mkdirs(tofield, true); todirs_made = true; - link_errno = link(fromfield, tofield) == 0 ? 0 : errno; + link_errno = hardlinkerr(fromfield, tofield); } if (link_errno != 0) { bool absolute = *fromfield == '/'; @@ -935,28 +946,35 @@ static const zic_t early_time = (WORK_AROUND_GNOME_BUG_730332 ? BIG_BANG : MINVAL(zic_t, TIME_T_BITS_IN_FILE)); -/* Return 1 if NAME is a directory, 2 if a symbolic link, 0 if - something else, -1 (setting errno) if trouble. */ -static int +/* Return true if NAME is a directory. */ +static bool itsdir(char const *name) { struct stat st; - int res = lstat(name, &st); - if (res == 0) { + int res = stat(name, &st); #ifdef S_ISDIR - return S_ISDIR(st.st_mode) ? 1 : S_ISLNK(st.st_mode) ? 2 : 0; -#else + 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 = lstat(nameslashdot, &st) == 0; + dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW; free(nameslashdot); return dir; -#endif } - return -1; + return false; +} + +/* Return true if NAME is a symbolic link. */ +static bool +itssymlink(char const *name) +{ + char c; + return 0 <= readlink(name, &c, 1); } /* @@ -3097,7 +3115,8 @@ 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 it for ARGNAME too. Exit with failure if there is trouble. + Do not consider an existing non-directory to be trouble. */ static void mkdirs(char const *argname, bool ancestors) { @@ -3126,7 +3145,7 @@ mkdirs(char const *argname, bool ancestors) */ if (mkdir(name, MKDIR_UMASK) != 0) { int err = errno; - if (err != EEXIST && itsdir(name) < 0) { + if (err != EEXIST && !itsdir(name)) { error(_("%s: Can't create directory %s: %s"), progname, name, strerror(err)); exit(EXIT_FAILURE); -- 2.7.4