X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fmkdir-p.c;h=188fa3a41eff13c2dd12b00ca78ede4837ef153e;hb=46f5f314f34a08c9305758482d7d2fdb0e999d09;hp=b074c0aa784169bb0428733b448d13745b9baa42;hpb=a095eb9d7165c3e5eb67762b6de68adb2384c842;p=gnulib.git diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c index b074c0aa7..188fa3a41 100644 --- a/lib/mkdir-p.c +++ b/lib/mkdir-p.c @@ -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 @@ -14,325 +14,180 @@ 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 . */ -/* Written by David MacKenzie and Jim Meyering. */ +/* Written by Paul Eggert, David MacKenzie, and Jim Meyering. */ -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include "mkdir-p.h" -#include - -#include -#include +#include #include #include -#include -#include -#include - #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" - -#ifndef ENOSYS -# define ENOSYS EEXIST -#endif - -#define WX_USR (S_IWUSR | S_IXUSR) - -/* Ensure that the directory ARG exists. - - 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. - - Set *CWD_ERRNO to a (nonzero) error number if this - function has changed the current working directory and is unable to - restore it to its initial state. Do not change - *CWD_ERRNO otherwise. - - Return true iff ARG exists as a directory with the proper ownership - and permissions when done. Note that this function returns true - even when it fails to return to the initial working directory. */ - -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, - int *cwd_errno) -{ - struct stat stats; - bool retval = true; - bool do_chdir = false; /* Whether to chdir before each mkdir. */ - struct saved_cwd cwd; - bool cwd_problem = false; - char const *fixup_permissions_dir = NULL; - char const *full_dir = arg; - - struct ptr_list - { - char *dirname_end; - struct ptr_list *next; - }; - struct ptr_list *leading_dirs = NULL; - - if (stat (arg, &stats) == 0) - { - if (! S_ISDIR (stats.st_mode)) - { - error (0, 0, _("%s exists but is not a directory"), quote (arg)); - return false; - } - - if (!preserve_existing) - fixup_permissions_dir = arg; - } - else if (errno != ENOENT || !*arg) - { - error (0, errno, "%s", quote (arg)); - return false; - } - else - { - char *slash; - mode_t tmp_mode; /* Initial perms for leading dirs. */ - bool re_protect; /* Should leading dirs be unwritable? */ - 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); - full_dir = 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) - { - free_cwd (&cwd); - do_chdir = false; - } - } - - slash = dir; - - /* Skip over leading slashes. */ - while (*slash == '/') - slash++; - - while (true) - { - /* 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; +#include "mkancesdirs.h" +#include "savewd.h" - *slash = '\0'; - if (mkdir (basename_dir, tmp_mode) == 0) - { - 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 +#ifndef HAVE_FCHMOD +# define HAVE_FCHMOD false #endif - ) - { - error (0, errno, _("cannot change owner and/or group of %s"), - quote (dir)); - retval = false; - break; - } - - if (re_protect) - { - struct ptr_list *new = (struct ptr_list *) - alloca (sizeof *new); - new->dirname_end = slash; - new->next = leading_dirs; - leading_dirs = new; - } - } - else if (errno == EEXIST || errno == ENOSYS) - { - /* A file is already there. Perhaps it is a directory. - If not, it will be diagnosed later. - The ENOSYS is for Solaris 8 NFS clients, which can - fail with errno == ENOSYS if mkdir is invoked on an - NFS mount point. */ - } - else - { - error (0, errno, _("cannot create directory %s"), quote (dir)); - retval = false; - break; - } +/* Ensure that the directory DIR exists. - /* 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 - 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)); - retval = false; - break; - } + WD is the working directory, as in savewd.c. - *slash++ = '/'; + 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. - /* Avoid unnecessary calls to mkdir when given - file names containing multiple adjacent slashes. */ - while (*slash == '/') - slash++; - } + 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 (!do_chdir) - basename_dir = dir; + Call ANNOUNCE (DIR, OPTIONS) just after successfully making DIR, + even if some of the following actions fail. - /* Done creating leading directories. Restore original umask. */ - umask (oldmask); + 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. - /* We're done making leading directories. - Create the final component of the file name. */ - if (retval) - { - if (mkdir (basename_dir, mode) != 0) - { - error (0, errno, _("cannot create directory %s"), quote (dir)); - retval = false; - } - else - { - if (verbose_fmt_string) - error (0, 0, verbose_fmt_string, quote (dir)); - fixup_permissions_dir = basename_dir; - } - } - } + 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. - if (fixup_permissions_dir) - { - /* 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. */ + However, if PRESERVE_EXISTING is true and DIR already exists, + do not attempt to set DIR's ownership and file mode bits. - if (owner != (uid_t) -1 || group != (gid_t) -1) - { - if (chown (fixup_permissions_dir, owner, group) != 0 -#ifdef AFS - && errno != EPERM -#endif - ) - { - error (0, errno, _("cannot change owner and/or group of %s"), - quote (full_dir)); - retval = false; - } - } + 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. */ - /* 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 (fixup_permissions_dir, mode) != 0) - { - error (0, errno, _("cannot change permissions of %s"), - quote (full_dir)); - retval = false; - } - } - - if (do_chdir) - { - if (restore_cwd (&cwd) != 0) - { - *cwd_errno = errno; - cwd_problem = true; - } - free_cwd (&cwd); - } +bool +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) +{ + int mkdir_errno = (IS_ABSOLUTE_FILE_NAME (dir) ? 0 : savewd_errno (wd)); - /* If the mode for leading directories didn't include owner "wx" - privileges, reset their protections to the correct value. */ - for (; leading_dirs != NULL; leading_dirs = leading_dirs->next) + if (mkdir_errno == 0) { - leading_dirs->dirname_end[0] = '\0'; - if ((cwd_problem && *full_dir != '/') - || chmod (full_dir, parent_mode) != 0) - { - error (0, (cwd_problem ? 0 : errno), - _("cannot change permissions of %s"), quote (full_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; }