(oatoi): declare arg to be const
[gnulib.git] / lib / makepath.c
index 4601294..99119a2 100644 (file)
@@ -1,5 +1,5 @@
 /* makepath.c -- Ensure that a directory path exists.
-   Copyright (C) 1990, 1997 Free Software Foundation, Inc.
+   Copyright (C) 1990, 1997, 1998 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
@@ -71,6 +71,16 @@ extern int errno;
 # endif
 #endif
 
+#ifndef S_IWUSR
+# define S_IWUSR 0200
+#endif
+
+#ifndef S_IXUSR
+# define S_IXUSR 0100
+#endif
+
+#define WX_USR (S_IWUSR | S_IXUSR)
+
 #ifdef __MSDOS__
 typedef int uid_t;
 typedef int gid_t;
@@ -87,7 +97,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);                              \
@@ -123,7 +133,6 @@ void strip_trailing_slashes ();
    Return 0 if ARGPATH exists as a directory with the proper
    ownership and permissions when done, otherwise 1.  */
 
-#if __STDC__
 int
 make_path (const char *argpath,
           int mode,
@@ -132,18 +141,6 @@ make_path (const char *argpath,
           gid_t group,
           int preserve_existing,
           const char *verbose_fmt_string)
-#else
-int
-make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
-          verbose_fmt_string)
-     const char *argpath;
-     int mode;
-     int parent_mode;
-     uid_t owner;
-     gid_t group;
-     int preserve_existing;
-     const char *verbose_fmt_string;
-#endif
 {
   struct stat stats;
   int retval = 0;
@@ -159,10 +156,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.  */
@@ -176,8 +172,8 @@ make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
       /* If leading directories shouldn't be writable or executable,
         or should have set[ug]id or sticky bits set and we are setting
         their owners, we need to fix their permissions after making them.  */
-      if (((parent_mode & 0300) != 0300)
-         || (owner != (uid_t) -1 && group != (gid_t) -1
+      if (((parent_mode & WX_USR) != WX_USR)
+         || ((owner != (uid_t) -1 || group != (gid_t) -1)
              && (parent_mode & 07000) != 0))
        {
          tmp_mode = 0700;
@@ -189,6 +185,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 +203,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 +212,73 @@ 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;
 
+         /* The mkdir and stat calls below appear to be reversed.
+            They are not.  It is important to call mkdir first and then to
+            call stat (to distinguish the three cases) only if mkdir fails.
+            The alternative to this approach is to `stat' each directory,
+            then to call mkdir if it doesn't exist.  But if some other process
+            were to create the directory between the stat & mkdir, the mkdir
+            would fail with EEXIST.  */
+
          *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 (owner != (uid_t) -1 && group != (gid_t) -1
-                     && chown (basename_dir, owner, group)
+         if (newly_created_dir)
+           {
+             if (verbose_fmt_string)
+               fprintf (stderr, verbose_fmt_string, dirpath);
+
+             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;
-                   }
+                 )
+               {
+                 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);
-             CLEANUP;
-             return 1;
            }
 
-         if (saved_cwd && chdir (basename_dir) < 0)
+         /* 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 +293,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.
@@ -347,7 +369,7 @@ make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
             On System V, users can give away files with chown and then not
             be able to chmod them.  So don't give files away.  */
 
-         if (owner != (uid_t) -1 && group != (gid_t) -1
+         if ((owner != (uid_t) -1 || group != (gid_t) -1)
              && chown (dirpath, owner, group)
 #ifdef AFS
              && errno != EPERM