changeset 6969:bbdf9204a185

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.
author Paul Eggert <eggert@cs.ucla.edu>
date Mon, 17 Jul 2006 06:06:48 +0000
parents 1bb5d63ec306
children 31455307b983
files ChangeLog MODULES.html.sh lib/ChangeLog lib/dirchownmod.c lib/dirchownmod.h lib/mkancesdirs.c lib/mkancesdirs.h lib/mkdir-p.c lib/mkdir-p.h lib/modechange.c lib/modechange.h lib/userspec.c m4/ChangeLog m4/mkancesdirs.m4 m4/mkdir-p.m4 modules/mkancesdirs modules/mkdir-p
diffstat 17 files changed, 558 insertions(+), 368 deletions(-) [+]
line wrap: on
line diff
--- 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.
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -1869,6 +1869,7 @@
   func_module fts-lgpl
   func_module isdir
   func_module lchown
+  func_module mkancestors
   func_module mkdir-p
   func_module modechange
   func_module mountlist
--- 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/.
new file mode 100644
--- /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;
+}
new file mode 100644
--- /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);
new file mode 100644
--- /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;
+}
new file mode 100644
--- /dev/null
+++ b/lib/mkancesdirs.h
@@ -0,0 +1,1 @@
+int mkancesdirs (char *, int (*) (char const *, void *), void *);
--- 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 <errno.h>
 #include <sys/stat.h>
-#include <unistd.h>
-
-#include <stdlib.h>
-#include <errno.h>
-#include <string.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.
 
-   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.
+   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 *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.
+   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.
 
-   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.  */
+   However, if PRESERVE_EXISTING is true and DIR already exists,
+   do not attempt to set DIR's ownership and file mode bits.
+
+   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.  */
 
 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 (!made_dir && make_ancestor && errno == ENOENT)
     {
-      if (! S_ISDIR (stats.st_mode))
+      if (mkancesdirs (dir, make_ancestor, options) == 0)
+	made_dir = (mkdir (dir, mode) == 0);
+      else
 	{
-	  error (0, 0, _("%s exists but is not a directory"), quote (arg));
-	  return false;
+	  /* mkancestdirs updated DIR for a better-looking
+	     diagnostic, so don't try to stat DIR below.  */
+	  make_ancestor = NULL;
 	}
+    }
 
-      if (!preserve_existing)
-	fixup_permissions_dir = arg;
-    }
-  else if (errno != ENOENT || !*arg)
+  if (made_dir)
     {
-      error (0, errno, "%s", quote (arg));
-      return false;
+      announce (dir, options);
+      preserve_existing =
+	(owner == (uid_t) -1 && group == (gid_t) -1
+	 && ! ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX)));
     }
   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;
-	}
-      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)
+      int mkdir_errno = errno;
+      struct stat st;
+      if (! (make_ancestor && mkdir_errno != ENOENT
+	     && stat (dir, &st) == 0 && S_ISDIR (st.st_mode)))
 	{
-	  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;
-	    }
+	  error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
+	  return false;
 	}
     }
 
-  if (fixup_permissions_dir)
+  if (! preserve_existing
+      && (dirchownmod (dir, (made_dir ? mode : (mode_t) -1),
+		       owner, group, mode, mode_bits)
+	  != 0))
     {
-      /* 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;
-	}
+      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;
     }
 
-  if (do_chdir)
-    {
-      if (restore_cwd (&cwd) != 0)
-	{
-	  *cwd_errno = errno;
-	  cwd_problem = true;
-	}
-      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)
-    {
-      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;
-	}
-    }
-
-  return retval;
+  return true;
 }
--- 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);
--- 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 @@
   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 @@
 
   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 @@
       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 @@
 	  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 @@
 
   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 @@
 	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 @@
 	  /* 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;
 }
--- 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
--- 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 @@
 {
   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 @@
 	{
 	  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;
--- 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.
new file mode 100644
--- /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])
+])
--- 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 @@
 
 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])
 ])
new file mode 100644
--- /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
--- a/modules/mkdir-p
+++ b/modules/mkdir-p
@@ -2,24 +2,19 @@
 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
+error
 gettext-h
-save-cwd
-dirname
-error
+lchown
+mkancesdirs
 quote
 stat-macros
 stdbool
@@ -36,4 +31,4 @@
 GPL
 
 Maintainer:
-Jim Meyering
+Paul Eggert, Jim Meyering