diff lib/acl.c @ 6527:6b31c8787689

Sync from coreutils. * doc/getdate.texi (General date syntax): Invalid dates are rejected. (Time of day items): Mention the possibility of leap seconds. Problem reported by Dr. David Alan Gilbert. * lib/chdir-long.c (cdb_free): Don't bother trying to open directory for write access: POSIX says that must fail. * lib/fts.c (diropen): Likewise. * lib/save-cwd.c (save_cwd): Likewise. * lib/chdir-long.c (cdb_free): Open with O_NOCTTY | O_NONBLOCK as well, for minor improvements on hosts that lack O_DIRECTORY. * lib/gettime.c (gettime) [!defined OK_TO_USE_1S_CLOCK]: Report an error at compile-time if only a 1-second nominal clock resolution is found. * lib/lchmod.h: New file. * lib/mkdir-p.c: Include lchmod.h, lchown.h. (make_dir_parents): Use lchown rather than chown, and lchmod rather than chmod. * lib/mountlist.c (ME_DUMMY): "none" and "proc" file systems are dummies too. Problem with "none" reported by Bob Proulx. Problem with "proc" reported by n0dalus. * lib/mountlist.c: Include <limits.h>. (dev_from_mount_options) [defined MOUNTED_GETMNTENT1 || defined MOUNTED_GETMNTENT2]: New function. It no longer assumes "dev=" has the System V meaning on Linux (since it doesn't). It also parses "dev=" more carefully. (read_file_system_list) [defined MOUNTED_GETMNTENT1 || defined MOUNTED_GETMNTENT2]: Use it. MOUNTED_GETMNTENT2 is new here; the code didn't used to look for dev= in that case. * lib/posixtm.h (PDS_PRE_2000): New macro. * lib/posixtm.c (year): Arg is now syntax_bits rather than allow_century. All usages changed. Reject dates outside the range 1969-1999 if PDS_PRE_2000 is used. * modules/mkdir-p (Files): Add chdir-safer.c, chdir-safer.h, lchmod.h, chdir-safer.m4, lchmod.m4. * modules/openat: Add mkdirat.c, openat-priv.h. * modules/lib-ignore: New file. * lib/version-etc.c (COPYRIGHT_YEAR): Update to 2006. Rewrite fts.c not to change the current working directory, by using openat, fstatat, fdopendir, etc.. * lib/fts.c [! _LIBC]: Include "openat.h", "unistd--.h", and "fcntl--.h". [_LIBC] (fchdir): Don't undef or define; no longer used. (FCHDIR): Define in terms of cwd_advance_fd rather than fchdir. Now, this `function' always succeeds, and consumes its file descriptor parameter -- so callers must not close such FDs. Update callers. (diropen_fd, opendirat, cwd_advance_fd): New functions. (diropen): Add parameter, SP. Adjust all callers. Implement using diropen_fd, rather than open. (fts_open): Initialize new member, fts_cwd_fd. Remove fts_rft-setting code. (fts_close): Close fts_cwd_fd, if necessary. (__opendir2): Define in terms of opendir or opendirat, depending on whether the FST_NOCHDIR flag is set. (fts_build): Since fts_safe_changedir consumes its FD, and since this code must do `closedir(dirp)', dup the dirfd(dirp) argument, and close the dup'd file descriptor upon failure. (fts_stat): Use fstatat(...AT_SYMLINK_NOFOLLOW) in place of lstat. (fts_safe_changedir): Tweak semantics to reflect that this function now calls cwd_advance_fd and hence consumes its FD argument. * lib/fts_.h [struct FTS] (fts_cwd_fd): New member. (fts_rft): Remove now-unused member. * lib/openat.c (fchownat): New function. * lib/openat.h (fchmodat, fchownat): Declare. (chmodat, lchmodat): Define convenience functions. (chownat, lchownat): Likewise. * lib/chdir-safer.h, chdir-safer.c: New files. * lib/modechange.c (mode_compile): Reject an invalid mode string that starts with an octal digit. From Andreas Gruenbacher. * lib/openat.c: Include "fcntl--.h" and "unistd--.h", to map open and dup to open_safer and dup_safer, respectively. (openat_permissive): Fix typo in comment. * lib/openat.c: Don't include <stdlib.h>, <unistd.h>, <fcntl.h>, "gettext.h"; either no longer needed or are guaranteed by openat.h. (_): Remove; no longer needed. (openat): Renamed from rpl_openat; no need for rpl_openat since openat.h renames openat for us. Replace most of the body with a call to openat_permissive, to avoid duplicate code. Port to (probably hypothetical) environments were mode_t is wider than int. (openat_permissive): Require mode arg, so that we can check types better. Put it just after flags. Change cwd failure indicator from pointer-to-bool to pointer-to-errno-value. All callers changed. Invoke openat_save_fail and/or openat_restore_fail if cwd_errno is null, so that openat can call us. (openat_permissive, fdopendir, fstatat, unlinkat): Simplify errno handling to avoid some duplicate code, as it's OK to set errno on success. * lib/openat.h: Revamp code so that function macros depend on __OPENAT_PREFIX only, not also on AT_FDCWD. (openat_ro): Remove. Caller changed to use openat_permissive. (openat_permissive): Now a macro, if not a function. (openat_restore_fail, openat_save_fail): Now always functions, since mkdirat needs them even if __OPENAT_PREFIX is defined. * lib/openat-priv.h: New file, defining macros used by mkdirat.c and openat.c. * lib/mkdirat.c: Include openat-priv.h. Remove definitions of macros defined therein. * lib/openat.c: Likewise. * lib/mkdirat.c (mkdirat): New file and function. * lib/openat.h (mkdirat): Declare. * lib/openat.c (fdopendir): Don't change errno when returning non-NULL. * lib/openat.h (openat_permissive): Declare. (openat_ro): Define. * lib/openat.c (EXPECTED_ERRNO): New macro. (openat_permissive): New function -- used in remove.c rewrite. (all functions): Set errno just before returning, only if there was an actual failure. Use EXPECTED_ERRNO rather than comparing against only ENOTDIR. Emulate openat-family functions using Linux's procfs, if possible. Idea and some code based on Ulrich Drepper's glibc changes. * lib/openat.c: (BUILD_PROC_NAME): New macro. Include <stdio.h>, <string.h>, "alloca.h" and "intprops.h". (rpl_openat): Emulate by trying to open /proc/self/fd/%d/%s, before falling back on save_cwd and restore_cwd. (fdopendir, fstatat, unlinkat): Likewise. * lib/openat.c (fstatat, unlinkat): Perform the syscall directly, skipping the save_cwd...restore_cwd overhead, if FILE is absolute. * lib/openat.c (rpl_openat): Use the promoted type (int), not mode_t, as second argument to va_arg. Otherwise, some versions of gcc warn that `if this code is reached, the program will abort'. Add POSIX ACL support * lib/acl.h (copy_acl, set_acl): Add declarations. * lib/acl.c (acl_entries): Add fallback implementation for POSIX ACL systems other than Linux. (chmod_or_fchmod): New function: use fchmod when possible, and chmod otherwise. (file_has_acl): Add a POSIX ACL implementation, with a Linux-specific subcase. (copy_acl): Add: copy an acl and S_ISUID, S_ISGID, and S_ISVTX from one file to another. Fall back to fchmod/chmod when acls are unsupported. (set_acl): Add: set a file's acl and S_ISUID, S_ISGID, and S_ISVTX to a defined value. Fall back to fchmod/chmod when acls are unsupported. * m4/lib-ignore.m4: New file. * m4/lchmod.m4: New file. * m4/chdir-safer.m4: New file. * m4/openat.m4 (gl_FUNC_OPENAT): Require and compile mkdirat.c. Require openat-priv.h. * m4/acl.m4 (AC_FUNC_ACL): Add POSIX ACL and Linux-specific acl tests.
author Paul Eggert <eggert@cs.ucla.edu>
date Mon, 09 Jan 2006 23:13:56 +0000 (2006-01-09)
parents 96c32553b4c6
children 8a1a9361108c
line wrap: on
line diff
--- a/lib/acl.c
+++ b/lib/acl.c
@@ -16,48 +16,394 @@
    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.  */
