summaryrefslogtreecommitdiff
path: root/lib/mkdir-p.c
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2006-09-16 19:58:25 +0000
committerPaul Eggert <eggert@cs.ucla.edu>2006-09-16 19:58:25 +0000
commiteeb96e1a6b5e0b31a4011d7d333999592c0bb2e9 (patch)
tree147b0b74905d1e83b2efe83145de258782d2f0e0 /lib/mkdir-p.c
parent5af1dfba7cbe99d263641b837a8b240e7b8f77bd (diff)
downloadgnulib-eeb96e1a6b5e0b31a4011d7d333999592c0bb2e9.tar.gz
* lib/dirchownmod.c: Don't include fcntl.h; no longer needed.
(dirchownmod): New arg FD. All callers changed. Use FD rather than opening the directory ourself, as opening is now the caller's responsibility. * lib/dirchownmod.h: Likewise. * lib/mkancesdirs.c: Include <sys/types.h>, for portability to older hosts that require <sys/types.h> before <sys/stat.h>. Include fcntl.h, savewd.h, and unistd.h, not dirname.h and stat-macros.h. (test_dir): Remove. (mkancesdirs): Return length of prefix of FILE that has already been made, or -2 if there is a child doing the work. Redo algorithm so that it is O(N) rather than O(N**2). Optimize away ".", and treat ".." specially since it might stray back into already-created areas. Use a subprocess if necessary. New arg WD; all users changed. MAKE_DIR function should now return 1 if it creates a directory that is not readable. Return -2 if a child process is spun off. * lib/mkancesdirs.h: Include <stddef.h>, for ptrdiff_t. Adjust signature to match code. * lib/mkdir-p.c: Include dirname.h, for IS_ABSOLUTE_FILE_NAME. (make_dir_parents): Use a subprocess if necessary. New arg WD; all users changed. * lib/savewd.c, lib/savewd.h: New files. * m4/savewd.m4: New file. * modules/mkancesdirs (Depends-on): Add fcntl. * modules/savewd: New file. * MODULES.html.sh (File system functions): Add savewd.
Diffstat (limited to 'lib/mkdir-p.c')
-rw-r--r--lib/mkdir-p.c141
1 files changed, 99 insertions, 42 deletions
diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c
index 3838c8e9ad..f097e67669 100644
--- a/lib/mkdir-p.c
+++ b/lib/mkdir-p.c
@@ -30,13 +30,17 @@
#define _(msgid) gettext (msgid)
#include "dirchownmod.c"
+#include "dirname.h"
#include "error.h"
#include "quote.h"
#include "mkancesdirs.h"
+#include "savewd.h"
#include "stat-macros.h"
/* 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 (ANCESTOR, OPTIONS).
This function should return zero if successful, -1 (setting errno)
@@ -46,7 +50,7 @@
created.
Create DIR as a new directory with using mkdir with permissions
- MODE. It is also OK if MAKE_ANCESTOR_DIR is not null and a
+ MODE. 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,
@@ -69,12 +73,15 @@
This implementation assumes the current umask is zero.
Return true if DIR exists as a directory with the proper ownership
- and file mode bits when done. Report a diagnostic and return false
- on failure, storing '\0' into *DIR if an ancestor directory had
- problems. */
+ 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 *, void *),
void *options,
mode_t mode,
@@ -84,51 +91,101 @@ make_dir_parents (char *dir,
gid_t group,
bool preserve_existing)
{
- bool made_dir = (mkdir (dir, mode) == 0);
+ int mkdir_errno = (IS_ABSOLUTE_FILE_NAME (dir) ? 0 : savewd_errno (wd));
- if (!made_dir && make_ancestor && errno == ENOENT)
+ if (mkdir_errno == 0)
{
- if (mkancesdirs (dir, make_ancestor, options) == 0)
- made_dir = (mkdir (dir, mode) == 0);
- else
+ ptrdiff_t prefix_len = 0;
+ int savewd_chdir_options = (HAVE_FCHMOD ? SAVEWD_CHDIR_SKIP_READABLE : 0);
+
+ if (make_ancestor)
{
- /* mkancestdirs updated DIR for a better-looking
- diagnostic, so don't try to stat DIR below. */
- make_ancestor = NULL;
+ prefix_len = mkancesdirs (dir, wd, make_ancestor, options);
+ if (prefix_len < 0)
+ {
+ if (prefix_len < -1)
+ return true;
+ mkdir_errno = errno;
+ }
}
- }
- if (made_dir)
- {
- announce (dir, options);
- preserve_existing =
- (owner == (uid_t) -1 && group == (gid_t) -1
- && ! ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX)));
- }
- else
- {
- int mkdir_errno = errno;
- struct stat st;
- if (! (make_ancestor && mkdir_errno != ENOENT
- && stat (dir, &st) == 0 && S_ISDIR (st.st_mode)))
+ if (0 <= prefix_len)
{
- error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
- return false;
+ if (mkdir (dir + prefix_len, mode) == 0)
+ {
+ announce (dir, options);
+ preserve_existing =
+ (owner == (uid_t) -1 && group == (gid_t) -1
+ && ! ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX)));
+ savewd_chdir_options |=
+ (SAVEWD_CHDIR_NOFOLLOW
+ | (mode & S_IRUSR ? SAVEWD_CHDIR_READABLE : 0));
+ }
+ else
+ mkdir_errno = errno;
+
+ 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
+ {
+ mode_t mkdir_mode = (mkdir_errno == 0 ? mode : -1);
+ 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]),
+ _(owner == (uid_t) -1 && group == (gid_t) -1
+ ? "cannot change permissions of %s"
+ : "cannot change owner and permissions of %s"),
+ quote (dir));
+ return false;
+ }
+ }
+ }
}
}
- if (! preserve_existing
- && (dirchownmod (dir, (made_dir ? mode : (mode_t) -1),
- owner, group, mode, mode_bits)
- != 0))
- {
- error (0, errno,
- _(owner == (uid_t) -1 && group == (gid_t) -1
- ? "cannot change permissions of %s"
- : "cannot change owner and permissions of %s"),
- quote (dir));
- return false;
- }
-
- return true;
+ error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
+ return false;
}