changeset 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
parents dc0ecc0fbaf2
children 38e8d472e939
files ChangeLog MODULES.html.sh lib/ChangeLog lib/dirchownmod.c lib/dirchownmod.h lib/mkancesdirs.c lib/mkancesdirs.h lib/mkdir-p.c lib/mkdir-p.h m4/ChangeLog modules/mkancesdirs
diffstat 11 files changed, 251 insertions(+), 142 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2006-09-15  Paul Eggert  <eggert@cs.ucla.edu>
 
+	* modules/mkancesdirs (Depends-on): Add fcntl.
+	* modules/savewd: New file.
+	* MODULES.html.sh (File system functions): Add savewd.
+
 	* modules/configmake (Makefile.am): Add support for the
 	Automake-supplied PKGLIBDIR, PKGINCLUDEDIR, PKGDATADIR.
 
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -1908,6 +1908,7 @@
   func_module same
   func_module save-cwd
   func_module savedir
+  func_module savewd
   func_module stat-time
   func_module tmpdir
   func_module unlinkdir
--- a/lib/ChangeLog
+++ b/lib/ChangeLog
@@ -1,3 +1,29 @@
+2006-09-15  Paul Eggert  <eggert@cs.ucla.edu>
+
+	* 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.
+	* dirchownmod.h: Likewise.
+	* 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.
+	* mkancesdirs.h: Include <stddef.h>, for ptrdiff_t.
+	Adjust signature to match code.
+	* 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.
+	* savewd.c, savewd.h: New files.
+
 2006-09-15  Jim Meyering  <jim@meyering.net>
 
 	* rename-dest-slash.c (has_trailing_slash): Use
--- a/lib/dirchownmod.c
+++ b/lib/dirchownmod.c
@@ -25,7 +25,6 @@
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <fcntl.h>
 #include <unistd.h>
 
 #include "lchmod.h"
@@ -37,7 +36,10 @@
 # define fchmod(fd, mode) (-1)
 #endif
 
-/* Change the ownership and mode bits of the directory DIR.
+/* Change the ownership and mode bits of a directory.  If FD is
+   nonnegative, it should be a file descriptor associated with the
+   directory; close it before returning.  DIR is the name of the
+   directory.
 
    If MKDIR_MODE is not (mode_t) -1, mkdir (DIR, MKDIR_MODE) has just
    been executed successfully with umask zero, so DIR should be a
@@ -58,27 +60,12 @@
    calls may do the chown but not the chmod.  */
 
 int
