I installed this fix as part of updates to coreutils. 2006-09-15 Paul Eggert <[EMAIL PROTECTED]>
* modules/mkancesdirs (Depends-on): Add fcntl. * modules/savewd: New file. * MODULES.html.sh (File system functions): Add savewd. * 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. Index: MODULES.html.sh =================================================================== RCS file: /cvsroot/gnulib/gnulib/MODULES.html.sh,v retrieving revision 1.149 diff -p -u -r1.149 MODULES.html.sh --- MODULES.html.sh 15 Sep 2006 13:49:12 -0000 1.149 +++ MODULES.html.sh 15 Sep 2006 22:36:56 -0000 @@ -1908,6 +1908,7 @@ func_all_modules () func_module same func_module save-cwd func_module savedir + func_module savewd func_module stat-time func_module tmpdir func_module unlinkdir Index: lib/dirchownmod.c =================================================================== RCS file: /cvsroot/gnulib/gnulib/lib/dirchownmod.c,v retrieving revision 1.4 diff -p -u -r1.4 dirchownmod.c --- lib/dirchownmod.c 13 Sep 2006 22:38:14 -0000 1.4 +++ lib/dirchownmod.c 15 Sep 2006 22:36:57 -0000 @@ -25,7 +25,6 @@ #include <errno.h> #include <sys/types.h> #include <sys/stat.h> -#include <fcntl.h> #include <unistd.h> #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) { Index: lib/dirchownmod.h =================================================================== RCS file: /cvsroot/gnulib/gnulib/lib/dirchownmod.h,v retrieving revision 1.1 diff -p -u -r1.1 dirchownmod.h --- lib/dirchownmod.h 17 Jul 2006 06:06:48 -0000 1.1 +++ lib/dirchownmod.h 15 Sep 2006 22:36:57 -0000 @@ -1,2 +1,2 @@ #include <sys/types.h> -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); Index: lib/mkancesdirs.c =================================================================== RCS file: /cvsroot/gnulib/gnulib/lib/mkancesdirs.c,v retrieving revision 1.2 diff -p -u -r1.2 mkancesdirs.c --- lib/mkancesdirs.c 13 Sep 2006 22:38:14 -0000 1.2 +++ lib/mkancesdirs.c 15 Sep 2006 22:36:57 -0000 @@ -22,94 +22,71 @@ #include "mkancesdirs.h" -#include <errno.h> +#include <sys/types.h> #include <sys/stat.h> +#include <fcntl.h> -#include "dirname.h" -#include "stat-macros.h" - -/* Return 0 if FILE is a directory, otherwise -1 (setting errno). */ +#include <errno.h> +#include <unistd.h> -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 "dirname.h" +#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. - 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. + WD points to the working directory, using the conventions of + savewd. - 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. */ + Create any ancestor directories that don't already exist, by + 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. + + 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. + + 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. */ -int -mkancesdirs (char *file, +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. */ + /* Address of the leftmost file name component that has not yet + been processed. */ + char *component = file; - for (p = last_component (file); prefix_end < p; p--) - if (ISSLASH (*p) && ! ISSLASH (p[-1])) - { - *p = '\0'; - - if (errno == ENOENT && make_dir (file, make_dir_arg) == 0) - { - *p = '/'; - break; - } - - if (errno != ENOENT) - { - if (test_dir (file) == 0) - { - *p = '/'; - break; - } - if (errno != ENOENT) - return -1; - } - - *p = '/'; - } + char *p = file + FILE_SYSTEM_PREFIX_LEN (file); + char c; + bool made_dir = false; - /* 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 @@ mkancesdirs (char *file, } 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; } Index: lib/mkancesdirs.h =================================================================== RCS file: /cvsroot/gnulib/gnulib/lib/mkancesdirs.h,v retrieving revision 1.1 diff -p -u -r1.1 mkancesdirs.h --- lib/mkancesdirs.h 17 Jul 2006 06:06:48 -0000 1.1 +++ lib/mkancesdirs.h 15 Sep 2006 22:36:57 -0000 @@ -1 +1,4 @@ -int mkancesdirs (char *, int (*) (char const *, void *), void *); +#include <stddef.h> +struct savewd; +ptrdiff_t mkancesdirs (char *, struct savewd *, + int (*) (char const *, void *), void *); Index: lib/mkdir-p.c =================================================================== RCS file: /cvsroot/gnulib/gnulib/lib/mkdir-p.c,v retrieving revision 1.7 diff -p -u -r1.7 mkdir-p.c --- lib/mkdir-p.c 13 Sep 2006 22:38:14 -0000 1.7 +++ lib/mkdir-p.c 15 Sep 2006 22:36:57 -0000 @@ -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; } Index: lib/mkdir-p.h =================================================================== RCS file: /cvsroot/gnulib/gnulib/lib/mkdir-p.h,v retrieving revision 1.3 diff -p -u -r1.3 mkdir-p.h --- lib/mkdir-p.h 17 Jul 2006 06:06:48 -0000 1.3 +++ lib/mkdir-p.h 15 Sep 2006 22:36:57 -0000 @@ -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 <stdbool.h> #include <sys/types.h> +struct savewd; bool make_dir_parents (char *dir, + struct savewd *wd, int (*make_ancestor) (char const *, void *), void *options, mode_t mode, Index: modules/mkancesdirs =================================================================== RCS file: /cvsroot/gnulib/gnulib/modules/mkancesdirs,v retrieving revision 1.2 diff -p -u -r1.2 mkancesdirs --- modules/mkancesdirs 21 Aug 2006 21:46:31 -0000 1.2 +++ modules/mkancesdirs 15 Sep 2006 22:36:57 -0000 @@ -8,6 +8,7 @@ m4/mkancesdirs.m4 Depends-on: dirname +fcntl stat-macros configure.ac: --- /dev/null 2005-09-24 22:00:15.000000000 -0700 +++ lib/savewd.c 2006-09-14 15:59:47.000000000 -0700 @@ -0,0 +1,305 @@ +/* Save and restore the working directory, possibly using a child process. + + Copyright (C) 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 + the Free Software Foundation; either version 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Eggert. */ + +#include <config.h> + +#include "savewd.h" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "exit.h" +#include "dirname.h" +#include "fcntl-safer.h" + + +/* Save the working directory into *WD, if it hasn't been saved + already. Return true if a child has been forked to do the real + work. */ +static bool +savewd_save (struct savewd *wd) +{ + switch (wd->state) + { + case INITIAL_STATE: + /* Save the working directory, or prepare to fall back if possible. */ + { + int fd = open_safer (".", O_RDONLY); + if (0 <= fd) + { + wd->state = FD_STATE; + wd->val.fd = fd; + break; + } + if (errno != EACCES) + { + wd->state = ERROR_STATE; + wd->val.errnum = errno; + break; + } + } + wd->state = FORKING_STATE; + wd->val.child = -1; + /* Fall through. */ + case FORKING_STATE: + if (wd->val.child < 0) + { + /* "Save" the initial working directory by forking a new + subprocess that will attempt all the work from the chdir + until until the next savewd_restore. */ + wd->val.child = fork (); + if (wd->val.child != 0) + { + if (0 < wd->val.child) + return true; + wd->state = ERROR_STATE; + wd->val.errnum = errno; + } + } + break; + + case FD_STATE: + case FD_POST_CHDIR_STATE: + case ERROR_STATE: + case FINAL_STATE: + break; + + default: + assert (false); + } + + return false; +} + +int +savewd_chdir (struct savewd *wd, char const *dir, int options, + int open_result[2]) +{ + int fd = -1; + int result = 0; + + /* Open the directory if requested, or if avoiding a race condition + is requested and possible. */ + if (open_result || (options & (O_NOFOLLOW ? SAVEWD_CHDIR_NOFOLLOW : 0))) + { + fd = open (dir, + (O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK + | (options & SAVEWD_CHDIR_NOFOLLOW ? O_NOFOLLOW : 0))); + + if (open_result) + { + open_result[0] = fd; + open_result[1] = errno; + } + + if (fd < 0 && (errno != EACCES || (options & SAVEWD_CHDIR_READABLE))) + result = -1; + } + + if (result == 0 && ! (0 <= fd && options & SAVEWD_CHDIR_SKIP_READABLE)) + { + if (savewd_save (wd)) + { + open_result = NULL; + result = -2; + } + else + { + result = (fd < 0 ? chdir (dir) : fchdir (fd)); + + if (result == 0) + switch (wd->state) + { + case FD_STATE: + wd->state = FD_POST_CHDIR_STATE; + break; + + case ERROR_STATE: + case FD_POST_CHDIR_STATE: + case FINAL_STATE: + break; + + case FORKING_STATE: + assert (wd->val.child == 0); + break; + + default: + assert (false); + } + } + } + + if (0 <= fd && ! open_result) + { + int e = errno; + close (fd); + errno = e; + } + + return result; +} + +int +savewd_restore (struct savewd *wd, int status) +{ + switch (wd->state) + { + case INITIAL_STATE: + case FD_STATE: + /* The working directory is the desired directory, so there's no + work to do. */ + break; + + case FD_POST_CHDIR_STATE: + /* Restore the working directory using fchdir. */ + if (fchdir (wd->val.fd) == 0) + { + wd->state = FD_STATE; + break; + } + else + { + int chdir_errno = errno; + close (wd->val.fd); + wd->state = ERROR_STATE; + wd->val.errnum = chdir_errno; + } + /* Fall through. */ + case ERROR_STATE: + /* Report an error if asked to restore the working directory. */ + errno = wd->val.errnum; + return -1; + + case FORKING_STATE: + /* "Restore" the working directory by waiting for the subprocess + to finish. */ + { + pid_t child = wd->val.child; + if (child == 0) + _exit (status); + if (0 < child) + { + int status; + while (waitpid (child, &status, 0) < 0) + assert (errno == EINTR); + wd->val.child = -1; + if (! WIFEXITED (status)) + raise (WTERMSIG (status)); + return WEXITSTATUS (status); + } + } + break; + + default: + assert (false); + } + + return 0; +} + +void +savewd_finish (struct savewd *wd) +{ + switch (wd->state) + { + case INITIAL_STATE: + case ERROR_STATE: + break; + + case FD_STATE: + case FD_POST_CHDIR_STATE: + close (wd->val.fd); + break; + + case FORKING_STATE: + assert (wd->val.child < 0); + break; + + default: + assert (false); + } + + wd->state = FINAL_STATE; +} + +/* Return true if the actual work is currently being done by a + subprocess. + + A true return means that the caller and the subprocess should + resynchronize later with savewd_restore, using only their own + memory to decide when to resynchronize; they should not consult the + file system to decide, because that might lead to race conditions. + This is why savewd_chdir is broken out into another function; + savewd_chdir's callers _can_ inspect the file system to decide + whether to call savewd_chdir. */ +static inline bool +savewd_delegating (struct savewd const *wd) +{ + return wd->state == FORKING_STATE && 0 < wd->val.child; +} + +int +savewd_process_files (int n_files, char **file, + int (*act) (char *, struct savewd *, void *), + void *options) +{ + int i = 0; + int last_relative; + int exit_status = EXIT_SUCCESS; + struct savewd wd; + savewd_init (&wd); + + for (last_relative = n_files - 1; 0 <= last_relative; last_relative--) + if (! IS_ABSOLUTE_FILE_NAME (file[last_relative])) + break; + + for (; i < last_relative; i++) + { + if (! savewd_delegating (&wd)) + { + int s = act (file[i], &wd, options); + if (exit_status < s) + exit_status = s; + } + + if (! IS_ABSOLUTE_FILE_NAME (file[i + 1])) + { + int r = savewd_restore (&wd, exit_status); + if (exit_status < r) + exit_status = r; + } + } + + savewd_finish (&wd); + + for (; i < n_files; i++) + { + int s = act (file[i], &wd, options); + if (exit_status < s) + exit_status = s; + } + + return exit_status; +} --- /dev/null 2005-09-24 22:00:15.000000000 -0700 +++ lib/savewd.h 2006-09-14 15:54:15.000000000 -0700 @@ -0,0 +1,149 @@ +/* Save and restore the working directory, possibly using a subprocess. + + Copyright (C) 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 + the Free Software Foundation; either version 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Eggert. */ + +#ifndef SAVEWD_H +# define SAVEWD_H 1 + +#include <stdbool.h> +#include <sys/types.h> + +/* A saved working directory. The member names and constants defined + by this structure are private to the savewd module. */ +struct savewd +{ + /* The state of this object. */ + enum + { + /* This object has been created but does not yet represent + the working directory. */ + INITIAL_STATE, + + /* val.fd is the original working directory's file descriptor. + It is still the working directory. */ + FD_STATE, + + /* Like FD_STATE, but the working directory has changed, so + restoring it will require a fchdir. */ + FD_POST_CHDIR_STATE, + + /* Fork and let the subprocess do the work. val.child is 0 in a + child, negative in a childless parent, and the child process + ID in a parent with a child. */ + FORKING_STATE, + + /* A serious problem argues against further efforts. val.errnum + contains the error number (e.g., EIO). */ + ERROR_STATE, + + /* savewd_finish has been called, so the application no longer + cares whether the working directory is saved, and there is no + more work to do. */ + FINAL_STATE + } state; + + /* The object's value. */ + union + { + int fd; + int errnum; + pid_t child; + } val; +}; + +/* Allocate and return a saved working directory object. */ +static inline void +savewd_init (struct savewd *wd) +{ + wd->state = INITIAL_STATE; +} + + +/* Options for savewd_chdir. */ +enum + { + /* Do not follow symbolic links, if supported. */ + SAVEWD_CHDIR_NOFOLLOW = 1, + + /* The directory should be readable, so fail if it happens to be + discovered that the directory is not readable. (Unreadable + directories are not necessarily diagnosed, though.) */ + SAVEWD_CHDIR_READABLE = 2, + + /* Do not chdir if the directory is readable; simply succeed + without invoking chdir if the directory was opened. */ + SAVEWD_CHDIR_SKIP_READABLE = 4 + }; + +/* Change the directory, and if successful, record into *WD the fact + that the process chdired into DIR. A process using this module + should use savewd_chdir rather than chdir or fchdir. Obey the + options specified in OPTIONS. + + If OPEN_RESULT is not null, store into OPEN_RESULT[0] a file + descriptor that accesses DIR if a file descriptor is successfully + obtained. Store -1 otherwise, setting OPEN_RESULT[1] to the error + number. Store through OPEN_RESULT regardless of whether the chdir + is successful. However, when -2 is returned, the contents of + OPEN_RESULT are indeterminate since the file descriptor is closed + in the parent. + + Return -2 if a subprocess was spun off to do the real work, -1 + (setting errno) if unsuccessful, 0 if successful. */ +int savewd_chdir (struct savewd *wd, char const *dir, int options, + int open_result[2]); + +/* Restore the working directory from *WD. STATUS indicates the exit + status corresponding to the work done since the last save; this is + used when the caller is in a subprocess. Return 0 if successful, + -1 (setting errno) on our failure, a positive subprocess exit + status if the working directory was restored in the parent but the + subprocess failed. */ +int savewd_restore (struct savewd *wd, int status); + +/* Return WD's error number, or 0 if WD is not in an error state. */ +static inline int +savewd_errno (struct savewd const *wd) +{ + return (wd->state == ERROR_STATE ? wd->val.errnum : 0); +} + +/* Deallocate any resources associated with WD. A program that chdirs + should restore before finishing. */ +void savewd_finish (struct savewd *wd); + +/* Process N_FILES file names, FILE[0] through FILE[N_FILES - 1], in + the context of the working directory WD. For each file name F, + call ACT (F, WD, OPTIONS); ACT should invoke savewd_chdir as + needed, and should return an exit status. WD may be in an + error state when ACT is called. + + Save and restore the working directory as needed by the file name + vector; assume that ACT does not require access to any relative + file names other than its first argument, and that it is OK if the + working directory is changed when this function returns. Some + actions may be applied in a subprocess. + + Return the maximum exit status that any call to ACT returned, or + EXIT_SUCCESS (i.e., 0) if no calls were made. */ +int savewd_process_files (int n_files, char **file, + int (*act) (char *, struct savewd *, void *), + void *options); + +#endif --- /dev/null 2005-09-24 22:00:15.000000000 -0700 +++ m4/savewd.m4 2006-09-10 11:14:07.000000000 -0700 @@ -0,0 +1,9 @@ +# Save and restore the working directory, possibly using a child process. + +dnl Copyright (C) 2004 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_SAVEWD], + [AC_REQUIRE([AC_C_INLINE])]) --- /dev/null 2005-09-24 22:00:15.000000000 -0700 +++ modules/savewd 2006-09-10 11:14:36.000000000 -0700 @@ -0,0 +1,29 @@ +Description: +Save and restore the working directory, possibly using a child process. + +Files: +lib/savewd.h +lib/savewd.c +m4/savewd.m4 + +Depends-on: +dirname +exit +fcntl-safer +stdbool +xalloc + +configure.ac: +gl_SAVEWD + +Makefile.am: +lib_SOURCES += savewd.h savewd.c + +Include: +"savewd.h" + +License: +GPL + +Maintainer: +Paul Eggert, Jim Meyering