bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

patch for chmod, mkdir, install races, bugs, incompatibilities


From: Paul Eggert
Subject: patch for chmod, mkdir, install races, bugs, incompatibilities
Date: Sun, 16 Jul 2006 20:47:41 -0700
User-agent: Gnus/5.1008 (Gnus v5.10.8) Emacs/21.4 (gnu/linux)

I installed the following long-promised patch to fix some race
conditions, bugs, and incompatibilities in mkdir, install, and chmod.
It's kind of a hairy area and fixing it took much longer than I
expected.  The new code tries to be simpler; at times it's more
efficient, and at times less.  I couldn't see any good way to untangle
this stuff into separate patches.

I was going to also tackle similar problems with make_dir_parents in
cp.c but that proved to be a bit more than I could chew off in one
patch; maybe later.  Among other things, for sanity's sake cp would
have to operate with umask equal to zero, as install and mkdir already
do.

It's too bad there isn't a system call that will run mkdir and give
you a file descriptor to the resulting directory; that would close
some other races.

2006-07-16  Paul Eggert  <address@hidden>

        * NEWS: chmod, install, and mkdir now leave setgid and setuid bits
        of directories alone unless you specify them explicitly.
        install and mkdir now implement X correctly.
        install now creates parent directories with mode 755, without
        changing their owner or group.

        * lib/dirchownmod.c, lib/dirchownmod.h: New files.
        * 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.

        * m4/mkancesdirs.m4: New file.
        * m4/afs.m4: Remove; no longer needed.
        * m4/jm-macros.m4 (gl_MACROS): Remove gl_AFS.
        * m4/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.
        * m4/prereq.m4 (gl_PREREQ): Require gl_MKANCESDIRS.

        * src/chmod.c (process_file): Adjust to mode_adjust API change.
        * src/install.c: Include mkancesdirs.h.
        (announce_mkdir, make_ancestor): New functions.
        (DEFAULT_MODE): New macro, specifying initial value of 'mode'.
        (mode): Use it.
        (dir_mode, dir_mode_bits): New vars.
        (main): Set dir modes separately from nondir, so that the X
        op of -m works correctly.
        (main): Remove cwd_errno cruft, since make_dir_parents no longer
        affects cwd.  Adjust to new make_dir_parents API.
        (install_file_in_file_parents): 2nd arg is now char *, not char
        const *.  Use mkancesdirs instead of rolling our own code.
        (change_attributes): Don't worry about AFS, since that kludge
        should not be needed any more.
        * src/mkdir.c (struct mkdir_options): New struct.
        (announce_mkdir, make_ancestor): New functions.
        (main): Use them.  Adjust to mode_adjust API change.  Stick with
        umask 0.  Use make_dir_parents for all the work.
        * src/mkfifo.c (main): Adjust to new mode_adjust API.
        * src/mknod.c (main): Likewise.

        * tests/chmod/setgid: Do the setgid test instead of bailing.
        * tests/mkdir/p-3: Remove re_protect case that no longer applies.
        GNU chmod now behaves like other versions of chmod.
        * tests/mkdir/perm: Add a test for the X bug.

Index: NEWS
===================================================================
RCS file: /fetish/cu/NEWS,v
retrieving revision 1.391
diff -p -u -r1.391 NEWS
--- NEWS        7 Jul 2006 06:38:36 -0000       1.391
+++ NEWS        17 Jul 2006 02:58:56 -0000
@@ -26,6 +26,14 @@ GNU coreutils NEWS                      
   basename and dirname now treat // as different from / on platforms
   where the two are distinct.
 
+  chmod, install, and mkdir now leave a directory's set-user-ID and
+  set-group-ID bits alone unless you explicitly request otherwise.
+  This is for compatibility with BSD and other systems.  For example,
+  `chmod 755 DIR' and `chmod u=rwx,go=rx DIR' now preserve DIR's
+  set-user-ID and set-group-ID bits instead of clearing them.  If
+  you want to clear the bits you can mention them explicitly, e.g.,
+  `chmod 0755 DIR' and `chmod a-s,u=rwx,go=rx DIR'.
+
   `cp --link --no-dereference' now works also on systems where the
   link system call cannot create a hard link to a symbolic link.
   This change has no effect on systems with a Linux-based kernel.
@@ -51,6 +59,14 @@ GNU coreutils NEWS                      
   used only for internal errors (such as integer overflow, which expr
   now checks for).
 
+  install and mkdir now implement the X permission symbol correctly,
+  e.g., `mkdir -m a+X dir'; previously the X was ignored.
+
+  install now creates parent directories with mode u=rwx,go=rx (755)
+  instead of using the mode specified by the -m option; and it does
+  not change the owner or group of parent directories.  This is for
+  compatibility with BSD and closes some race conditions.
+
   ln now uses different (and we hope clearer) diagnostics when it fails.
   ln -v now acts more like FreeBSD, so it generates output only when
   successful and the output is easier to parse.
Index: doc/coreutils.texi
===================================================================
RCS file: /fetish/cu/doc/coreutils.texi,v
retrieving revision 1.338
diff -p -u -r1.338 coreutils.texi
--- doc/coreutils.texi  8 Jul 2006 06:03:50 -0000       1.338
+++ doc/coreutils.texi  17 Jul 2006 02:58:58 -0000
@@ -257,7 +257,7 @@ Operating on sorted files
 * Charset selection in ptx::    Underlying character set considerations.
 * Input processing in ptx::     Input fields, contexts, and keyword selection.
 * Output formatting in ptx::    Types of output format, and sizing the fields.
-* Compatibility in ptx::        The GNU extensions to @command{ptx}
+* Compatibility in ptx::        The @acronym{GNU} extensions to @command{ptx}
 
 Operating on fields within a line
 
@@ -434,6 +434,7 @@ File permissions
 * Mode Structure::               Structure of File Permissions
 * Symbolic Modes::               Mnemonic permissions representation
 * Numeric Modes::                Permissions as octal numbers
