Commit eeb96e1a authored by Paul Eggert's avatar Paul Eggert

* 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.
parent 5af1dfba
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.
......
......@@ -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
......
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
......
......@@ -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)
{
......
#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);
......@@ -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.
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))
......@@ -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;
}
int mkancesdirs (char *, int (*) (char const *, void *), void *);
#include <stddef.h>
struct savewd;
ptrdiff_t mkancesdirs (char *, struct savewd *,
int (*) (char const *, void *), void *);
......@@ -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;
}
/* 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,
......
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.
......
......@@ -8,6 +8,7 @@ m4/mkancesdirs.m4
Depends-on:
dirname
fcntl
stat-macros
configure.ac:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment