[tz] [PROPOSED PATCH 3/5] zic is now more careful about symlinks

Paul Eggert eggert at cs.ucla.edu
Tue Sep 6 04:40:35 UTC 2016


This avoids some problems in creating nonworking hard links to
symlinks, as well as removing the need for some stat calls.
* NEWS: Mention this.
* zic.c (lstat, S_ISLNK) [!HAVE_SYMLINK]: New fallback macros.
(dolink): Remove destination before linking; this is more consistent
with what is done with fopen, and simplifies the code.  It also lets
us avoid some mkdir calls in some cases.
(itsdir): Document that errno is set when -1 is returned.
Use lstat, not stat.  Avoid tricky code with EOVERFLOW, as it doesn't
work with lstat and probably wasn't worth the trouble anyway.
(writezone): Do not bother statting the destination before removing
it.  The old code catered to ancient platforms that lacked the
'remove' function and where trouble could ensue if a program unlinked
a directory.  We don't need to worry about these platforms any more,
as the code has been successfully using 'remove' since the 1980s.
(mkdirs): Exit on failure, and return true if some directories are made.
All callers changed.  Allow symlinks as well as directories.
---
 NEWS  |  3 ++-
 zic.c | 98 ++++++++++++++++++++++++++++++++++++-------------------------------
 2 files changed, 54 insertions(+), 47 deletions(-)

diff --git a/NEWS b/NEWS
index 192deb7..a2a57fc 100644
--- a/NEWS
+++ b/NEWS
@@ -55,7 +55,8 @@ Unreleased, experimental changes
     stamps on the reference platform.  (Thanks to Alexander Belopolsky
     for reporting the bug and suggesting a way forward.)
 
-    zic now avoids some unnecessary mkdir and stat system calls.
+    zic now avoids hard linking to symbolic links, and avoids some
+    unnecessary mkdir and stat system calls.
 
     zdump has a new -i option to generate transitions in a
     more-compact but still human-readable format.  This option is
diff --git a/zic.c b/zic.c
index 1b7124a..1c8a193 100644
--- a/zic.c
+++ b/zic.c
@@ -106,7 +106,9 @@ extern int	optind;
 # define link(from, to) (errno = ENOTSUP, -1)
 #endif
 #if ! HAVE_SYMLINK
+# define lstat(name, st) stat(name, st)
 # define symlink(from, to) (errno = ENOTSUP, -1)
+# define S_ISLNK(m) 0
 #endif
 
 static void	addtt(zic_t starttime, int type);
@@ -762,6 +764,7 @@ dolink(char const *fromfield, char const *tofield)
 	register char *	fromname;
 	register char *	toname;
 	register int fromisdir;
+	int todirs_made = -1;
 
 	fromname = relname(directory, fromfield);
 	toname = relname(directory, tofield);
@@ -776,23 +779,21 @@ dolink(char const *fromfield, char const *tofield)
 			progname, fromname, e);
 		exit(EXIT_FAILURE);
 	}
