openat: detect Solaris fchownat bug
[pspp] / lib / file-has-acl.c
index 5b851b96e7472eb7b35a450e65dc6cbf73ceddfe..4dda3ade2aca725518b302dadc88628e588bc2c6 100644 (file)
@@ -1,6 +1,6 @@
 /* Test whether a file has a nontrivial access control list.
 
-   Copyright (C) 2002-2003, 2005-2008 Free Software Foundation, Inc.
+   Copyright (C) 2002-2003, 2005-2009 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
@@ -55,11 +55,11 @@ acl_access_nontrivial (acl_t acl)
 #  if HAVE_ACL_FIRST_ENTRY /* Linux, FreeBSD */
 
   acl_entry_t ace;
-  int at_end;
+  int got_one;
 
-  for (at_end = acl_get_entry (acl, ACL_FIRST_ENTRY, &ace);
-       !at_end;
-       at_end = acl_get_entry (acl, ACL_NEXT_ENTRY, &ace))
+  for (got_one = acl_get_entry (acl, ACL_FIRST_ENTRY, &ace);
+       got_one > 0;
+       got_one = acl_get_entry (acl, ACL_NEXT_ENTRY, &ace))
     {
       acl_tag_t tag;
       if (acl_get_tag_type (ace, &tag) < 0)
@@ -67,7 +67,7 @@ acl_access_nontrivial (acl_t acl)
       if (!(tag == ACL_USER_OBJ || tag == ACL_GROUP_OBJ || tag == ACL_OTHER))
        return 1;
     }
-  return 0;
+  return got_one;
 
 #  else /* IRIX, Tru64 */
 #   if HAVE_ACL_TO_SHORT_TEXT /* IRIX */
@@ -120,6 +120,8 @@ acl_access_nontrivial (acl_t acl)
 
 #elif USE_ACL && HAVE_ACL && defined GETACL /* Solaris, Cygwin, not HP-UX */
 
+# if !defined ACL_NO_TRIVIAL /* Solaris <= 10, Cygwin */
+
 /* Test an ACL retrieved with GETACL.
    Return 1 if the given ACL, consisting of COUNT entries, is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
@@ -146,7 +148,7 @@ acl_nontrivial (int count, aclent_t *entries)
   return 0;
 }
 
-# if defined ACE_GETACL && defined ALLOW && defined ACE_OWNER
+#  ifdef ACE_GETACL
 
 /* Test an ACL retrieved with ACE_GETACL.
    Return 1 if the given ACL, consisting of COUNT entries, is non-trivial.
@@ -156,22 +158,59 @@ acl_ace_nontrivial (int count, ace_t *entries)
 {
   int i;
 
+  /* The flags in the ace_t structure changed in a binary incompatible way
+     when ACL_NO_TRIVIAL etc. were introduced in <sys/acl.h> version 1.15.
+     How to distinguish the two conventions at runtime?
+     In the old convention, usually three ACEs have a_flags = ACE_OWNER /
+     ACE_GROUP / ACE_OTHER, in the range 0x0100..0x0400.  In the new
+     convention, these values are not used.  */
+  int old_convention = 0;
+
   for (i = 0; i < count; i++)
-    {
-      ace_t *ace = &entries[i];
-
-      /* Note: If ace->a_flags = ACE_OWNER, ace->a_who is the st_uid from
-        stat().  If ace->a_flags = ACE_GROUP, ace->a_who is the st_gid from
-        stat().  We don't need to check ace->a_who in these cases.  */
-      if (!(ace->a_type == ALLOW
-           && (ace->a_flags == ACE_OWNER
-               || ace->a_flags == ACE_GROUP
-               || ace->a_flags == ACE_OTHER)))
-       return 1;
-    }
+    if (entries[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_OTHER))
+      {
+       old_convention = 1;
+       break;
+      }
+
+  if (old_convention)
+    /* Running on Solaris 10.  */
+    for (i = 0; i < count; i++)
+      {
+       ace_t *ace = &entries[i];
+
+       /* Note:
+          If ace->a_flags = ACE_OWNER, ace->a_who is the st_uid from stat().
+          If ace->a_flags = ACE_GROUP, ace->a_who is the st_gid from stat().
+          We don't need to check ace->a_who in these cases.  */
+       if (!(ace->a_type == ALLOW
+             && (ace->a_flags == ACE_OWNER
+                 || ace->a_flags == ACE_GROUP
+                 || ace->a_flags == ACE_OTHER)))
+         return 1;
+      }
+  else
+    /* Running on Solaris 10 (newer version) or Solaris 11.  */
+    for (i = 0; i < count; i++)
+      {
+       ace_t *ace = &entries[i];
+
+       if (!(ace->a_type == ACE_ACCESS_ALLOWED_ACE_TYPE
+             && (ace->a_flags == NEW_ACE_OWNER
+                 || ace->a_flags
+                    == (NEW_ACE_GROUP | NEW_ACE_IDENTIFIER_GROUP)
+                 || ace->a_flags == ACE_EVERYONE)
+             && (ace->a_access_mask
+                 & ~(NEW_ACE_READ_DATA | NEW_ACE_WRITE_DATA | NEW_ACE_EXECUTE))
+                == 0))
+         return 1;
+      }
+
   return 0;
 }
 