+   Written by Paul Eggert and Andreas Gruenbacher.  */
 
 #ifdef HAVE_CONFIG_H
 # include <config.h>
 #endif
 
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #ifndef S_ISLNK
 # define S_ISLNK(Mode) 0
 #endif
 
+#ifdef HAVE_ACL_LIBACL_H
+# include <acl/libacl.h>
+#endif
+
 #include "acl.h"
+#include "error.h"
+#include "quote.h"
 
 #include <errno.h>
 #ifndef ENOSYS
 # define ENOSYS (-1)
 #endif
+#ifndef ENOTSUP
+# define ENOTSUP (-1)
+#endif
 
-#ifndef MIN_ACL_ENTRIES
-# define MIN_ACL_ENTRIES 4
+#if ENABLE_NLS
+# include <libintl.h>
+# define _(Text) gettext (Text)
+#else
+# define _(Text) Text
+#endif
+
+#ifndef HAVE_FCHMOD
+# define HAVE_FCHMOD false
+# define fchmod(fd, mode) (-1)
+#endif
+
+/* POSIX 1003.1e (draft 17) */
+#ifndef HAVE_ACL_GET_FD
+# define HAVE_ACL_GET_FD false
+# define acl_get_fd(fd) (NULL)
+#endif
+
+/* POSIX 1003.1e (draft 17) */
+#ifndef HAVE_ACL_SET_FD
+# define HAVE_ACL_SET_FD false
+# define acl_set_fd(fd, acl) (-1)
+#endif
+
+/* Linux-specific */
+#ifndef HAVE_ACL_EXTENDED_FILE
+# define HAVE_ACL_EXTENDED_FILE false
+# define acl_extended_file(name) (-1)
 #endif
 