+	if (remove(toname) == 0)
+	  todirs_made = 0;
+	else if (errno != ENOENT) {
+	  char const *e = strerror(errno);
+	  fprintf(stderr, _("%s: Can't remove %s: %s\n"), progname, toname, e);
+	  exit(EXIT_FAILURE);
+	}
 	if (link(fromname, toname) != 0) {
 	  int link_errno = errno;
-	  bool todirs_made = false;
-	  bool retry_if_link_supported = false;
-
-	  if (link_errno == ENOENT) {
-	    if (! mkdirs(toname))
-	      exit(EXIT_FAILURE);
-	    todirs_made = true;
-	    retry_if_link_supported = true;
+
+	  if (link_errno == ENOENT && todirs_made < 0) {
+	    todirs_made = mkdirs(toname);
+	    if (todirs_made)
+	      link_errno = link(fromname, toname) == 0 ? 0 : errno;
 	  }
-	  if ((link_errno == EEXIST || link_errno == ENOTSUP)
-	      && itsdir(toname) == 0
-	      && (remove(toname) == 0 || errno == ENOENT))
-	    retry_if_link_supported = true;
-	  if (retry_if_link_supported && link_errno != ENOTSUP)
-	    link_errno = link(fromname, toname) == 0 ? 0 : errno;
 	  if (link_errno != 0) {
 	    const char *s = fromfield;
 	    const char *t;
@@ -813,11 +814,9 @@ dolink(char const *fromfield, char const *tofield)
 	      memcpy(p, "../", 3);
 	    strcpy(p, t);
 	    symlink_result = symlink(symlinkcontents, toname);
-	    if (!todirs_made && symlink_result != 0 && errno == ENOENT) {
-	      if (! mkdirs(toname))
-		exit(EXIT_FAILURE);
+	    if (symlink_result != 0 && errno == ENOENT
+		&& todirs_made < 0 && mkdirs(toname))
 	      symlink_result = symlink(symlinkcontents, toname);
-	    }
 	    free(symlinkcontents);
 	    if (symlink_result == 0) {
 	      if (link_errno != ENOTSUP)
@@ -899,21 +898,22 @@ 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, 0 if it's something else, -1 if trouble.  */
+/* Return 1 if NAME is a directory or a symbolic link, 0 if it's
+   something else, -1 (setting errno) if trouble.  */
 static int
 itsdir(char const *name)
 {
 	struct stat st;
-	int res = stat(name, &st);
+	int res = lstat(name, &st);
+	if (res == 0) {
 #ifdef S_ISDIR
-	if (res == 0)
-		return S_ISDIR(st.st_mode) != 0;
-#endif
-	if (res == 0 || errno == EOVERFLOW) {
+		return S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode);
+#else
 		char *nameslashdot = relname(name, ".");
-		bool dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW;
+		bool dir = lstat(nameslashdot, &st) == 0;
 		free(nameslashdot);
 		return dir;
+#endif
 	}
 	return -1;
 }
@@ -1640,6 +1640,7 @@ writezone(const char *const name, const char *const string, char version)
 	char *				fullname;
 	static const struct tzhead	tzh0;
 	static struct tzhead		tzh;
+	bool dir_checked = false;
 	zic_t one = 1;
 	zic_t y2038_boundary = one << 31;
 	int nats = timecnt + WORK_AROUND_QTBUG_53071;
@@ -1743,7 +1744,9 @@ writezone(const char *const name, const char *const string, char version)
 	/*
 	** Remove old file, if any, to snap links.
 	*/
-	if (itsdir(fullname) == 0 && remove(fullname) != 0 && errno != ENOENT) {
+	if (remove(fullname) == 0)
+		dir_checked = true;
+	else if (errno != ENOENT) {
 		const char *e = strerror(errno);
 
 		fprintf(stderr, _("%s: Can't remove %s: %s\n"),
@@ -1751,17 +1754,18 @@ writezone(const char *const name, const char *const string, char version)
 		exit(EXIT_FAILURE);
 	}
 	fp = fopen(fullname, "wb");
-	if (!fp && errno == ENOENT) {
-		if (! mkdirs(fullname))
-			exit(EXIT_FAILURE);
-		fp = fopen(fullname, "wb");
-		if (!fp) {
-			const char *e = strerror(errno);
-
-			fprintf(stderr, _("%s: Can't create %s: %s\n"),
-				progname, fullname, e);
-			exit(EXIT_FAILURE);
-		}
+	if (!fp) {
+	  int fopen_errno = errno;
+	  if (fopen_errno == ENOENT && !dir_checked && mkdirs(fullname)) {
+	    fp = fopen(fullname, "wb");
+	    fopen_errno = errno;
+	  }
+	  if (!fp) {
+	    char const *e = strerror(fopen_errno);
+	    fprintf(stderr, _("%s: Can't create %s: %s\n"),
+		    progname, fullname, e);
+	    exit(EXIT_FAILURE);
+	  }
 	}
 	for (pass = 1; pass <= 2; ++pass) {
 		register int	thistimei, thistimecnt;
@@ -3027,14 +3031,17 @@ mp = _("time zone abbreviation differs from POSIX standard");
 	charcnt += i;
 }
 
+/* Ensure that the parent directories of ARGNAME exist, by making any
+   missing ones.  Return true if some directories are made (perhaps by
+   some other process), false if the directories already exist, and
+   exit with failure if there is trouble.  */
 static bool
 mkdirs(char *argname)
 {
 	register char *	name;
 	register char *	cp;
+	bool dirs_made = false;
 
-	if (argname == NULL || *argname == '\0')
-		return true;
 	cp = name = ecpyalloc(argname);
 
 	/* Do not mkdir a root directory, as it must exist.  */
@@ -3056,17 +3063,16 @@ mkdirs(char *argname)
 		*/
 		if (mkdir(name, MKDIR_UMASK) != 0) {
 			int err = errno;
-			if (err != EEXIST && itsdir(name) <= 0) {
+			if (err != EEXIST && itsdir(name) < 0) {
 				char const *e = strerror(err);
-				warning(_("%s: Can't create directory"
-					  " %s: %s"),
-					progname, name, e);
-				free(name);
-				return false;
+				error(_("%s: Can't create directory %s: %s"),
+				      progname, name, e);
+				exit(EXIT_FAILURE);
 			}
 		}
 		*cp = '/';
+		dirs_made = true;
 	}
 	free(name);
-	return true;
+	return dirs_made;
 }
-- 
2.7.4



More information about the tz mailing list