+* Directory Setuid and Setgid::  Set-user-ID and set-group-ID on directories.
 
 Date input formats
 
@@ -895,14 +896,14 @@ zettabyte: @math{10^21 = 1,000,000,000,0
 @item Z
 @itemx ZiB
 @math{2^70 = 1,180,591,620,717,411,303,424}.
-(@samp{Zi} is a GNU extension to IEC 60027-2.)
+(@samp{Zi} is a @acronym{GNU} extension to IEC 60027-2.)
 @item YB
 @cindex yottabyte, definition of
 yottabyte: @math{10^24 = 1,000,000,000,000,000,000,000,000}.
 @item Y
 @itemx YiB
 @math{2^80 = 1,208,925,819,614,629,174,706,176}.
-(@samp{Yi} is a GNU extension to IEC 60027-2.)
+(@samp{Yi} is a @acronym{GNU} extension to IEC 60027-2.)
 @end table
 
 @opindex -k
@@ -4726,7 +4727,7 @@ ranges of selected bytes.
 
 @item --complement
 @opindex --complement
-This option is a GNU extension.
+This option is a @acronym{GNU} extension.
 Select for printing the complement of the bytes, characters or fields
 selected with the @option{-b}, @option{-c} or @option{-f} options.
 In other words, do @emph{not} print the bytes, characters or fields
@@ -4840,7 +4841,7 @@ sort a file on its default join field, b
 locale, join field, separator, or comparison options, then you should
 do so consistently between @command{join} and @command{sort}.
 
-As a GNU extension, if the input has no unpairable lines the
+As a @acronym{GNU} extension, if the input has no unpairable lines the
 sort order can be any order that considers two fields to be equal if and
 only if the sort comparison described above considers them to be equal.
 For example:
@@ -5861,12 +5862,12 @@ third character of each set of permissio
 
 @table @samp
 @item s
-If the setuid or setgid bit and the corresponding executable bit
+If the set-user-ID or set-group-ID bit and the corresponding executable bit
 are both set.
 
 @item S
-If the setuid or setgid bit is set but the corresponding executable bit
-is not set.
+If the set-user-ID or set-group-ID bit is set but the corresponding
+executable bit is not set.
 
 @item t
 If the sticky bit and the other-executable bit are both set.
@@ -6731,7 +6732,8 @@ of one or more of the following strings:
 Preserve the file mode bits and access control lists.
 @itemx ownership
 Preserve the owner and group.  On most modern systems,
-only the super-user may change the owner of a file, and regular users
+only users with appropriate privileges may change the owner of a file,
+and ordinary users
 may preserve the group ownership of a file only if they happen to be
 a member of the desired group.
 @itemx timestamps
@@ -7252,7 +7254,9 @@ directory, using the @var{source}s' name
 @item
 If the @option{--directory} (@option{-d}) option is given,
 @command{install} creates each @var{directory} and any missing parent
-directories.
+directories.  Parent directories are created with mode
address@hidden,go=rx} (755), regardless of the @option{-m} option or the
+current umask.
 @end itemize
 
 @cindex Makefiles, installing programs in
@@ -7278,11 +7282,9 @@ Ignored; for compatibility with old Unix
 @cindex directories, creating with given attributes
 @cindex parent directories, creating missing
 @cindex leading directories, creating missing
-Create each given directory and any missing parent directories, setting
-the owner, group and mode as given on the command line or to the
-defaults.  It also gives any parent directories it creates those
-attributes.  (This is different from the SunOS 4.x @command{install}, which
-gives directories that it creates the default attributes.)
+Create any missing parent directories, giving them the default
+attributes.  Then create each given directory, setting their owner,
+group and mode as given on the command line or to the defaults.
 
 @item -g @var{group}
 @itemx address@hidden
@@ -7302,8 +7304,9 @@ Set the file mode bits for the installed
 which can be either an octal number, or a symbolic mode as in
 @command{chmod}, with @samp{a=} (no access allowed to anyone) as the
 point of departure (@pxref{File permissions}).
-The default mode is @samp{u=rwx,go=rx}---read, write,
-and execute for the owner, and read and execute for group and other.
+The default mode is @samp{u=rwx,go=rx,a-s} (0755)---read, write, and
+execute for the owner, read and execute for group and other, and with
+set-user-ID and set-group-ID disabled.
 
 @item -o @var{owner}
 @itemx address@hidden
@@ -7969,7 +7972,8 @@ The program accepts the following option
 @opindex -F
 @opindex --directory
 @cindex hard links to directories
-Allow the super-user to attempt to make hard links to directories.
+Allow users with appropriate privileges to attempt to make hard links
+to directories.
 However, note that this will probably fail due to
 system restrictions, even for the super-user.
 
@@ -8072,14 +8076,9 @@ ln -s ../adir/afile yetanotherfile
 mkdir address@hidden@dots{} @address@hidden
 @end example
 
-If a @var{name} is an existing file but not a directory, @command{mkdir} prints
-a warning message on stderr and will exit with a status of 1 after
-processing any remaining @var{name}s.  The same is done when a @var{name} is an
-existing directory and the -p option is not given.  If a @var{name} is an
-existing directory and the -p option is given, @command{mkdir} will ignore it.
-That is, @command{mkdir} will not print a warning, raise an error, or change
-the mode of the directory (even if the -m option is given), and will
-move on to processing any remaining @var{name}s.
address@hidden creates each directory @var{name} in the order given.
+It reports an error if @var{name} already exists, unless the
address@hidden option is given and @var{name} is a directory.
 
 The program accepts the following options.  Also see @ref{Common options}.
 
@@ -8090,10 +8089,17 @@ The program accepts the following option
 @opindex -m
 @opindex --mode
 @cindex modes of created directories, setting
-Set the mode of created directories to @var{mode}, which is symbolic as
+Set the file permission bits of created directories to @var{mode},
+which uses the same syntax as
 in @command{chmod} and uses @samp{a=rwx} (read, write and execute allowed for
 everyone) for the point of the departure.  @xref{File permissions}.
 
