diff lib/mkdir-p.c @ 7312:796e1837ac66

* lib/dirchownmod.c: Don't include fcntl.h; no longer needed. (dirchownmod): New arg FD. All callers changed. Use FD rather than opening the directory ourself, as opening is now the caller's responsibility. * lib/dirchownmod.h: Likewise. * lib/mkancesdirs.c: Include <sys/types.h>, for portability to older hosts that require <sys/types.h> before <sys/stat.h>. Include fcntl.h, savewd.h, and unistd.h, not dirname.h and stat-macros.h. (test_dir): Remove. (mkancesdirs): Return length of prefix of FILE that has already been made, or -2 if there is a child doing the work. Redo algorithm so that it is O(N) rather than O(N**2). Optimize away ".", and treat ".." specially since it might stray back into already-created areas. Use a subprocess if necessary. New arg WD; all users changed. MAKE_DIR function should now return 1 if it creates a directory that is not readable. Return -2 if a child process is spun off. * lib/mkancesdirs.h: Include <stddef.h>, for ptrdiff_t. Adjust signature to match code. * lib/mkdir-p.c: Include dirname.h, for IS_ABSOLUTE_FILE_NAME. (make_dir_parents): Use a subprocess if necessary. New arg WD; all users changed. * lib/savewd.c, lib/savewd.h: New files. * m4/savewd.m4: New file. * modules/mkancesdirs (Depends-on): Add fcntl. * modules/savewd: New file. * MODULES.html.sh (File system functions): Add savewd.
author Paul Eggert <eggert@cs.ucla.edu>
date Sat, 16 Sep 2006 19:58:25 +0000 (2006-09-16)
parents 8a1a9361108c
children d12ab9dc1e7e
line wrap: on
line diff
--- a/lib/mkdir-p.c
+++ b/lib/mkdir-p.c
@@ -30,13 +30,17 @@
 #define _(msgid) gettext (msgid)
 
 #include "dirchownmod.c"
+#include "dirname.h"
 #include "error.h"
 #include "quote.h"
 #include "mkancesdirs.h"
