(oatoi): declare arg to be const
[gnulib.git] / lib / makepath.c
index 1784431..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,18 +212,19 @@ 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 save_cwd could not record the current directory, then don't
-            do the chdir optimization and resort to using full pathnames.  */
-
-         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 (mkdir (basename_dir, tmp_mode))
            {
@@ -234,38 +243,42 @@ make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
              else
                {
                  /* DIRPATH already exists and is a directory. */
+                 newly_created_dir = 0;
                }
            }
 
-         if (verbose_fmt_string != NULL)
-           error (0, 0, verbose_fmt_string, dirpath);
+         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 ((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;
-           }
+                 )
+               {
+                 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;
+             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 (saved_cwd && chdir (basename_dir) < 0)
+         if (do_chdir && chdir (basename_dir) < 0)
            {
              error (0, errno, "cannot chdir to directory, %s", dirpath);
              CLEANUP;
@@ -280,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.
@@ -356,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