bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

proposed fix for some race conditions in mkdir and install


From: Paul Eggert
Subject: proposed fix for some race conditions in mkdir and install
Date: Fri, 15 Sep 2006 15:39:48 -0700
User-agent: Gnus/5.1008 (Gnus v5.10.8) Emacs/21.4 (gnu/linux)

Like most utilities, mkdir and install are vulnerable to race
conditions involving file names, e.g., if given a file name that
resolves through a non-sticky directory that is writeable by some
other user.  Here's a patch to fix that, just for these two utilities.
It's a bit outre, since it requires forking off a subprocess in some
cases, but it should resist the race condtions better.

I'll attach two patches, one for gnulib and one for coreutils.

Index: ChangeLog
===================================================================
RCS file: /cvsroot/gnulib/gnulib/ChangeLog,v
retrieving revision 1.679
diff -p -u -r1.679 ChangeLog
--- ChangeLog   15 Sep 2006 18:54:47 -0000      1.679
+++ ChangeLog   15 Sep 2006 22:36:56 -0000
@@ -1,5 +1,9 @@
 2006-09-15  Paul Eggert  <address@hidden>
 
+       * 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: MODULES.html.sh
===================================================================
RCS file: /cvsroot/gnulib/gnulib/MODULES.html.sh,v
retrieving revision 1.149
diff -p -u -r1.149 MODULES.html.sh
--- MODULES.html.sh     15 Sep 2006 13:49:12 -0000      1.149
+++ MODULES.html.sh     15 Sep 2006 22:36:56 -0000
@@ -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: lib/ChangeLog
===================================================================
RCS file: /cvsroot/gnulib/gnulib/lib/ChangeLog,v
retrieving revision 1.1292
diff -p -u -r1.1292 ChangeLog
--- lib/ChangeLog       15 Sep 2006 18:48:09 -0000      1.1292
+++ lib/ChangeLog       15 Sep 2006 22:36:57 -0000
@@ -1,3 +1,29 @@
+2006-09-15  Paul Eggert  <address@hidden>
+
+       * 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  <address@hidden>
 
        * rename-dest-slash.c (has_trailing_slash): Use
