X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fmkdir-p.c;h=566aadb0360ebdb3aa8dc736001506dd7d6ee54a;hb=b94f2b3ac7049ef66bded4596431c453e3710209;hp=4bad68621ba9d613a1d51c962bc43973190a5756;hpb=1e0d1c838be253f29e27af92ef50ca4ac893028c;p=gnulib.git diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c index 4bad68621..566aadb03 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-2011 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,385 +14,195 @@ 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. */ -#if HAVE_CONFIG_H -# include -#endif +#include #include "mkdir-p.h" -#include - -#include -#include -#include -#if HAVE_UNISTD_H -# include -#endif - -#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" - -#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. */ +#ifndef HAVE_FCHMOD +# define HAVE_FCHMOD false +#endif -bool -make_dir (char const *dir, char const *fulldir, mode_t mode, - bool *created_dir_p) -{ - bool ok = true; - bool created_dir; +/* Ensure that the directory DIR exists. - created_dir = (mkdir (dir, mode) == 0); + WD is the working directory, as in savewd.c. - if (!created_dir) - { - struct stat stats; - int saved_errno = errno; + 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. - /* 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. */ + Create DIR as a new directory with using mkdir with permissions + MODE. It is also OK if MAKE_ANCESTOR is not null and a + directory DIR already exists. - 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. */ - } - } + Call ANNOUNCE (DIR, OPTIONS) just after successfully making DIR, + even if some of the following actions fail. - if (created_dir_p) - *created_dir_p = created_dir; + 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. - return ok; -} + 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. MODE should be a subset of MODE_BITS, which in turn + should be a subset of CHMOD_MODE_BITS. Changing the mode in this + way is necessary if DIR already existed or if MODE and MODE_BITS + specify non-permissions bits like S_ISUID. -/* Ensure that the directory ARG exists. + However, if PRESERVE_EXISTING is true and DIR already exists, + do not attempt to set DIR's ownership and file 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. + This implementation assumes the current umask is zero. - 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; + int mkdir_errno = (IS_ABSOLUTE_FILE_NAME (dir) ? 0 : savewd_errno (wd)); - if (stat (arg, &stats) != 0) + if (mkdir_errno == 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; - - /* 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 - { - /* 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 + writeable 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) + { + announce (dir, options); + preserve_existing = keep_owner & keep_special_mode_bits; + 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); + int chdir_errno = errno; + int fd = open_result[0]; + bool chdir_failed_unexpectedly = + (mkdir_errno == 0 + && ((! chdir_ok && (mode & S_IXUSR)) + || (fd < 0 && (mode & S_IRUSR)))); + + if (chdir_failed_unexpectedly) + { + /* No need to save errno here; it's irrelevant. */ + if (0 <= fd) + close (fd); + } + else + { + char const *subdir = (chdir_ok ? "." : dir + prefix_len); + if (dirchownmod (fd, subdir, mkdir_mode, owner, group, + mode, mode_bits) + == 0) + return true; + } + + if (mkdir_errno == 0 + || (mkdir_errno != ENOENT && make_ancestor + && errno != ENOTDIR)) + { + error (0, + (! chdir_failed_unexpectedly ? errno + : ! chdir_ok && (mode & S_IXUSR) ? chdir_errno + : open_result[1]), + _(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; }