diff options
Diffstat (limited to '')
-rw-r--r-- | lib/mkdir-p.c | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c new file mode 100644 index 0000000..00651e9 --- /dev/null +++ b/lib/mkdir-p.c @@ -0,0 +1,202 @@ +/* mkdir-p.c -- Ensure that a directory and its parents exist. + + Copyright (C) 1990, 1997-2000, 2002-2007, 2009-2022 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 + 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 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + 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, see <https://www.gnu.org/licenses/>. */ + +/* Written by Paul Eggert, David MacKenzie, and Jim Meyering. */ + +#include <config.h> + +#include "mkdir-p.h" + +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +#include "dirchownmod.h" +#include "dirname.h" +#include "error.h" +#include "quote.h" +#include "mkancesdirs.h" +#include "savewd.h" + +#ifndef HAVE_FCHMOD +# define HAVE_FCHMOD false +#endif + +/* Ensure that the directory DIR exists. + + WD is the working directory, as in savewd.c. + + 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. + + 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. + + Call ANNOUNCE (DIR, OPTIONS) just after successfully making DIR, + even if some of the following actions fail. + + 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. + + 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. + + However, if PRESERVE_EXISTING is true and DIR already exists, + do not attempt to set DIR's ownership and file mode bits. + + 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 *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 (mkdir_errno == 0) + { + 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; + } + else + { + mkdir_errno = errno; + mkdir_mode = -1; + } + + if (preserve_existing) + { + if (mkdir_errno == 0) + return true; + if (mkdir_errno != ENOENT && make_ancestor) + { + struct stat st; + if (stat (dir + prefix_len, &st) == 0) + { + if (S_ISDIR (st.st_mode)) + return true; + } + else if (mkdir_errno == EEXIST + && errno != ENOENT && errno != ENOTDIR) + { + error (0, errno, _("cannot stat %s"), quote (dir)); + return false; + } + } + } + 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; + } + } + } + } + } + + error (0, mkdir_errno, _("cannot create directory %s"), quote (dir)); + return false; +} |