-/* Return 1 if FILE has a nontrivial access control list, 0 if not,
-   and -1 (setting errno) if an error is encountered.  */
+/* Linux-specific */
+#ifndef HAVE_ACL_FROM_MODE
+# define HAVE_ACL_FROM_MODE false
+# define acl_from_mode(mode) (NULL)
+#endif
+
+/* We detect presence of POSIX 1003.1e (draft 17 -- abandoned) support
+   by checking for HAVE_ACL_GET_FILE, HAVE_ACL_SET_FILE, and HAVE_ACL_FREE.
+   Systems that have acl_get_file, acl_set_file, and acl_free must also
+   have acl_to_text, acl_from_text, and acl_delete_def_file (all defined
+   in the draft); systems that don't would hit #error statements here.  */
+
+#if USE_ACL && HAVE_ACL_GET_FILE && !HAVE_ACL_ENTRIES
+# ifndef HAVE_ACL_TO_TEXT
+#  error Must have acl_to_text (see POSIX 1003.1e draft 17).
+# endif
+
+/* Return the number of entries in ACL. Linux implements acl_entries
+   as a more efficient extension than using this workaround.  */
+
+static int
+acl_entries (acl_t acl)
+{
+  char *text = acl_to_text (acl, NULL), *t;
+  int entries;
+  if (text == NULL)
+    return -1;
+  for (entries = 0, t = text; ; t++, entries++) {
+    t = strchr (t, '\n');
+    if (t == NULL)
+      break;
+  }
+  acl_free (text);
+  return entries;
+}
+#endif
+
+/* If DESC is a valid file descriptor use fchmod to change the
+   file's mode to MODE on systems that have fchown. On systems
+   that don't have fchown and if DESC is invalid, use chown on
+   NAME instead.  */
 
 int