Index: lib/dirchownmod.c
===================================================================
RCS file: /cvsroot/gnulib/gnulib/lib/dirchownmod.c,v
retrieving revision 1.4
diff -p -u -r1.4 dirchownmod.c
--- lib/dirchownmod.c   13 Sep 2006 22:38:14 -0000      1.4
+++ lib/dirchownmod.c   15 Sep 2006 22:36:57 -0000
@@ -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)
     {
Index: lib/dirchownmod.h
===================================================================
RCS file: /cvsroot/gnulib/gnulib/lib/dirchownmod.h,v
retrieving revision 1.1
diff -p -u -r1.1 dirchownmod.h
--- lib/dirchownmod.h   17 Jul 2006 06:06:48 -0000      1.1
+++ lib/dirchownmod.h   15 Sep 2006 22:36:57 -0000
@@ -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: lib/mkancesdirs.c
===================================================================
RCS file: /cvsroot/gnulib/gnulib/lib/mkancesdirs.c,v
retrieving revision 1.2
diff -p -u -r1.2 mkancesdirs.c
--- lib/mkancesdirs.c   13 Sep 2006 22:38:14 -0000      1.2
+++ lib/mkancesdirs.c   15 Sep 2006 22:36:57 -0000
@@ -22,94 +22,71 @@
 
 #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.
+   function in parallel.  Modify FILE as necessary to access the
+   ancestor directories, but restore FILE to an equivalent value
+   if successful.
 
-   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.
+   WD points to the working directory, using the conventions of
+   savewd.
 
-   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.  */
+   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.  */
 
-int
-mkancesdirs (char *file,
+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.  */
+  /* Address of the leftmost file name component that has not yet
+     been processed.  */
+  char *component = file;
 
-  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;
-         }
-
-       *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: lib/mkancesdirs.h
===================================================================
RCS file: /cvsroot/gnulib/gnulib/lib/mkancesdirs.h,v
retrieving revision 1.1
diff -p -u -r1.1 mkancesdirs.h
--- lib/mkancesdirs.h   17 Jul 2006 06:06:48 -0000      1.1
+++ lib/mkancesdirs.h   15 Sep 2006 22:36:57 -0000
@@ -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: lib/mkdir-p.c
===================================================================
RCS file: /cvsroot/gnulib/gnulib/lib/mkdir-p.c,v
retrieving revision 1.7
diff -p -u -r1.7 mkdir-p.c
--- lib/mkdir-p.c       13 Sep 2006 22:38:14 -0000      1.7
+++ lib/mkdir-p.c       15 Sep 2006 22:36:57 -0000
@@ -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 @@ 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: lib/mkdir-p.h
===================================================================
RCS file: /cvsroot/gnulib/gnulib/lib/mkdir-p.h,v
retrieving revision 1.3
diff -p -u -r1.3 mkdir-p.h
--- lib/mkdir-p.h       17 Jul 2006 06:06:48 -0000      1.3
+++ lib/mkdir-p.h       15 Sep 2006 22:36:57 -0000
@@ -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: m4/ChangeLog
===================================================================
RCS file: /cvsroot/gnulib/gnulib/m4/ChangeLog,v
retrieving revision 1.934
diff -p -u -r1.934 ChangeLog
--- m4/ChangeLog        15 Sep 2006 13:49:12 -0000      1.934
+++ m4/ChangeLog        15 Sep 2006 22:36:57 -0000
@@ -1,3 +1,7 @@
+2006-09-15  Paul Eggert  <address@hidden>
+
+       * savewd.m4: New file.
+
 2006-09-15  Jim Meyering  <address@hidden>
 
        * rename-dest-slash.m4 (gl_FUNC_RENAME_TRAILING_DEST_SLASH): New file.
Index: modules/mkancesdirs
===================================================================
RCS file: /cvsroot/gnulib/gnulib/modules/mkancesdirs,v
retrieving revision 1.2
diff -p -u -r1.2 mkancesdirs
--- modules/mkancesdirs 21 Aug 2006 21:46:31 -0000      1.2
+++ modules/mkancesdirs 15 Sep 2006 22:36:57 -0000
@@ -8,6 +8,7 @@ m4/mkancesdirs.m4
 
 Depends-on:
 dirname
+fcntl
 stat-macros
 
 configure.ac:
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ lib/savewd.c        2006-09-14 15:59:47.000000000 -0700
@@ -0,0 +1,305 @@
+/* Save and restore the working directory, possibly using a child process.
+
+   Copyright (C) 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
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+/* Written by Paul Eggert.  */
+
+#include <config.h>
+
+#include "savewd.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "exit.h"
+#include "dirname.h"
+#include "fcntl-safer.h"
+
+
+/* Save the working directory into *WD, if it hasn't been saved
+   already.  Return true if a child has been forked to do the real
+   work.  */
+static bool
+savewd_save (struct savewd *wd)
+{
+  switch (wd->state)
+    {
+    case INITIAL_STATE:
+      /* Save the working directory, or prepare to fall back if possible.  */
+      {
+       int fd = open_safer (".", O_RDONLY);
+       if (0 <= fd)
+         {
+           wd->state = FD_STATE;
+           wd->val.fd = fd;
+           break;
+         }
+       if (errno != EACCES)
+         {
+           wd->state = ERROR_STATE;
+           wd->val.errnum = errno;
+           break;
+         }
+      }
+      wd->state = FORKING_STATE;
+      wd->val.child = -1;
+      /* Fall through.  */
+    case FORKING_STATE:
+      if (wd->val.child < 0)
+       {
+         /* "Save" the initial working directory by forking a new
+            subprocess that will attempt all the work from the chdir
+            until until the next savewd_restore.  */
+         wd->val.child = fork ();
+         if (wd->val.child != 0)
+           {
+             if (0 < wd->val.child)
+               return true;
+             wd->state = ERROR_STATE;
+             wd->val.errnum = errno;
+           }
+       }
+      break;
+
+    case FD_STATE:
+    case FD_POST_CHDIR_STATE:
+    case ERROR_STATE:
+    case FINAL_STATE:
+      break;
+
+    default:
+      assert (false);
+    }
+
+  return false;
+}
+
+int
+savewd_chdir (struct savewd *wd, char const *dir, int options,
+             int open_result[2])
+{
+  int fd = -1;
+  int result = 0;
+
+  /* Open the directory if requested, or if avoiding a race condition
+     is requested and possible.  */
+  if (open_result || (options & (O_NOFOLLOW ? SAVEWD_CHDIR_NOFOLLOW : 0)))
+    {
+      fd = open (dir,
+                (O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
+                 | (options & SAVEWD_CHDIR_NOFOLLOW ? O_NOFOLLOW : 0)));
+
+      if (open_result)
+       {
+         open_result[0] = fd;
+         open_result[1] = errno;
+       }
+
+      if (fd < 0 && (errno != EACCES || (options & SAVEWD_CHDIR_READABLE)))
+       result = -1;
+    }
+
+  if (result == 0 && ! (0 <= fd && options & SAVEWD_CHDIR_SKIP_READABLE))
+    {
+      if (savewd_save (wd))
+       {
+         open_result = NULL;
+         result = -2;
+       }
+      else
+       {
+         result = (fd < 0 ? chdir (dir) : fchdir (fd));
+
+         if (result == 0)
+           switch (wd->state)
+             {
+             case FD_STATE:
+               wd->state = FD_POST_CHDIR_STATE;
+               break;
+
+             case ERROR_STATE:
+             case FD_POST_CHDIR_STATE:
+             case FINAL_STATE:
+               break;
+
+             case FORKING_STATE:
+               assert (wd->val.child == 0);
+               break;
+
+             default:
+               assert (false);
+             }
+       }
+    }
+
+  if (0 <= fd && ! open_result)
+    {
+      int e = errno;
+      close (fd);
+      errno = e;
+    }
+
+  return result;
+}
+
+int
+savewd_restore (struct savewd *wd, int status)
+{
+  switch (wd->state)
+    {
+    case INITIAL_STATE:
+    case FD_STATE:
+      /* The working directory is the desired directory, so there's no
+        work to do.  */
+      break;
+
+    case FD_POST_CHDIR_STATE:
+      /* Restore the working directory using fchdir.  */
+      if (fchdir (wd->val.fd) == 0)
+       {
+         wd->state = FD_STATE;
+         break;
+       }
+      else
+       {
+         int chdir_errno = errno;
+         close (wd->val.fd);
+         wd->state = ERROR_STATE;
+         wd->val.errnum = chdir_errno;
+       }
+      /* Fall through.  */
+    case ERROR_STATE:
+      /* Report an error if asked to restore the working directory.  */
+      errno = wd->val.errnum;
+      return -1;
+
+    case FORKING_STATE:
+      /* "Restore" the working directory by waiting for the subprocess
+        to finish.  */
+      {
+       pid_t child = wd->val.child;
+       if (child == 0)
+         _exit (status);
+       if (0 < child)
+         {
+           int status;
+           while (waitpid (child, &status, 0) < 0)
+             assert (errno == EINTR);
+           wd->val.child = -1;
+           if (! WIFEXITED (status))
+             raise (WTERMSIG (status));
+           return WEXITSTATUS (status);
+         }
+      }
+      break;
+
+    default:
+      assert (false);
+    }
+
+  return 0;
+}
+
+void
+savewd_finish (struct savewd *wd)
+{
+  switch (wd->state)
+    {
+    case INITIAL_STATE:
+    case ERROR_STATE:
+      break;
+
+    case FD_STATE:
+    case FD_POST_CHDIR_STATE:
+      close (wd->val.fd);
+      break;
+
+    case FORKING_STATE:
+      assert (wd->val.child < 0);
+      break;
+
+    default:
+      assert (false);
+    }
+
+  wd->state = FINAL_STATE;
+}
+
+/* Return true if the actual work is currently being done by a
+   subprocess.
+
+   A true return means that the caller and the subprocess should
+   resynchronize later with savewd_restore, using only their own
+   memory to decide when to resynchronize; they should not consult the
+   file system to decide, because that might lead to race conditions.
+   This is why savewd_chdir is broken out into another function;
+   savewd_chdir's callers _can_ inspect the file system to decide
+   whether to call savewd_chdir.  */
+static inline bool
+savewd_delegating (struct savewd const *wd)
+{
+  return wd->state == FORKING_STATE && 0 < wd->val.child;
+}
+
+int
+savewd_process_files (int n_files, char **file,
+                     int (*act) (char *, struct savewd *, void *),
+                     void *options)
+{
+  int i = 0;
+  int last_relative;
+  int exit_status = EXIT_SUCCESS;
+  struct savewd wd;
+  savewd_init (&wd);
+
+  for (last_relative = n_files - 1; 0 <= last_relative; last_relative--)
+    if (! IS_ABSOLUTE_FILE_NAME (file[last_relative]))
+      break;
+
+  for (; i < last_relative; i++)
+    {
+      if (! savewd_delegating (&wd))
+       {
+         int s = act (file[i], &wd, options);
+         if (exit_status < s)
+           exit_status = s;
+       }
+
+      if (! IS_ABSOLUTE_FILE_NAME (file[i + 1]))
+       {
+         int r = savewd_restore (&wd, exit_status);
+         if (exit_status < r)
+           exit_status = r;
+       }
+    }
+
+  savewd_finish (&wd);
+
+  for (; i < n_files; i++)
+    {
+      int s = act (file[i], &wd, options);
+      if (exit_status < s)
+       exit_status = s;
+    }
+
+  return exit_status;
+}
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ lib/savewd.h        2006-09-14 15:54:15.000000000 -0700
@@ -0,0 +1,149 @@
+/* Save and restore the working directory, possibly using a subprocess.
+
+   Copyright (C) 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
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+/* Written by Paul Eggert.  */
+
+#ifndef SAVEWD_H
+# define SAVEWD_H 1
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+/* A saved working directory.  The member names and constants defined
+   by this structure are private to the savewd module.  */
+struct savewd
+{
+  /* The state of this object.  */
+  enum
+    {
+      /* This object has been created but does not yet represent
+        the working directory.  */
+      INITIAL_STATE,
+
+      /* val.fd is the original working directory's file descriptor.
+        It is still the working directory.  */
+      FD_STATE,
+
+      /* Like FD_STATE, but the working directory has changed, so
+        restoring it will require a fchdir.  */
+      FD_POST_CHDIR_STATE,
+
+      /* Fork and let the subprocess do the work.  val.child is 0 in a
+        child, negative in a childless parent, and the child process
+        ID in a parent with a child.  */
+      FORKING_STATE,
+
+      /* A serious problem argues against further efforts.  val.errnum
+        contains the error number (e.g., EIO).  */
+      ERROR_STATE,
+
+      /* savewd_finish has been called, so the application no longer
+        cares whether the working directory is saved, and there is no
+        more work to do.  */
+      FINAL_STATE
+    } state;
+
+  /* The object's value.  */
+  union
+  {
+    int fd;
+    int errnum;
+    pid_t child;
+  } val;
+};
+
+/* Allocate and return a saved working directory object.  */
+static inline void
+savewd_init (struct savewd *wd)
+{
+  wd->state = INITIAL_STATE;
+}
+
+
+/* Options for savewd_chdir.  */
+enum
+  {
+    /* Do not follow symbolic links, if supported.  */
+    SAVEWD_CHDIR_NOFOLLOW = 1,
+
+    /* The directory should be readable, so fail if it happens to be
+       discovered that the directory is not readable.  (Unreadable
+       directories are not necessarily diagnosed, though.)  */
+    SAVEWD_CHDIR_READABLE = 2,
+
+    /* Do not chdir if the directory is readable; simply succeed
+       without invoking chdir if the directory was opened.  */
+    SAVEWD_CHDIR_SKIP_READABLE = 4
+  };
+
+/* Change the directory, and if successful, record into *WD the fact
+   that the process chdired into DIR.  A process using this module
+   should use savewd_chdir rather than chdir or fchdir.  Obey the
+   options specified in OPTIONS.
+
+   If OPEN_RESULT is not null, store into OPEN_RESULT[0] a file
+   descriptor that accesses DIR if a file descriptor is successfully
+   obtained.  Store -1 otherwise, setting OPEN_RESULT[1] to the error
+   number.  Store through OPEN_RESULT regardless of whether the chdir
+   is successful.  However, when -2 is returned, the contents of
+   OPEN_RESULT are indeterminate since the file descriptor is closed
+   in the parent.
+
+   Return -2 if a subprocess was spun off to do the real work, -1
+   (setting errno) if unsuccessful, 0 if successful.  */
+int savewd_chdir (struct savewd *wd, char const *dir, int options,
+                 int open_result[2]);
+
+/* Restore the working directory from *WD.  STATUS indicates the exit
+   status corresponding to the work done since the last save; this is
+   used when the caller is in a subprocess.  Return 0 if successful,
+   -1 (setting errno) on our failure, a positive subprocess exit
+   status if the working directory was restored in the parent but the
+   subprocess failed.  */
+int savewd_restore (struct savewd *wd, int status);
+
+/* Return WD's error number, or 0 if WD is not in an error state.  */
+static inline int
+savewd_errno (struct savewd const *wd)
+{
+  return (wd->state == ERROR_STATE ? wd->val.errnum : 0);
+}
+
+/* Deallocate any resources associated with WD.  A program that chdirs
+   should restore before finishing.  */
+void savewd_finish (struct savewd *wd);
+
+/* Process N_FILES file names, FILE[0] through FILE[N_FILES - 1], in
+   the context of the working directory WD.  For each file name F,
+   call ACT (F, WD, OPTIONS); ACT should invoke savewd_chdir as
+   needed, and should return an exit status.  WD may be in an
+   error state when ACT is called.
+
+   Save and restore the working directory as needed by the file name
+   vector; assume that ACT does not require access to any relative
+   file names other than its first argument, and that it is OK if the
+   working directory is changed when this function returns.  Some
+   actions may be applied in a subprocess.
+
+   Return the maximum exit status that any call to ACT returned, or
+   EXIT_SUCCESS (i.e., 0) if no calls were made.  */
+int savewd_process_files (int n_files, char **file,
+                         int (*act) (char *, struct savewd *, void *),
+                         void *options);
+
+#endif
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ m4/savewd.m4        2006-09-10 11:14:07.000000000 -0700
@@ -0,0 +1,9 @@
+# Save and restore the working directory, possibly using a child process.
+
+dnl Copyright (C) 2004 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_SAVEWD],
+  [AC_REQUIRE([AC_C_INLINE])])
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ modules/savewd      2006-09-10 11:14:36.000000000 -0700
@@ -0,0 +1,29 @@
+Description:
+Save and restore the working directory, possibly using a child process.
+
+Files:
+lib/savewd.h
+lib/savewd.c
+m4/savewd.m4
+
+Depends-on:
+dirname
+exit
+fcntl-safer
+stdbool
+xalloc
+
+configure.ac:
+gl_SAVEWD
+
+Makefile.am:
+lib_SOURCES += savewd.h savewd.c
+
+Include:
+"savewd.h"
+
+License:
+GPL
+
+Maintainer:
+Paul Eggert, Jim Meyering
2006-09-15  Paul Eggert  <address@hidden>

        * NEWS: Document that mkdir -p and install -d now fork on occasion.
        * bootstrap.conf (gnulib_modules): Add savewd.
        * src/install.c: Include savewd.h.
        (process_dir): New function.
        (main, install_file_in_file_parents): Use it, along with the new
        savewd module, to avoid some race conditions.
        * src/mkdir.c: Include savewd.h.
        (struct mkdir_options): New members make_ancestor_function, mode,
        mode_bits.
        (make_ancestor): Return 1 if the resulting directory is not readable.
        (process_dir): New function.
        (main): Use it, along with new savewd module, to avoid some
        race conditions.  Fill in new slots of struct mkdir_options, so
        that callees get the values.
        * tests/install/basic-1: Test for coreutils 5.97 bug that was
        fixed in coreutils 6.0, and which should still be fixed with
        this change.
        * tests/mkdir/p-3: Likewise.

