autoupdate
[gnulib.git] / lib / mkancesdirs.c
index 4737742..0c60a93 100644 (file)
@@ -1,11 +1,11 @@
 /* Make a file's ancestor directories.
 
-   Copyright (C) 2006 Free Software Foundation, Inc.
+   Copyright (C) 2006, 2009-2013 Free Software Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or modify
+   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.
+   the Free Software Foundation; either version 3 of the License, 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
    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.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 /* Written by Paul Eggert.  */
 
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
+#include <config.h>
 
 #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,
-            int (*make_dir) (char const *, void *),
-            void *make_dir_arg)
+   Create any ancestor directories that don't already exist, by
+   invoking MAKE_DIR (FILE, 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 *, 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))
       {
-       if (! ISSLASH (c))
-         sep = p;
+        if (! ISSLASH (c))
+          sep = p;
       }
     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 (file, 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;
 }