+#  endif
+
 # endif
 
 #elif USE_ACL && HAVE_GETACL /* HP-UX */
@@ -195,11 +234,7 @@ acl_nontrivial (int count, struct acl_entry *entries, struct stat *sb)
   return 0;
 }
 
-#elif USE_ACL && HAVE_ACLX_GET && 0 /* AIX */
-
-/* TODO */
-
-#elif USE_ACL && HAVE_STATACL /* older AIX */
+#elif USE_ACL && (HAVE_ACLX_GET || HAVE_STATACL) /* AIX */
 
 /* Return 1 if the given ACL is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
@@ -225,6 +260,40 @@ acl_nontrivial (struct acl *a)
   return (acl_last (a) != a->acl_ext ? 1 : 0);
 }
 
+# if HAVE_ACLX_GET && defined ACL_AIX_WIP /* newer AIX */
+
+/* Return 1 if the given ACL is non-trivial.
+   Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
+int
+acl_nfs4_nontrivial (nfs4_acl_int_t *a)
+{
+#  if 1 /* let's try this first */
+  return (a->aclEntryN > 0 ? 1 : 0);
+#  else
+  int count = a->aclEntryN;
+  int i;
+
+  for (i = 0; i < count; i++)
+    {
+      nfs4_ace_int_t *ace = &a->aclEntry[i];
+
+      if (!((ace->flags & ACE4_ID_SPECIAL) != 0
+           && (ace->aceWho.special_whoid == ACE4_WHO_OWNER
+               || ace->aceWho.special_whoid == ACE4_WHO_GROUP
+               || ace->aceWho.special_whoid == ACE4_WHO_EVERYONE)
+           && ace->aceType == ACE4_ACCESS_ALLOWED_ACE_TYPE
+           && ace->aceFlags == 0
+           && (ace->aceMask & ~(ACE4_READ_DATA | ACE4_LIST_DIRECTORY
+                                | ACE4_WRITE_DATA | ACE4_ADD_FILE
+                                | ACE4_EXECUTE)) == 0))
+       return 1;
+    }
+  return 0;
+#  endif
+}
+
+# endif
+
 #endif
 
 
@@ -285,7 +354,7 @@ file_has_acl (char const *name, struct stat const *sb)
              /* On Linux, FreeBSD, IRIX, acl_get_file (name, ACL_TYPE_ACCESS)
                 and acl_get_file (name, ACL_TYPE_DEFAULT) on a directory
                 either both succeed or both fail; it depends on the
-                filesystem.  Therefore there is no point in making the second
+                file system.  Therefore there is no point in making the second
                 call if the first one already failed.  */
              if (ret == 0 && S_ISDIR (sb->st_mode))
                {
@@ -310,7 +379,7 @@ file_has_acl (char const *name, struct stat const *sb)
 
 # elif HAVE_ACL && defined GETACLCNT /* Solaris, Cygwin, not HP-UX */
 
-#  if HAVE_ACL_TRIVIAL
+#  if defined ACL_NO_TRIVIAL
 
       /* Solaris 10 (newer version), which has additional API declared in
         <sys/acl.h> (acl_t) and implemented in libsec (acl_set, acl_trivial,
@@ -460,9 +529,65 @@ file_has_acl (char const *name, struct stat const *sb)
             Repeat.  */
        }
 
-# elif HAVE_ACLX_GET && 0 /* AIX */
+# elif HAVE_ACLX_GET && defined ACL_AIX_WIP /* AIX */
+
+      acl_type_t type;
+      char aclbuf[1024];
+      void *acl = aclbuf;
+      size_t aclsize = sizeof (aclbuf);
+      mode_t mode;
+
+      for (;;)
+       {
+         /* The docs say that type being 0 is equivalent to ACL_ANY, but it
+            is not true, in AIX 5.3.  */
+         type.u64 = ACL_ANY;
+         if (aclx_get (name, 0, &type, aclbuf, &aclsize, &mode) >= 0)
+           break;
+         if (errno != ENOSPC)
+           {
+             if (acl != aclbuf)
+               {
+                 int saved_errno = errno;
+                 free (acl);
+                 errno = saved_errno;
+               }
+             return -1;
+           }
+         aclsize = 2 * aclsize;
+         if (acl != aclbuf)
+           free (acl);
+         acl = malloc (aclsize);
+         if (acl == NULL)
+           {
+             errno = ENOMEM;
+             return -1;
+           }
+       }
 
-      /* TODO: use aclx_get(), and then?  */
+      if (type.u64 == ACL_AIXC)
+       {
+         int result = acl_nontrivial ((struct acl *) acl);
+         if (acl != aclbuf)
+           free (acl);
+         return result;
+       }
+      else if (type.u64 == ACL_NFS4)
+       {
+         int result = acl_nfs4_nontrivial ((nfs4_acl_int_t *) acl);
+         if (acl != aclbuf)
+           free (acl);
+         return result;
+       }
+      else
+       {
+         /* A newer type of ACL has been introduced in the system.
+            We should better support it.  */
+         if (acl != aclbuf)
+           free (acl);
+         errno = EINVAL;
+         return -1;
+       }
 
 # elif HAVE_STATACL /* older AIX */