(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.
2006-09-15 Paul Eggert <eggert@cs.ucla.edu>
+ * 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.
func_module same
func_module save-cwd
func_module savedir
+ func_module savewd
func_module stat-time
func_module tmpdir
func_module unlinkdir
+2006-09-15 Paul Eggert <eggert@cs.ucla.edu>
+
+ * 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 <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.
+ * mkancesdirs.h: Include <stddef.h>, 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 <jim@meyering.net>
* rename-dest-slash.c (has_trailing_slash): Use
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <fcntl.h>
#include <unistd.h>
#include "lchmod.h"
# 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
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)
{
#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);
#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.
-
- 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.
+ function in parallel. Modify FILE as necessary to access the
+ ancestor directories, but restore FILE to an equivalent value
+ if successful.
- 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. */
+ WD points to the working directory, using the conventions of
+ savewd.
-int
-mkancesdirs (char *file,
+ 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. */
+
+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';
-
- 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;
- }
+ /* Address of the leftmost file name component that has not yet
+ been processed. */
+ char *component = file;
- *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))
}
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;
}
-int mkancesdirs (char *, int (*) (char const *, void *), void *);
+#include <stddef.h>
+struct savewd;
+ptrdiff_t mkancesdirs (char *, struct savewd *,
+ int (*) (char const *, void *), void *);
#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)
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,
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,
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;
}
/* 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
#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,
+2006-09-15 Paul Eggert <eggert@cs.ucla.edu>
+
+ * savewd.m4: New file.
+
2006-09-15 Jim Meyering <jim@meyering.net>
* rename-dest-slash.m4 (gl_FUNC_RENAME_TRAILING_DEST_SLASH): New file.
Depends-on:
dirname
+fcntl
stat-macros
configure.ac: