summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2006-07-17 06:06:48 +0000
committerPaul Eggert <eggert@cs.ucla.edu>2006-07-17 06:06:48 +0000
commit5f531596a92e1205a700218dedfe7a525eef21c8 (patch)
tree43198350d38d7a770d9c309265e06e3588b2c762
parentc7c220336bdebb2f0302e2a618a3537a80f8e979 (diff)
downloadgnulib-5f531596a92e1205a700218dedfe7a525eef21c8.tar.gz
Import from coreutils.
* MODULES.html.sh: Add mkancestors. * modules/mkancesdirs: New module. * modules/mkdir-p (Files): Remove lib/chdir-safer.c, lib/chdir-safer.h, lib/same-inode.h, m4/afs.m4, m4/chdir-safer.m4. The chdir-safer and afs files are now orphans; I'll remove them unless someone speaks up. Add lib/dirchownmod.c, lib/dirchownmod.h. (Depends-on): Remove alloca, chown, save-cwd, dirname. Add lchown, mkancesdirs. (Maintainer): Add self. * lib/dirchownmod.c, lib/dirchownmod.h: * lib/mkancesdirs.c, lib/mkancesdirs.h: New files. * lib/mkdir-p.c: Don't include alloca.h, stdio.h, sys/types.h, unistd.h, string.h, chdir-safer.h, dirname.h, lchmod.h, lchown.h, save-cwd.h. Instead, include dirchownmod.h and mkancesdirs.h. (make_dir_parents): New args MAKE_ANCESTOR, OPTIONS, ANNOUNCE, MODE_BITS. Remove options VERBOSE_FMT_STRING, CWD_ERRNO. All callers changed. Revamp internals significantly, by not attempting to create directories that are temporarily more permissive than the final results. Do not attempt to use save_cwd/restore_cwd; it isn't worth it for mkdir and install. This removes some race conditions, fixes some bugs, and simplifies things. Use new dirchownmod function to do owner and mode changes. * lib/mkdir-p.h: Likewise. * lib/modechange.c (octal_to_mode): New function. (struct mode_change): New member mentioned. (make_node_op_equals): New arg mentioned. All callers changed. (mode_compile): Keep track of which mode bits the user has explicitly mentioned. (mode_adjust): New arg DIR, so that we implement the X op correctly. New arg PMODE_BITS, to keep track of which mode bits the user mentioned; it treats S_ISUID and S_ISGID speciall. All callers changed. * lib/modechange.h: Likewise. * mkancesdirs.m4: New file. * mkdir-p.m4 (gl_MKDIR_PARENTS): Mention dirchownmod.c, dirchownmod.h. Don't require AC_FUNC_ALLOCA, gl_AFS, gl_CHDIR_SAFER; no longer needed. Require gl_FUNC_LCHOWN, since dirchownmod.c needs it.
-rw-r--r--ChangeLog13
-rwxr-xr-xMODULES.html.sh1
-rw-r--r--lib/ChangeLog27
-rw-r--r--lib/dirchownmod.c159
-rw-r--r--lib/dirchownmod.h2
-rw-r--r--lib/mkancesdirs.c132
-rw-r--r--lib/mkancesdirs.h1
-rw-r--r--lib/mkdir-p.c369
-rw-r--r--lib/mkdir-p.h13
-rw-r--r--lib/modechange.c117
-rw-r--r--lib/modechange.h8
-rw-r--r--lib/userspec.c11
-rw-r--r--m4/ChangeLog7
-rw-r--r--m4/mkancesdirs.m411
-rw-r--r--m4/mkdir-p.m411
-rw-r--r--modules/mkancesdirs25
-rw-r--r--modules/mkdir-p17
17 files changed, 557 insertions, 367 deletions
diff --git a/ChangeLog b/ChangeLog
index 5194f3d16c..2255265b3d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2006-07-16 Paul Eggert <eggert@cs.ucla.edu>
+
+ * MODULES.html.sh: Add mkancestors.
+ * modules/mkancesdirs: New module.
+ * modules/mkdir-p (Files): Remove lib/chdir-safer.c, lib/chdir-safer.h,
+ lib/same-inode.h, m4/afs.m4, m4/chdir-safer.m4.
+ The chdir-safer and afs files are now orphans; I'll remove them
+ unless someone speaks up.
+ Add lib/dirchownmod.c, lib/dirchownmod.h.
+ (Depends-on): Remove alloca, chown, save-cwd, dirname.
+ Add lchown, mkancesdirs.
+ (Maintainer): Add self.
+
2006-07-15 Karl Berry <karl@gnu.org>
* gnulib-tool: help message wording/arrangement.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 1fc6628e30..3c6ce5f15d 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -1869,6 +1869,7 @@ func_all_modules ()
func_module fts-lgpl
func_module isdir
func_module lchown
+ func_module mkancestors
func_module mkdir-p
func_module modechange
func_module mountlist
diff --git a/lib/ChangeLog b/lib/ChangeLog
index 977f4be08a..8dca116d70 100644
--- a/lib/ChangeLog
+++ b/lib/ChangeLog
@@ -1,3 +1,30 @@
+2006-07-16 Paul Eggert <eggert@cs.ucla.edu>
+
+ * dirchownmod.c, dirchownmod.h, mkancesdirs.c, mkancesdirs.h:
+ New files.
+ * mkdir-p.c: Don't include alloca.h, stdio.h, sys/types.h,
+ unistd.h, string.h, chdir-safer.h, dirname.h, lchmod.h, lchown.h,
+ save-cwd.h. Instead, include dirchownmod.h and mkancesdirs.h.
+ (make_dir_parents): New args MAKE_ANCESTOR, OPTIONS, ANNOUNCE,
+ MODE_BITS. Remove options VERBOSE_FMT_STRING, CWD_ERRNO. All
+ callers changed. Revamp internals significantly, by not
+ attempting to create directories that are temporarily more
+ permissive than the final results. Do not attempt to use
+ save_cwd/restore_cwd; it isn't worth it for mkdir and install.
+ This removes some race conditions, fixes some bugs, and simplifies
+ things. Use new dirchownmod function to do owner and mode changes.
+ * mkdir-p.h: Likewise.
+ * modechange.c (octal_to_mode): New function.
+ (struct mode_change): New member mentioned.
+ (make_node_op_equals): New arg mentioned. All callers changed.
+ (mode_compile): Keep track of which mode bits the user has explicitly
+ mentioned.
+ (mode_adjust): New arg DIR, so that we implement the X op correctly.
+ New arg PMODE_BITS, to keep track of which mode bits the user
+ mentioned; it treats S_ISUID and S_ISGID speciall.
+ All callers changed.
+ * modechange.h: Likewise.
+
2006-07-11 Derek R. Price <derek@ximbiot.com>
* glob.c: s/NAMLEN/_D_EXACT_NAMLEN/.
diff --git a/lib/dirchownmod.c b/lib/dirchownmod.c
new file mode 100644
index 0000000000..50e5fe1170
--- /dev/null
+++ b/lib/dirchownmod.c
@@ -0,0 +1,159 @@
+/* Change the ownership and mode bits of a directory.
+
+ 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. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "dirchownmod.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "lchmod.h"
+#include "stat-macros.h"
+
+#ifndef O_DIRECTORY
+# define O_DIRECTORY 0
+#endif
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
+/* Change the ownership and mode bits of the directory DIR.
+
+ 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
+ directory (not a symbolic link).
+
+ First, set the file's owner to OWNER and group to GROUP, but leave
+ the owner alone if OWNER is (uid_t) -1, and similarly for GROUP.
+
+ Then, set the file's mode bits to MODE, except preserve any of the
+ bits that correspond to zero bits in MODE_BITS. In other words,
+ MODE_BITS is a mask that specifies which of the file's mode bits
+ should be set or cleared. MODE should be a subset of MODE_BITS,
+ which in turn should be a subset of CHMOD_MODE_BITS.
+
+ This implementation assumes the current umask is zero.
+
+ Return 0 if successful, -1 (setting errno) otherwise. Unsuccessful
+ calls may do the chown but not the chmod. */
+
+int
+dirchownmod (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);
+
+ if (result == 0)
+ {
+ mode_t dir_mode = st.st_mode;
+
+ /* Check whether DIR is a directory. If FD is nonnegative, this
+ check avoids changing the ownership and mode bits of the
+ wrong file in many cases. This doesn't fix all the race
+ conditions, but it is better than nothing. */
+ if (! S_ISDIR (dir_mode))
+ {
+ errno = ENOTDIR;
+ result = -1;
+ }
+ else
+ {
+ /* If at least one of the S_IXUGO bits are set, chown might
+ clear the S_ISUID and S_SGID bits. Keep track of any
+ file mode bits whose values are indeterminate due to this
+ issue. */
+ mode_t indeterminate = 0;
+
+ /* On some systems, chown clears S_ISUID and S_ISGID, so do
+ chown before chmod. On older System V hosts, ordinary
+ users can give their files away via chown; don't worry
+ about that here, since users shouldn't do that. */
+
+ if ((owner != (uid_t) -1 && owner != st.st_uid)
+ || (group != (gid_t) -1 && group != st.st_gid))
+ {
+ result = (0 <= fd
+ ? fchown (fd, owner, group)
+ : mkdir_mode != (mode_t) -1
+ ? lchown (dir, owner, group)
+ : chown (dir, owner, group));
+
+ /* Either the user cares about an indeterminate bit and
+ it'll be set properly by chmod below, or the user
+ doesn't care and it's OK to use the bit's pre-chown
+ value. So there's no need to re-stat DIR here. */
+
+ if (result == 0 && (dir_mode & S_IXUGO))
+ indeterminate = dir_mode & (S_ISUID | S_ISGID);
+ }
+
+ /* If the file mode bits might not be right, use chmod to
+ change them. Don't change bits the user doesn't care
+ about. */
+ if (result == 0 && (((dir_mode ^ mode) | indeterminate) & mode_bits))
+ {
+ mode_t chmod_mode =
+ mode | (dir_mode & CHMOD_MODE_BITS & ~mode_bits);
+ result = (0 <= fd
+ ? fchmod (fd, chmod_mode)
+ : mkdir_mode != (mode_t) -1
+ ? lchmod (dir, chmod_mode)
+ : chmod (dir, chmod_mode));
+ }
+ }
+ }
+
+ if (0 <= fd)
+ {
+ if (result == 0)
+ result = close (fd);
+ else
+ {
+ int e = errno;
+ close (fd);
+ errno = e;
+ }
+ }
+
+ return result;
+}
diff --git a/lib/dirchownmod.h b/lib/dirchownmod.h
new file mode 100644
index 0000000000..e841f47870
--- /dev/null
+++ b/lib/dirchownmod.h
@@ -0,0 +1,2 @@
+#include <sys/types.h>
+int dirchownmod (char const *, mode_t, uid_t, gid_t, mode_t, mode_t);
diff --git a/lib/mkancesdirs.c b/lib/mkancesdirs.c
new file mode 100644
index 0000000000..4737742366
--- /dev/null
+++ b/lib/mkancesdirs.c
@@ -0,0 +1,132 @@
+/* Make a file's ancestor directories.
+
+ 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. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "mkancesdirs.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+
+#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;
+}
+
+/* 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.
+
+ 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. */
+
+int
+mkancesdirs (char *file,
+ 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;
+ }
+
+ *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. */
+
+ while ((c = *p++))
+ if (ISSLASH (*p))
+ {
+ if (! ISSLASH (c))
+ sep = p;
+ }
+ else if (ISSLASH (c) && *p && sep)
+ {
+ *sep = '\0';
+ if (make_dir (file, make_dir_arg) != 0 && test_dir (file) != 0)
+ return -1;
+ *sep = '/';
+ }
+
+
+ return 0;
+}
diff --git a/lib/mkancesdirs.h b/lib/mkancesdirs.h
new file mode 100644
index 0000000000..a698c9cf00
--- /dev/null
+++ b/lib/mkancesdirs.h
@@ -0,0 +1 @@
+int mkancesdirs (char *, int (*) (char const *, void *), void *);
diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c
index dd42b6ca55..697de7768c 100644
--- a/lib/mkdir-p.c
+++ b/lib/mkdir-p.c
@@ -17,7 +17,7 @@
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 David MacKenzie <djm@gnu.ai.mit.edu> and Jim Meyering. */
+/* Written by Paul Eggert, David MacKenzie, and Jim Meyering. */
#ifdef HAVE_CONFIG_H
# include <config.h>
@@ -25,333 +25,112 @@
#include "mkdir-p.h"
-#include <alloca.h>
-
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <stdlib.h>
#include <errno.h>
-#include <string.h>
+#include <sys/stat.h>
#include "gettext.h"
#define _(msgid) gettext (msgid)
-#include "chdir-safer.h"
-#include "dirname.h"
+#include "dirchownmod.c"
#include "error.h"
-#include "lchmod.h"
-#include "lchown.h"
#include "quote.h"
-#include "save-cwd.h"
+#include "mkancesdirs.h"
#include "stat-macros.h"
-/* Ensure that the directory ARG exists.
+/* Ensure that the directory DIR exists.
+
+ 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)
+ otherwise. In this case, DIR may be modified by storing '\0' bytes
+ into it, to access the ancestor directories, and this modification
+ is retained on return if the ancestor directories could not be
+ 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
+ directory DIR already exists.
+
+ Call ANNOUNCE (DIR, OPTIONS) just after successfully making DIR,
+ even if some of the following actions fail.
+
+ Set DIR's owner to OWNER and group to GROUP, but leave the owner
+ alone if OWNER is (uid_t) -1, and similarly for GROUP.
+
+ Set DIR's mode bits to MODE, except preserve any of the bits that
+ correspond to zero bits in MODE_BITS. In other words, MODE_BITS is
+ a mask that specifies which of DIR's mode bits should be set or
+ cleared. MODE should be a subset of MODE_BITS, which in turn
+ should be a subset of CHMOD_MODE_BITS. Changing the mode in this
+ way is necessary if DIR already existed or if MODE and MODE_BITS
+ specify non-permissions bits like S_ISUID.
- Create any leading directories that don't already exist, with
- permissions PARENT_MODE.
- If the last element of ARG does not exist, create it as
- a new directory with permissions MODE.
- If OWNER and GROUP are non-negative, use them to set the UID and GID of
- any created directories.
- If VERBOSE_FMT_STRING is nonzero, use it as a printf format
- string for printing a message after successfully making a directory,
- with the name of the directory that was just made as an argument.
- If PRESERVE_EXISTING is true and ARG is an existing directory,
- then do not attempt to set its permissions and ownership.
+ However, if PRESERVE_EXISTING is true and DIR already exists,
+ do not attempt to set DIR's ownership and file mode bits.
- Set *CWD_ERRNO to a (nonzero) error number if this
- function has changed the current working directory and is unable to
- restore it to its initial state. Do not change
- *CWD_ERRNO otherwise.
+ This implementation assumes the current umask is zero.
- Return true iff ARG exists as a directory with the proper ownership
- and permissions when done. Note that this function returns true
- even when it fails to return to the initial working directory. */
+ 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. */
bool
-make_dir_parents (char const *arg,
+make_dir_parents (char *dir,
+ int (*make_ancestor) (char const *, void *),
+ void *options,
mode_t mode,
- mode_t parent_mode,
+ void (*announce) (char const *, void *),
+ mode_t mode_bits,
uid_t owner,
gid_t group,
- bool preserve_existing,
- char const *verbose_fmt_string,
- int *cwd_errno)
+ bool preserve_existing)
{
- struct stat stats;
- bool retval = true;
- bool do_chdir = false; /* Whether to chdir before each mkdir. */
- struct saved_cwd cwd;
- bool cwd_problem = false;
- char const *fixup_permissions_dir = NULL;
- char const *full_dir = arg;
+ bool made_dir = (mkdir (dir, mode) == 0);
- struct ptr_list
- {
- char *dirname_end;
- struct ptr_list *next;
- };
- struct ptr_list *leading_dirs = NULL;
-
- if (stat (arg, &stats) == 0)
- {
- if (! S_ISDIR (stats.st_mode))
- {
- error (0, 0, _("%s exists but is not a directory"), quote (arg));
- return false;
- }
-
- if (!preserve_existing)
- fixup_permissions_dir = arg;
- }
- else if (errno != ENOENT || !*arg)
+ if (!made_dir && make_ancestor && errno == ENOENT)
{
- error (0, errno, "%s", quote (arg));
- return false;
- }
- else
- {
- char *slash;
- mode_t tmp_mode; /* Initial perms for leading dirs. */
- bool re_protect; /* Should leading dirs be unwritable? */
- char *basename_dir;
- char *dir;
-
- /* Temporarily relax umask in case it's overly restrictive. */
- mode_t oldmask = umask (0);
-
- /* Make a copy of ARG that we can scribble NULs on. */
- dir = alloca (strlen (arg) + 1);
- strcpy (dir, arg);
- strip_trailing_slashes (dir);
- full_dir = dir;
-
- /* If leading directories shouldn't be readable, writable or executable,
- or should have set[ug]id or sticky bits set and we are setting
- their owners, we need to fix their permissions after making them. */
- if (((parent_mode & S_IRWXU) != S_IRWXU)
- || ((owner != (uid_t) -1 || group != (gid_t) -1)
- && (parent_mode & (S_ISUID | S_ISGID | S_ISVTX)) != 0))
- {
- tmp_mode = S_IRWXU;
- re_protect = true;
- }
+ if (mkancesdirs (dir, make_ancestor, options) == 0)
+ made_dir = (mkdir (dir, mode) == 0);
else
{
- tmp_mode = parent_mode;
- re_protect = false;
- }
-
- /* If we can record the current working directory, we may be able
- to do the chdir optimization. */
- do_chdir = (save_cwd (&cwd) == 0);
-
- /* If we've saved the cwd and DIR is an absolute file name,
- we must chdir to `/' in order to enable the chdir optimization.
- So if chdir ("/") fails, turn off the optimization. */
- if (do_chdir && dir[0] == '/')
- {
- /* POSIX says "//" might be special, so chdir to "//" if the
- file name starts with exactly two slashes. */
- char const *root = "//" + (dir[1] != '/' || dir[2] == '/');
- if (chdir (root) != 0)
- {
- free_cwd (&cwd);
- do_chdir = false;
- }
- }
-
- slash = dir;
-
- /* Skip over leading slashes. */
- while (*slash == '/')
- slash++;
-
- while (true)
- {
- bool dir_known_to_exist;
- int mkdir_errno;
-
- /* slash points to the leftmost unprocessed component of dir. */
- basename_dir = slash;
-
- slash = strchr (slash, '/');
- if (slash == NULL)
- break;
-
- /* If we're *not* doing chdir before each mkdir, then we have to refer
- to the target using the full (multi-component) directory name. */
- if (!do_chdir)
- basename_dir = dir;
-
- *slash = '\0';
- dir_known_to_exist = (mkdir (basename_dir, tmp_mode) == 0);
- mkdir_errno = errno;
-
- if (dir_known_to_exist)
- {
- if (verbose_fmt_string)
- error (0, 0, verbose_fmt_string, quote (dir));
-
- if ((owner != (uid_t) -1 || group != (gid_t) -1)
- && lchown (basename_dir, owner, group)
-#if defined AFS && defined EPERM
- && errno != EPERM
-#endif
- )
- {
- error (0, errno, _("cannot change owner and/or group of %s"),
- quote (dir));
- retval = false;
- break;
- }
-
- if (re_protect)
- {
- struct ptr_list *new = alloca (sizeof *new);
- new->dirname_end = slash;
- new->next = leading_dirs;
- leading_dirs = new;
- }
- }
-
- /* If we were able to save the initial working directory,
- then we can use chdir to change into each directory before
- creating an entry in that directory. This avoids making
- mkdir process O(n^2) file name components. */
- if (do_chdir)
- {
- /* If we know that basename_dir is a directory (because we've
- just created it), then ensure that when we change to it,
- that final component is not a symlink. Otherwise, we must
- accept the possibility that basename_dir is a preexisting
- symlink-to-directory and chdir through the symlink. */
- if ((dir_known_to_exist
- ? chdir_no_follow (basename_dir)
- : chdir (basename_dir)) == 0)
- dir_known_to_exist = true;
- else if (dir_known_to_exist)
- {
- error (0, errno, _("cannot chdir to directory %s"),
- quote (dir));
- retval = false;
- break;
- }
- }
- else if (!dir_known_to_exist)
- dir_known_to_exist = (stat (basename_dir, &stats) == 0
- && S_ISDIR (stats.st_mode));
-
- if (!dir_known_to_exist)
- {
- error (0, mkdir_errno, _("cannot create directory %s"),
- quote (dir));
- retval = false;
- break;
- }
-
- *slash++ = '/';
-
- /* Avoid unnecessary calls to mkdir when given
- file names containing multiple adjacent slashes. */
- while (*slash == '/')
- slash++;
- }
-
- if (!do_chdir)
- basename_dir = dir;
-
- /* Done creating leading directories. Restore original umask. */
- umask (oldmask);
-
- /* We're done making leading directories.
- Create the final component of the file name. */
- if (retval)
- {
- bool dir_known_to_exist = (mkdir (basename_dir, mode) == 0);
- int mkdir_errno = errno;
- struct stat sbuf;
-
- if ( ! dir_known_to_exist)
- dir_known_to_exist = (stat (basename_dir, &sbuf) == 0
- && S_ISDIR (sbuf.st_mode));
-
- if ( ! dir_known_to_exist)
- {
- error (0, mkdir_errno,
- _("cannot create directory %s"), quote (dir));
- retval = false;
- }
- else
- {
- if (verbose_fmt_string)
- error (0, 0, verbose_fmt_string, quote (dir));
- fixup_permissions_dir = basename_dir;
- }
+ /* mkancestdirs updated DIR for a better-looking
+ diagnostic, so don't try to stat DIR below. */
+ make_ancestor = NULL;
}
}
- if (fixup_permissions_dir)
+ if (made_dir)
{
- /* chown must precede chmod because on some systems,
- chown clears the set[ug]id bits for non-superusers,
- resulting in incorrect permissions.
- On System V, users can give away files with chown and then not
- be able to chmod them. So don't give files away. */
-
- if (owner != (uid_t) -1 || group != (gid_t) -1)
- {
- if (lchown (fixup_permissions_dir, owner, group) != 0
-#ifdef AFS
- && errno != EPERM
-#endif
- )
- {
- error (0, errno, _("cannot change owner and/or group of %s"),
- quote (full_dir));
- retval = false;
- }
- }
-
- /* The above chown may have turned off some permission bits in MODE.
- Another reason we may have to use chmod here is that mkdir(2) is
- required to honor only the file permission bits. In particular,
- it need not honor the `special' bits, so if MODE includes any
- special bits, set them here. */
- if ((mode & ~S_IRWXUGO) && lchmod (fixup_permissions_dir, mode) != 0)
- {
- error (0, errno, _("cannot change permissions of %s"),
- quote (full_dir));
- retval = false;
- }
+ announce (dir, options);
+ preserve_existing =
+ (owner == (uid_t) -1 && group == (gid_t) -1
+ && ! ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX)));
}
-
- if (do_chdir)
+ else
{
- if (restore_cwd (&cwd) != 0)
+ int mkdir_errno = errno;
+ struct stat st;
+ if (! (make_ancestor && mkdir_errno != ENOENT
+ && stat (dir, &st) == 0 && S_ISDIR (st.st_mode)))
{
- *cwd_errno = errno;
- cwd_problem = true;
+ error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
+ return false;
}
- free_cwd (&cwd);
}
- /* If the mode for leading directories didn't include owner "wx"
- privileges, reset their protections to the correct value. */
- for (; leading_dirs != NULL; leading_dirs = leading_dirs->next)
+ if (! preserve_existing
+ && (dirchownmod (dir, (made_dir ? mode : (mode_t) -1),
+ owner, group, mode, mode_bits)
+ != 0))
{
- leading_dirs->dirname_end[0] = '\0';
- if ((cwd_problem && *full_dir != '/')
- || lchmod (full_dir, parent_mode) != 0)
- {
- error (0, (cwd_problem ? 0 : errno),
- _("cannot change permissions of %s"), quote (full_dir));
- retval = false;
- }
+ 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 retval;
+ return true;
}
diff --git a/lib/mkdir-p.h b/lib/mkdir-p.h
index 6e0c848fd6..77a11d6b2e 100644
--- a/lib/mkdir-p.h
+++ b/lib/mkdir-p.h
@@ -17,16 +17,17 @@
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 David MacKenzie <djm@gnu.ai.mit.edu> and Jim Meyering. */
+/* Written by Paul Eggert, David MacKenzie, and Jim Meyering. */
#include <stdbool.h>
#include <sys/types.h>
-bool make_dir_parents (char const *argname,
+bool make_dir_parents (char *dir,
+ int (*make_ancestor) (char const *, void *),
+ void *options,
mode_t mode,
- mode_t parent_mode,
+ void (*announce) (char const *, void *),
+ mode_t mode_bits,
uid_t owner,
gid_t group,
- bool preserve_existing,
- char const *verbose_fmt_string,
- int *cwd_errno);
+ bool preserve_existing);
diff --git a/lib/modechange.c b/lib/modechange.c
index 52c5debca3..51684a4eb0 100644
--- a/lib/modechange.c
+++ b/lib/modechange.c
@@ -1,7 +1,7 @@
/* modechange.c -- file mode manipulation
- Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005
- Free Software Foundation, Inc.
+ Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 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
@@ -51,6 +51,32 @@
#define XOTH 00001
#define ALLM 07777 /* all octal mode bits */
+/* Convert OCTAL, which uses one of the traditional octal values, to
+ an internal mode_t value. */
+static mode_t
+octal_to_mode (unsigned int octal)
+{
+ /* Help the compiler optimize the usual case where mode_t uses
+ the traditional octal representation. */
+ return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
+ && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR
+ && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP
+ && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH)
+ ? octal
+ : (mode_t) ((octal & SUID ? S_ISUID : 0)
+ | (octal & SGID ? S_ISGID : 0)
+ | (octal & SVTX ? S_ISVTX : 0)
+ | (octal & RUSR ? S_IRUSR : 0)
+ | (octal & WUSR ? S_IWUSR : 0)
+ | (octal & XUSR ? S_IXUSR : 0)
+ | (octal & RGRP ? S_IRGRP : 0)
+ | (octal & WGRP ? S_IWGRP : 0)
+ | (octal & XGRP ? S_IXGRP : 0)
+ | (octal & ROTH ? S_IROTH : 0)
+ | (octal & WOTH ? S_IWOTH : 0)
+ | (octal & XOTH ? S_IXOTH : 0)));
+}
+
/* Special operations flags. */
enum
{
@@ -78,19 +104,22 @@ struct mode_change
char flag; /* Special operations flag. */
mode_t affected; /* Set for u, g, o, or a. */
mode_t value; /* Bits to add/remove. */
+ mode_t mentioned; /* Bits explicitly mentioned. */
};
/* Return a mode_change array with the specified `=ddd'-style
- mode change operation, where NEW_MODE is `ddd'. */
+ mode change operation, where NEW_MODE is `ddd' and MENTIONED
+ contains the bits explicitly mentioned in the mode are MENTIONED. */
static struct mode_change *
-make_node_op_equals (mode_t new_mode)
+make_node_op_equals (mode_t new_mode, mode_t mentioned)
{
struct mode_change *p = xmalloc (2 * sizeof *p);
p->op = '=';
p->flag = MODE_ORDINARY_CHANGE;
p->affected = CHMOD_MODE_BITS;
p->value = new_mode;
+ p->mentioned = mentioned;
p[1].flag = MODE_DONE;
return p;
}
@@ -113,13 +142,14 @@ mode_compile (char const *mode_string)
if ('0' <= *mode_string && *mode_string < '8')
{
- mode_t mode;
- unsigned int octal_value = 0;
+ unsigned int octal_mode = 0;
+ unsigned int octal_mentioned = 0;
do
{
- octal_value = 8 * octal_value + *mode_string++ - '0';
- if (ALLM < octal_value)
+ octal_mode = 8 * octal_mode + *mode_string++ - '0';
+ octal_mentioned = 8 * octal_mentioned + 7;
+ if (ALLM < octal_mode)
return NULL;
}
while ('0' <= *mode_string && *mode_string < '8');
@@ -127,27 +157,8 @@ mode_compile (char const *mode_string)
if (*mode_string)
return NULL;
- /* Help the compiler optimize the usual case where mode_t uses
- the traditional octal representation. */
- mode = ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
- && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR
- && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP
- && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH)
- ? octal_value
- : (mode_t) ((octal_value & SUID ? S_ISUID : 0)
- | (octal_value & SGID ? S_ISGID : 0)
- | (octal_value & SVTX ? S_ISVTX : 0)
- | (octal_value & RUSR ? S_IRUSR : 0)
- | (octal_value & WUSR ? S_IWUSR : 0)
- | (octal_value & XUSR ? S_IXUSR : 0)
- | (octal_value & RGRP ? S_IRGRP : 0)
- | (octal_value & WGRP ? S_IWGRP : 0)
- | (octal_value & XGRP ? S_IXGRP : 0)
- | (octal_value & ROTH ? S_IROTH : 0)
- | (octal_value & WOTH ? S_IWOTH : 0)
- | (octal_value & XOTH ? S_IXOTH : 0)));
-
- return make_node_op_equals (mode);
+ return make_node_op_equals (octal_to_mode (octal_mode),
+ octal_to_mode (octal_mentioned & ALLM));
}
/* Allocate enough space to hold the result. */
@@ -251,6 +262,7 @@ mode_compile (char const *mode_string)
change->flag = flag;
change->affected = affected;
change->value = value;
+ change->mentioned = (affected ? affected & value : value);
}
while (*mode_string == '=' || *mode_string == '+'
|| *mode_string == '-');
@@ -280,25 +292,36 @@ mode_create_from_ref (const char *ref_file)
if (stat (ref_file, &ref_stats) != 0)
return NULL;
- return make_node_op_equals (ref_stats.st_mode);
+ return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS);
}
-/* Return file mode OLDMODE, adjusted as indicated by the list of change
- operations CHANGES, which are interpreted assuming the umask is
- UMASK_VALUE. If OLDMODE is a directory, the type `X'
- change affects it even if no execute bits were set in OLDMODE.
- The returned value has the S_IFMT bits cleared. */
+/* Return the file mode bits of OLDMODE (which is the mode of a
+ directory if DIR), assuming the umask is UMASK_VALUE, adjusted as
+ indicated by the list of change operations CHANGES. If DIR, the
+ type 'X' change affects the returned value even if no execute bits
+ were set in OLDMODE. If PMODE_BITS is not null, store into
+ *PMODE_BITS a mask denoting file mode bits that are affected by
+ CHANGES.
+
+ The returned value and *PMODE_BITS contain only file mode bits.
+ For example, they have the S_IFMT bits cleared on a standard
+ Unix-like host. */
mode_t
-mode_adjust (mode_t oldmode, struct mode_change const *changes,
- mode_t umask_value)
+mode_adjust (mode_t oldmode, bool dir, mode_t umask_value,
+ struct mode_change const *changes, mode_t *pmode_bits)
{
/* The adjusted mode. */
mode_t newmode = oldmode & CHMOD_MODE_BITS;
+ /* File mode bits that CHANGES cares about. */
+ mode_t mode_bits = 0;
+
for (; changes->flag != MODE_DONE; changes++)
{
mode_t affected = changes->affected;
+ mode_t omit_change =
+ (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned;
mode_t value = changes->value;
switch (changes->flag)
@@ -322,14 +345,15 @@ mode_adjust (mode_t oldmode, struct mode_change const *changes,
case MODE_X_IF_ANY_X:
/* Affect the execute bits if execute bits are already set
or if the file is a directory. */
- if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) || S_ISDIR (oldmode))
+ if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir)
value |= S_IXUSR | S_IXGRP | S_IXOTH;
break;
}
/* If WHO was specified, limit the change to the affected bits.
- Otherwise, apply the umask. */
- value &= (affected ? affected : ~umask_value);
+ Otherwise, apply the umask. Either way, omit changes as
+ requested. */
+ value &= (affected ? affected : ~umask_value) & ~ omit_change;
switch (changes->op)
{
@@ -337,17 +361,26 @@ mode_adjust (mode_t oldmode, struct mode_change const *changes,
/* If WHO was specified, preserve the previous values of
bits that are not affected by this change operation.
Otherwise, clear all the bits. */
- newmode = (affected ? newmode & ~affected : 0);
- /* Fall through. */
+ {
+ mode_t preserved = (affected ? ~affected : 0) | omit_change;
+ mode_bits |= CHMOD_MODE_BITS & ~preserved;
+ newmode = (newmode & preserved) | value;
+ break;
+ }
+
case '+':
+ mode_bits |= value;
newmode |= value;
break;
case '-':
+ mode_bits |= value;
newmode &= ~value;
break;
}
}
+ if (pmode_bits)
+ *pmode_bits = mode_bits;
return newmode;
}
diff --git a/lib/modechange.h b/lib/modechange.h
index a94b33d4da..30a45943fa 100644
--- a/lib/modechange.h
+++ b/lib/modechange.h
@@ -1,7 +1,7 @@
/* modechange.h -- definitions for file mode manipulation
- Copyright (C) 1989, 1990, 1997, 2003, 2004, 2005 Free Software
- Foundation, Inc.
+ Copyright (C) 1989, 1990, 1997, 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,10 +22,12 @@
#if ! defined MODECHANGE_H_
# define MODECHANGE_H_
+# include <stdbool.h>
# include <sys/types.h>
struct mode_change *mode_compile (const char *);
struct mode_change *mode_create_from_ref (const char *);
-mode_t mode_adjust (mode_t, struct mode_change const *, mode_t);
+mode_t mode_adjust (mode_t, bool, mode_t, struct mode_change const *,
+ mode_t *);
#endif
diff --git a/lib/userspec.c b/lib/userspec.c
index fbdc2f9a13..fd2c941f81 100644
--- a/lib/userspec.c
+++ b/lib/userspec.c
@@ -1,5 +1,5 @@
/* userspec.c -- Parse a user and group string.
- Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2005 Free Software
+ Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2006 Free Software
Foundation, Inc.
This program is free software; you can redistribute it and/or modify
@@ -109,8 +109,7 @@ parse_with_separator (char const *spec, char const *separator,
{
static const char *E_invalid_user = N_("invalid user");
static const char *E_invalid_group = N_("invalid group");
- static const char *E_bad_spec =
- N_("cannot get the login group of a numeric UID");
+ static const char *E_bad_spec = N_("invalid spec");
const char *error_msg;
struct passwd *pwd;
@@ -164,7 +163,11 @@ parse_with_separator (char const *spec, char const *separator,
{
bool use_login_group = (separator != NULL && g == NULL);
if (use_login_group)
- error_msg = E_bad_spec;
+ {
+ /* If there is no group,
+ then there may not be a trailing ":", either. */
+ error_msg = E_bad_spec;
+ }
else
{
unsigned long int tmp;
diff --git a/m4/ChangeLog b/m4/ChangeLog
index 7244067d5c..f890da73e2 100644
--- a/m4/ChangeLog
+++ b/m4/ChangeLog
@@ -1,3 +1,10 @@
+2006-07-16 Paul Eggert <eggert@cs.ucla.edu>
+
+ * mkancesdirs.m4: New file.
+ * mkdir-p.m4 (gl_MKDIR_PARENTS): Mention dirchownmod.c, dirchownmod.h.
+ Don't require AC_FUNC_ALLOCA, gl_AFS, gl_CHDIR_SAFER; no longer needed.
+ Require gl_FUNC_LCHOWN, since dirchownmod.c needs it.
+
2006-07-11 Eric Blake <ebb9@byu.net>
* absolute-header.m4: Fix comments to match recent change.
diff --git a/m4/mkancesdirs.m4 b/m4/mkancesdirs.m4
new file mode 100644
index 0000000000..cd44d4874c
--- /dev/null
+++ b/m4/mkancesdirs.m4
@@ -0,0 +1,11 @@
+# Make a file's ancestor directories.
+dnl Copyright (C) 2006 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_MKANCESDIRS],
+[
+ AC_LIBSOURCES([mkancesdirs.c, mkancesdirs.h])
+ AC_LIBOBJ([mkancesdirs])
+])
diff --git a/m4/mkdir-p.m4 b/m4/mkdir-p.m4
index 692ed0406d..b0ceb18211 100644
--- a/m4/mkdir-p.m4
+++ b/m4/mkdir-p.m4
@@ -1,4 +1,4 @@
-# mkdir-p.m4 serial 10
+# mkdir-p.m4 serial 11
dnl Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -6,12 +6,11 @@ dnl with or without modifications, as long as this notice is preserved.
AC_DEFUN([gl_MKDIR_PARENTS],
[
- AC_LIBSOURCES([mkdir-p.c, mkdir-p.h])
+ AC_LIBSOURCES([dirchownmod.c, dirchownmod.h, mkdir-p.c, mkdir-p.h])
+ AC_LIBOBJ([dirchownmod])
AC_LIBOBJ([mkdir-p])
- dnl Prerequisites of lib/mkdir-p.c.
- AC_REQUIRE([AC_FUNC_ALLOCA])
- AC_REQUIRE([gl_AFS])
+ dnl Prerequisites of lib/dirchownmod.c.
AC_REQUIRE([gl_FUNC_LCHMOD])
- AC_REQUIRE([gl_CHDIR_SAFER])
+ AC_REQUIRE([gl_FUNC_LCHOWN])
])
diff --git a/modules/mkancesdirs b/modules/mkancesdirs
new file mode 100644
index 0000000000..73d9b5dfdc
--- /dev/null
+++ b/modules/mkancesdirs
@@ -0,0 +1,25 @@
+Description:
+Ensure the existence of the ancestor directories of a file.
+
+Files:
+lib/mkancesdirs.c
+lib/mkancesdirs.h
+m4/mkancesdirs.m4
+
+Depends-on:
+dirname
+stat-macros
+
+configure.ac:
+gl_MKANCESDIRS
+
+Makefile.am:
+
+Include:
+"mkancesdirs.h"
+
+License:
+GPL
+
+Maintainer:
+Paul Eggert, Jim Meyering
diff --git a/modules/mkdir-p b/modules/mkdir-p
index 0ef6f6044a..28cabda217 100644
--- a/modules/mkdir-p
+++ b/modules/mkdir-p
@@ -2,24 +2,19 @@ Description:
Ensure that a directory and its parents exist.
Files:
-lib/chdir-safer.c
-lib/chdir-safer.h
+lib/dirchownmod.c
+lib/dirchownmod.h
lib/lchmod.h
lib/mkdir-p.c
lib/mkdir-p.h
-lib/same-inode.h
-m4/afs.m4
-m4/chdir-safer.m4
m4/lchmod.m4
m4/mkdir-p.m4
Depends-on:
-alloca
-chown
-gettext-h
-save-cwd
-dirname
error
+gettext-h
+lchown
+mkancesdirs
quote
stat-macros
stdbool
@@ -36,4 +31,4 @@ License:
GPL
Maintainer:
-Jim Meyering
+Paul Eggert, Jim Meyering