+Normally the directory has the desired file mode bits at the moment it
+is created.  As a @acronym{GNU} extension, @var{mode} may also mention
+special mode bits, but in this case there may be a temporary window
+during which the directory exists but its special mode bits are
+incorrect.
+
 @item -p
 @itemx --parents
 @opindex -p
@@ -8101,7 +8107,8 @@ everyone) for the point of the departure
 @cindex parent directories, creating
 Make any missing parent directories for each argument.  The file permission
 bits of parent directories are set to the umask modified by @samp{u+wx}.
-Ignore arguments corresponding to existing directories.
+Ignore arguments corresponding to existing directories, and do not
+change their file mode bits.
 
 @item -v
 @item --verbose
@@ -8484,7 +8491,8 @@ set-group-ID permission bits.  This beha
 functionality of the underlying @code{chown} system call, which may
 make system-dependent file mode modifications outside the control of
 the @command{chown} command.  For example, the @command{chown} command
-might not affect those bits when operated as the superuser, or if the
+might not affect those bits when invoked by a user with appropriate
+privileges, or when the
 bits signify some function other than executable permission (e.g.,
 mandatory locking).
 When in doubt, check the underlying system behavior.
@@ -8770,6 +8778,15 @@ line, @command{chmod} changes the permis
 In contrast, @command{chmod} ignores symbolic links encountered during
 recursive directory traversals.
 
+A successful use of @command{chmod} clears the set-group-ID bit of a
+regular file if the file's group ID does not match the user's
+effective group ID or one of the user's supplementary group IDs,
+unless the user has appropriate privileges.  Additional restrictions
+may cause the set-user-ID and set-group-ID bits of @var{mode} or
address@hidden to be ignored.  This behavior depends on the policy and
+functionality of the underlying @code{chmod} system call.  When in
+doubt, check the underlying system behavior.
+
 If used, @var{mode} specifies the new file mode bits.
 For details, see the section on @ref{File permissions}.
 If you really want @var{mode} to have a leading @samp{-}, you should
@@ -9582,7 +9599,7 @@ The valid format sequences for files are
 The valid format sequences for file systems are:
 
 @itemize @bullet
address@hidden %a - Free blocks available to non-superuser
address@hidden %a - Free blocks available to non-super-user
 @item %b - Total data blocks in file system
 @item %c - Total file nodes in file system
 @item %d - Free file nodes in file system
@@ -10446,7 +10463,7 @@ or an operator like @code{/}.
 This makes it possible to test @code{expr length + "$x"} or
 @code{expr + "$x" : '.*/\(.\)'} and have it do the right thing even if
 the value of @var{$x} happens to be (for example) @code{/} or @code{index}.
-This operator is a GNU extension.  Portable shell scripts should use
+This operator is a @acronym{GNU} extension.  Portable shell scripts should use
 @address@hidden" $token"} : @w{' \(.*\)'}} instead of @code{+ "$token"}.
 
 @end table
@@ -13181,7 +13198,7 @@ read its login startup file(s).
 Do not change the environment variables @env{HOME}, @env{USER},
 @env{LOGNAME}, or @env{SHELL}.  Run the shell given in the environment
 variable @env{SHELL} instead of the shell from @var{user}'s passwd
-entry, unless the user running @command{su} is not the superuser and
+entry, unless the user running @command{su} is not the super-user and
 @var{user}'s shell is restricted.  A @dfn{restricted shell} is one that
 is not listed in the file @file{/etc/shells}, or in a compiled-in list
 if that file does not exist.  Parts of what this option does can be
@@ -13192,7 +13209,7 @@ overridden by @option{--login} and @opti
 @opindex -s
 @opindex --shell
 Run @var{shell} instead of the shell from @var{user}'s passwd entry,
-unless the user running @command{su} is not the superuser and @var{user}'s
+unless the user running @command{su} is not the super-user and @var{user}'s
 shell is restricted (see @option{-m} just above).
 
 @end table
Index: doc/perm.texi
===================================================================
RCS file: /fetish/cu/doc/perm.texi,v
retrieving revision 1.15
diff -p -u -r1.15 perm.texi
--- doc/perm.texi       2 Jan 2006 07:42:35 -0000       1.15
+++ doc/perm.texi       17 Jul 2006 02:58:58 -0000
@@ -6,6 +6,7 @@ symbolic form or as an octal number.
 * Mode Structure::              Structure of file permissions.
 * Symbolic Modes::              Mnemonic permissions representation.
 * Numeric Modes::               Permissions as octal numbers.
+* Directory Setuid and Setgid:: Set-user-ID and set-group-ID on directories.
 @end menu
 
 @node Mode Structure
@@ -55,25 +56,32 @@ can change the owner and group of a file
 
 In addition to the three sets of three permissions listed above, the
 file mode bits have three special components, which affect only
-executable files (programs) and, on some systems, directories:
+executable files (programs) and, on most systems, directories:
 
 @enumerate
 @item
address@hidden set-user-ID
 @cindex setuid
 Set the process's effective user ID to that of the file upon execution
-(called the @dfn{setuid bit}).  No effect on directories.
+(called the @dfn{set-user-ID bit}, or sometimes the @dfn{setgid bit}).
+For directories on a few systems, give files created in the directory
+the same owner as the directory, no matter who creates them, and set
+the set-user-ID bit of newly-created subdirectories.
 @item
address@hidden set-group-ID
 @cindex setgid
 Set the process's effective group ID to that of the file upon execution
-(called the @dfn{setgid bit}).  For directories on some systems, put
-files created in the directory into the same group as the directory, no
-matter what group the user who creates them is in.
+(called the @dfn{set-group-ID bit}, or sometimes the @dfn{setgid bit}).
+For directories on most systems, give files created in the directory
+the same group as the directory, no matter what group the user who
+creates them is in, and set the set-group-ID bit of newly-created
+subdirectories.
 @item
 @cindex sticky
 @cindex swap space, saving text image in
 @cindex text image, saving in swap space
 @cindex restricted deletion flag
-prevent users from removing or renaming a file in a directory
+Prevent users from removing or renaming a file in a directory
 unless they own the file or the directory; this is called the
 @dfn{restricted deletion flag} for the directory.
 For regular files on some systems, save the program's text image on the
@@ -287,16 +295,16 @@ you can change its special mode bits.  @
 summary of these special mode bits.
 
 To change the file mode bits to set the user ID on execution, use
address@hidden in the @var{users} part of the symbolic mode and
address@hidden instead of the @var{permissions} part.
address@hidden or @samp{a} in the @var{users} part of the symbolic mode and
address@hidden in the @var{permissions} part.
 
 To change the file mode bits to set the group ID on execution, use
address@hidden in the @var{users} part of the symbolic mode and
address@hidden instead of the @var{permissions} part.
address@hidden or @samp{a} in the @var{users} part of the symbolic mode and
address@hidden in the @var{permissions} part.
 
 To change the file mode bits to set the restricted deletion flag or sticky bit,
 omit the @var{users} part of the symbolic mode (or use @samp{a}) and use
address@hidden instead of the @var{permissions} part.
address@hidden in the @var{permissions} part.
 
 For example, to set the set-user-ID mode bit of a program,
 you can use the mode:
@@ -309,7 +317,7 @@ To remove both set-user-ID and set-group
 it, you can use the mode:
 
 @example
-ug-s
+a-s
 @end example
 
 To set the restricted deletion flag or sticky bit, you can use
@@ -323,8 +331,8 @@ The combination @samp{o+s} has no effect
 the combinations @samp{u+t} and @samp{g+t} have no effect, and
 @samp{o+t} acts like plain @samp{+t}.
 
-The @samp{=} operator is not very useful with special mode bits; for
-example, the mode:
+The @samp{=} operator is not very useful with special mode bits.
+For example, the mode:
 
 @example
 o=t
@@ -335,6 +343,9 @@ does set the restricted deletion flag or
 removes all read, write, and execute permissions that users not in the
 file's group might have had for it.
 
address@hidden Setuid and Setgid}, for additional rules concerning
+set-user-ID and set-group-ID bits and directories.
+
 @node Conditional Executability
 @subsection Conditional Executability
 
@@ -466,11 +477,13 @@ As an
 alternative to giving a symbolic mode, you can give an octal (base 8)
 number that represents the new mode.
 This number is always interpreted in octal; you do not have to add a
-leading 0, as you do in C.  Mode 0055 is the same as mode 55.
+leading @samp{0}, as you do in C.
 
 A numeric mode is usually shorter than the corresponding symbolic
-mode, but it is limited in that it cannot take into account the
+mode, but it is limited in that normally it cannot take into account the
 previous file mode bits; it can only set them absolutely.
+(As discussed in the next section, the set-user-ID and set-group-ID
+bits of directories are an exception to this general limitation.)
 
 The permissions granted to the user,
 to other users in the file's group,
@@ -506,6 +519,51 @@ Mode      Mode Bit
 @end example
 
 For example, numeric mode 4755 corresponds to symbolic mode
address@hidden,go=rx}, and numeric mode 664 corresponds to symbolic mode
address@hidden,go=rx,g-s}, and numeric mode 664 corresponds to symbolic mode
 @samp{ug=rw,o=r}.  Numeric mode 0 corresponds to symbolic mode
 @samp{a=}.
+
address@hidden Directory Setuid and Setgid
address@hidden Directories and the Set-User-ID and Set-Group-ID Bits
+
+On most systems, if a directory's set-group-ID bit is set, newly
+created subfiles inherit the same group as the directory, and newly
+created subdirectories inherit the set-group-ID bit of the parent
+directory.  On a few systems, a directory's set-user-ID bit has a
+similar effect on the ownership of new subfiles and the set-user-ID
+bits of new subdirectories.  These mechanisms let users share files
+more easily, by lessening the need to use @command{chmod} or
address@hidden to share new files.
+
+These convenience mechanisms rely on the set-group-ID and set-user-ID
+bits of directories.  If commands like @command{chmod} and
address@hidden routinely cleared these bits on directories, the
+mechanisms would be less convenient and it would be harder to share
+files.  Therefore, a command like @command{chmod} does not affect the
+set-user-ID or set-group-ID bits of a directory unless the user
+specifically mentions them.  For example, on systems that support
+set-group-ID inheritance:
+
address@hidden
+# These commands leave the set-user-ID and
+# set-group-ID bits of the subdirectories alone,
+# so that they retain their default values.
+mkdir a b
+chmod 755 a
+chmod u=rwx,go=rx b
+mkdir -m 755 c
+mkdir -m u=rwx,go=rx d
address@hidden example
+
+If you want to clear these bits, you must mention them explicitly in
+the symbolic or numeric modes, e.g.:
+
address@hidden
+# These commands clear the set-user-ID
+# and set-group-ID bits of the subdirectories.
+mkdir a b
+chmod 0755 a
+chmod a-s,u=rwx,go=rx b
+mkdir -m 0755 c
+mkdir -m a-s,u=rwx,go=rx d
address@hidden example
Index: lib/mkdir-p.c
===================================================================
RCS file: /fetish/cu/lib/mkdir-p.c,v
retrieving revision 1.19
diff -p -u -r1.19 mkdir-p.c
--- lib/mkdir-p.c       2 Jan 2006 06:33:12 -0000       1.19
+++ lib/mkdir-p.c       17 Jul 2006 02:58:58 -0000
@@ -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 <address@hidden> 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.
 
-   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 *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.
-
-   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.  */
+   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.
+
+   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;
-
-  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;
-       }
+  bool made_dir = (mkdir (dir, mode) == 0);
 
-      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)
-    {
-      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;
-       }
+  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 retval;
+  return true;
 }
Index: lib/mkdir-p.h
===================================================================
RCS file: /fetish/cu/lib/mkdir-p.h,v
retrieving revision 1.3
diff -p -u -r1.3 mkdir-p.h
--- lib/mkdir-p.h       14 Jun 2005 23:56:17 -0000      1.3
+++ lib/mkdir-p.h       17 Jul 2006 02:58:58 -0000
@@ -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 <address@hidden> 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);
Index: lib/modechange.c
===================================================================
RCS file: /fetish/cu/lib/modechange.c,v
retrieving revision 1.33
diff -p -u -r1.33 modechange.c
--- lib/modechange.c    20 Oct 2005 14:20:34 -0000      1.33
+++ lib/modechange.c    17 Jul 2006 02:58:58 -0000
@@ -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_fi
 
   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 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
        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
          /* 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;
 }
Index: lib/modechange.h
===================================================================
RCS file: /fetish/cu/lib/modechange.h,v
retrieving revision 1.17
diff -p -u -r1.17 modechange.h
--- lib/modechange.h    14 May 2005 07:58:06 -0000      1.17
+++ lib/modechange.h    17 Jul 2006 02:58:58 -0000
@@ -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
Index: m4/jm-macros.m4
===================================================================
RCS file: /fetish/cu/m4/jm-macros.m4,v
retrieving revision 1.241
diff -p -u -r1.241 jm-macros.m4
--- m4/jm-macros.m4     9 Jul 2006 16:59:35 -0000       1.241
+++ m4/jm-macros.m4     17 Jul 2006 02:58:58 -0000
@@ -1,4 +1,4 @@
-#serial 100   -*- autoconf -*-
+#serial 102   -*- autoconf -*-
 
 dnl Misc type-related macros for coreutils.
 
@@ -57,7 +57,6 @@ AC_DEFUN([gl_MACROS],
   AC_REQUIRE([AC_FUNC_LSTAT])
   AC_REQUIRE([AC_FUNC_STRERROR_R])
   AC_REQUIRE([gl_FUNC_GROUP_MEMBER])
-  AC_REQUIRE([gl_AFS])
   AC_REQUIRE([gl_AC_FUNC_LINK_FOLLOWS_SYMLINK])
   AC_REQUIRE([gl_FUNC_FPENDING])
 
Index: m4/mkdir-p.m4
===================================================================
RCS file: /fetish/cu/m4/mkdir-p.m4,v
retrieving revision 1.3
diff -p -u -r1.3 mkdir-p.m4
--- m4/mkdir-p.m4       24 Feb 2006 07:24:18 -0000      1.3
+++ m4/mkdir-p.m4       17 Jul 2006 02:58:58 -0000
@@ -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 lo
 
 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])
 ])
Index: m4/prereq.m4
===================================================================
RCS file: /fetish/cu/m4/prereq.m4,v
retrieving revision 1.126
diff -p -u -r1.126 prereq.m4
--- m4/prereq.m4        4 Jul 2006 05:37:58 -0000       1.126
+++ m4/prereq.m4        17 Jul 2006 02:58:58 -0000
@@ -1,4 +1,4 @@
-#serial 66
+#serial 67
 
 dnl We use gl_ for non Autoconf macros.
 m4_pattern_forbid([^gl_[ABCDEFGHIJKLMNOPQRSTUVXYZ]])dnl
@@ -117,6 +117,7 @@ AC_DEFUN([gl_PREREQ],
   AC_REQUIRE([gl_MBSWIDTH])
   AC_REQUIRE([gl_MD5])
   AC_REQUIRE([gl_MEMCOLL])
+  AC_REQUIRE([gl_MKANCESDIRS])
   AC_REQUIRE([gl_MKDIR_PARENTS])
   AC_REQUIRE([gl_MODECHANGE])
   AC_REQUIRE([gl_MOUNTLIST])
Index: src/chmod.c
===================================================================
RCS file: /fetish/cu/src/chmod.c,v
retrieving revision 1.116
diff -p -u -r1.116 chmod.c
--- src/chmod.c 20 May 2006 17:27:07 -0000      1.116
+++ src/chmod.c 17 Jul 2006 02:58:58 -0000
@@ -222,7 +222,8 @@ process_file (FTS *fts, FTSENT *ent)
   if (ok)
     {
       old_mode = file_stats->st_mode;
-      new_mode = mode_adjust (old_mode, change, umask_value);
+      new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
+                             change, NULL);
 
       if (! S_ISLNK (old_mode))
        {
@@ -256,7 +257,8 @@ process_file (FTS *fts, FTSENT *ent)
 
   if (chmod_succeeded & diagnose_surprises)
     {
-      mode_t naively_expected_mode = mode_adjust (old_mode, change, 0);
+      mode_t naively_expected_mode =
+       mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
       if (new_mode & ~naively_expected_mode)
        {
          char new_perms[12];
Index: src/install.c
===================================================================
RCS file: /fetish/cu/src/install.c,v
retrieving revision 1.192
diff -p -u -r1.192 install.c
--- src/install.c       26 Mar 2006 12:06:45 -0000      1.192
+++ src/install.c       17 Jul 2006 02:58:58 -0000
@@ -32,6 +32,7 @@
 #include "copy.h"
 #include "dirname.h"
 #include "filenamecat.h"
+#include "mkancesdirs.h"
 #include "mkdir-p.h"
 #include "modechange.h"
 #include "quote.h"
@@ -69,14 +70,16 @@ static bool change_timestamps (struct st
 static bool change_attributes (char const *name);
 static bool copy_file (const char *from, const char *to,
                       const struct cp_options *x);
-static bool install_file_in_file_parents (char const *from, char const *to,
-                                         struct cp_options const *x);
+static bool install_file_in_file_parents (char const *from, char *to,
+                                         struct cp_options *x);
 static bool install_file_in_dir (const char *from, const char *to_dir,
                                 const struct cp_options *x);
 static bool install_file_in_file (const char *from, const char *to,
                                  const struct cp_options *x);
 static void get_ids (void);
 static void strip (char const *name);
+static void announce_mkdir (char const *dir, void *options);
+static int make_ancestor (char const *dir, void *options);
 void usage (int status);
 
 /* The name this program was run with, for error messages. */
@@ -96,9 +99,20 @@ static char *group_name;
 /* The group ID corresponding to `group_name'. */
 static gid_t group_id;
 
-/* The permissions to which the files will be set.  The umask has
+#define DEFAULT_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
+
+/* The file mode bits to which non-directory files will be set.  The umask has
    no effect. */
-static mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+static mode_t mode = DEFAULT_MODE;
+
+/* Similar, but for directories.  */
+static mode_t dir_mode = DEFAULT_MODE;
+
+/* The file mode bits that the user cares about.  This should be a
+   superset of DIR_MODE and a subset of CHMOD_MODE_BITS.  This matters
+   for directories, since otherwise directories may keep their S_ISUID
+   or S_ISGID bits.  */
+static mode_t dir_mode_bits = CHMOD_MODE_BITS;
 
 /* If true, strip executable files after copying them. */
 static bool strip_files;
@@ -340,7 +354,8 @@ main (int argc, char **argv)
       struct mode_change *change = mode_compile (specified_mode);
       if (!change)
        error (EXIT_FAILURE, 0, _("invalid mode %s"), quote (specified_mode));
-      mode = mode_adjust (0, change, 0);
+      mode = mode_adjust (0, false, 0, change, NULL);
+      dir_mode = mode_adjust (0, true, 0, change, &dir_mode_bits);
       free (change);
     }
 
@@ -349,20 +364,10 @@ main (int argc, char **argv)
   if (dir_arg)
     {
       int i;
-      int cwd_errno = 0;
       for (i = 0; i < n_files; i++)
-       {
-         if (cwd_errno != 0 && IS_RELATIVE_FILE_NAME (file[i]))
-           {
-             error (0, cwd_errno, _("cannot return to working directory"));
-             ok = false;
-           }
-         else
-           ok &=
-             make_dir_parents (file[i], mode, mode, owner_id, group_id, false,
-                               (x.verbose ? _("creating directory %s") : NULL),
-                               &cwd_errno);
-       }
+       ok &= make_dir_parents (file[i], make_ancestor, &x,
+                               dir_mode, announce_mkdir,
+                               dir_mode_bits, owner_id, group_id, false);
     }
   else
     {
@@ -395,39 +400,16 @@ main (int argc, char **argv)
    Return true if successful.  */
 
 static bool
-install_file_in_file_parents (char const *from, char const *to,
-                             struct cp_options const *x)
+install_file_in_file_parents (char const *from, char *to,
+                             struct cp_options *x)
 {
-  char *dest_dir = dir_name (to);
-  bool ok = true;
-
-  /* Make sure that the parent of the destination is a directory.  */
-  if (! STREQ (dest_dir, "."))
+  if (mkancesdirs (to, make_ancestor, x) != 0)
     {
-      /* Someone will probably ask for a new option or three to specify
-        owner, group, and permissions for parent directories.  Remember
-        that this option is intended mainly to help installers when the
-        distribution doesn't provide proper install rules.  */
-      mode_t dir_mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
-      int cwd_errno = 0;
-      ok = make_dir_parents (dest_dir, dir_mode, dir_mode,
-                            owner_id, group_id, true,
-                            (x->verbose ? _("creating directory %s") : NULL),
-                            &cwd_errno);
-      if (ok && cwd_errno != 0
-         && (IS_RELATIVE_FILE_NAME (from) || IS_RELATIVE_FILE_NAME (to)))
-       {
-         error (0, cwd_errno, _("cannot return to current directory"));
-         ok = false;
-       }
+      error (0, errno, _("cannot create directory %s"), to);
+      return false;
     }
 
-  free (dest_dir);
-
-  if (ok)
-    ok = install_file_in_file (from, to, x);
-
-  return ok;
+  return install_file_in_file (from, to, x);
 }
 
 /* Copy file FROM onto file TO and give TO the appropriate
@@ -493,8 +475,6 @@ copy_file (const char *from, const char 
 static bool
 change_attributes (char const *name)
 {
-  bool ok = true;
-
   /* chown must precede chmod because on some systems,
      chown clears the set[ug]id bits for non-superusers,
      resulting in incorrect permissions.
@@ -505,26 +485,17 @@ change_attributes (char const *name)
      the install command is that the file is supposed to end up with
      precisely the attributes that the user specified (or defaulted).
      If the file doesn't end up with the group they asked for, they'll
-     want to know.  But AFS returns EPERM when you try to change a
-     file's group; thus the kludge.  */
-
-  if (chown (name, owner_id, group_id) != 0
-#ifdef AFS
-      && errno != EPERM
-#endif
-      )
-    {
-      error (0, errno, _("cannot change ownership of %s"), quote (name));
-      ok = false;
-    }
+     want to know.  */
 
-  if (ok && chmod (name, mode) != 0)
-    {
-      error (0, errno, _("cannot change permissions of %s"), quote (name));
-      ok = false;
-    }
+  if (! (owner_id == (uid_t) -1 && group_id == (gid_t) -1)
+      && chown (name, owner_id, group_id) != 0)
+    error (0, errno, _("cannot change ownership of %s"), quote (name));
+  else if (chmod (name, mode) != 0)
+    error (0, errno, _("cannot change permissions of %s"), quote (name));
+  else
+    return true;
 
-  return ok;
+  return false;
 }
 
 /* Set the timestamps of file TO to match those of file FROM.
@@ -621,6 +592,25 @@ get_ids (void)
     group_id = (gid_t) -1;
 }
 
+/* Report that directory DIR was made, if OPTIONS requests this.  */
+static void
+announce_mkdir (char const *dir, void *options)
+{
+  struct cp_options const *x = options;
+  if (x->verbose)
+    error (0, 0, _("creating directory %s"), quote (dir));
+}
+
+/* Make ancestor directory DIR, with options OPTIONS.  */
+static int
+make_ancestor (char const *dir, void *options)
+{
+  int r = mkdir (dir, DEFAULT_MODE);
+  if (r == 0)
+    announce_mkdir (dir, options);
+  return r;
+}
+
 void
 usage (int status)
 {
Index: src/mkdir.c
===================================================================
RCS file: /fetish/cu/src/mkdir.c,v
retrieving revision 1.103
diff -p -u -r1.103 mkdir.c
--- src/mkdir.c 2 Jan 2006 06:37:36 -0000       1.103
+++ src/mkdir.c 17 Jul 2006 02:58:58 -0000
@@ -76,17 +76,47 @@ Mandatory arguments to long options are 
   exit (status);
 }
 
+/* Options for announce_mkdir and make_ancestor.  */
+struct mkdir_options
+{
+  /* Mode for ancestor directory.  */
+  mode_t ancestor_mode;
+
+  /* If not null, format to use when reporting newly made directories.  */
+  char const *created_directory_format;
+};
+
+/* Report that directory DIR was made, if OPTIONS requests this.  */
+static void
+announce_mkdir (char const *dir, void *options)
+{
+  struct mkdir_options const *o = options;
+  if (o->created_directory_format)
+    error (0, 0, o->created_directory_format, quote (dir));
+}
+
+/* Make ancestor directory DIR, with options OPTIONS.  */
+static int
+make_ancestor (char const *dir, void *options)
+{
+  struct mkdir_options const *o = options;
+  int r = mkdir (dir, o->ancestor_mode);
+  if (r == 0)
+    announce_mkdir (dir, options);
+  return r;
+}
+
 int
 main (int argc, char **argv)
 {
-  mode_t newmode;
-  mode_t parent_mode IF_LINT (= 0);
+  mode_t mode = S_IRWXUGO;
+  mode_t mode_bits = 0;
+  int (*make_ancestor_function) (char const *, void *) = NULL;
   const char *specified_mode = NULL;
-  const char *verbose_fmt_string = NULL;
-  bool create_parents = false;
   int exit_status = EXIT_SUCCESS;
   int optc;
-  int cwd_errno = 0;
+  struct mkdir_options options;
+  options.created_directory_format = NULL;
 
   initialize_main (&argc, &argv);
   program_name = argv[0];
@@ -101,13 +131,13 @@ main (int argc, char **argv)
       switch (optc)
        {
        case 'p':
-         create_parents = true;
+         make_ancestor_function = make_ancestor;
          break;
        case 'm':
          specified_mode = optarg;
          break;
        case 'v': /* --verbose  */
-         verbose_fmt_string = _("created directory %s");
+         options.created_directory_format = _("created directory %s");
          break;
        case_GETOPT_HELP_CHAR;
        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
@@ -122,13 +152,11 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
-  newmode = S_IRWXUGO;
-
-  if (specified_mode || create_parents)
+  if (make_ancestor_function || specified_mode)
     {
       mode_t umask_value = umask (0);
 
-      parent_mode = (S_IRWXUGO & ~umask_value) | (S_IWUSR | S_IXUSR);
+      options.ancestor_mode = (S_IRWXUGO & ~umask_value) | (S_IWUSR | S_IXUSR);
 
       if (specified_mode)
        {
@@ -136,58 +164,19 @@ main (int argc, char **argv)
          if (!change)
            error (EXIT_FAILURE, 0, _("invalid mode %s"),
                   quote (specified_mode));
-         newmode = mode_adjust (S_IRWXUGO, change, umask_value);
+         mode = mode_adjust (S_IRWXUGO, true, umask_value, change,
+                             &mode_bits);
          free (change);
        }
       else
-       umask (umask_value);
+       mode &= ~umask_value;
     }
 
   for (; optind < argc; ++optind)
-    {
-      char *dir = argv[optind];
-      bool ok;
-
-      if (create_parents)
-       {
-         if (cwd_errno != 0 && IS_RELATIVE_FILE_NAME (dir))
-           {
-             error (0, cwd_errno, _("cannot return to working directory"));
-             ok = false;
-           }
-         else
-           ok = make_dir_parents (dir, newmode, parent_mode,
-                                  -1, -1, true, verbose_fmt_string,
-                                  &cwd_errno);
-       }
-      else
-       {
-         ok = (mkdir (dir, newmode) == 0);
-
-         if (! ok)
-           error (0, errno, _("cannot create directory %s"), quote (dir));
-         else if (verbose_fmt_string)
-           error (0, 0, verbose_fmt_string, quote (dir));
-
-         /* mkdir(2) is required to honor only the file permission bits.
-            In particular, it needn't do anything about `special' bits,
-            so if any were set in newmode, apply them with lchmod.  */
-
-         /* Set the permissions only if this directory has just
-            been created.  */
-
-         if (ok && specified_mode && (newmode & ~S_IRWXUGO)
-             && lchmod (dir, newmode) != 0)
-           {
-             error (0, errno, _("cannot set permissions of directory %s"),
-                    quote (dir));
-             ok = false;
-           }
-       }
-
-      if (! ok)
-       exit_status = EXIT_FAILURE;
-    }
+    if (! make_dir_parents (argv[optind], make_ancestor_function, &options,
+                           mode, announce_mkdir,
+                           mode_bits, (uid_t) -1, (gid_t) -1, true))
+      exit_status = EXIT_FAILURE;
 
   exit (exit_status);
 }
Index: src/mkfifo.c
===================================================================
RCS file: /fetish/cu/src/mkfifo.c,v
retrieving revision 1.81
diff -p -u -r1.81 mkfifo.c
--- src/mkfifo.c        2 Jan 2006 06:38:33 -0000       1.81
+++ src/mkfifo.c        17 Jul 2006 02:58:58 -0000
@@ -116,7 +116,7 @@ main (int argc, char **argv)
       struct mode_change *change = mode_compile (specified_mode);
       if (!change)
        error (EXIT_FAILURE, 0, _("invalid mode"));
-      newmode = mode_adjust (newmode, change, umask (0));
+      newmode = mode_adjust (newmode, false, umask (0), change, NULL);
       free (change);
       if (newmode & ~S_IRWXUGO)
        error (EXIT_FAILURE, 0,
Index: src/mknod.c
===================================================================
RCS file: /fetish/cu/src/mknod.c,v
retrieving revision 1.92
diff -p -u -r1.92 mknod.c
--- src/mknod.c 2 Jan 2006 06:39:06 -0000       1.92
+++ src/mknod.c 17 Jul 2006 02:58:58 -0000
@@ -121,7 +121,7 @@ main (int argc, char **argv)
       struct mode_change *change = mode_compile (specified_mode);
       if (!change)
        error (EXIT_FAILURE, 0, _("invalid mode"));
-      newmode = mode_adjust (newmode, change, umask (0));
+      newmode = mode_adjust (newmode, false, umask (0), change, NULL);
       free (change);
       if (newmode & ~S_IRWXUGO)
        error (EXIT_FAILURE, 0,
Index: tests/chmod/setgid
===================================================================
RCS file: /fetish/cu/tests/chmod/setgid,v
retrieving revision 1.8
diff -p -u -r1.8 setgid
--- tests/chmod/setgid  19 Apr 2005 07:08:08 -0000      1.8
+++ tests/chmod/setgid  17 Jul 2006 02:58:58 -0000
@@ -25,8 +25,6 @@ cd $pwd || framework_failure=1
 mkdir $tmp || framework_failure=1
 cd $tmp || framework_failure=1
 
-. $abs_srcdir/../setgid-check
-
 umask 0
 mkdir d || framework_failure=1
 
@@ -36,13 +34,11 @@ chmod g+s d 2> /dev/null ||
     # it may happen that when you create a directory, its group isn't one
     # to which you belong.  When that happens, the above chmod fails.  So
     # here, upon failure, we try to set the group, then rerun the chmod 
command.
-    group=${COREUTILS_GROUP-`(id -g || /usr/xpg4/bin/id -g) 2>/dev/null`}
-    if test "$group"; then
-      chgrp "$group" d || framework_failure=1
-      chmod g+s d || framework_failure=1
-    else
-      framework_failure=1
-    fi
+
+    id_g=`id -g` &&
+    test -n "$id_g" &&
+    chgrp "$id_g" d &&
+    chmod g+s d || framework_failure=1
   }
 
 if test $framework_failure = 1; then
@@ -54,20 +50,6 @@ fail=0
 
 chmod 755 d
 
-# To be compatible with chmod from other vendors,
-# GNU chmod must not reset a directory's setgid bit.
-# The latest POSIX draft (d5) allows either behavior.  It says:
-#
-#   For regular files, for each bit set in the octal number
-#   corresponding to the set-user-ID-on-execution or the
-#   set-group-ID-on-execution, bits shown in the following table shall
-#   be set; if these bits are not set in the octal number, they are
-#   cleared. For other file types, it is implementation-defined whether
-#   or not requests to set or clear the set-user-ID-on-execution or
-#   set-group-ID-on-execution bits are honored.
-
-# FIXME: consider changing GNU chmod to work like other versions of chmod.
-# For now, this test simply confirms the existing behavior.
-p=`ls -ld d|sed 's/ .*//'`; case $p in drwxr-xr-x);; *) fail=1;; esac
+case `ls -ld d` in drwxr-sr-x*);; *) fail=1;; esac
 
 (exit $fail); exit $fail
