(st_blocks): long -> off_t.
[gnulib.git] / lib / makepath.c
index 4601294..7631c48 100644 (file)
@@ -87,7 +87,7 @@ void strip_trailing_slashes ();
     {                                                  \
       /* We're done operating on basename_dir.         \
         Restore working directory.  */                 \
-      if (saved_cwd)                                   \
+      if (do_chdir)                                    \
        {                                               \
          int fail = restore_cwd (&cwd, NULL, NULL);    \
          free_cwd (&cwd);                              \
@@ -159,10 +159,9 @@ make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
        struct ptr_list *next;
       };
       struct ptr_list *p, *leading_dirs = NULL;
-      int saved_cwd = 0;
+      int do_chdir;            /* Whether to chdir before each mkdir.  */
       struct saved_cwd cwd;
       char *basename_dir;
-      int first_subdir = 1;
       char *dirpath;
 
       /* Temporarily relax umask in case it's overly restrictive.  */
@@ -189,6 +188,16 @@ make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
          re_protect = 0;
        }
 
+      /* If we can record the current working directory, we may be able
+        to do the chdir optimization.  */
+      do_chdir = !save_cwd (&cwd);
+
+      /* If we've saved the cwd and DIRPATH is an absolute pathname,
+        we must chdir to `/' in order to enable the chdir optimization.
+         So if chdir ("/") fails, turn off the optimization.  */
+      if (do_chdir && *dirpath == '/' && chdir ("/") < 0)
+       do_chdir = 0;
+
       slash = dirpath;
 
       /* Skip over leading slashes.  */
@@ -197,6 +206,8 @@ make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
 
       while (1)
        {
+         int newly_created_dir = 1;
+
          /* slash points to the leftmost unprocessed component of dirpath.  */
          basename_dir = slash;
 
@@ -204,59 +215,62 @@ make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
          if (slash == NULL)
            break;
 
-         if (first_subdir)
-           {
-             first_subdir = 0;
-             saved_cwd = !save_cwd (&cwd);
-           }
-
-         if (!saved_cwd)
+         /* If we're *not* doing chdir before each mkdir, then we have to refer
+            to the target using the full (multi-component) directory name.  */
+         if (!do_chdir)
            basename_dir = dirpath;
 
          *slash = '\0';
-         if (stat (basename_dir, &stats))
+         if (mkdir (basename_dir, tmp_mode))
            {
-             if (mkdir (basename_dir, tmp_mode))
+             if (stat (basename_dir, &stats))
                {
                  error (0, errno, "cannot create directory `%s'", dirpath);
                  CLEANUP;
                  return 1;
                }
+             else if (!S_ISDIR (stats.st_mode))
+               {
+                 error (0, 0, "`%s' exists but is not a directory", dirpath);
+                 CLEANUP;
+                 return 1;
+               }
              else
                {
-                 if (verbose_fmt_string != NULL)
-                   error (0, 0, verbose_fmt_string, dirpath);
+                 /* DIRPATH already exists and is a directory. */
+                 newly_created_dir = 0;
+               }
+           }
+
+         if (newly_created_dir && verbose_fmt_string != NULL)
+           fprintf (stderr, verbose_fmt_string, dirpath);
 
-                 if (owner != (uid_t) -1 && group != (gid_t) -1
-                     && chown (basename_dir, owner, group)
+         if (owner != (uid_t) -1 && group != (gid_t) -1
+             && chown (basename_dir, owner, group)
 #if defined(AFS) && defined (EPERM)
-                     && errno != EPERM
+             && errno != EPERM
 #endif
-                     )
-                   {
-                     error (0, errno, "%s", dirpath);
-                     CLEANUP;
-                     return 1;
-                   }
-
-                 if (re_protect)
-                   {
-                     struct ptr_list *new = (struct ptr_list *)
-                       alloca (sizeof (struct ptr_list));
-                     new->dirname_end = slash;
-                     new->next = leading_dirs;
-                     leading_dirs = new;
-                   }
-               }
-           }
-         else if (!S_ISDIR (stats.st_mode))
+             )
            {
-             error (0, 0, "`%s' exists but is not a directory", dirpath);
+             error (0, errno, "%s", dirpath);
              CLEANUP;
              return 1;
            }
 
-         if (saved_cwd && chdir (basename_dir) < 0)
+         if (re_protect)
+           {
+             struct ptr_list *new = (struct ptr_list *)
+               alloca (sizeof (struct ptr_list));
+             new->dirname_end = slash;
+             new->next = leading_dirs;
+             leading_dirs = new;
+           }
+
+         /* If we were able to save the initial working directory,
+            then we can use chdir to change into each directory before
+            creating an entry in that directory.  This avoids making
+            stat and mkdir process O(n^2) file name components.  */
+         if (do_chdir && chdir (basename_dir) < 0)
            {
              error (0, errno, "cannot chdir to directory, %s", dirpath);
              CLEANUP;
@@ -271,7 +285,7 @@ make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
            slash++;
        }
 
-      if (!saved_cwd)
+      if (!do_chdir)
        basename_dir = dirpath;
 
       /* We're done making leading directories.