summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2006-07-17 03:05:23 +0000
committerPaul Eggert <eggert@cs.ucla.edu>2006-07-17 03:05:23 +0000
commitf25ddb12c65aae1d74ca1fc5c2a3a9d84a119759 (patch)
tree384d89e7b155f14d0fee0f69a5aa15828ec25fdf /lib
parent999eeed90f8e614cad8f52657d5fc4b3a611213e (diff)
downloadcoreutils-f25ddb12c65aae1d74ca1fc5c2a3a9d84a119759.tar.xz
Initial version.
Diffstat (limited to 'lib')
-rw-r--r--lib/dirchownmod.c159
-rw-r--r--lib/dirchownmod.h2
-rw-r--r--lib/mkancesdirs.c132
-rw-r--r--lib/mkancesdirs.h1
4 files changed, 294 insertions, 0 deletions
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 <config.h>
+#endif
+
+#include "dirchownmod.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "lchmod.h"
+#include "stat-macros.h"
+
+#ifndef O_DIRECTORY
+# define O_DIRECTORY 0
+#endif
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
+/* Change the ownership and mode bits of the directory DIR.
+
+ If MKDIR_MODE is not (mode_t) -1, mkdir (DIR, MKDIR_MODE) has just
+ been executed successfully with umask zero, so DIR should be a
+ directory (not a symbolic link).
+
+ First, set the file's owner to OWNER and group to GROUP, but leave
+ the owner alone if OWNER is (uid_t) -1, and similarly for GROUP.
+
+ Then, set the file's mode bits to MODE, except preserve any of the
+ bits that correspond to zero bits in MODE_BITS. In other words,
+ MODE_BITS is a mask that specifies which of the file's mode bits
+ should be set or cleared. MODE should be a subset of MODE_BITS,
+ which in turn should be a subset of CHMOD_MODE_BITS.
+
+ This implementation assumes the current umask is zero.
+
+ Return 0 if successful, -1 (setting errno) otherwise. Unsuccessful
+ calls may do the chown but not the chmod. */
+
+int
+dirchownmod (char const *dir, mode_t mkdir_mode,
+ uid_t owner, gid_t group,
+ mode_t mode, mode_t mode_bits)
+{
+ struct stat st;
+ int result;
+
+ /* Manipulate DIR via a file descriptor if possible, to avoid some races. */
+ int open_flags = O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK;
+ int fd = open (dir, open_flags);
+
+ /* Fail if the directory is unreadable, the directory previously
+ existed or was created without read permission. Otherwise, get
+ the file's status. */
+ if (0 <= fd)
+ result = fstat (fd, &st);
+ else if (errno != EACCES
+ || (mkdir_mode != (mode_t) -1 && mkdir_mode & S_IRUSR))
+ return fd;
+ else
+ result = stat (dir, &st);
+
+ if (result == 0)
+ {
+ mode_t dir_mode = st.st_mode;
+
+ /* Check whether DIR is a directory. If FD is nonnegative, this
+ check avoids changing the ownership and mode bits of the
+ wrong file in many cases. This doesn't fix all the race
+ conditions, but it is better than nothing. */
+ if (! S_ISDIR (dir_mode))
+ {
+ errno = ENOTDIR;
+ result = -1;
+ }
+ else
+ {
+ /* If at least one of the S_IXUGO bits are set, chown might
+ clear the S_ISUID and S_SGID bits. Keep track of any
+ file mode bits whose values are indeterminate due to this
+ issue. */
+ mode_t indeterminate = 0;
+
+ /* On some systems, chown clears S_ISUID and S_ISGID, so do
+ chown before chmod. On older System V hosts, ordinary
+ users can give their files away via chown; don't worry
+ about that here, since users shouldn't do that. */
+
+ if ((owner != (uid_t) -1 && owner != st.st_uid)
+ || (group != (gid_t) -1 && group != st.st_gid))
+ {
+ result = (0 <= fd
+ ? fchown (fd, owner, group)
+ : mkdir_mode != (mode_t) -1
+ ? lchown (dir, owner, group)
+ : chown (dir, owner, group));
+
+ /* Either the user cares about an indeterminate bit and
+ it'll be set properly by chmod below, or the user
+ doesn't care and it's OK to use the bit's pre-chown
+ value. So there's no need to re-stat DIR here. */
+
+ if (result == 0 && (dir_mode & S_IXUGO))
+ indeterminate = dir_mode & (S_ISUID | S_ISGID);
+ }
+
+ /* If the file mode bits might not be right, use chmod to
+ change them. Don't change bits the user doesn't care
+ about. */
+ if (result == 0 && (((dir_mode ^ mode) | indeterminate) & mode_bits))
+ {
+ mode_t chmod_mode =
+ mode | (dir_mode & CHMOD_MODE_BITS & ~mode_bits);
+ result = (0 <= fd
+ ? fchmod (fd, chmod_mode)
+ : mkdir_mode != (mode_t) -1
+ ? lchmod (dir, chmod_mode)
+ : chmod (dir, chmod_mode));
+ }
+ }
+ }
+
+ if (0 <= fd)
+ {
+ if (result == 0)
+ result = close (fd);
+ else
+ {
+ int e = errno;
+ close (fd);
+ errno = e;
+ }
+ }
+
+ return result;
+}
diff --git a/lib/dirchownmod.h b/lib/dirchownmod.h
new file mode 100644
index 000000000..e841f4787
--- /dev/null
+++ b/lib/dirchownmod.h
@@ -0,0 +1,2 @@
+#include <sys/types.h>
+int dirchownmod (char const *, mode_t, uid_t, gid_t, mode_t, mode_t);
diff --git a/lib/mkancesdirs.c b/lib/mkancesdirs.c
new file mode 100644
index 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 <config.h>
+#endif
+
+#include "mkancesdirs.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "dirname.h"
+#include "stat-macros.h"
+
+/* Return 0 if FILE is a directory, otherwise -1 (setting errno). */
+
+static int
+test_dir (char const *file)
+{
+ struct stat st;
+ if (stat (file, &st) == 0)
+ {
+ if (S_ISDIR (st.st_mode))
+ return 0;
+ errno = ENOTDIR;
+ }
+ return -1;
+}
+
+/* Ensure that the ancestor directories of FILE exist, using an
+ algorithm that should work even if two processes execute this
+ function in parallel. Temporarily modify FILE by storing '\0'
+ bytes into it, to access the ancestor directories.
+
+ Create any ancestor directories that don't already exist, by
+ invoking MAKE_DIR (ANCESTOR, MAKE_DIR_ARG). This function should
+ return zero if successful, -1 (setting errno) otherwise.
+
+ If successful, return 0 with FILE set back to its original value;
+ otherwise, return -1 (setting errno), storing a '\0' into *FILE so
+ that it names the ancestor directory that had problems. */
+
+int
+mkancesdirs (char *file,
+ int (*make_dir) (char const *, void *),
+ void *make_dir_arg)
+{
+ /* This algorithm is O(N**2) but in typical practice the fancier
+ O(N) algorithms are slower. */
+
+ /* Address of the previous directory separator that follows an
+ ordinary byte in a file name in the left-to-right scan, or NULL
+ if no such separator precedes the current location P. */
+ char *sep = NULL;
+
+ char const *prefix_end = file + FILE_SYSTEM_PREFIX_LEN (file);
+ char *p;
+ char c;
+
+ /* Search backward through FILE using mkdir to create the
+ furthest-away ancestor that is needed. This loop isn't needed
+ for correctness, but typically ancestors already exist so this
+ loop speeds things up a bit.
+
+ This loop runs a bit faster if errno initially contains an error
+ number corresponding to a failed access to FILE. However, things
+ work correctly regardless of errno's initial value. */
+
+ for (p = last_component (file); prefix_end < p; p--)
+ if (ISSLASH (*p) && ! ISSLASH (p[-1]))
+ {
+ *p = '\0';
+
+ if (errno == ENOENT && make_dir (file, make_dir_arg) == 0)
+ {
+ *p = '/';
+ break;
+ }
+
+ if (errno != ENOENT)
+ {
+ if (test_dir (file) == 0)
+ {
+ *p = '/';
+ break;
+ }
+ if (errno != ENOENT)
+ return -1;
+ }
+
+ *p = '/';
+ }
+
+ /* Scan forward through FILE, creating directories along the way.
+ Try mkdir before stat, so that the procedure works even when two
+ or more processes are executing it in parallel. */
+
+ while ((c = *p++))
+ if (ISSLASH (*p))
+ {
+ if (! ISSLASH (c))
+ sep = p;
+ }
+ else if (ISSLASH (c) && *p && sep)
+ {
+ *sep = '\0';
+ if (make_dir (file, make_dir_arg) != 0 && test_dir (file) != 0)
+ return -1;
+ *sep = '/';
+ }
+
+
+ return 0;
+}
diff --git a/lib/mkancesdirs.h b/lib/mkancesdirs.h
new file mode 100644
index 000000000..a698c9cf0
--- /dev/null
+++ b/lib/mkancesdirs.h
@@ -0,0 +1 @@
+int mkancesdirs (char *, int (*) (char const *, void *), void *);