-dirchownmod (char const *dir, mode_t mkdir_mode,
+dirchownmod (int fd, char const *dir, mode_t mkdir_mode,
 	     uid_t owner, gid_t group,
 	     mode_t mode, mode_t mode_bits)
 {
   struct stat st;
-  int result;
-
-  /* Manipulate DIR via a file descriptor if possible, to avoid some races.  */
-  int open_flags = O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK;
-  int fd = open (dir, open_flags);
-
-  /* Fail if the directory is unreadable, the directory previously
-     existed or was created without read permission.  Otherwise, get
-     the file's status.  */
-  if (0 <= fd)
-    result = fstat (fd, &st);
-  else if (errno != EACCES
-	   || (mkdir_mode != (mode_t) -1 && mkdir_mode & S_IRUSR))
-    return fd;
-  else
-    result = stat (dir, &st);
+  int result = (fd < 0 ? stat (dir, &st) : fstat (fd, &st));
 
   if (result == 0)
     {
--- a/lib/dirchownmod.h
+++ b/lib/dirchownmod.h
@@ -1,2 +1,2 @@
 #include <sys/types.h>
-int dirchownmod (char const *, mode_t, uid_t, gid_t, mode_t, mode_t);
+int dirchownmod (int, char const *, mode_t, uid_t, gid_t, mode_t, mode_t);
--- a/lib/mkancesdirs.c
+++ b/lib/mkancesdirs.c
@@ -22,94 +22,71 @@
 
 #include "mkancesdirs.h"
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
 #include <errno.h>
-#include <sys/stat.h>
+#include <unistd.h>
 
 #include "dirname.h"
-#include "stat-macros.h"
-
-/* Return 0 if FILE is a directory, otherwise -1 (setting errno).  */
-
-static int
-test_dir (char const *file)
-{
-  struct stat st;
-  if (stat (file, &st) == 0)
-    {
-      if (S_ISDIR (st.st_mode))
-	return 0;
-      errno = ENOTDIR;
-    }
-  return -1;
-}
+#include "savewd.h"
 
 /* Ensure that the ancestor directories of FILE exist, using an
    algorithm that should work even if two processes execute this
-   function in parallel.  Temporarily modify FILE by storing '\0'
-   bytes into it, to access the ancestor directories.
+   function in parallel.  Modify FILE as necessary to access the
+   ancestor directories, but restore FILE to an equivalent value
+   if successful.
+
+   WD points to the working directory, using the conventions of
+   savewd.
 
    Create any ancestor directories that don't already exist, by
-   invoking MAKE_DIR (ANCESTOR, MAKE_DIR_ARG).  This function should
-   return zero if successful, -1 (setting errno) otherwise.
+   invoking MAKE_DIR (COMPONENT, MAKE_DIR_ARG).  This function should
+   return 0 if successful and the resulting directory is readable, 1
+   if successful but the resulting directory might not be readable, -1
+   (setting errno) otherwise.  If COMPONENT is relative, it is
+   relative to the temporary working directory, which may differ from
+   *WD.
 
-   If successful, return 0 with FILE set back to its original value;
-   otherwise, return -1 (setting errno), storing a '\0' into *FILE so
-   that it names the ancestor directory that had problems.  */
+   Ordinarily MAKE_DIR is executed with the working directory changed
+   to reflect the already-made prefix, and mkancesdirs returns with
+   the working directory changed a prefix of FILE.  However, if the
+   initial working directory cannot be saved in a file descriptor,
+   MAKE_DIR is invoked in a subprocess and this function returns in
+   both the parent and child process, so the caller should not assume
+   any changed state survives other than the EXITMAX component of WD,
+   and the caller should take care that the parent does not attempt to
+   do the work that the child is doing.
 
-int
-mkancesdirs (char *file,
+   If successful and if this process can go ahead and create FILE,
+   return the length of the prefix of FILE that has already been made.
+   If successful so far but a child process is doing the actual work,
+   return -2.  If unsuccessful, return -1 and set errno.  */
+
+ptrdiff_t
+mkancesdirs (char *file, struct savewd *wd,
 	     int (*make_dir) (char const *, void *),
 	     void *make_dir_arg)
 {
-  /* This algorithm is O(N**2) but in typical practice the fancier
-     O(N) algorithms are slower.  */
-
   /* Address of the previous directory separator that follows an
      ordinary byte in a file name in the left-to-right scan, or NULL
      if no such separator precedes the current location P.  */
   char *sep = NULL;
 
-  char const *prefix_end = file + FILE_SYSTEM_PREFIX_LEN (file);
-  char *p;
-  char c;
-
-  /* Search backward through FILE using mkdir to create the
-     furthest-away ancestor that is needed.  This loop isn't needed
-     for correctness, but typically ancestors already exist so this
-     loop speeds things up a bit.
-
-     This loop runs a bit faster if errno initially contains an error
-     number corresponding to a failed access to FILE.  However, things
-     work correctly regardless of errno's initial value.  */
-
-  for (p = last_component (file); prefix_end < p; p--)
-    if (ISSLASH (*p) && ! ISSLASH (p[-1]))
-      {
-	*p = '\0';
+  /* Address of the leftmost file name component that has not yet
+     been processed.  */
+  char *component = file;
 
-	if (errno == ENOENT && make_dir (file, make_dir_arg) == 0)
-	  {
-	    *p = '/';
-	    break;
-	  }
+  char *p = file + FILE_SYSTEM_PREFIX_LEN (file);
+  char c;
+  bool made_dir = false;
 
-	if (errno != ENOENT)
-	  {
-	    if (test_dir (file) == 0)
-	      {
-		*p = '/';
-		break;
-	      }
-	    if (errno != ENOENT)
-	      return -1;
-	  }
-
-	*p = '/';
-      }
-
-  /* Scan forward through FILE, creating directories along the way.
-     Try mkdir before stat, so that the procedure works even when two
-     or more processes are executing it in parallel.  */
+  /* Scan forward through FILE, creating and chdiring into directories
+     along the way.  Try MAKE_DIR before chdir, so that the procedure
+     works even when two or more processes are executing it in
+     parallel.  Isolate each file name component by having COMPONENT
+     point to its start and SEP point just after its end.  */
 
   while ((c = *p++))
     if (ISSLASH (*p))
@@ -119,12 +96,59 @@
       }
     else if (ISSLASH (c) && *p && sep)
       {
-	*sep = '\0';
-	if (make_dir (file, make_dir_arg) != 0 && test_dir (file) != 0)
-	  return -1;
-	*sep = '/';
+	/* Don't bother to make or test for "." since it does not
+	   affect the algorithm.  */
+	if (! (sep - component == 1 && component[0] == '.'))
+	  {
+	    int make_dir_errno = 0;
+	    int savewd_chdir_options = 0;
+	    int chdir_result;
+
+	    /* Temporarily modify FILE to isolate this file name
+	       component.  */
+	    *sep = '\0';
+
+	    /* Invoke MAKE_DIR on this component, except don't bother
+	       with ".." since it must exist if its "parent" does.  */
+	    if (sep - component == 2
+		&& component[0] == '.' && component[1] == '.')
+	      made_dir = false;
+	    else
+	      switch (make_dir (component, make_dir_arg))
+		{
+		case -1:
+		  make_dir_errno = errno;
+		  break;
+
+		case 0:
+		  savewd_chdir_options |= SAVEWD_CHDIR_READABLE;
+		  /* Fall through.  */
+		case 1:
+		  made_dir = true;
+		  break;
+		}
+
+	    if (made_dir)
+	      savewd_chdir_options |= SAVEWD_CHDIR_NOFOLLOW;
+
+	    chdir_result =
+	      savewd_chdir (wd, component, savewd_chdir_options, NULL);
+
+	    /* Undo the temporary modification to FILE, unless there
+	       was a failure.  */
+	    if (chdir_result != -1)
+	      *sep = '/';
+
+	    if (chdir_result != 0)
+	      {
+		if (make_dir_errno != 0 && errno == ENOENT)
+		  errno = make_dir_errno;
+		return chdir_result;
+	      }
+	  }
+
+	component = p;
       }
 
-
-  return 0;
+  return component - file;
 }
--- a/lib/mkancesdirs.h
+++ b/lib/mkancesdirs.h
@@ -1,1 +1,4 @@
-int mkancesdirs (char *, int (*) (char const *, void *), void *);
+#include <stddef.h>
+struct savewd;
+ptrdiff_t mkancesdirs (char *, struct savewd *,
+		       int (*) (char const *, void *), void *);
--- 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;
 }
--- a/lib/mkdir-p.h
+++ b/lib/mkdir-p.h
@@ -1,7 +1,7 @@
 /* mkdir-p.h -- Ensure that a directory and its parents exist.
 
-   Copyright (C) 1994, 1995, 1996, 1997, 2000, 2003, 2004, 2005 Free
-   Software Foundation, Inc.
+   Copyright (C) 1994, 1995, 1996, 1997, 2000, 2003, 2004, 2005, 2006
+   Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -22,7 +22,9 @@
 #include <stdbool.h>
 #include <sys/types.h>
 
+struct savewd;
 bool make_dir_parents (char *dir,
+		       struct savewd *wd,
 		       int (*make_ancestor) (char const *, void *),
 		       void *options,
 		       mode_t mode,
--- a/m4/ChangeLog
+++ b/m4/ChangeLog
@@ -1,3 +1,7 @@
+2006-09-15  Paul Eggert  <eggert@cs.ucla.edu>
+
+	* savewd.m4: New file.
+
 2006-09-15  Jim Meyering  <jim@meyering.net>
 
 	* rename-dest-slash.m4 (gl_FUNC_RENAME_TRAILING_DEST_SLASH): New file.
--- a/modules/mkancesdirs
+++ b/modules/mkancesdirs
@@ -8,6 +8,7 @@
 
 Depends-on:
 dirname
+fcntl
 stat-macros
 
 configure.ac: