# HG changeset patch # User Paul Eggert # Date 1158436705 0 # Node ID 796e1837ac66daac16ccc7d06597f3af12ed1928 # Parent dc0ecc0fbaf2f40d7b2467159aa28cae08ef0c51 * 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 , for portability to older hosts that require before . 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 , 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. diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2006-09-15 Paul Eggert + * modules/mkancesdirs (Depends-on): Add fcntl. + * modules/savewd: New file. + * MODULES.html.sh (File system functions): Add savewd. + * modules/configmake (Makefile.am): Add support for the Automake-supplied PKGLIBDIR, PKGINCLUDEDIR, PKGDATADIR. diff --git a/MODULES.html.sh b/MODULES.html.sh --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -1908,6 +1908,7 @@ func_module same func_module save-cwd func_module savedir + func_module savewd func_module stat-time func_module tmpdir func_module unlinkdir diff --git a/lib/ChangeLog b/lib/ChangeLog --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,29 @@ +2006-09-15 Paul Eggert + + * 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. + * dirchownmod.h: Likewise. + * mkancesdirs.c: Include , for portability to older + hosts that require before . 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. + * mkancesdirs.h: Include , for ptrdiff_t. + Adjust signature to match code. + * 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. + * savewd.c, savewd.h: New files. + 2006-09-15 Jim Meyering * rename-dest-slash.c (has_trailing_slash): Use diff --git a/lib/dirchownmod.c b/lib/dirchownmod.c --- a/lib/dirchownmod.c +++ b/lib/dirchownmod.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include "lchmod.h" @@ -37,7 +36,10 @@ # define fchmod(fd, mode) (-1) #endif -/* Change the ownership and mode bits of the directory DIR. +/* Change the ownership and mode bits of a directory. If FD is + nonnegative, it should be a file descriptor associated with the + directory; close it before returning. DIR is the name of the + directory. If MKDIR_MODE is not (mode_t) -1, mkdir (DIR, MKDIR_MODE) has just been executed successfully with umask zero, so DIR should be a @@ -58,27 +60,12 @@ calls may do the chown but not the chmod. */ int -dirchownmod (char const *dir, mode_t mkdir_mode, +dirchownmod (int fd, char const *dir, mode_t mkdir_mode, uid_t owner, gid_t group, mode_t mode, mode_t mode_bits) { struct stat st; - int result; - - /* Manipulate DIR via a file descriptor if possible, to avoid some races. */ - int open_flags = O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK; - int fd = open (dir, open_flags); - - /* Fail if the directory is unreadable, the directory previously - existed or was created without read permission. Otherwise, get - the file's status. */ - if (0 <= fd) - result = fstat (fd, &st); - else if (errno != EACCES - || (mkdir_mode != (mode_t) -1 && mkdir_mode & S_IRUSR)) - return fd; - else - result = stat (dir, &st); + int result = (fd < 0 ? stat (dir, &st) : fstat (fd, &st)); if (result == 0) { diff --git a/lib/dirchownmod.h b/lib/dirchownmod.h --- a/lib/dirchownmod.h +++ b/lib/dirchownmod.h @@ -1,2 +1,2 @@ #include -int dirchownmod (char const *, mode_t, uid_t, gid_t, mode_t, mode_t); +int dirchownmod (int, char const *, mode_t, uid_t, gid_t, mode_t, mode_t); diff --git a/lib/mkancesdirs.c b/lib/mkancesdirs.c --- a/lib/mkancesdirs.c +++ b/lib/mkancesdirs.c @@ -22,94 +22,71 @@ #include "mkancesdirs.h" +#include +#include +#include + #include -#include +#include #include "dirname.h" -#include "stat-macros.h" - -/* Return 0 if FILE is a directory, otherwise -1 (setting errno). */ - -static int -test_dir (char const *file) -{ - struct stat st; - if (stat (file, &st) == 0) - { - if (S_ISDIR (st.st_mode)) - return 0; - errno = ENOTDIR; - } - return -1; -} +#include "savewd.h" /* Ensure that the ancestor directories of FILE exist, using an algorithm that should work even if two processes execute this - function in parallel. Temporarily modify FILE by storing '\0' - bytes into it, to access the ancestor directories. + function in parallel. Modify FILE as necessary to access the + ancestor directories, but restore FILE to an equivalent value + if successful. + + WD points to the working directory, using the conventions of + savewd. Create any ancestor directories that don't already exist, by - invoking MAKE_DIR (ANCESTOR, MAKE_DIR_ARG). This function should - return zero if successful, -1 (setting errno) otherwise. + invoking MAKE_DIR (COMPONENT, MAKE_DIR_ARG). This function should + return 0 if successful and the resulting directory is readable, 1 + if successful but the resulting directory might not be readable, -1 + (setting errno) otherwise. If COMPONENT is relative, it is + relative to the temporary working directory, which may differ from + *WD. - If successful, return 0 with FILE set back to its original value; - otherwise, return -1 (setting errno), storing a '\0' into *FILE so - that it names the ancestor directory that had problems. */ + Ordinarily MAKE_DIR is executed with the working directory changed + to reflect the already-made prefix, and mkancesdirs returns with + the working directory changed a prefix of FILE. However, if the + initial working directory cannot be saved in a file descriptor, + MAKE_DIR is invoked in a subprocess and this function returns in + both the parent and child process, so the caller should not assume + any changed state survives other than the EXITMAX component of WD, + and the caller should take care that the parent does not attempt to + do the work that the child is doing. -int -mkancesdirs (char *file, + If successful and if this process can go ahead and create FILE, + return the length of the prefix of FILE that has already been made. + If successful so far but a child process is doing the actual work, + return -2. If unsuccessful, return -1 and set errno. */ + +ptrdiff_t +mkancesdirs (char *file, struct savewd *wd, int (*make_dir) (char const *, void *), void *make_dir_arg) { - /* This algorithm is O(N**2) but in typical practice the fancier - O(N) algorithms are slower. */ - /* Address of the previous directory separator that follows an ordinary byte in a file name in the left-to-right scan, or NULL if no such separator precedes the current location P. */ char *sep = NULL; - char const *prefix_end = file + FILE_SYSTEM_PREFIX_LEN (file); - char *p; - char c; - - /* Search backward through FILE using mkdir to create the - furthest-away ancestor that is needed. This loop isn't needed - for correctness, but typically ancestors already exist so this - loop speeds things up a bit. - - This loop runs a bit faster if errno initially contains an error - number corresponding to a failed access to FILE. However, things - work correctly regardless of errno's initial value. */ - - for (p = last_component (file); prefix_end < p; p--) - if (ISSLASH (*p) && ! ISSLASH (p[-1])) - { - *p = '\0'; + /* Address of the leftmost file name component that has not yet + been processed. */ + char *component = file; - if (errno == ENOENT && make_dir (file, make_dir_arg) == 0) - { - *p = '/'; - break; - } + char *p = file + FILE_SYSTEM_PREFIX_LEN (file); + char c; + bool made_dir = false; - if (errno != ENOENT) - { - if (test_dir (file) == 0) - { - *p = '/'; - break; - } - if (errno != ENOENT) - return -1; - } - - *p = '/'; - } - - /* Scan forward through FILE, creating directories along the way. - Try mkdir before stat, so that the procedure works even when two - or more processes are executing it in parallel. */ + /* Scan forward through FILE, creating and chdiring into directories + along the way. Try MAKE_DIR before chdir, so that the procedure + works even when two or more processes are executing it in + parallel. Isolate each file name component by having COMPONENT + point to its start and SEP point just after its end. */ while ((c = *p++)) if (ISSLASH (*p)) @@ -119,12 +96,59 @@ } else if (ISSLASH (c) && *p && sep) { - *sep = '\0'; - if (make_dir (file, make_dir_arg) != 0 && test_dir (file) != 0) - return -1; - *sep = '/'; + /* Don't bother to make or test for "." since it does not + affect the algorithm. */ + if (! (sep - component == 1 && component[0] == '.')) + { + int make_dir_errno = 0; + int savewd_chdir_options = 0; + int chdir_result; + + /* Temporarily modify FILE to isolate this file name + component. */ + *sep = '\0'; + + /* Invoke MAKE_DIR on this component, except don't bother + with ".." since it must exist if its "parent" does. */ + if (sep - component == 2 + && component[0] == '.' && component[1] == '.') + made_dir = false; + else + switch (make_dir (component, make_dir_arg)) + { + case -1: + make_dir_errno = errno; + break; + + case 0: + savewd_chdir_options |= SAVEWD_CHDIR_READABLE; + /* Fall through. */ + case 1: + made_dir = true; + break; + } + + if (made_dir) + savewd_chdir_options |= SAVEWD_CHDIR_NOFOLLOW; + + chdir_result = + savewd_chdir (wd, component, savewd_chdir_options, NULL); + + /* Undo the temporary modification to FILE, unless there + was a failure. */ + if (chdir_result != -1) + *sep = '/'; + + if (chdir_result != 0) + { + if (make_dir_errno != 0 && errno == ENOENT) + errno = make_dir_errno; + return chdir_result; + } + } + + component = p; } - - return 0; + return component - file; } diff --git a/lib/mkancesdirs.h b/lib/mkancesdirs.h --- a/lib/mkancesdirs.h +++ b/lib/mkancesdirs.h @@ -1,1 +1,4 @@ -int mkancesdirs (char *, int (*) (char const *, void *), void *); +#include +struct savewd; +ptrdiff_t mkancesdirs (char *, struct savewd *, + int (*) (char const *, void *), void *); diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c --- 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 @@ 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) + { + 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) { - /* mkancestdirs updated DIR for a better-looking - diagnostic, so don't try to stat DIR below. */ - make_ancestor = NULL; + 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 (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))) - { - error (0, mkdir_errno, _("cannot create directory %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; } diff --git a/lib/mkdir-p.h b/lib/mkdir-p.h --- a/lib/mkdir-p.h +++ b/lib/mkdir-p.h @@ -1,7 +1,7 @@ /* mkdir-p.h -- Ensure that a directory and its parents exist. - Copyright (C) 1994, 1995, 1996, 1997, 2000, 2003, 2004, 2005 Free - Software Foundation, Inc. + Copyright (C) 1994, 1995, 1996, 1997, 2000, 2003, 2004, 2005, 2006 + 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 @@ -22,7 +22,9 @@ #include #include +struct savewd; bool make_dir_parents (char *dir, + struct savewd *wd, int (*make_ancestor) (char const *, void *), void *options, mode_t mode, diff --git a/m4/ChangeLog b/m4/ChangeLog --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,7 @@ +2006-09-15 Paul Eggert + + * savewd.m4: New file. + 2006-09-15 Jim Meyering * rename-dest-slash.m4 (gl_FUNC_RENAME_TRAILING_DEST_SLASH): New file. diff --git a/modules/mkancesdirs b/modules/mkancesdirs --- a/modules/mkancesdirs +++ b/modules/mkancesdirs @@ -8,6 +8,7 @@ Depends-on: dirname +fcntl stat-macros configure.ac: