From: Paul Eggert Date: Mon, 17 Jul 2006 06:06:48 +0000 (+0000) Subject: Import from coreutils. X-Git-Tag: cvs-readonly~2217 X-Git-Url: http://erislabs.net/gitweb/?a=commitdiff_plain;h=5f531596a92e1205a700218dedfe7a525eef21c8;p=gnulib.git 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. --- diff --git a/ChangeLog b/ChangeLog index 5194f3d16..2255265b3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2006-07-16 Paul Eggert + + * 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 * gnulib-tool: help message wording/arrangement. diff --git a/MODULES.html.sh b/MODULES.html.sh index 1fc6628e3..3c6ce5f15 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -1869,6 +1869,7 @@ func_all_modules () func_module fts-lgpl func_module isdir func_module lchown + func_module mkancestors func_module mkdir-p func_module modechange func_module mountlist diff --git a/lib/ChangeLog b/lib/ChangeLog index 977f4be08..8dca116d7 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,30 @@ +2006-07-16 Paul Eggert + + * 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 * glob.c: s/NAMLEN/_D_EXACT_NAMLEN/. diff --git a/lib/dirchownmod.c b/lib/dirchownmod.c new file mode 100644 index 000000000..50e5fe117 --- /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 +#endif + +#include "dirchownmod.h" + +#include +#include +#include +#include +#include + +#include "lchmod.h" +#include "stat-macros.h" + +#ifndef O_DIRECTORY +# define O_DIRECTORY 0 +#endif +#ifndef O_NOFOLLOW +# define O_NOFOLLOW 0 +#endif + +/* Change the ownership and mode bits of the directory DIR. + + If MKDIR_MODE is not (mode_t) -1, mkdir (DIR, MKDIR_MODE) has just + been executed successfully with umask zero, so DIR should be a + directory (not a symbolic link). + + First, set the file's owner to OWNER and group to GROUP, but leave + the owner alone if OWNER is (uid_t) -1, and similarly for GROUP. + + Then, set the file's mode bits to MODE, except preserve any of the + bits that correspond to zero bits in MODE_BITS. In other words, + MODE_BITS is a mask that specifies which of the file's mode bits + should be set or cleared. MODE should be a subset of MODE_BITS, + which in turn should be a subset of CHMOD_MODE_BITS. + + This implementation assumes the current umask is zero. + + Return 0 if successful, -1 (setting errno) otherwise. Unsuccessful + calls may do the chown but not the chmod. */ + +int +dirchownmod (char const *dir, mode_t mkdir_mode, + uid_t owner, gid_t group, + mode_t mode, mode_t mode_bits) +{ + struct stat st; + int result; + + /* Manipulate DIR via a file descriptor if possible, to avoid some races. */ + int open_flags = O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK; + int fd = open (dir, open_flags); + + /* Fail if the directory is unreadable, the directory previously + existed or was created without read permission. Otherwise, get + the file's status. */ + if (0 <= fd) + result = fstat (fd, &st); + else if (errno != EACCES + || (mkdir_mode != (mode_t) -1 && mkdir_mode & S_IRUSR)) + return fd; + else + result = stat (dir, &st); + + if (result == 0) + { + mode_t dir_mode = st.st_mode; + + /* Check whether DIR is a directory. If FD is nonnegative, this + check avoids changing the ownership and mode bits of the + wrong file in many cases. This doesn't fix all the race + conditions, but it is better than nothing. */ + if (! S_ISDIR (dir_mode)) + { + errno = ENOTDIR; + result = -1; + } + else + { + /* If at least one of the S_IXUGO bits are set, chown might + clear the S_ISUID and S_SGID bits. Keep track of any + file mode bits whose values are indeterminate due to this + issue. */ + mode_t indeterminate = 0; + + /* On some systems, chown clears S_ISUID and S_ISGID, so do + chown before chmod. On older System V hosts, ordinary + users can give their files away via chown; don't worry + about that here, since users shouldn't do that. */ + + if ((owner != (uid_t) -1 && owner != st.st_uid) + || (group != (gid_t) -1 && group != st.st_gid)) + { + result = (0 <= fd + ? fchown (fd, owner, group) + : mkdir_mode != (mode_t) -1 + ? lchown (dir, owner, group) + : chown (dir, owner, group)); + + /* Either the user cares about an indeterminate bit and + it'll be set properly by chmod below, or the user + doesn't care and it's OK to use the bit's pre-chown + value. So there's no need to re-stat DIR here. */ + + if (result == 0 && (dir_mode & S_IXUGO)) + indeterminate = dir_mode & (S_ISUID | S_ISGID); + } + + /* If the file mode bits might not be right, use chmod to + change them. Don't change bits the user doesn't care + about. */ + if (result == 0 && (((dir_mode ^ mode) | indeterminate) & mode_bits)) + { + mode_t chmod_mode = + mode | (dir_mode & CHMOD_MODE_BITS & ~mode_bits); + result = (0 <= fd + ? fchmod (fd, chmod_mode) + : mkdir_mode != (mode_t) -1 + ? lchmod (dir, chmod_mode) + : chmod (dir, chmod_mode)); + } + } + } + + if (0 <= fd) + { + if (result == 0) + result = close (fd); + else + { + int e = errno; + close (fd); + errno = e; + } + } + + return result; +} diff --git a/lib/dirchownmod.h b/lib/dirchownmod.h new file mode 100644 index 000000000..e841f4787 --- /dev/null +++ b/lib/dirchownmod.h @@ -0,0 +1,2 @@ +#include +int dirchownmod (char const *, mode_t, uid_t, gid_t, mode_t, mode_t); diff --git a/lib/mkancesdirs.c b/lib/mkancesdirs.c new file mode 100644 index 000000000..473774236 --- /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 +#endif + +#include "mkancesdirs.h" + +#include +#include + +#include "dirname.h" +#include "stat-macros.h" + +/* Return 0 if FILE is a directory, otherwise -1 (setting errno). */ + +static int +test_dir (char const *file) +{ + struct stat st; + if (stat (file, &st) == 0) + { + if (S_ISDIR (st.st_mode)) + return 0; + errno = ENOTDIR; + } + return -1; +} + +/* Ensure that the ancestor directories of FILE exist, using an + algorithm that should work even if two processes execute this + function in parallel. Temporarily modify FILE by storing '\0' + bytes into it, to access the ancestor directories. + + Create any ancestor directories that don't already exist, by + invoking MAKE_DIR (ANCESTOR, MAKE_DIR_ARG). This function should + return zero if successful, -1 (setting errno) otherwise. + + If successful, return 0 with FILE set back to its original value; + otherwise, return -1 (setting errno), storing a '\0' into *FILE so + that it names the ancestor directory that had problems. */ + +int +mkancesdirs (char *file, + int (*make_dir) (char const *, void *), + void *make_dir_arg) +{ + /* This algorithm is O(N**2) but in typical practice the fancier + O(N) algorithms are slower. */ + + /* Address of the previous directory separator that follows an + ordinary byte in a file name in the left-to-right scan, or NULL + if no such separator precedes the current location P. */ + char *sep = NULL; + + char const *prefix_end = file + FILE_SYSTEM_PREFIX_LEN (file); + char *p; + char c; + + /* Search backward through FILE using mkdir to create the + furthest-away ancestor that is needed. This loop isn't needed + for correctness, but typically ancestors already exist so this + loop speeds things up a bit. + + This loop runs a bit faster if errno initially contains an error + number corresponding to a failed access to FILE. However, things + work correctly regardless of errno's initial value. */ + + for (p = last_component (file); prefix_end < p; p--) + if (ISSLASH (*p) && ! ISSLASH (p[-1])) + { + *p = '\0'; + + if (errno == ENOENT && make_dir (file, make_dir_arg) == 0) + { + *p = '/'; + break; + } + + if (errno != ENOENT) + { + if (test_dir (file) == 0) + { + *p = '/'; + break; + } + if (errno != ENOENT) + return -1; + } + + *p = '/'; + } + + /* Scan forward through FILE, creating directories along the way. + Try mkdir before stat, so that the procedure works even when two + or more processes are executing it in parallel. */ + + while ((c = *p++)) + if (ISSLASH (*p)) + { + if (! ISSLASH (c)) + sep = p; + } + else if (ISSLASH (c) && *p && sep) + { + *sep = '\0'; + if (make_dir (file, make_dir_arg) != 0 && test_dir (file) != 0) + return -1; + *sep = '/'; + } + + + return 0; +} diff --git a/lib/mkancesdirs.h b/lib/mkancesdirs.h new file mode 100644 index 000000000..a698c9cf0 --- /dev/null +++ b/lib/mkancesdirs.h @@ -0,0 +1 @@ +int mkancesdirs (char *, int (*) (char const *, void *), void *); diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c index dd42b6ca5..697de7768 100644 --- a/lib/mkdir-p.c +++ b/lib/mkdir-p.c @@ -17,7 +17,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/* Written by David MacKenzie and Jim Meyering. */ +/* Written by Paul Eggert, David MacKenzie, and Jim Meyering. */ #ifdef HAVE_CONFIG_H # include @@ -25,333 +25,112 @@ #include "mkdir-p.h" -#include - -#include -#include -#include -#include - -#include #include -#include +#include #include "gettext.h" #define _(msgid) gettext (msgid) -#include "chdir-safer.h" -#include "dirname.h" +#include "dirchownmod.c" #include "error.h" -#include "lchmod.h" -#include "lchown.h" #include "quote.h" -#include "save-cwd.h" +#include "mkancesdirs.h" #include "stat-macros.h" -/* Ensure that the directory ARG exists. +/* Ensure that the directory DIR exists. + + If MAKE_ANCESTOR is not null, create any ancestor directories that + don't already exist, by invoking MAKE_ANCESTOR (ANCESTOR, OPTIONS). + This function should return zero if successful, -1 (setting errno) + otherwise. In this case, DIR may be modified by storing '\0' bytes + into it, to access the ancestor directories, and this modification + is retained on return if the ancestor directories could not be + created. + + Create DIR as a new directory with using mkdir with permissions + MODE. It is also OK if MAKE_ANCESTOR_DIR is not null and a + directory DIR already exists. + + Call ANNOUNCE (DIR, OPTIONS) just after successfully making DIR, + even if some of the following actions fail. + + Set DIR's owner to OWNER and group to GROUP, but leave the owner + alone if OWNER is (uid_t) -1, and similarly for GROUP. + + Set DIR's mode bits to MODE, except preserve any of the bits that + correspond to zero bits in MODE_BITS. In other words, MODE_BITS is + a mask that specifies which of DIR's mode bits should be set or + cleared. MODE should be a subset of MODE_BITS, which in turn + should be a subset of CHMOD_MODE_BITS. Changing the mode in this + way is necessary if DIR already existed or if MODE and MODE_BITS + specify non-permissions bits like S_ISUID. - Create any leading directories that don't already exist, with - permissions PARENT_MODE. - If the last element of ARG does not exist, create it as - a new directory with permissions MODE. - If OWNER and GROUP are non-negative, use them to set the UID and GID of - any created directories. - If VERBOSE_FMT_STRING is nonzero, use it as a printf format - string for printing a message after successfully making a directory, - with the name of the directory that was just made as an argument. - If PRESERVE_EXISTING is true and ARG is an existing directory, - then do not attempt to set its permissions and ownership. + However, if PRESERVE_EXISTING is true and DIR already exists, + do not attempt to set DIR's ownership and file mode bits. - Set *CWD_ERRNO to a (nonzero) error number if this - function has changed the current working directory and is unable to - restore it to its initial state. Do not change - *CWD_ERRNO otherwise. + This implementation assumes the current umask is zero. - Return true iff ARG exists as a directory with the proper ownership - and permissions when done. Note that this function returns true - even when it fails to return to the initial working directory. */ + Return true if DIR exists as a directory with the proper ownership + and file mode bits when done. Report a diagnostic and return false + on failure, storing '\0' into *DIR if an ancestor directory had + problems. */ bool -make_dir_parents (char const *arg, +make_dir_parents (char *dir, + int (*make_ancestor) (char const *, void *), + void *options, mode_t mode, - mode_t parent_mode, + void (*announce) (char const *, void *), + mode_t mode_bits, uid_t owner, gid_t group, - bool preserve_existing, - char const *verbose_fmt_string, - int *cwd_errno) + bool preserve_existing) { - struct stat stats; - bool retval = true; - bool do_chdir = false; /* Whether to chdir before each mkdir. */ - struct saved_cwd cwd; - bool cwd_problem = false; - char const *fixup_permissions_dir = NULL; - char const *full_dir = arg; + bool made_dir = (mkdir (dir, mode) == 0); - struct ptr_list - { - char *dirname_end; - struct ptr_list *next; - }; - struct ptr_list *leading_dirs = NULL; - - if (stat (arg, &stats) == 0) - { - if (! S_ISDIR (stats.st_mode)) - { - error (0, 0, _("%s exists but is not a directory"), quote (arg)); - return false; - } - - if (!preserve_existing) - fixup_permissions_dir = arg; - } - else if (errno != ENOENT || !*arg) + if (!made_dir && make_ancestor && errno == ENOENT) { - error (0, errno, "%s", quote (arg)); - return false; - } - else - { - char *slash; - mode_t tmp_mode; /* Initial perms for leading dirs. */ - bool re_protect; /* Should leading dirs be unwritable? */ - char *basename_dir; - char *dir; - - /* Temporarily relax umask in case it's overly restrictive. */ - mode_t oldmask = umask (0); - - /* Make a copy of ARG that we can scribble NULs on. */ - dir = alloca (strlen (arg) + 1); - strcpy (dir, arg); - strip_trailing_slashes (dir); - full_dir = dir; - - /* If leading directories shouldn't be readable, writable or executable, - or should have set[ug]id or sticky bits set and we are setting - their owners, we need to fix their permissions after making them. */ - if (((parent_mode & S_IRWXU) != S_IRWXU) - || ((owner != (uid_t) -1 || group != (gid_t) -1) - && (parent_mode & (S_ISUID | S_ISGID | S_ISVTX)) != 0)) - { - tmp_mode = S_IRWXU; - re_protect = true; - } + if (mkancesdirs (dir, make_ancestor, options) == 0) + made_dir = (mkdir (dir, mode) == 0); else { - tmp_mode = parent_mode; - re_protect = false; - } - - /* If we can record the current working directory, we may be able - to do the chdir optimization. */ - do_chdir = (save_cwd (&cwd) == 0); - - /* If we've saved the cwd and DIR is an absolute file name, - we must chdir to `/' in order to enable the chdir optimization. - So if chdir ("/") fails, turn off the optimization. */ - if (do_chdir && dir[0] == '/') - { - /* POSIX says "//" might be special, so chdir to "//" if the - file name starts with exactly two slashes. */ - char const *root = "//" + (dir[1] != '/' || dir[2] == '/'); - if (chdir (root) != 0) - { - free_cwd (&cwd); - do_chdir = false; - } - } - - slash = dir; - - /* Skip over leading slashes. */ - while (*slash == '/') - slash++; - - while (true) - { - bool dir_known_to_exist; - int mkdir_errno; - - /* slash points to the leftmost unprocessed component of dir. */ - basename_dir = slash; - - slash = strchr (slash, '/'); - if (slash == NULL) - break; - - /* If we're *not* doing chdir before each mkdir, then we have to refer - to the target using the full (multi-component) directory name. */ - if (!do_chdir) - basename_dir = dir; - - *slash = '\0'; - dir_known_to_exist = (mkdir (basename_dir, tmp_mode) == 0); - mkdir_errno = errno; - - if (dir_known_to_exist) - { - if (verbose_fmt_string) - error (0, 0, verbose_fmt_string, quote (dir)); - - if ((owner != (uid_t) -1 || group != (gid_t) -1) - && lchown (basename_dir, owner, group) -#if defined AFS && defined EPERM - && errno != EPERM -#endif - ) - { - error (0, errno, _("cannot change owner and/or group of %s"), - quote (dir)); - retval = false; - break; - } - - if (re_protect) - { - struct ptr_list *new = alloca (sizeof *new); - new->dirname_end = slash; - new->next = leading_dirs; - leading_dirs = new; - } - } - - /* If we were able to save the initial working directory, - then we can use chdir to change into each directory before - creating an entry in that directory. This avoids making - mkdir process O(n^2) file name components. */ - if (do_chdir) - { - /* If we know that basename_dir is a directory (because we've - just created it), then ensure that when we change to it, - that final component is not a symlink. Otherwise, we must - accept the possibility that basename_dir is a preexisting - symlink-to-directory and chdir through the symlink. */ - if ((dir_known_to_exist - ? chdir_no_follow (basename_dir) - : chdir (basename_dir)) == 0) - dir_known_to_exist = true; - else if (dir_known_to_exist) - { - error (0, errno, _("cannot chdir to directory %s"), - quote (dir)); - retval = false; - break; - } - } - else if (!dir_known_to_exist) - dir_known_to_exist = (stat (basename_dir, &stats) == 0 - && S_ISDIR (stats.st_mode)); - - if (!dir_known_to_exist) - { - error (0, mkdir_errno, _("cannot create directory %s"), - quote (dir)); - retval = false; - break; - } - - *slash++ = '/'; - - /* Avoid unnecessary calls to mkdir when given - file names containing multiple adjacent slashes. */ - while (*slash == '/') - slash++; - } - - if (!do_chdir) - basename_dir = dir; - - /* Done creating leading directories. Restore original umask. */ - umask (oldmask); - - /* We're done making leading directories. - Create the final component of the file name. */ - if (retval) - { - bool dir_known_to_exist = (mkdir (basename_dir, mode) == 0); - int mkdir_errno = errno; - struct stat sbuf; - - if ( ! dir_known_to_exist) - dir_known_to_exist = (stat (basename_dir, &sbuf) == 0 - && S_ISDIR (sbuf.st_mode)); - - if ( ! dir_known_to_exist) - { - error (0, mkdir_errno, - _("cannot create directory %s"), quote (dir)); - retval = false; - } - else - { - if (verbose_fmt_string) - error (0, 0, verbose_fmt_string, quote (dir)); - fixup_permissions_dir = basename_dir; - } + /* mkancestdirs updated DIR for a better-looking + diagnostic, so don't try to stat DIR below. */ + make_ancestor = NULL; } } - if (fixup_permissions_dir) + if (made_dir) { - /* chown must precede chmod because on some systems, - chown clears the set[ug]id bits for non-superusers, - resulting in incorrect permissions. - On System V, users can give away files with chown and then not - be able to chmod them. So don't give files away. */ - - if (owner != (uid_t) -1 || group != (gid_t) -1) - { - if (lchown (fixup_permissions_dir, owner, group) != 0 -#ifdef AFS - && errno != EPERM -#endif - ) - { - error (0, errno, _("cannot change owner and/or group of %s"), - quote (full_dir)); - retval = false; - } - } - - /* The above chown may have turned off some permission bits in MODE. - Another reason we may have to use chmod here is that mkdir(2) is - required to honor only the file permission bits. In particular, - it need not honor the `special' bits, so if MODE includes any - special bits, set them here. */ - if ((mode & ~S_IRWXUGO) && lchmod (fixup_permissions_dir, mode) != 0) - { - error (0, errno, _("cannot change permissions of %s"), - quote (full_dir)); - retval = false; - } + announce (dir, options); + preserve_existing = + (owner == (uid_t) -1 && group == (gid_t) -1 + && ! ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX))); } - - if (do_chdir) + else { - if (restore_cwd (&cwd) != 0) + int mkdir_errno = errno; + struct stat st; + if (! (make_ancestor && mkdir_errno != ENOENT + && stat (dir, &st) == 0 && S_ISDIR (st.st_mode))) { - *cwd_errno = errno; - cwd_problem = true; + error (0, mkdir_errno, _("cannot create directory %s"), quote (dir)); + return false; } - free_cwd (&cwd); } - /* If the mode for leading directories didn't include owner "wx" - privileges, reset their protections to the correct value. */ - for (; leading_dirs != NULL; leading_dirs = leading_dirs->next) + if (! preserve_existing + && (dirchownmod (dir, (made_dir ? mode : (mode_t) -1), + owner, group, mode, mode_bits) + != 0)) { - leading_dirs->dirname_end[0] = '\0'; - if ((cwd_problem && *full_dir != '/') - || lchmod (full_dir, parent_mode) != 0) - { - error (0, (cwd_problem ? 0 : errno), - _("cannot change permissions of %s"), quote (full_dir)); - retval = false; - } + error (0, errno, + _(owner == (uid_t) -1 && group == (gid_t) -1 + ? "cannot change permissions of %s" + : "cannot change owner and permissions of %s"), + quote (dir)); + return false; } - return retval; + return true; } diff --git a/lib/mkdir-p.h b/lib/mkdir-p.h index 6e0c848fd..77a11d6b2 100644 --- a/lib/mkdir-p.h +++ b/lib/mkdir-p.h @@ -17,16 +17,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/* Written by David MacKenzie and Jim Meyering. */ +/* Written by Paul Eggert, David MacKenzie, and Jim Meyering. */ #include #include -bool make_dir_parents (char const *argname, +bool make_dir_parents (char *dir, + int (*make_ancestor) (char const *, void *), + void *options, mode_t mode, - mode_t parent_mode, + void (*announce) (char const *, void *), + mode_t mode_bits, uid_t owner, gid_t group, - bool preserve_existing, - char const *verbose_fmt_string, - int *cwd_errno); + bool preserve_existing); diff --git a/lib/modechange.c b/lib/modechange.c index 52c5debca..51684a4eb 100644 --- a/lib/modechange.c +++ b/lib/modechange.c @@ -1,7 +1,7 @@ /* modechange.c -- file mode manipulation - Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005 - Free Software Foundation, Inc. + Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005, + 2006 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -51,6 +51,32 @@ #define XOTH 00001 #define ALLM 07777 /* all octal mode bits */ +/* Convert OCTAL, which uses one of the traditional octal values, to + an internal mode_t value. */ +static mode_t +octal_to_mode (unsigned int octal) +{ + /* Help the compiler optimize the usual case where mode_t uses + the traditional octal representation. */ + return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX + && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR + && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP + && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH) + ? octal + : (mode_t) ((octal & SUID ? S_ISUID : 0) + | (octal & SGID ? S_ISGID : 0) + | (octal & SVTX ? S_ISVTX : 0) + | (octal & RUSR ? S_IRUSR : 0) + | (octal & WUSR ? S_IWUSR : 0) + | (octal & XUSR ? S_IXUSR : 0) + | (octal & RGRP ? S_IRGRP : 0) + | (octal & WGRP ? S_IWGRP : 0) + | (octal & XGRP ? S_IXGRP : 0) + | (octal & ROTH ? S_IROTH : 0) + | (octal & WOTH ? S_IWOTH : 0) + | (octal & XOTH ? S_IXOTH : 0))); +} + /* Special operations flags. */ enum { @@ -78,19 +104,22 @@ struct mode_change char flag; /* Special operations flag. */ mode_t affected; /* Set for u, g, o, or a. */ mode_t value; /* Bits to add/remove. */ + mode_t mentioned; /* Bits explicitly mentioned. */ }; /* Return a mode_change array with the specified `=ddd'-style - mode change operation, where NEW_MODE is `ddd'. */ + mode change operation, where NEW_MODE is `ddd' and MENTIONED + contains the bits explicitly mentioned in the mode are MENTIONED. */ static struct mode_change * -make_node_op_equals (mode_t new_mode) +make_node_op_equals (mode_t new_mode, mode_t mentioned) { struct mode_change *p = xmalloc (2 * sizeof *p); p->op = '='; p->flag = MODE_ORDINARY_CHANGE; p->affected = CHMOD_MODE_BITS; p->value = new_mode; + p->mentioned = mentioned; p[1].flag = MODE_DONE; return p; } @@ -113,13 +142,14 @@ mode_compile (char const *mode_string) if ('0' <= *mode_string && *mode_string < '8') { - mode_t mode; - unsigned int octal_value = 0; + unsigned int octal_mode = 0; + unsigned int octal_mentioned = 0; do { - octal_value = 8 * octal_value + *mode_string++ - '0'; - if (ALLM < octal_value) + octal_mode = 8 * octal_mode + *mode_string++ - '0'; + octal_mentioned = 8 * octal_mentioned + 7; + if (ALLM < octal_mode) return NULL; } while ('0' <= *mode_string && *mode_string < '8'); @@ -127,27 +157,8 @@ mode_compile (char const *mode_string) if (*mode_string) return NULL; - /* Help the compiler optimize the usual case where mode_t uses - the traditional octal representation. */ - mode = ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX - && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR - && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP - && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH) - ? octal_value - : (mode_t) ((octal_value & SUID ? S_ISUID : 0) - | (octal_value & SGID ? S_ISGID : 0) - | (octal_value & SVTX ? S_ISVTX : 0) - | (octal_value & RUSR ? S_IRUSR : 0) - | (octal_value & WUSR ? S_IWUSR : 0) - | (octal_value & XUSR ? S_IXUSR : 0) - | (octal_value & RGRP ? S_IRGRP : 0) - | (octal_value & WGRP ? S_IWGRP : 0) - | (octal_value & XGRP ? S_IXGRP : 0) - | (octal_value & ROTH ? S_IROTH : 0) - | (octal_value & WOTH ? S_IWOTH : 0) - | (octal_value & XOTH ? S_IXOTH : 0))); - - return make_node_op_equals (mode); + return make_node_op_equals (octal_to_mode (octal_mode), + octal_to_mode (octal_mentioned & ALLM)); } /* Allocate enough space to hold the result. */ @@ -251,6 +262,7 @@ mode_compile (char const *mode_string) change->flag = flag; change->affected = affected; change->value = value; + change->mentioned = (affected ? affected & value : value); } while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-'); @@ -280,25 +292,36 @@ mode_create_from_ref (const char *ref_file) if (stat (ref_file, &ref_stats) != 0) return NULL; - return make_node_op_equals (ref_stats.st_mode); + return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS); } -/* Return file mode OLDMODE, adjusted as indicated by the list of change - operations CHANGES, which are interpreted assuming the umask is - UMASK_VALUE. If OLDMODE is a directory, the type `X' - change affects it even if no execute bits were set in OLDMODE. - The returned value has the S_IFMT bits cleared. */ +/* Return the file mode bits of OLDMODE (which is the mode of a + directory if DIR), assuming the umask is UMASK_VALUE, adjusted as + indicated by the list of change operations CHANGES. If DIR, the + type 'X' change affects the returned value even if no execute bits + were set in OLDMODE. If PMODE_BITS is not null, store into + *PMODE_BITS a mask denoting file mode bits that are affected by + CHANGES. + + The returned value and *PMODE_BITS contain only file mode bits. + For example, they have the S_IFMT bits cleared on a standard + Unix-like host. */ mode_t -mode_adjust (mode_t oldmode, struct mode_change const *changes, - mode_t umask_value) +mode_adjust (mode_t oldmode, bool dir, mode_t umask_value, + struct mode_change const *changes, mode_t *pmode_bits) { /* The adjusted mode. */ mode_t newmode = oldmode & CHMOD_MODE_BITS; + /* File mode bits that CHANGES cares about. */ + mode_t mode_bits = 0; + for (; changes->flag != MODE_DONE; changes++) { mode_t affected = changes->affected; + mode_t omit_change = + (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned; mode_t value = changes->value; switch (changes->flag) @@ -322,14 +345,15 @@ mode_adjust (mode_t oldmode, struct mode_change const *changes, case MODE_X_IF_ANY_X: /* Affect the execute bits if execute bits are already set or if the file is a directory. */ - if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) || S_ISDIR (oldmode)) + if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir) value |= S_IXUSR | S_IXGRP | S_IXOTH; break; } /* If WHO was specified, limit the change to the affected bits. - Otherwise, apply the umask. */ - value &= (affected ? affected : ~umask_value); + Otherwise, apply the umask. Either way, omit changes as + requested. */ + value &= (affected ? affected : ~umask_value) & ~ omit_change; switch (changes->op) { @@ -337,17 +361,26 @@ mode_adjust (mode_t oldmode, struct mode_change const *changes, /* If WHO was specified, preserve the previous values of bits that are not affected by this change operation. Otherwise, clear all the bits. */ - newmode = (affected ? newmode & ~affected : 0); - /* Fall through. */ + { + mode_t preserved = (affected ? ~affected : 0) | omit_change; + mode_bits |= CHMOD_MODE_BITS & ~preserved; + newmode = (newmode & preserved) | value; + break; + } + case '+': + mode_bits |= value; newmode |= value; break; case '-': + mode_bits |= value; newmode &= ~value; break; } } + if (pmode_bits) + *pmode_bits = mode_bits; return newmode; } diff --git a/lib/modechange.h b/lib/modechange.h index a94b33d4d..30a45943f 100644 --- a/lib/modechange.h +++ b/lib/modechange.h @@ -1,7 +1,7 @@ /* modechange.h -- definitions for file mode manipulation - Copyright (C) 1989, 1990, 1997, 2003, 2004, 2005 Free Software - Foundation, Inc. + Copyright (C) 1989, 1990, 1997, 2003, 2004, 2005, 2006 Free + Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,10 +22,12 @@ #if ! defined MODECHANGE_H_ # define MODECHANGE_H_ +# include # include struct mode_change *mode_compile (const char *); struct mode_change *mode_create_from_ref (const char *); -mode_t mode_adjust (mode_t, struct mode_change const *, mode_t); +mode_t mode_adjust (mode_t, bool, mode_t, struct mode_change const *, + mode_t *); #endif diff --git a/lib/userspec.c b/lib/userspec.c index fbdc2f9a1..fd2c941f8 100644 --- a/lib/userspec.c +++ b/lib/userspec.c @@ -1,5 +1,5 @@ /* userspec.c -- Parse a user and group string. - Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2005 Free Software + Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2006 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify @@ -109,8 +109,7 @@ parse_with_separator (char const *spec, char const *separator, { static const char *E_invalid_user = N_("invalid user"); static const char *E_invalid_group = N_("invalid group"); - static const char *E_bad_spec = - N_("cannot get the login group of a numeric UID"); + static const char *E_bad_spec = N_("invalid spec"); const char *error_msg; struct passwd *pwd; @@ -164,7 +163,11 @@ parse_with_separator (char const *spec, char const *separator, { bool use_login_group = (separator != NULL && g == NULL); if (use_login_group) - error_msg = E_bad_spec; + { + /* If there is no group, + then there may not be a trailing ":", either. */ + error_msg = E_bad_spec; + } else { unsigned long int tmp; diff --git a/m4/ChangeLog b/m4/ChangeLog index 7244067d5..f890da73e 100644 --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,10 @@ +2006-07-16 Paul Eggert + + * 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 * absolute-header.m4: Fix comments to match recent change. diff --git a/m4/mkancesdirs.m4 b/m4/mkancesdirs.m4 new file mode 100644 index 000000000..cd44d4874 --- /dev/null +++ b/m4/mkancesdirs.m4 @@ -0,0 +1,11 @@ +# Make a file's ancestor directories. +dnl Copyright (C) 2006 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_MKANCESDIRS], +[ + AC_LIBSOURCES([mkancesdirs.c, mkancesdirs.h]) + AC_LIBOBJ([mkancesdirs]) +]) diff --git a/m4/mkdir-p.m4 b/m4/mkdir-p.m4 index 692ed0406..b0ceb1821 100644 --- a/m4/mkdir-p.m4 +++ b/m4/mkdir-p.m4 @@ -1,4 +1,4 @@ -# mkdir-p.m4 serial 10 +# mkdir-p.m4 serial 11 dnl Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -6,12 +6,11 @@ dnl with or without modifications, as long as this notice is preserved. AC_DEFUN([gl_MKDIR_PARENTS], [ - AC_LIBSOURCES([mkdir-p.c, mkdir-p.h]) + AC_LIBSOURCES([dirchownmod.c, dirchownmod.h, mkdir-p.c, mkdir-p.h]) + AC_LIBOBJ([dirchownmod]) AC_LIBOBJ([mkdir-p]) - dnl Prerequisites of lib/mkdir-p.c. - AC_REQUIRE([AC_FUNC_ALLOCA]) - AC_REQUIRE([gl_AFS]) + dnl Prerequisites of lib/dirchownmod.c. AC_REQUIRE([gl_FUNC_LCHMOD]) - AC_REQUIRE([gl_CHDIR_SAFER]) + AC_REQUIRE([gl_FUNC_LCHOWN]) ]) diff --git a/modules/mkancesdirs b/modules/mkancesdirs new file mode 100644 index 000000000..73d9b5dfd --- /dev/null +++ b/modules/mkancesdirs @@ -0,0 +1,25 @@ +Description: +Ensure the existence of the ancestor directories of a file. + +Files: +lib/mkancesdirs.c +lib/mkancesdirs.h +m4/mkancesdirs.m4 + +Depends-on: +dirname +stat-macros + +configure.ac: +gl_MKANCESDIRS + +Makefile.am: + +Include: +"mkancesdirs.h" + +License: +GPL + +Maintainer: +Paul Eggert, Jim Meyering diff --git a/modules/mkdir-p b/modules/mkdir-p index 0ef6f6044..28cabda21 100644 --- a/modules/mkdir-p +++ b/modules/mkdir-p @@ -2,24 +2,19 @@ Description: Ensure that a directory and its parents exist. Files: -lib/chdir-safer.c -lib/chdir-safer.h +lib/dirchownmod.c +lib/dirchownmod.h lib/lchmod.h lib/mkdir-p.c lib/mkdir-p.h -lib/same-inode.h -m4/afs.m4 -m4/chdir-safer.m4 m4/lchmod.m4 m4/mkdir-p.m4 Depends-on: -alloca -chown -gettext-h -save-cwd -dirname error +gettext-h +lchown +mkancesdirs quote stat-macros stdbool @@ -36,4 +31,4 @@ License: GPL Maintainer: -Jim Meyering +Paul Eggert, Jim Meyering