-file_has_acl (char const *file, struct stat const *filestat)
+chmod_or_fchmod (const char *name, int desc, mode_t mode)
+{
+  if (HAVE_FCHMOD && desc != -1)
+    return fchmod (desc, mode);
+  else
+    return chmod (name, mode);
+}
+
+/* Return 1 if NAME has a nontrivial access control list, 0 if
+   NAME only has no or a base access control list, and -1 on
+   error.  SB must be set to the stat buffer of FILE.  */
+
+int
+file_has_acl (char const *name, struct stat const *sb)
 {
-  /* FIXME: This implementation should work on recent-enough versions
-     of HP-UX, Solaris, and Unixware, but it simply returns 0 with
-     POSIX 1003.1e (draft 17 -- abandoned), AIX, GNU/Linux, Irix, and
-     Tru64.  Please see Samba's source/lib/sysacls.c file for
-     fix-related ideas.  */
+#if USE_ACL && HAVE_ACL && defined GETACLCNT
+  /* This implementation should work on recent-enough versions of HP-UX,
+     Solaris, and Unixware.  */
+
+# ifndef MIN_ACL_ENTRIES
+#  define MIN_ACL_ENTRIES 4
+# endif
+
+  if (! S_ISLNK (sb->st_mode))
+    {
+      int n = acl (name, GETACLCNT, 0, NULL);
+      return n < 0 ? (errno == ENOSYS ? 0 : -1) : (MIN_ACL_ENTRIES < n);
+    }
+#elif USE_ACL && HAVE_ACL_GET_FILE && HAVE_ACL_FREE
+  /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
+
+  if (! S_ISLNK (sb->st_mode))
+    {
+      int ret;
 
-#if HAVE_ACL && defined GETACLCNT
-  if (! S_ISLNK (filestat->st_mode))
-    {
-      int n = acl (file, GETACLCNT, 0, NULL);
-      return n < 0 ? (errno == ENOSYS ? 0 : -1) : (MIN_ACL_ENTRIES < n);
+      if (HAVE_ACL_EXTENDED_FILE)
+	ret = acl_extended_file (name);
+      else
+	{
+	  acl_t acl = acl_get_file (name, ACL_TYPE_ACCESS);
+	  if (acl)
+	    {
+	      ret = (3 < acl_entries (acl));
+	      acl_free (acl);
+	      if (ret == 0 && S_ISDIR (sb->st_mode))
+		{
+		  acl = acl_get_file (name, ACL_TYPE_DEFAULT);
+		  if (acl)
+		    {
+		      ret = (0 < acl_entries (acl));
+		      acl_free (acl);
+		    }
+		  else
+		    ret = -1;
+		}
+	    }
+	  else
+	    ret = -1;
+	}
+      if (ret < 0)
+	return (errno == ENOSYS || errno == ENOTSUP) ? 0 : -1;
+      return ret;
     }
 #endif
 
+  /* FIXME: Add support for AIX, Irix, and Tru64.  Please see Samba's
+     source/lib/sysacls.c file for fix-related ideas.  */
+
   return 0;
 }
+
+/* Copy access control lists from one file to another. If SOURCE_DESC is
+   a valid file descriptor, use file descriptor operations, else use
+   filename based operations on SRC_NAME. Likewise for DEST_DESC and
+   DEST_NAME.
+   If access control lists are not available, fchmod the target file to
+   MODE.  Also sets the non-permission bits of the destination file
+   (S_ISUID, S_ISGID, S_ISVTX) to those from MODE if any are set.
+   System call return value semantics.  */
+
+int
+copy_acl (const char *src_name, int source_desc, const char *dst_name,
+	  int dest_desc, mode_t mode)
+{
+  int ret;
+
+#if USE_ACL && HAVE_ACL_GET_FILE && HAVE_ACL_SET_FILE && HAVE_ACL_FREE
+  /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
+
+  acl_t acl;
+  if (HAVE_ACL_GET_FD && source_desc != -1)
+    acl = acl_get_fd (source_desc);
+  else
+    acl = acl_get_file (src_name, ACL_TYPE_ACCESS);
+  if (acl == NULL)
+    {
+      if (errno == ENOSYS || errno == ENOTSUP)
+	return set_acl (dst_name, dest_desc, mode);
+      else
+        {
+	  error (0, errno, "%s", quote (src_name));
+	  return -1;
+	}
+    }
+
+  if (HAVE_ACL_SET_FD && dest_desc != -1)
+    ret = acl_set_fd (dest_desc, acl);
+  else
+    ret = acl_set_file (dst_name, ACL_TYPE_ACCESS, acl);
+  if (ret != 0)
+    {
+      int saved_errno = errno;
+
+      if (errno == ENOSYS || errno == ENOTSUP)
+        {
+	  int n = acl_entries (acl);
+
+	  acl_free (acl);
+	  if (n == 3)
+	    {
+	      if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
+		saved_errno = errno;
+	      else
+		return 0;
+	    }
+	  else
+	    chmod_or_fchmod (dst_name, dest_desc, mode);
+	}
+      else
+	{
+	  acl_free (acl);
+	  chmod_or_fchmod (dst_name, dest_desc, mode);
+	}
+      error (0, saved_errno, _("preserving permissions for %s"),
+	     quote (dst_name));
+      return -1;
+    }
+  else
+    acl_free (acl);
+
+  if (mode & (S_ISUID | S_ISGID | S_ISVTX))
+    {
+      /* We did not call chmod so far, so the special bits have not yet
+         been set.  */
+
+      if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
+	{
+	  error (0, errno, _("preserving permissions for %s"),
+		 quote (dst_name));
+	  return -1;
+	}
+    }
+
+  if (S_ISDIR (mode))
+    {
+      acl = acl_get_file (src_name, ACL_TYPE_DEFAULT);
+      if (acl == NULL)
+	{
+	  error (0, errno, "%s", quote (src_name));
+	  return -1;
+	}
+
+      if (acl_set_file (dst_name, ACL_TYPE_DEFAULT, acl))
+	{
+	  error (0, errno, _("preserving permissions for %s"),
+		 quote (dst_name));
+	  acl_free (acl);
+	  return -1;
+	}
+      else
+        acl_free (acl);
+    }
+  return 0;
+#else
+  ret = chmod_or_fchmod (dst_name, dest_desc, mode);
+  if (ret != 0)
+    error (0, errno, _("preserving permissions for %s"), quote (dst_name));
+  return ret;
+#endif
+}
+
+/* Set the access control lists of a file. If DESC is a valid file
+   descriptor, use file descriptor operations where available, else use
+   filename based operations on NAME.  If access control lists are not
+   available, fchmod the target file to MODE.  Also sets the
+   non-permission bits of the destination file (S_ISUID, S_ISGID, S_ISVTX)
+   to those from MODE if any are set.  System call return value
+   semantics.  */
+
+int
+set_acl (char const *name, int desc, mode_t mode)
+{
+#if USE_ACL && HAVE_ACL_SET_FILE && HAVE_ACL_FREE
+  /* POSIX 1003.1e draft 17 (abandoned) specific version.  */
+
+  /* We must also have have_acl_from_text and acl_delete_def_file.
+     (acl_delete_def_file could be emulated with acl_init followed
+      by acl_set_file, but acl_set_file with an empty acl is
+      unspecified.)  */
+
+# ifndef HAVE_ACL_FROM_TEXT
+#  error Must have acl_from_text (see POSIX 1003.1e draft 17).
+# endif
+# ifndef HAVE_ACL_DELETE_DEF_FILE
+#  error Must have acl_delete_def_file (see POSIX 1003.1e draft 17).
+# endif
+
+  acl_t acl;
+  int ret;
+
+  if (HAVE_ACL_FROM_MODE)
+    {
+      acl = acl_from_mode (mode);
+      if (!acl)
+	{
+	  error (0, errno, "%s", quote (name));
+	  return -1;
+	}
+    }
+  else
+    {
+      char acl_text[] = "u::---,g::---,o::---";
+
+      if (mode & S_IRUSR) acl_text[ 3] = 'r';
+      if (mode & S_IWUSR) acl_text[ 4] = 'w';
+      if (mode & S_IXUSR) acl_text[ 5] = 'x';
+      if (mode & S_IRGRP) acl_text[10] = 'r';
+      if (mode & S_IWGRP) acl_text[11] = 'w';
+      if (mode & S_IXGRP) acl_text[12] = 'x';
+      if (mode & S_IROTH) acl_text[17] = 'r';
+      if (mode & S_IWOTH) acl_text[18] = 'w';
+      if (mode & S_IXOTH) acl_text[19] = 'x';
+
+      acl = acl_from_text (acl_text);
+      if (!acl)
+	{
+	  error (0, errno, "%s", quote (name));
+	  return -1;
+	}
+    }
+  if (HAVE_ACL_SET_FD && desc != -1)
+    ret = acl_set_fd (desc, acl);
+  else
+    ret = acl_set_file (name, ACL_TYPE_ACCESS, acl);
+  if (ret != 0)
+    {
+      int saved_errno = errno;
+      acl_free (acl);
+
+      if (errno == ENOTSUP || errno == ENOSYS)
+	{
+	  if (chmod_or_fchmod (name, desc, mode) != 0)
+	    saved_errno = errno;
+	  else
+	    return 0;
+	}
+      error (0, saved_errno, _("setting permissions for %s"), quote (name));
+      return -1;
+    }
+  else
+    acl_free (acl);
+
+  if (S_ISDIR (mode) && acl_delete_def_file (name))
+    {
+      error (0, errno, _("setting permissions for %s"), quote (name));
+      return -1;
+    }
+
+  if (mode & (S_ISUID | S_ISGID | S_ISVTX))
+    {
+      /* We did not call chmod so far, so the special bits have not yet
+         been set.  */
+
+      if (chmod_or_fchmod (name, desc, mode))
+	{
+	  error (0, errno, _("preserving permissions for %s"), quote (name));
+	  return -1;
+	}
+    }
+  return 0;
+#else
+   int ret = chmod_or_fchmod (name, desc, mode);
+   if (ret)
+     error (0, errno, _("setting permissions for %s"), quote (name));
+   return ret;
+#endif
+}