+#include "savewd.h"
 #include "stat-macros.h"
 
 /* Ensure that the directory DIR exists.
 
+   WD is the working directory, as in savewd.c.
+
    If MAKE_ANCESTOR is not null, create any ancestor directories that
    don't already exist, by invoking MAKE_ANCESTOR (ANCESTOR, OPTIONS).
    This function should return zero if successful, -1 (setting errno)
@@ -46,7 +50,7 @@
    created.
 
    Create DIR as a new directory with using mkdir with permissions
-   MODE.  It is also OK if MAKE_ANCESTOR_DIR is not null and a
+   MODE.  It is also OK if MAKE_ANCESTOR is not null and a
    directory DIR already exists.
 
    Call ANNOUNCE (DIR, OPTIONS) just after successfully making DIR,
@@ -69,12 +73,15 @@
    This implementation assumes the current umask is zero.
 
    Return true if DIR exists as a directory with the proper ownership
-   and file mode bits when done.  Report a diagnostic and return false
-   on failure, storing '\0' into *DIR if an ancestor directory had
-   problems.  */
+   and file mode bits when done, or if a child process has been
+   dispatched to do the real work (though the child process may not
+   have finished yet -- it is the caller's responsibility to handle
+   this).  Report a diagnostic and return false on failure, storing
+   '\0' into *DIR if an ancestor directory had problems.  */
 
 bool
 make_dir_parents (char *dir,
+		  struct savewd *wd,
 		  int (*make_ancestor) (char const *, void *),
 		  void *options,
 		  mode_t mode,
@@ -84,51 +91,101 @@
 		  gid_t group,
 		  bool preserve_existing)
 {
-  bool made_dir = (mkdir (dir, mode) == 0);
+  int mkdir_errno = (IS_ABSOLUTE_FILE_NAME (dir) ? 0 : savewd_errno (wd));
 
-  if (!made_dir && make_ancestor && errno == ENOENT)
+  if (mkdir_errno == 0)
     {
-      if (mkancesdirs (dir, make_ancestor, options) == 0)
-	made_dir = (mkdir (dir, mode) == 0);
-      else
+      ptrdiff_t prefix_len = 0;
+      int savewd_chdir_options = (HAVE_FCHMOD ? SAVEWD_CHDIR_SKIP_READABLE : 0);
+
+      if (make_ancestor)
+	{
+	  prefix_len = mkancesdirs (dir, wd, make_ancestor, options);
+	  if (prefix_len < 0)
+	    {
+	      if (prefix_len < -1)
+		return true;
+	      mkdir_errno = errno;
+	    }
+	}
+
+      if (0 <= prefix_len)
 	{
-	  /* mkancestdirs updated DIR for a better-looking
-	     diagnostic, so don't try to stat DIR below.  */
-	  make_ancestor = NULL;
+	  if (mkdir (dir + prefix_len, mode) == 0)
+	    {
+	      announce (dir, options);
+	      preserve_existing =
+		(owner == (uid_t) -1 && group == (gid_t) -1
+		 && ! ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX)));
+	      savewd_chdir_options |=
+		(SAVEWD_CHDIR_NOFOLLOW
+		 | (mode & S_IRUSR ? SAVEWD_CHDIR_READABLE : 0));
+	    }
+	  else
+	    mkdir_errno = errno;
+
+	  if (preserve_existing)
+	    {
+	      struct stat st;
+	      if (mkdir_errno == 0
+		  || (mkdir_errno != ENOENT && make_ancestor
+		      && stat (dir + prefix_len, &st) == 0
+		      && S_ISDIR (st.st_mode)))
+		return true;
+	    }
+	  else
+	    {
+	      int open_result[2];
+	      int chdir_result =
+		savewd_chdir (wd, dir + prefix_len,
+			      savewd_chdir_options, open_result);
+	      if (chdir_result < -1)
+		return true;
+	      else
+		{
+		  bool chdir_ok = (chdir_result == 0);
+		  int chdir_errno = errno;
+		  int fd = open_result[0];
+		  bool chdir_failed_unexpectedly =
+		    (mkdir_errno == 0
+		     && ((! chdir_ok && (mode & S_IXUSR))
+			 || (fd < 0 && (mode & S_IRUSR))));
+
+		  if (chdir_failed_unexpectedly)
+		    {
+		      /* No need to save errno here; it's irrelevant.  */
+		      if (0 <= fd)
+			close (fd);
+		    }
+		  else
+		    {
+		      mode_t mkdir_mode = (mkdir_errno == 0 ? mode : -1);
+		      char const *subdir = (chdir_ok ? "." : dir + prefix_len);
+		      if (dirchownmod (fd, subdir, mkdir_mode, owner, group,
+				       mode, mode_bits)
+			  == 0)
+			return true;
+		    }
+
+		  if (mkdir_errno == 0
+		      || (mkdir_errno != ENOENT && make_ancestor
+			  && errno != ENOTDIR))
+		    {
+		      error (0,
+			     (! chdir_failed_unexpectedly ? errno
+			      : ! chdir_ok && (mode & S_IXUSR) ? chdir_errno
+			      : open_result[1]),
+			     _(owner == (uid_t) -1 && group == (gid_t) -1
+			       ? "cannot change permissions of %s"
+			       : "cannot change owner and permissions of %s"),
+			     quote (dir));
+		      return false;
+		    }
+		}
+	    }
 	}
     }
 
-  if (made_dir)
-    {
-      announce (dir, options);
-      preserve_existing =
-	(owner == (uid_t) -1 && group == (gid_t) -1
-	 && ! ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX)));
-    }
-  else
-    {
-      int mkdir_errno = errno;
-      struct stat st;
-      if (! (make_ancestor && mkdir_errno != ENOENT
-	     && stat (dir, &st) == 0 && S_ISDIR (st.st_mode)))
-	{
-	  error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
-	  return false;
-	}
-    }
-
-  if (! preserve_existing
-      && (dirchownmod (dir, (made_dir ? mode : (mode_t) -1),
-		       owner, group, mode, mode_bits)
-	  != 0))
-    {
-      error (0, errno,
-	     _(owner == (uid_t) -1 && group == (gid_t) -1
-	       ? "cannot change permissions of %s"
-	       : "cannot change owner and permissions of %s"),
-	     quote (dir));
-      return false;
-    }
-
-  return true;
+  error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
+  return false;
 }