Index: tests/mkdir/p-3
===================================================================
RCS file: /fetish/cu/tests/mkdir/p-3,v
retrieving revision 1.5
diff -p -u -r1.5 p-3
--- tests/mkdir/p-3     30 Oct 2005 21:44:53 -0000      1.5
+++ tests/mkdir/p-3     17 Jul 2006 02:58:58 -0000
@@ -40,14 +40,4 @@ b=`ls $p/a|tr -d '\n'`
 # With coreutils-5.3.0, this would fail with $b=bu.
 test "x$b" = xb || fail=1
 
-# Ensure that the re_protect code is run on absolute names, even
-# after failure to return to the initial working directory.
-# This is actually a test of the underlying mkdir-p.c code.
-# The part in question cannot be tested via mkdir(1) because that
-# program cannot create leading directories that lack u=wx permissions,
-# so we have to test with install (aka ginstall in the build directory).
-(cd no-acce3s; chmod 0 . && ginstall -m 0 -d $p/c/b $p/y/z) || fail=1
-p=`ls -ld $p/y|sed 's/ .*//'`
-case $p in d---------);; *) fail=1;; esac
-
 exit $fail
Index: tests/mkdir/perm
===================================================================
RCS file: /fetish/cu/tests/mkdir/perm,v
retrieving revision 1.18
diff -p -u -r1.18 perm
--- tests/mkdir/perm    22 Jun 2005 18:08:13 -0000      1.18
+++ tests/mkdir/perm    17 Jul 2006 02:58:58 -0000
@@ -43,6 +43,7 @@ tests='
     160  :   empty    : drwx--xrwx : drw---xrwx :
     160  :   -m 743   : drwx--xrwx : drwxr---wx :
     027  :   -m =+x   : drwxr-x--- : d--x--x--- :
+    027  :   -m =+X   : drwxr-x--- : d--x--x--- :
     -    :   -        : last       : last       :
     '
 colon_tests=`echo $tests | sed 's/^ *//; s/ *: */:/g'`
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ lib/dirchownmod.c   2006-07-16 20:05:23.000000000 -0700
@@ -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;
+}
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ lib/dirchownmod.h   2006-07-16 20:05:23.000000000 -0700
@@ -0,0 +1,2 @@
+#include <sys/types.h>
+int dirchownmod (char const *, mode_t, uid_t, gid_t, mode_t, mode_t);
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ lib/mkancesdirs.c   2006-07-16 20:05:23.000000000 -0700
@@ -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;
+}
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ lib/mkancesdirs.h   2006-07-16 20:05:23.000000000 -0700
@@ -0,0 +1 @@
+int mkancesdirs (char *, int (*) (char const *, void *), void *);
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ m4/mkancesdirs.m4   2006-07-16 20:05:23.000000000 -0700
@@ -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])
+])




reply via email to

[Prev in Thread] Current Thread [Next in Thread]