* lib/dirchownmod.c: Don't include fcntl.h; no longer needed.
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 16 Sep 2006 19:58:25 +0000 (19:58 +0000)
committerPaul Eggert <eggert@cs.ucla.edu>
Sat, 16 Sep 2006 19:58:25 +0000 (19:58 +0000)
(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.

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

index ed51d96..f1fd260 100644 (file)
--- 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.
 
index b752def..31ab8de 100755 (executable)
@@ -1908,6 +1908,7 @@ func_all_modules ()
   func_module same
   func_module save-cwd
   func_module savedir
+  func_module savewd
   func_module stat-time
   func_module tmpdir
   func_module unlinkdir
index fe690cd..12f8fbe 100644 (file)
@@ -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
index 11abe90..3b65446 100644 (file)
@@ -25,7 +25,6 @@
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <fcntl.h>
 #include <unistd.h>
 
 #include "lchmod.h"
 # 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
    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)
     {
index e841f47..3f07748 100644 (file)
@@ -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);
index 3db352d..efdabb4 100644 (file)
 
 #include "mkancesdirs.h"
 
-#include <errno.h>
+#include <sys/types.h>
 #include <sys/stat.h>
+#include <fcntl.h>
 
-#include "dirname.h"
-#include "stat-macros.h"
-
-/* Return 0 if FILE is a directory, otherwise -1 (setting errno).  */
+#include <errno.h>
+#include <unistd.h>
 
-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 "dirname.h"
+#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.
-
-   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.
+   function in parallel.  Modify FILE as necessary to access the
+   ancestor directories, but restore FILE to an equivalent value
+   if successful.
 
-   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.  */
+   WD points to the working directory, using the conventions of
+   savewd.
 
-int
-mkancesdirs (char *file,
+   Create any ancestor directories that don't already exist, by
+   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.
+
+   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.
+
+   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';
-
-       if (errno == ENOENT && make_dir (file, make_dir_arg) == 0)
-         {
-           *p = '/';
-           break;
-         }
-
-       if (errno != ENOENT)
-         {
-           if (test_dir (file) == 0)
-             {
-               *p = '/';
-               break;
-             }
-           if (errno != ENOENT)
-             return -1;
-         }
+  /* Address of the leftmost file name component that has not yet
+     been processed.  */
+  char *component = file;
 
-       *p = '/';
-      }
+  char *p = file + FILE_SYSTEM_PREFIX_LEN (file);
+  char c;
+  bool made_dir = false;
 
-  /* 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 @@ mkancesdirs (char *file,
       }
     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;
 }
index a698c9c..06c6f3d 100644 (file)
@@ -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 *);
index 3838c8e..f097e67 100644 (file)
 #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,
    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 @@ make_dir_parents (char *dir,
                  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)
        {
-         /* mkancestdirs updated DIR for a better-looking
-            diagnostic, so don't try to stat DIR below.  */
-         make_ancestor = NULL;
+         prefix_len = mkancesdirs (dir, wd, make_ancestor, options);
+         if (prefix_len < 0)
+           {
+             if (prefix_len < -1)
+               return true;
+             mkdir_errno = errno;
+           }
        }
-    }
 
-  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)))
+      if (0 <= prefix_len)
        {
-         error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
-         return false;
+         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 (! 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;
 }
index 77a11d6..67cc301 100644 (file)
@@ -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,
index 8ce9344..796ea02 100644 (file)
@@ -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.
index 71a11a7..b567ad4 100644 (file)
@@ -8,6 +8,7 @@ m4/mkancesdirs.m4
 
 Depends-on:
 dirname
+fcntl
 stat-macros
 
 configure.ac: