maint: update copyright
[gnulib.git] / lib / mkdir-p.c
index efaa8d2..188fa3a 100644 (file)
@@ -1,12 +1,12 @@
 /* mkdir-p.c -- Ensure that a directory and its parents exist.
 
-   Copyright (C) 1990, 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005
-   Free Software Foundation, Inc.
+   Copyright (C) 1990, 1997-2000, 2002-2007, 2009-2014 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 David MacKenzie <djm@gnu.ai.mit.edu> and Jim Meyering.  */
+/* Written by Paul Eggert, David MacKenzie, and Jim Meyering.  */
 
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
+#include <config.h>
 
 #include "mkdir-p.h"
 
-#include <alloca.h>
-
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#if HAVE_UNISTD_H
-# include <unistd.h>
-#endif
-
-#include <stdlib.h>
 #include <errno.h>
-#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
-#include "save-cwd.h"
+#include "dirchownmod.h"
 #include "dirname.h"
 #include "error.h"
 #include "quote.h"
-#include "stat-macros.h"
-
-#define WX_USR (S_IWUSR | S_IXUSR)
+#include "mkancesdirs.h"
+#include "savewd.h"
 
-#define CLEANUP_CWD                                    \
-  do                                                   \
-    {                                                  \
-      /* We're done operating on basename_dir.         \
-        Restore working directory.  */                 \
-      if (do_chdir)                                    \
-       {                                               \
-         if (restore_cwd (&cwd) != 0)                  \
-           {                                           \
-             int _saved_errno = errno;                 \
-             error (0, errno,                          \
-               _("failed to return to initial working directory")); \
-             free_cwd (&cwd);                          \
-             errno = _saved_errno;                     \
-             return 1;                                 \
-           }                                           \
-         free_cwd (&cwd);                              \
-       }                                               \
-    }                                                  \
-  while (0)
-
-#define CLEANUP                                                \
-  do                                                   \
-    {                                                  \
-      umask (oldmask);                                 \
-      CLEANUP_CWD;                                     \
-    }                                                  \
-  while (0)
-
-/* Attempt to create directory DIR (aka FULLDIR) with the specified MODE.
-   If CREATED_DIR_P is non-NULL, set *CREATED_DIR_P if this
-   function creates DIR and clear it otherwise.  Give a diagnostic and
-   return false if DIR cannot be created or cannot be determined to
-   exist already.  Use FULLDIR in any diagnostic, not DIR.
-   Note that if DIR already exists, this function returns true
-   (indicating success) and clears *CREATED_DIR_P.  */
-
-bool
-make_dir (char const *dir, char const *fulldir, mode_t mode,
-         bool *created_dir_p)
-{
-  bool ok = true;
-  bool created_dir;
+#ifndef HAVE_FCHMOD
+# define HAVE_FCHMOD false
+#endif
 
-  created_dir = (mkdir (dir, mode) == 0);
+/* Ensure that the directory DIR exists.
 
-  if (!created_dir)
-    {
-      struct stat stats;
-      int saved_errno = errno;
+   WD is the working directory, as in savewd.c.
 
-      /* The mkdir and stat calls below may 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.  */
+   If MAKE_ANCESTOR is not null, create any ancestor directories that
+   don't already exist, by invoking MAKE_ANCESTOR (DIR, ANCESTOR, OPTIONS).
+   This function should return zero if successful, -1 (setting errno)
+   otherwise.  In this case, DIR may be modified by storing '\0' bytes
+   into it, to access the ancestor directories, and this modification
+   is retained on return if the ancestor directories could not be
+   created.
 
-      if (stat (dir, &stats))
-       {
-         error (0, saved_errno, _("cannot create directory %s"),
-                quote (fulldir));
-         ok = false;
-       }
-      else if (!S_ISDIR (stats.st_mode))
-       {
-         error (0, 0, _("%s exists but is not a directory"), quote (fulldir));
-         ok = false;
-       }
-      else
-       {
-         /* DIR (aka FULLDIR) already exists and is a directory. */
-       }
-    }
+   Create DIR as a new directory, using mkdir with permissions MODE;
+   here, MODE is affected by the umask in the usual way.  It is also
+   OK if MAKE_ANCESTOR is not null and a directory DIR already exists.
 
-  if (created_dir_p)
-    *created_dir_p = created_dir;
+   Call ANNOUNCE (DIR, OPTIONS) just after successfully making DIR,
+   even if some of the following actions fail.
 
-  return ok;
-}
+   Set DIR's owner to OWNER and group to GROUP, but leave the owner
+   alone if OWNER is (uid_t) -1, and similarly for GROUP.
 
-/* Ensure that the directory ARG exists.
+   Set DIR's mode bits to MODE, except preserve any of the bits that
+   correspond to zero bits in MODE_BITS.  In other words, MODE_BITS is
+   a mask that specifies which of DIR's mode bits should be set or
+   cleared.  Changing the mode in this way is necessary if DIR already
+   existed, if MODE and MODE_BITS specify non-permissions bits like
+   S_ISUID, or if MODE and MODE_BITS specify permissions bits that are
+   masked out by the umask.  MODE_BITS should be a subset of
+   CHMOD_MODE_BITS.
 
-   Create any leading directories that don't already exist, with
-   permissions PARENT_MODE.
-   If the last element of ARG does not exist, create it as
-   a new directory with permissions MODE.
-   If OWNER and GROUP are non-negative, use them to set the UID and GID of
-   any created directories.
-   If VERBOSE_FMT_STRING is nonzero, use it as a printf format
-   string for printing a message after successfully making a directory,
-   with the name of the directory that was just made as an argument.
-   If PRESERVE_EXISTING is true and ARG is an existing directory,
-   then do not attempt to set its permissions and ownership.
+   However, if PRESERVE_EXISTING is true and DIR already exists,
+   do not attempt to set DIR's ownership and file mode bits.
 
-   Return true iff ARG exists as a directory with the proper
-   ownership and permissions when done.  */
+   Return true if DIR exists as a directory with the proper ownership
+   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 const *arg,
-                 mode_t mode,
-                 mode_t parent_mode,
-                 uid_t owner,
-                 gid_t group,
-                 bool preserve_existing,
-                 char const *verbose_fmt_string)
+make_dir_parents (char *dir,
+                  struct savewd *wd,
+                  int (*make_ancestor) (char const *, char const *, void *),
+                  void *options,
+                  mode_t mode,
+                  void (*announce) (char const *, void *),
+                  mode_t mode_bits,
+                  uid_t owner,
+                  gid_t group,
+                  bool preserve_existing)
 {
-  struct stat stats;
-  bool retval = true;
-
-  if (stat (arg, &stats) != 0)
-    {
-      char *slash;
-      mode_t tmp_mode;         /* Initial perms for leading dirs.  */
-      bool re_protect;         /* Should leading dirs be unwritable? */
-      struct ptr_list
-      {
-       char *dirname_end;
-       struct ptr_list *next;
-      };
-      struct ptr_list *p, *leading_dirs = NULL;
-      bool do_chdir;           /* Whether to chdir before each mkdir.  */
-      struct saved_cwd cwd;
-      char *basename_dir;
-      char *dir;
-
-      /* Temporarily relax umask in case it's overly restrictive.  */
-      mode_t oldmask = umask (0);
-
-      /* Make a copy of ARG that we can scribble NULs on.  */
-      dir = (char *) alloca (strlen (arg) + 1);
-      strcpy (dir, arg);
-      strip_trailing_slashes (dir);
-
-      /* 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 & WX_USR) != WX_USR)
-         || ((owner != (uid_t) -1 || group != (gid_t) -1)
-             && (parent_mode & (S_ISUID | S_ISGID | S_ISVTX)) != 0))
-       {
-         tmp_mode = S_IRWXU;
-         re_protect = true;
-       }
-      else
-       {
-         tmp_mode = parent_mode;
-         re_protect = false;
-       }
-
-      /* If we can record the current working directory, we may be able
-        to do the chdir optimization.  */
-      do_chdir = (save_cwd (&cwd) == 0);
-
-      /* If we've saved the cwd and DIR is an absolute file name,
-        we must chdir to `/' in order to enable the chdir optimization.
-         So if chdir ("/") fails, turn off the optimization.  */
-      if (do_chdir && dir[0] == '/')
-       {
-         /* POSIX says "//" might be special, so chdir to "//" if the
-            file name starts with exactly two slashes.  */
-         char const *root = "//" + (dir[1] != '/' || dir[2] == '/');
-         if (chdir (root) != 0)
-           do_chdir = false;
-       }
-
-      slash = dir;
-
-      /* Skip over leading slashes.  */
-      while (*slash == '/')
-       slash++;
-
-      while (1)
-       {
-         bool newly_created_dir;
+  int mkdir_errno = (IS_ABSOLUTE_FILE_NAME (dir) ? 0 : savewd_errno (wd));
 
-         /* slash points to the leftmost unprocessed component of dir.  */
-         basename_dir = slash;
-
-         slash = strchr (slash, '/');
-         if (slash == NULL)
-           break;
-
-         /* 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 = dir;
-
-         *slash = '\0';
-         if (! make_dir (basename_dir, dir, tmp_mode, &newly_created_dir))
-           {
-             CLEANUP;
-             return false;
-           }
-
-         if (newly_created_dir)
-           {
-             if (verbose_fmt_string)
-               error (0, 0, verbose_fmt_string, quote (dir));
-
-             if ((owner != (uid_t) -1 || group != (gid_t) -1)
-                 && chown (basename_dir, owner, group)
-#if defined AFS && defined EPERM
-                 && errno != EPERM
-#endif
-                 )
-               {
-                 error (0, errno, _("cannot change owner and/or group of %s"),
-                        quote (dir));
-                 CLEANUP;
-                 return false;
-               }
-
-             if (re_protect)
-               {
-                 struct ptr_list *new = (struct ptr_list *)
-                   alloca (sizeof *new);
-                 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"),
-                    quote (dir));
-             CLEANUP;
-             return false;
-           }
-
-         *slash++ = '/';
-
-         /* Avoid unnecessary calls to `stat' when given
-            file names containing multiple adjacent slashes.  */
-         while (*slash == '/')
-           slash++;
-       }
-
-      if (!do_chdir)
-       basename_dir = dir;
-
-      /* Done creating leading directories.  Restore original umask.  */
-      umask (oldmask);
-
-      /* We're done making leading directories.
-        Create the final component of the file name.  */
-
-      if (! make_dir (basename_dir, dir, mode, NULL))
-       {
-         CLEANUP;
-         return false;
-       }
-
-      if (verbose_fmt_string != NULL)
-       error (0, 0, verbose_fmt_string, quote (dir));
-
-      if (owner != (uid_t) -1 || group != (gid_t) -1)
-       {
-         if (chown (basename_dir, owner, group)
-#ifdef AFS
-             && errno != EPERM
-#endif
-             )
-           {
-             error (0, errno, _("cannot change owner and/or group of %s"),
-                    quote (dir));
-             retval = false;
-           }
-       }
-
-      /* The above chown may have turned off some permission bits in MODE.
-        Another reason we may have to use chmod here is that mkdir(2) is
-        required to honor only the file permission bits.  In particular,
-        it need not honor the `special' bits, so if MODE includes any
-        special bits, set them here.  */
-      if ((mode & ~S_IRWXUGO)
-         && chmod (basename_dir, mode))
-       {
-         error (0, errno, _("cannot change permissions of %s"),
-                quote (dir));
-         retval = false;
-       }
-
-      CLEANUP_CWD;
-
-      /* If the mode for leading directories didn't include owner "wx"
-        privileges, we have to reset their protections to the correct
-        value.  */
-      for (p = leading_dirs; p != NULL; p = p->next)
-       {
-         *(p->dirname_end) = '\0';
-         if (chmod (dir, parent_mode) != 0)
-           {
-             error (0, errno, _("cannot change permissions of %s"),
-                    quote (dir));
-             retval = false;
-           }
-       }
-    }
-  else
+  if (mkdir_errno == 0)
     {
-      /* We get here if the file already exists.  */
-
-      char const *dir = arg;
-
-      if (!S_ISDIR (stats.st_mode))
-       {
-         error (0, 0, _("%s exists but is not a directory"), quote (dir));
-         return false;
-       }
-
-      if (!preserve_existing)
-       {
-         /* chown must precede chmod because on some systems,
-            chown clears the set[ug]id bits for non-superusers,
-            resulting in incorrect permissions.
-            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)
-             && chown (dir, owner, group)
-#ifdef AFS
-             && errno != EPERM
-#endif
-             )
-           {
-             error (0, errno, _("cannot change owner and/or group of %s"),
-                    quote (dir));
-             retval = false;
-           }
-         if (chmod (dir, mode) != 0)
-           {
-             error (0, errno, _("cannot change permissions of %s"),
-                                quote (dir));
-             retval = false;
-           }
-       }
+      ptrdiff_t prefix_len = 0;
+      int savewd_chdir_options = (HAVE_FCHMOD ? SAVEWD_CHDIR_SKIP_READABLE : 0);
+
+      if (make_ancestor)
+        {
+          prefix_len = mkancesdirs (dir, wd, make_ancestor, options);
+          if (prefix_len < 0)
+            {
+              if (prefix_len < -1)
+                return true;
+              mkdir_errno = errno;
+            }
+        }
+
+      if (0 <= prefix_len)
+        {
+          /* If the ownership might change, or if the directory will be
+             writable to other users and its special mode bits may
+             change after the directory is created, create it with
+             more restrictive permissions at first, so unauthorized
+             users cannot nip in before the directory is ready.  */
+          bool keep_owner = owner == (uid_t) -1 && group == (gid_t) -1;
+          bool keep_special_mode_bits =
+            ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX)) == 0;
+          mode_t mkdir_mode = mode;
+          if (! keep_owner)
+            mkdir_mode &= ~ (S_IRWXG | S_IRWXO);
+          else if (! keep_special_mode_bits)
+            mkdir_mode &= ~ (S_IWGRP | S_IWOTH);
+
+          if (mkdir (dir + prefix_len, mkdir_mode) == 0)
+            {
+              /* True if the caller does not care about the umask's
+                 effect on the permissions.  */
+              bool umask_must_be_ok = (mode & mode_bits & S_IRWXUGO) == 0;
+
+              announce (dir, options);
+              preserve_existing = (keep_owner & keep_special_mode_bits
+                                   & umask_must_be_ok);
+              savewd_chdir_options |=
+                (SAVEWD_CHDIR_NOFOLLOW
+                 | (mode & S_IRUSR ? SAVEWD_CHDIR_READABLE : 0));
+            }
+          else
+            {
+              mkdir_errno = errno;
+              mkdir_mode = -1;
+            }
+
+          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);
+                  char const *subdir = (chdir_ok ? "." : dir + prefix_len);
+                  if (dirchownmod (open_result[0], subdir, mkdir_mode,
+                                   owner, group, mode, mode_bits)
+                      == 0)
+                    return true;
+
+                  if (mkdir_errno == 0
+                      || (mkdir_errno != ENOENT && make_ancestor
+                          && errno != ENOTDIR))
+                    {
+                      error (0, errno,
+                             _(keep_owner
+                               ? "cannot change permissions of %s"
+                               : "cannot change owner and permissions of %s"),
+                             quote (dir));
+                      return false;
+                    }
+                }
+            }
+        }
     }
 
-  return retval;
+  error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
+  return false;
 }