Index: NEWS
===================================================================
RCS file: /fetish/cu/NEWS,v
retrieving revision 1.418
diff -p -u -r1.418 NEWS
--- NEWS        8 Sep 2006 17:19:51 -0000       1.418
+++ NEWS        15 Sep 2006 22:15:09 -0000
@@ -4,6 +4,11 @@ GNU coreutils NEWS                      
 
 ** Changes in behavior
 
+  mkdir -p and install -d (or -D) now use a method that forks a child
+  process if the working directory is unreadable and a later argument
+  uses a relative file name.  This avoids some race conditions, but it
+  means you may need to kill two processes to stop these programs.
+
   rm now rejects attempts to remove the root directory, e.g., `rm -fr /'
   now fails without removing anything.  Likewise for any file name with
   a final `./' or `../' component.
Index: bootstrap.conf
===================================================================
RCS file: /fetish/cu/bootstrap.conf,v
retrieving revision 1.9
diff -p -u -r1.9 bootstrap.conf
--- bootstrap.conf      15 Sep 2006 14:11:39 -0000      1.9
+++ bootstrap.conf      15 Sep 2006 22:15:09 -0000
@@ -55,7 +55,7 @@ gnulib_modules="
        quote quotearg raise readlink readtokens readtokens0 readutmp
        realloc regex rename-dest-slash rmdir rmdir-errno rpmatch
        safe-read same
