| /* mkdir-p.c -- Ensure that a directory and its parents exist. |
| |
| Copyright (C) 1990, 1997-2000, 2002-2007, 2009-2020 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; |
| } |