Initial version.
authorPaul Eggert <eggert@cs.ucla.edu>
Mon, 17 Jul 2006 03:05:23 +0000 (03:05 +0000)
committerPaul Eggert <eggert@cs.ucla.edu>
Mon, 17 Jul 2006 03:05:23 +0000 (03:05 +0000)
lib/dirchownmod.c [new file with mode: 0644]
lib/dirchownmod.h [new file with mode: 0644]
lib/mkancesdirs.c [new file with mode: 0644]
lib/mkancesdirs.h [new file with mode: 0644]
m4/mkancesdirs.m4 [new file with mode: 0644]

diff --git a/lib/dirchownmod.c b/lib/dirchownmod.c
new file mode 100644 (file)
index 0000000..50e5fe1
--- /dev/null
@@ -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 (file)
index 0000000..e841f47
--- /dev/null
@@ -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 (file)
index 0000000..4737742
--- /dev/null
@@ -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 (file)
index 0000000..a698c9c
--- /dev/null
@@ -0,0 +1 @@
+int mkancesdirs (char *, int (*) (char const *, void *), void *);
diff --git a/m4/mkancesdirs.m4 b/m4/mkancesdirs.m4
new file mode 100644 (file)
index 0000000..cd44d48
--- /dev/null
@@ -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])
+])