-       save-cwd savedir settime sha1 sig2str ssize_t stat-macros
+       save-cwd savedir savewd settime sha1 sig2str ssize_t stat-macros
        stat-time stdbool stdlib-safer stpcpy strcase strftime
        strpbrk strtoimax strtoumax strverscmp timespec tzset
        unicodeio unistd-safer unlink-busy unlinkdir unlocked-io
Index: src/install.c
===================================================================
RCS file: /fetish/cu/src/install.c,v
retrieving revision 1.194
diff -p -u -r1.194 install.c
--- src/install.c       3 Sep 2006 02:53:16 -0000       1.194
+++ src/install.c       15 Sep 2006 22:15:09 -0000
@@ -35,6 +35,7 @@
 #include "mkdir-p.h"
 #include "modechange.h"
 #include "quote.h"
+#include "savewd.h"
 #include "stat-time.h"
 #include "utimens.h"
 #include "xstrtol.h"
@@ -193,11 +194,23 @@ target_directory_operand (char const *fi
   return is_a_dir;
 }
 
+/* Process a command-line file name, for the -d option.  */
+static int
+process_dir (char *dir, struct savewd *wd, void *options)
+{
+  return (make_dir_parents (dir, wd,
+                           make_ancestor, options,
+                           dir_mode, announce_mkdir,
+                           dir_mode_bits, owner_id, group_id, false)
+         ? EXIT_SUCCESS
+         : EXIT_FAILURE);
+}
+
 int
 main (int argc, char **argv)
 {
   int optc;
-  bool ok = true;
+  int exit_status = EXIT_SUCCESS;
   const char *specified_mode = NULL;
   bool make_backups = false;
   char *backup_suffix_string;
@@ -361,13 +374,7 @@ main (int argc, char **argv)
   get_ids ();
 
   if (dir_arg)
-    {
-      int i;
-      for (i = 0; i < n_files; i++)
-       ok &= make_dir_parents (file[i], make_ancestor, &x,
-                               dir_mode, announce_mkdir,
-                               dir_mode_bits, owner_id, group_id, false);
-    }
+    exit_status = savewd_process_files (n_files, file, process_dir, &x);
   else
     {
       /* FIXME: it's a little gross that this initialization is
@@ -376,23 +383,22 @@ main (int argc, char **argv)
 
       if (!target_directory)
         {
-          if (mkdir_and_install)
-           ok = install_file_in_file_parents (file[0], file[1], &x);
-         else
-           ok = install_file_in_file (file[0], file[1], &x);
+          if (! (mkdir_and_install
+                ? install_file_in_file_parents (file[0], file[1], &x)
+                : install_file_in_file (file[0], file[1], &x)))
+           exit_status = EXIT_FAILURE;
        }
       else
        {
          int i;
          dest_info_init (&x);
          for (i = 0; i < n_files; i++)
-           {
-             ok &= install_file_in_dir (file[i], target_directory, &x);
-           }
+           if (! install_file_in_dir (file[i], target_directory, &x))
+             exit_status = EXIT_FAILURE;
        }
     }
 
-  exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+  exit (exit_status);
 }
 
 /* Copy file FROM onto file TO, creating any missing parent directories of TO.
@@ -402,13 +408,36 @@ static bool
 install_file_in_file_parents (char const *from, char *to,
                              struct cp_options *x)
 {
-  if (mkancesdirs (to, make_ancestor, x) != 0)
+  bool save_working_directory =
+    ! (IS_ABSOLUTE_FILE_NAME (from) && IS_ABSOLUTE_FILE_NAME (to));
+  int status = EXIT_SUCCESS;
+
+  struct savewd wd;
+  savewd_init (&wd);
+  if (! save_working_directory)
+    savewd_finish (&wd);
+
+  if (mkancesdirs (to, &wd, make_ancestor, x) == -1)
     {
       error (0, errno, _("cannot create directory %s"), to);
-      return false;
+      status = EXIT_FAILURE;
+    }
+
+  if (save_working_directory)
+    {
+      int restore_result = savewd_restore (&wd, status);
+      int restore_errno = errno;
+      savewd_finish (&wd);
+      if (EXIT_SUCCESS < restore_result)
+       return false;
+      if (restore_result < 0 && status == EXIT_SUCCESS)
+       {
+         error (0, restore_errno, _("cannot create directory %s"), to);
+         return false;
+       }
     }
 
-  return install_file_in_file (from, to, x);
+  return (status == EXIT_SUCCESS && install_file_in_file (from, to, x));
 }
 
 /* Copy file FROM onto file TO and give TO the appropriate
Index: src/mkdir.c
===================================================================
RCS file: /fetish/cu/src/mkdir.c,v
retrieving revision 1.105
diff -p -u -r1.105 mkdir.c
--- src/mkdir.c 3 Sep 2006 02:53:16 -0000       1.105
+++ src/mkdir.c 15 Sep 2006 22:15:09 -0000
@@ -28,6 +28,7 @@
 #include "mkdir-p.h"
 #include "modechange.h"
 #include "quote.h"
+#include "savewd.h"
 
 /* The official name of this program (e.g., no `g' prefix).  */
 #define PROGRAM_NAME "mkdir"
@@ -75,12 +76,22 @@ Mandatory arguments to long options are 
   exit (status);
 }
 
-/* Options for announce_mkdir and make_ancestor.  */
+/* Options passed to subsidiary functions.  */
 struct mkdir_options
 {
+  /* Function to make an ancestor, or NULL if ancestors should not be
+     made.  */
+  int (*make_ancestor_function) (char const *, void *);
+
   /* Mode for ancestor directory.  */
   mode_t ancestor_mode;
 
+  /* Mode for directory itself.  */
+  mode_t mode;
+
+  /* File mode bits affected by MODE.  */
+  mode_t mode_bits;
+
   /* If not null, format to use when reporting newly made directories.  */
   char const *created_directory_format;
 };
@@ -94,27 +105,44 @@ announce_mkdir (char const *dir, void *o
     error (0, 0, o->created_directory_format, quote (dir));
 }
 
-/* Make ancestor directory DIR, with options OPTIONS.  */
+/* Make ancestor directory DIR, with options OPTIONS.  Return 0 if
+   successful and the resulting directory is readable, 1 if successful
+   but the resulting directory is not readable, -1 (setting errno)
+   otherwise.  */
 static int
 make_ancestor (char const *dir, void *options)
 {
   struct mkdir_options const *o = options;
   int r = mkdir (dir, o->ancestor_mode);
   if (r == 0)
-    announce_mkdir (dir, options);
+    {
+      r = ! (o->ancestor_mode & S_IRUSR);
+      announce_mkdir (dir, options);
+    }
   return r;
 }
 
+/* Process a command-line file name.  */
+static int
+process_dir (char *dir, struct savewd *wd, void *options)
+{
+  struct mkdir_options const *o = options;
+  return (make_dir_parents (dir, wd, o->make_ancestor_function, options,
+                           o->mode, announce_mkdir,
+                           o->mode_bits, (uid_t) -1, (gid_t) -1, true)
+         ? EXIT_SUCCESS
+         : EXIT_FAILURE);
+}
+
 int
 main (int argc, char **argv)
 {
-  mode_t mode = S_IRWXUGO;
-  mode_t mode_bits = 0;
-  int (*make_ancestor_function) (char const *, void *) = NULL;
   const char *specified_mode = NULL;
-  int exit_status = EXIT_SUCCESS;
   int optc;
   struct mkdir_options options;
+  options.make_ancestor_function = NULL;
+  options.mode = S_IRWXUGO;
+  options.mode_bits = 0;
   options.created_directory_format = NULL;
 
   initialize_main (&argc, &argv);
@@ -130,7 +158,7 @@ main (int argc, char **argv)
       switch (optc)
        {
        case 'p':
-         make_ancestor_function = make_ancestor;
+         options.make_ancestor_function = make_ancestor;
          break;
        case 'm':
          specified_mode = optarg;
@@ -151,7 +179,7 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
-  if (make_ancestor_function || specified_mode)
+  if (options.make_ancestor_function || specified_mode)
     {
       mode_t umask_value = umask (0);
 
@@ -163,19 +191,14 @@ main (int argc, char **argv)
          if (!change)
            error (EXIT_FAILURE, 0, _("invalid mode %s"),
                   quote (specified_mode));
-         mode = mode_adjust (S_IRWXUGO, true, umask_value, change,
-                             &mode_bits);
+         options.mode = mode_adjust (S_IRWXUGO, true, umask_value, change,
+                                     &options.mode_bits);
          free (change);
        }
       else
-       mode &= ~umask_value;
+       options.mode = S_IRWXUGO & ~umask_value;
     }
 
-  for (; optind < argc; ++optind)
-    if (! make_dir_parents (argv[optind], make_ancestor_function, &options,
-                           mode, announce_mkdir,
-                           mode_bits, (uid_t) -1, (gid_t) -1, true))
-      exit_status = EXIT_FAILURE;
-
-  exit (exit_status);
+  exit (savewd_process_files (argc - optind, argv + optind,
+                             process_dir, &options));
 }
Index: tests/install/basic-1
===================================================================
RCS file: /fetish/cu/tests/install/basic-1,v
retrieving revision 1.19
diff -p -u -r1.19 basic-1
--- tests/install/basic-1       17 Aug 2006 19:58:28 -0000      1.19
+++ tests/install/basic-1       15 Sep 2006 22:15:09 -0000
@@ -1,7 +1,7 @@
 #! /bin/sh
 # Basic tests for "install".
 
-# Copyright (C) 1998, 2000, 2001, 2002, 2004, 2005 Free Software
+# Copyright (C) 1998, 2000, 2001, 2002, 2004, 2005, 2006 Free Software
 # Foundation, Inc.
 
 # This program is free software; you can redistribute it and/or modify
@@ -95,16 +95,30 @@ test -d newdir3 || fail=1
 # initial working directory ($abs) after creating the first argument, and
 # hence cannot do anything meaningful with the following relative-named dirs.
 abs=$pwd/$tmp
-mkdir sub && cd sub
-chmod 0 .; ginstall -d $abs/xx/yy rel/sub1 rel/sub2 2> /dev/null && fail=1
-chmod 755 $abs/sub
+mkdir sub || fail=1
+(cd sub && chmod 0 . && ginstall -d $abs/xx/yy rel/sub1 rel/sub2 2> /dev/null) 
&& fail=1
+chmod 755 sub
 
 # Ensure that the first argument-dir has been created.
-test -d $abs/xx/yy || fail=1
+test -d xx/yy || fail=1
 
 # Make sure that the `rel' directory was not created...
-test -d $abs/sub/rel && fail=1
+test -d sub/rel && fail=1
 # and make sure it was not created in the wrong place.
-test -d $abs/xx/rel && fail=1
+test -d xx/rel && fail=1
+
+# Test that we can install from an unreadable directory with an inaccessible 
parent.
+# coreutils 5.97 fails this test.
+mkdir -p sub1/d || fail=1
+(cd sub1/d && chmod a-rx .. && chmod a-r . &&
+ ginstall -d $abs/xx/zz rel/a rel/b 2> /dev/null) || fail=1
+chmod 755 sub1 || fail=1
+chmod 755 sub1/d || fail=1
+test -d xx/zz || fail=1
+test -d sub1/d/rel/a || fail=1
+test -d sub1/d/rel/b || fail=1
+
+# Test whether you can install from a directory that is not readable
+# and whose parent is not accessible.  coreutils 5.97 fails this test.
 
 (exit $fail); exit $fail
Index: tests/mkdir/p-3
===================================================================
RCS file: /fetish/cu/tests/mkdir/p-3,v
retrieving revision 1.7
diff -p -u -r1.7 p-3
--- tests/mkdir/p-3     17 Aug 2006 19:58:33 -0000      1.7
+++ tests/mkdir/p-3     15 Sep 2006 22:15:09 -0000
@@ -37,7 +37,7 @@ mkdir -p $tmp || framework_failure=1
 cd $tmp || framework_failure=1
 mkdir no-access || framework_failure=1
 mkdir no-acce2s || framework_failure=1
-mkdir no-acce3s || framework_failure=1
+mkdir -p no-acce3s/d || framework_failure=1
 
 if test $framework_failure = 1; then
   echo "$0: failure in testing framework" 1>&2
@@ -45,13 +45,18 @@ if test $framework_failure = 1; then
 fi
 
 p=$pwd/$tmp
-(cd no-access; chmod 0 . && mkdir -p $p/a/b u/v) 2> /dev/null && fail=1
+(cd no-access && chmod 0 . && mkdir -p $p/a/b u/v) 2> /dev/null && fail=1
 test -d $p/a/b || fail=1
 
 # Same as above, but with a following *absolute* name, it should succeed
-(cd no-acce2s; chmod 0 . && mkdir -p $p/b/b $p/z) || fail=1
+(cd no-acce2s && chmod 0 . && mkdir -p $p/b/b $p/z) || fail=1
+test -d $p/b/b && test -d $p/z || fail=1
 
-test -d $p/z || fail=1
+# Same as above, but a trailing relative name in an unreadable directory
+# whose parent is inaccessible.  coreutils 5.97 fails this test.
+(cd no-acce3s/d && chmod a-rx .. && chmod a-r . && mkdir -p a/b $p/b/c d/e &&
+ test -d a/b && test -d d/e) || fail=1
+test -d $p/b/c || fail=1
 
 b=`ls $p/a|tr -d '\n'`
 # With coreutils-5.3.0, this would fail with $b=bu.

reply via email to

[Prev in Thread] Current Thread [Next in Thread]