bug-gnulib
[Top][All Lists]
Advanced

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

new module savewd, plus changes to mkdir-p


From: Paul Eggert
Subject: new module savewd, plus changes to mkdir-p
Date: Sat, 16 Sep 2006 13:00:27 -0700
User-agent: Gnus/5.1008 (Gnus v5.10.8) Emacs/21.4 (gnu/linux)

I installed this fix as part of updates to coreutils.

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.
        * 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.

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/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: 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




reply via email to

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