]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add POSIX.1e and NFSv4 ACL support for Solaris and derivates
authorMartin Matuska <martin@matuska.org>
Tue, 10 Jan 2017 16:03:17 +0000 (17:03 +0100)
committerMartin Matuska <martin@matuska.org>
Tue, 10 Jan 2017 16:03:17 +0000 (17:03 +0100)
CMakeLists.txt
Makefile.am
NEWS
configure.ac
libarchive/archive_platform.h
libarchive/archive_read_disk_entry_from_file.c
libarchive/archive_write_disk_acl.c
libarchive/test/CMakeLists.txt
libarchive/test/test_acl_solaris_nfs4.c [new file with mode: 0644]
libarchive/test/test_acl_solaris_posix1e.c [new file with mode: 0644]

index fa651eb23381beda1597cc687202b25cf655cf37..14649e9970cafad626f98689b00c096ceb26ab16 100644 (file)
@@ -1609,6 +1609,21 @@ IF(ENABLE_ACL)
   # MacOS has an acl.h that isn't POSIX.  It can be detected by
   # checking for ACL_USER
   CHECK_SYMBOL_EXISTS(ACL_USER "${INCLUDES}" HAVE_ACL_USER)
+
+  # Solaris and derivates ACLs
+  CHECK_LIBRARY_EXISTS(sec "acl_get" "" HAVE_LIBSEC)
+  IF(HAVE_LIBSEC)
+    SET(CMAKE_REQUIRED_LIBRARIES "sec")
+    FIND_LIBRARY(SEC_LIBRARY NAMES sec)
+    LIST(APPEND ADDITIONAL_LIBS ${SEC_LIBRARY})
+  ENDIF(HAVE_LIBSEC)
+  #
+  CHECK_TYPE_EXISTS(aclent_t "${INCLUDES}" HAVE_ACLENT_T)
+  CHECK_TYPE_EXISTS(ace_t "${INCLUDES}" HAVE_ACE_T)
+  CHECK_FUNCTION_EXISTS(acl_get HAVE_FACL_GET)
+  CHECK_FUNCTION_EXISTS(facl_get HAVE_FACL_GET)
+  CHECK_FUNCTION_EXISTS(acl_set HAVE_FACL_SET)
+  CHECK_FUNCTION_EXISTS(facl_set HAVE_FACL_SET)
 ELSE(ENABLE_ACL)
   # If someone runs cmake, then disables ACL support, we need
   # to forcibly override the cached values for these.
@@ -1624,6 +1639,11 @@ ELSE(ENABLE_ACL)
   SET(HAVE_ACL_SET_FD_NP FALSE)
   SET(HAVE_ACL_SET_FILE FALSE)
   SET(HAVE_ACL_USER FALSE)
+  SET(HAVE_ACL_GET FALSE)
+  SET(HAVE_FACL_GET FALSE)
+  SET(HAVE_ACL_SET FALSE)
+  SET(HAVE_FACL_SET FALSE)
+  SET(HAVE_ACL_TRIVIAL FALSE)
 ENDIF(ENABLE_ACL)
 
 #
index f92d723a42806d53e4bd1b309b29771956c7fbcd..318c7ac5ac481ed8764feb9496da02739e3d68db 100644 (file)
@@ -323,11 +323,13 @@ libarchive_test_SOURCES= \
        libarchive/test/main.c \
        libarchive/test/read_open_memory.c \
        libarchive/test/test.h \
-       libarchive/test/test_acl_freebsd_posix1e.c \
        libarchive/test/test_acl_freebsd_nfs4.c \
+       libarchive/test/test_acl_freebsd_posix1e.c \
        libarchive/test/test_acl_nfs4.c \
        libarchive/test/test_acl_pax.c \
        libarchive/test/test_acl_posix1e.c \
+       libarchive/test/test_acl_solaris_nfs4.c \
+       libarchive/test/test_acl_solaris_posix1e.c \
        libarchive/test/test_acl_text.c \
        libarchive/test/test_archive_api_feature.c \
        libarchive/test/test_archive_clear_error.c \
diff --git a/NEWS b/NEWS
index 4d875b2a9df56acdcf5e4db2ca329c7401c38a05..3dffdf9d8426dd84492ed3d618d9940aa6d6c122 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,5 @@
+Jan 10, 2017: POSIX.1e and NFSv4 ACL support for Solaris and derivates
+
 Dec 27, 2016: NFSv4 ACL read and write support for pax
     Deprecated functions: archive_entry_acl_text(), archive_entry_acl_text_w()
 
index ffefa6c51d1d9d0ab847a7e720abe8b8508680b1..f97ef854d0e7995f8ae246e8c2dcd42d066040b7 100644 (file)
@@ -730,6 +730,12 @@ if test "x$enable_acl" != "xno"; then
                [AC_DEFINE(HAVE_ACL_USER, 1, [True for systems with POSIX ACL support])],
                [],
                [#include <sys/acl.h>])
+
+    # Solaris and derivates ACLs
+    AC_CHECK_LIB([sec], [acl_get])
+    AC_CHECK_TYPES([aclent_t], [], [], [[#include <sys/acl.h>]])
+    AC_CHECK_TYPES([ace_t], [], [], [[#include <sys/acl.h>]])
+    AC_CHECK_FUNCS(acl_get facl_get acl_set facl_set)
 fi
 
 # Additional requirements
index e44c93277cf838c0dfc03d9e1c9ace37f77df075..eb9d4bbe89caaf9f7151264675c5cad8a5723803 100644 (file)
 #define        HAVE_POSIX_ACL  1
 #endif
 
+/*
+ * If this platform has <sys/acl.h>, acl_get(), facl_get(), acl_set(),
+ * facl_set() and types aclent_t and ace_t it uses Solaris-style ACL functions
+ */
+#if HAVE_SYS_ACL_H && HAVE_ACL_GET && HAVE_FACL_GET && HAVE_ACL_SET && HAVE_FACL_SET && HAVE_ACLENT_T && HAVE_ACE_T
+#define        HAVE_SUN_ACL    1
+#endif
+
 /*
  * If we can't restore metadata using a file descriptor, then
  * for compatibility's sake, close files before trying to restore metadata.
index ce576e0acda2ce2913ec931ea0e1f837155945bf..e8bf07e967cf3d7fc474d315e48f416d6b72507d 100644 (file)
@@ -405,17 +405,26 @@ setup_mac_metadata(struct archive_read_disk *a,
 }
 #endif
 
-
-#ifdef HAVE_POSIX_ACL
+#if HAVE_POSIX_ACL || HAVE_SUN_ACL
 static int translate_acl(struct archive_read_disk *a,
-    struct archive_entry *entry, acl_t acl, int archive_entry_acl_type);
+    struct archive_entry *entry,
+#if HAVE_SUN_ACL
+    acl_t *acl,
+#else
+    acl_t acl,
+#endif
+    int archive_entry_acl_type);
 
 static int
 setup_acls(struct archive_read_disk *a,
     struct archive_entry *entry, int *fd)
 {
        const char      *accpath;
-       acl_t            acl;
+#if HAVE_SUN_ACL
+       acl_t           *acl;
+#else
+       acl_t           acl;
+#endif
        int             r;
 
        accpath = archive_entry_sourcepath(entry);
@@ -440,10 +449,12 @@ setup_acls(struct archive_read_disk *a,
 
        acl = NULL;
 
-#ifdef ACL_TYPE_NFS4
+#if defined(ACL_TYPE_NFS4) || HAVE_SUN_ACL
        /* Try NFSv4 ACL first. */
        if (*fd >= 0)
-#if HAVE_ACL_GET_FD_NP
+#if HAVE_SUN_ACL
+               facl_get(*fd, 0, &acl);
+#elif HAVE_ACL_GET_FD_NP
                acl = acl_get_fd_np(*fd, ACL_TYPE_NFS4);
 #else
                acl = acl_get_fd(*fd);
@@ -459,12 +470,24 @@ setup_acls(struct archive_read_disk *a,
                acl = NULL;
 #endif
        else
+#if HAVE_SUN_ACL
+               acl_get(accpath, 0, &acl);
+#else
                acl = acl_get_file(accpath, ACL_TYPE_NFS4);
+#endif
 
-#if HAVE_ACL_IS_TRIVIAL_NP
-       if (acl != NULL && acl_is_trivial_np(acl, &r) == 0) {
-               /* Ignore "trivial" ACLs that just mirror the file mode. */
-               if (r) {
+
+#if HAVE_ACL_IS_TRIVIAL_NP || HAVE_SUN_ACL
+       /* Ignore "trivial" ACLs that just mirror the file mode. */
+       if (acl != NULL) {
+#if HAVE_SUN_ACL
+               /* TODO: Check if ace_t is trivial like acl_is_trivial_np()  */
+               if (acl->acl_type == ACLENT_T &&
+                       (acl->acl_flags & ACL_IS_TRIVIAL) != 0)
+#elif HAVE_ACL_IS_TRIVIAL_NP
+               if (acl_is_trivial_np(acl, &r) == 0 && r == 1)
+#endif
+               {
                        acl_free(acl);
                        acl = NULL;
                        /*
@@ -474,18 +497,23 @@ setup_acls(struct archive_read_disk *a,
                        return (ARCHIVE_OK);
                }
        }
-#endif
+#endif /* HAVE_ACL_IS_TRIVIAL_NP || HAVE_SUN_ACL */
        if (acl != NULL) {
                r = translate_acl(a, entry, acl, ARCHIVE_ENTRY_ACL_TYPE_NFS4);
                acl_free(acl);
                if (r != ARCHIVE_OK) {
                        archive_set_error(&a->archive, errno,
+#if HAVE_SUN_ACL
+                           "Couldn't translate ACLs: %s", accpath);
+#else
                            "Couldn't translate NFSv4 ACLs: %s", accpath);
+#endif
                }
                return (r);
        }
-#endif /* ACL_TYPE_NFS4 */
+#endif /* defined(ACL_TYPE_NFS4) || HAVE_SUN_ACL */
 
+#if !HAVE_SUN_ACL
        /* Retrieve access ACL from file. */
        if (*fd >= 0)
                acl = acl_get_fd(*fd);
@@ -513,8 +541,7 @@ setup_acls(struct archive_read_disk *a,
 #endif
 
        if (acl != NULL) {
-               r = translate_acl(a, entry, acl,
-                   ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
+               r = translate_acl(a, entry, acl, ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
                acl_free(acl);
                acl = NULL;
                if (r != ARCHIVE_OK) {
@@ -544,13 +571,53 @@ setup_acls(struct archive_read_disk *a,
                        }
                }
        }
+#endif /* !HAVE_SUN_ACL */
        return (ARCHIVE_OK);
 }
 
+#if HAVE_SUN_ACL
 /*
- * Translate system ACL into libarchive internal structure.
+ * Translate Solaris system NFSv4 ACL into libarchive internal structure.
  */
+static struct {
+        int archive_perm;
+        int platform_perm;
+} acl_perm_map[] = {
+        {ARCHIVE_ENTRY_ACL_EXECUTE, ACE_EXECUTE},
+        {ARCHIVE_ENTRY_ACL_READ_DATA, ACE_READ_DATA},
+        {ARCHIVE_ENTRY_ACL_LIST_DIRECTORY, ACE_LIST_DIRECTORY},
+        {ARCHIVE_ENTRY_ACL_WRITE_DATA, ACE_WRITE_DATA},
+        {ARCHIVE_ENTRY_ACL_ADD_FILE, ACE_ADD_FILE},
+        {ARCHIVE_ENTRY_ACL_APPEND_DATA, ACE_APPEND_DATA},
+        {ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY, ACE_ADD_SUBDIRECTORY},
+        {ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS, ACE_READ_NAMED_ATTRS},
+        {ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS, ACE_WRITE_NAMED_ATTRS},
+        {ARCHIVE_ENTRY_ACL_DELETE_CHILD, ACE_DELETE_CHILD},
+        {ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES, ACE_READ_ATTRIBUTES},
+        {ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES, ACE_WRITE_ATTRIBUTES},
+        {ARCHIVE_ENTRY_ACL_DELETE, ACE_DELETE},
+        {ARCHIVE_ENTRY_ACL_READ_ACL, ACE_READ_ACL},
+        {ARCHIVE_ENTRY_ACL_WRITE_ACL, ACE_WRITE_ACL},
+        {ARCHIVE_ENTRY_ACL_WRITE_OWNER, ACE_WRITE_OWNER},
+        {ARCHIVE_ENTRY_ACL_SYNCHRONIZE, ACE_SYNCHRONIZE}
+};
 
+static struct {
+        int archive_inherit;
+        int platform_inherit;
+} acl_inherit_map[] = {
+        {ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT, ACE_FILE_INHERIT_ACE},
+       {ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT, ACE_DIRECTORY_INHERIT_ACE},
+       {ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT, ACE_NO_PROPAGATE_INHERIT_ACE},
+       {ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY, ACE_INHERIT_ONLY_ACE},
+       {ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS, ACE_SUCCESSFUL_ACCESS_ACE_FLAG},
+       {ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS, ACE_FAILED_ACCESS_ACE_FLAG},
+       {ARCHIVE_ENTRY_ACL_ENTRY_INHERITED, ACE_INHERITED_ACE}
+};
+#else  /* !HAVE_SUN_ACL */
+/*
+ * Translate FreeBSD system ACL into libarchive internal structure.
+ */
 static struct {
         int archive_perm;
         int platform_perm;
@@ -592,6 +659,139 @@ static struct {
        {ARCHIVE_ENTRY_ACL_ENTRY_INHERITED, ACL_ENTRY_INHERITED}
 };
 #endif
+#endif /* !HAVE_SUN_ACL */
+
+#if HAVE_SUN_ACL
+static int
+translate_acl(struct archive_read_disk *a,
+    struct archive_entry *entry, acl_t *acl, int default_entry_acl_type)
+{
+       int e, i;
+       int ae_id, ae_tag, ae_perm;
+       int entry_acl_type;
+       const char *ae_name;
+       aclent_t *aclent;
+       ace_t *ace;
+
+       (void)default_entry_acl_type;
+
+       if (acl->acl_cnt <= 0)
+               return (ARCHIVE_OK);
+
+       for (e = 0; e < acl->acl_cnt; e++) {
+               ae_name = NULL;
+               ae_tag = 0;
+               ae_perm = 0;
+
+               if (acl->acl_type == ACE_T) {
+                       ace = &((ace_t *)acl->acl_aclp)[e];
+                       ae_id = ace->a_who;
+
+                       switch(ace->a_type) {
+                       case ACE_ACCESS_ALLOWED_ACE_TYPE:
+                               entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALLOW;
+                               break;
+                       case ACE_ACCESS_DENIED_ACE_TYPE:
+                               entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_DENY;
+                               break;
+                       case ACE_SYSTEM_AUDIT_ACE_TYPE:
+                               entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
+                               break;
+                       case ACE_SYSTEM_ALARM_ACE_TYPE:
+                               entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALARM;
+                               break;
+                       default:
+                               /* Unknown entry type, skip */
+                               continue;
+                       }
+
+                       if ((ace->a_flags & ACE_OWNER) != 0)
+                               ae_tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
+                       else if ((ace->a_flags & ACE_GROUP) != 0)
+                               ae_tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
+                       else if ((ace->a_flags & ACE_EVERYONE) != 0)
+                               ae_tag = ARCHIVE_ENTRY_ACL_EVERYONE;
+                       else if ((ace->a_flags & ACE_IDENTIFIER_GROUP) != 0) {
+                               ae_tag = ARCHIVE_ENTRY_ACL_GROUP;
+                               ae_name = archive_read_disk_gname(&a->archive,
+                                   ae_id);
+                       } else {
+                               ae_tag = ARCHIVE_ENTRY_ACL_USER;
+                               ae_name = archive_read_disk_uname(&a->archive,
+                                   ae_id);
+                       }
+
+                       for (i = 0; i < (int)(sizeof(acl_inherit_map) /
+                           sizeof(acl_inherit_map[0])); ++i) {
+                               if ((ace->a_flags &
+                                   acl_inherit_map[i].platform_inherit) != 0)
+                                       ae_perm |=
+                                           acl_inherit_map[i].archive_inherit;
+                       }
+
+                       for (i = 0; i < (int)(sizeof(acl_perm_map) /
+                           sizeof(acl_perm_map[0])); ++i) {
+                               if ((ace->a_access_mask &
+                                   acl_perm_map[i].platform_perm) != 0)
+                                       ae_perm |=
+                                           acl_perm_map[i].archive_perm;
+                       }
+               } else {
+                       aclent = &((aclent_t *)acl->acl_aclp)[e];
+                       if ((aclent->a_type & ACL_DEFAULT) != 0)
+                               entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
+                       else
+                               entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
+                       ae_id = aclent->a_id;
+
+                       switch(aclent->a_type) {
+                       case DEF_USER:
+                       case USER:
+                               ae_name = archive_read_disk_uname(&a->archive,
+                                   ae_id);
+                               ae_tag = ARCHIVE_ENTRY_ACL_USER;
+                               break;
+                       case DEF_GROUP:
+                       case GROUP:
+                               ae_name = archive_read_disk_gname(&a->archive,
+                                   ae_id);
+                               ae_tag = ARCHIVE_ENTRY_ACL_GROUP;
+                               break;
+                       case DEF_CLASS_OBJ:
+                       case CLASS_OBJ:
+                               ae_tag = ARCHIVE_ENTRY_ACL_MASK;
+                               break;
+                       case DEF_USER_OBJ:
+                       case USER_OBJ:
+                               ae_tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
+                               break;
+                       case DEF_GROUP_OBJ:
+                       case GROUP_OBJ:
+                               ae_tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
+                               break;
+                       case DEF_OTHER_OBJ:
+                       case OTHER_OBJ:
+                               ae_tag = ARCHIVE_ENTRY_ACL_OTHER;
+                               break;
+                       default:
+                               /* Unknown tag type, skip */
+                               continue;
+                       }
+
+                       if ((aclent->a_perm & 1) != 0)
+                               ae_perm |= ARCHIVE_ENTRY_ACL_EXECUTE;
+                       if ((aclent->a_perm & 2) != 0)
+                               ae_perm |= ARCHIVE_ENTRY_ACL_WRITE;
+                       if ((aclent->a_perm & 4) != 0)
+                               ae_perm |= ARCHIVE_ENTRY_ACL_READ;
+               } /* default_entry_acl_type != ARCHIVE_ENTRY_ACL_TYPE_NFS4 */
+
+               archive_entry_acl_add_entry(entry, entry_acl_type,
+                   ae_perm, ae_tag, ae_id, ae_name);
+       }
+       return (ARCHIVE_OK);
+}
+#else  /* !HAVE_SUN_ACL */
 static int
 translate_acl(struct archive_read_disk *a,
     struct archive_entry *entry, acl_t acl, int default_entry_acl_type)
@@ -644,7 +844,6 @@ translate_acl(struct archive_read_disk *a,
        }
 #endif
 
-
        s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry);
        if (s == -1) {
                archive_set_error(&a->archive, errno,
@@ -783,7 +982,8 @@ translate_acl(struct archive_read_disk *a,
        }
        return (ARCHIVE_OK);
 }
-#else
+#endif /* !HAVE_SUN_ACL */
+#else  /* !HAVE_POSIX_ACL && !HAVE_SUN_ACL */
 static int
 setup_acls(struct archive_read_disk *a,
     struct archive_entry *entry, int *fd)
@@ -793,7 +993,7 @@ setup_acls(struct archive_read_disk *a,
        (void)fd;     /* UNUSED */
        return (ARCHIVE_OK);
 }
-#endif
+#endif /* !HAVE_POSIX_ACL && !HAVE_SUN_ACL */
 
 #if (HAVE_FGETXATTR && HAVE_FLISTXATTR && HAVE_LISTXATTR && \
     HAVE_LLISTXATTR && HAVE_GETXATTR && HAVE_LGETXATTR) || \
index 706ab62a07c8584ffe78c0787c6944be847650f0..4a0112e5db310e699d02619c00ad7b492eb0d45b 100644 (file)
@@ -43,7 +43,7 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_write_disk.c 201159 2009-12-29 0
 #include "archive_acl_private.h"
 #include "archive_write_disk_private.h"
 
-#ifndef HAVE_POSIX_ACL
+#if !HAVE_POSIX_ACL && !HAVE_SUN_ACL
 /* Default empty function body to satisfy mainline code. */
 int
 archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
@@ -56,7 +56,7 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
        return (ARCHIVE_OK);
 }
 
-#else
+#else /* HAVE_POSIX_ACL || HAVE_SUN_ACL */
 
 static int     set_acl(struct archive *, int fd, const char *,
                        struct archive_acl *,
@@ -71,24 +71,84 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
 {
        int              ret;
 
-       if (archive_acl_count(abstract_acl, ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) > 0) {
+       if (archive_acl_count(abstract_acl,
+           ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) > 0) {
+#if HAVE_SUN_ACL
+               /* Solaris writes POSIX.1e access and default ACLs together */
+               ret = set_acl(a, fd, name, abstract_acl, ACLENT_T,
+                   ARCHIVE_ENTRY_ACL_TYPE_POSIX1E, "posix1e");
+#else
                ret = set_acl(a, fd, name, abstract_acl, ACL_TYPE_ACCESS,
                    ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access");
                if (ret != ARCHIVE_OK)
                        return (ret);
                ret = set_acl(a, fd, name, abstract_acl, ACL_TYPE_DEFAULT,
                    ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default");
+#endif
                return (ret);
-#ifdef ACL_TYPE_NFS4
-       } else if (archive_acl_count(abstract_acl, ARCHIVE_ENTRY_ACL_TYPE_NFS4) > 0) {
+       }
+#if defined(ACL_TYPE_NFS4) || HAVE_SUN_ACL
+       else if (archive_acl_count(abstract_acl,
+           ARCHIVE_ENTRY_ACL_TYPE_NFS4) > 0) {
+#if HAVE_SUN_ACL
+               ret = set_acl(a, fd, name, abstract_acl, ACE_T,
+                   ARCHIVE_ENTRY_ACL_TYPE_NFS4, "nfs4");
+#else  /* !HAVE_SUN_ACL */
                ret = set_acl(a, fd, name, abstract_acl, ACL_TYPE_NFS4,
                    ARCHIVE_ENTRY_ACL_TYPE_NFS4, "nfs4");
+#endif /* !HAVE_SUN_ACL */
                return (ret);
-#endif
-       } else
+       }
+#endif /* defined(ACL_TYPE_NFS4) && HAVE_SUN_ACL */
+       else {
+               /* No ACLs found */
                return ARCHIVE_OK;
+       }
 }
 
+#if HAVE_SUN_ACL
+/*
+ * Translate Solaris system NFSv4 ACL into libarchive internal structure.
+ */
+static struct {
+        int archive_perm;
+        int platform_perm;
+} acl_perm_map[] = {
+        {ARCHIVE_ENTRY_ACL_EXECUTE, ACE_EXECUTE},
+        {ARCHIVE_ENTRY_ACL_READ_DATA, ACE_READ_DATA},
+        {ARCHIVE_ENTRY_ACL_LIST_DIRECTORY, ACE_LIST_DIRECTORY},
+        {ARCHIVE_ENTRY_ACL_WRITE_DATA, ACE_WRITE_DATA},
+        {ARCHIVE_ENTRY_ACL_ADD_FILE, ACE_ADD_FILE},
+        {ARCHIVE_ENTRY_ACL_APPEND_DATA, ACE_APPEND_DATA},
+        {ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY, ACE_ADD_SUBDIRECTORY},
+        {ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS, ACE_READ_NAMED_ATTRS},
+        {ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS, ACE_WRITE_NAMED_ATTRS},
+        {ARCHIVE_ENTRY_ACL_DELETE_CHILD, ACE_DELETE_CHILD},
+        {ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES, ACE_READ_ATTRIBUTES},
+        {ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES, ACE_WRITE_ATTRIBUTES},
+        {ARCHIVE_ENTRY_ACL_DELETE, ACE_DELETE},
+        {ARCHIVE_ENTRY_ACL_READ_ACL, ACE_READ_ACL},
+        {ARCHIVE_ENTRY_ACL_WRITE_ACL, ACE_WRITE_ACL},
+        {ARCHIVE_ENTRY_ACL_WRITE_OWNER, ACE_WRITE_OWNER},
+        {ARCHIVE_ENTRY_ACL_SYNCHRONIZE, ACE_SYNCHRONIZE}
+};
+
+static struct {
+        int archive_inherit;
+        int platform_inherit;
+} acl_inherit_map[] = {
+        {ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT, ACE_FILE_INHERIT_ACE},
+       {ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT, ACE_DIRECTORY_INHERIT_ACE},
+
+       {ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY, ACE_INHERIT_ONLY_ACE},
+       {ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS, ACE_SUCCESSFUL_ACCESS_ACE_FLAG},
+       {ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS, ACE_FAILED_ACCESS_ACE_FLAG},
+       {ARCHIVE_ENTRY_ACL_ENTRY_INHERITED, ACE_INHERITED_ACE}
+};
+#else  /* HAVE_SUN_ACL */
+/*
+ * Translate FreeBSD system ACL into libarchive internal structure.
+ */
 static struct {
        int archive_perm;
        int platform_perm;
@@ -130,15 +190,23 @@ static struct {
        {ARCHIVE_ENTRY_ACL_ENTRY_INHERITED, ACL_ENTRY_INHERITED}
 };
 #endif
+#endif /* HAVE_SUN_ACL */
 
 static int
 set_acl(struct archive *a, int fd, const char *name,
     struct archive_acl *abstract_acl,
     acl_type_t acl_type, int ae_requested_type, const char *tname)
 {
+#if HAVE_SUN_ACL
+       aclent_t         *aclent;
+       ace_t            *ace;
+       int              e, r;
+       acl_t            *acl;
+#else
        acl_t            acl;
        acl_entry_t      acl_entry;
        acl_permset_t    acl_permset;
+#endif
 #ifdef ACL_TYPE_NFS4
        acl_flagset_t    acl_flagset;
        int              r;
@@ -155,22 +223,114 @@ set_acl(struct archive *a, int fd, const char *name,
        entries = archive_acl_reset(abstract_acl, ae_requested_type);
        if (entries == 0)
                return (ARCHIVE_OK);
+
+#if HAVE_SUN_ACL
+       acl = NULL;
+       acl = malloc(sizeof(acl_t));
+       if (acl == NULL) {
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                       "Invalid ACL type");
+               return (ARCHIVE_FAILED);
+       }
+       if (acl_type == ACE_T)
+               acl->acl_entry_size = sizeof(ace_t);
+       else if (acl_type == ACLENT_T)
+               acl->acl_entry_size = sizeof(aclent_t);
+       else {
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                       "Invalid ACL type");
+               acl_free(acl);
+               return (ARCHIVE_FAILED);
+       }
+       acl->acl_type = acl_type;
+       acl->acl_cnt = entries;
+
+       acl->acl_aclp = malloc(entries * acl->acl_entry_size);
+       if (acl->acl_aclp == NULL) {
+               archive_set_error(a, errno,
+                   "Can't allocate memory for acl buffer");
+               acl_free(acl);
+               return (ARCHIVE_FAILED);
+       }
+#else  /* !HAVE_SUN_ACL */
        acl = acl_init(entries);
        if (acl == (acl_t)NULL) {
                archive_set_error(a, errno,
                    "Failed to initialize ACL working storage");
                return (ARCHIVE_FAILED);
        }
+#endif /* !HAVE_SUN_ACL */
+#if HAVE_SUN_ACL
+       e = 0;
+#endif
        while (archive_acl_next(a, abstract_acl, ae_requested_type, &ae_type,
                   &ae_permset, &ae_tag, &ae_id, &ae_name) == ARCHIVE_OK) {
+#if HAVE_SUN_ACL
+               ace = NULL;
+               aclent = NULL;
+               if (acl->acl_type == ACE_T)  {
+                       ace = &((ace_t *)acl->acl_aclp)[e];
+                       ace->a_who = -1;
+                       ace->a_access_mask = 0;
+                       ace->a_flags = 0;
+               } else {
+                       aclent = &((aclent_t *)acl->acl_aclp)[e];
+                       aclent->a_id = -1;
+                       aclent->a_type = 0;
+                       aclent->a_perm = 0;
+               }
+#else  /* !HAVE_SUN_ACL */
                if (acl_create_entry(&acl, &acl_entry) != 0) {
                        archive_set_error(a, errno,
                            "Failed to create a new ACL entry");
                        ret = ARCHIVE_FAILED;
                        goto exit_free;
                }
-
+#endif /* !HAVE_SUN_ACL */
                switch (ae_tag) {
+#if HAVE_SUN_ACL
+               case ARCHIVE_ENTRY_ACL_USER:
+                       ae_uid = archive_write_disk_uid(a, ae_name, ae_id);
+                       if (acl->acl_type == ACE_T)
+                               ace->a_who = ae_uid;
+                       else {
+                               aclent->a_id = ae_uid;
+                               aclent->a_type |= USER;
+                       }
+                       break;
+               case ARCHIVE_ENTRY_ACL_GROUP:
+                       ae_gid = archive_write_disk_gid(a, ae_name, ae_id);
+                       if (acl->acl_type == ACE_T) {
+                               ace->a_who = ae_gid;
+                               ace->a_flags |= ACE_IDENTIFIER_GROUP;
+                       } else {
+                               aclent->a_id = ae_gid;
+                               aclent->a_type |= GROUP;
+                       }
+                       break;
+               case ARCHIVE_ENTRY_ACL_USER_OBJ:
+                       if (acl->acl_type == ACE_T)
+                               ace->a_flags |= ACE_OWNER;
+                       else
+                               aclent->a_type |= USER_OBJ;
+                       break;
+               case ARCHIVE_ENTRY_ACL_GROUP_OBJ:
+                       if (acl->acl_type == ACE_T) {
+                               ace->a_flags |= ACE_GROUP;
+                               ace->a_flags |= ACE_IDENTIFIER_GROUP;
+                       } else
+                               aclent->a_type |= GROUP_OBJ;
+                       break;
+               case ARCHIVE_ENTRY_ACL_MASK:
+                       aclent->a_type |= CLASS_OBJ;
+                       break;
+               case ARCHIVE_ENTRY_ACL_OTHER:
+                       aclent->a_type |= OTHER_OBJ;
+                       break;
+               case ARCHIVE_ENTRY_ACL_EVERYONE:
+                       ace->a_flags |= ACE_EVERYONE;
+                       break;
+#else  /* !HAVE_SUN_ACL */
                case ARCHIVE_ENTRY_ACL_USER:
                        acl_set_tag_type(acl_entry, ACL_USER);
                        ae_uid = archive_write_disk_uid(a, ae_name, ae_id);
@@ -198,6 +358,7 @@ set_acl(struct archive *a, int fd, const char *name,
                        acl_set_tag_type(acl_entry, ACL_EVERYONE);
                        break;
 #endif
+#endif /* !HAVE_SUN_ACL */
                default:
                        archive_set_error(a, ARCHIVE_ERRNO_MISC,
                            "Unknown ACL tag");
@@ -205,9 +366,45 @@ set_acl(struct archive *a, int fd, const char *name,
                        goto exit_free;
                }
 
-#ifdef ACL_TYPE_NFS4
+#if defined(ACL_TYPE_NFS4) || HAVE_SUN_ACL
                r = 0;
                switch (ae_type) {
+#if HAVE_SUN_ACL
+               case ARCHIVE_ENTRY_ACL_TYPE_ALLOW:
+                       if (ace != NULL)
+                               ace->a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
+                       else
+                               r = -1;
+                       break;
+               case ARCHIVE_ENTRY_ACL_TYPE_DENY:
+                       if (ace != NULL)
+                               ace->a_type = ACE_ACCESS_DENIED_ACE_TYPE;
+                       else
+                               r = -1;
+                       break;
+               case ARCHIVE_ENTRY_ACL_TYPE_AUDIT:
+                       if (ace != NULL)
+                               ace->a_type = ACE_SYSTEM_AUDIT_ACE_TYPE;
+                       else
+                               r = -1;
+                       break;
+               case ARCHIVE_ENTRY_ACL_TYPE_ALARM:
+                       if (ace != NULL)
+                               ace->a_type = ACE_SYSTEM_ALARM_ACE_TYPE;
+                       else
+                               r = -1;
+                       break;
+               case ARCHIVE_ENTRY_ACL_TYPE_ACCESS:
+                       if (aclent == NULL)
+                               r = -1;
+                       break;
+               case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT:
+                       if (aclent != NULL)
+                               aclent->a_type |= ACL_DEFAULT;
+                       else
+                               r = -1;
+                       break;
+#else  /* !HAVE_SUN_ACL */
                case ARCHIVE_ENTRY_ACL_TYPE_ALLOW:
                        r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALLOW);
                        break;
@@ -224,20 +421,35 @@ set_acl(struct archive *a, int fd, const char *name,
                case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT:
                        // These don't translate directly into the system ACL.
                        break;
+#endif /* !HAVE_SUN_ACL */
                default:
                        archive_set_error(a, ARCHIVE_ERRNO_MISC,
                            "Unknown ACL entry type");
                        ret = ARCHIVE_FAILED;
                        goto exit_free;
                }
+
                if (r != 0) {
+#if HAVE_SUN_ACL
+                       errno = EINVAL;
+#endif
                        archive_set_error(a, errno,
                            "Failed to set ACL entry type");
                        ret = ARCHIVE_FAILED;
                        goto exit_free;
                }
-#endif
+#endif /* defined(ACL_TYPE_NFS4) || HAVE_SUN_ACL */
 
+#if HAVE_SUN_ACL
+               if (acl->acl_type == ACLENT_T) {
+                       if (ae_permset & ARCHIVE_ENTRY_ACL_EXECUTE)
+                               aclent->a_perm |= 1;
+                       if (ae_permset & ARCHIVE_ENTRY_ACL_WRITE)
+                               aclent->a_perm |= 2;
+                       if (ae_permset & ARCHIVE_ENTRY_ACL_READ)
+                               aclent->a_perm |= 4;
+               } else
+#else
                if (acl_get_permset(acl_entry, &acl_permset) != 0) {
                        archive_set_error(a, errno,
                            "Failed to get ACL permission set");
@@ -250,9 +462,13 @@ set_acl(struct archive *a, int fd, const char *name,
                        ret = ARCHIVE_FAILED;
                        goto exit_free;
                }
-
+#endif /* !HAVE_SUN_ACL */
                for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) {
-                       if (ae_permset & acl_perm_map[i].archive_perm)
+                       if (ae_permset & acl_perm_map[i].archive_perm) {
+#if HAVE_SUN_ACL
+                               ace->a_access_mask |=
+                                   acl_perm_map[i].platform_perm;
+#else
                                if (acl_add_perm(acl_permset,
                                    acl_perm_map[i].platform_perm) != 0) {
                                        archive_set_error(a, errno,
@@ -260,10 +476,18 @@ set_acl(struct archive *a, int fd, const char *name,
                                        ret = ARCHIVE_FAILED;
                                        goto exit_free;
                                }
+#endif
+                       }
                }
 
-#ifdef ACL_TYPE_NFS4
-               if (acl_type == ACL_TYPE_NFS4) {
+#if defined(ACL_TYPE_NFS4) || HAVE_SUN_ACL
+#if HAVE_SUN_ACL
+               if (acl_type == ACE_T)
+#else
+               if (acl_type == ACL_TYPE_NFS4)
+#endif
+               {
+#ifdef HAVE_POSIX_ACL
                        /*
                         * acl_get_flagset_np() fails with non-NFSv4 ACLs
                         */
@@ -279,8 +503,13 @@ set_acl(struct archive *a, int fd, const char *name,
                                ret = ARCHIVE_FAILED;
                                goto exit_free;
                        }
-                       for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) {
+#endif
+                       for (i = 0; i < (int)(sizeof(acl_inherit_map) /sizeof(acl_inherit_map[0])); ++i) {
                                if (ae_permset & acl_inherit_map[i].archive_inherit) {
+#if HAVE_SUN_ACL
+                                       ace->a_flags |=
+                                           acl_inherit_map[i].platform_inherit;
+#else  /* !HAVE_SUN_ACL */
                                        if (acl_add_flag_np(acl_flagset,
                                                        acl_inherit_map[i].platform_inherit) != 0) {
                                                archive_set_error(a, errno,
@@ -288,19 +517,29 @@ set_acl(struct archive *a, int fd, const char *name,
                                                ret = ARCHIVE_FAILED;
                                                goto exit_free;
                                        }
+#endif /* HAVE_SUN_ACLi */
                                }
                        }
                }
+#endif
+#if HAVE_SUN_ACL
+       e++;
 #endif
        }
 
+#if HAVE_ACL_SET_FD_NP || HAVE_ACL_SET_FD || HAVE_SUN_ACL
        /* Try restoring the ACL through 'fd' if we can. */
-#if HAVE_ACL_SET_FD_NP || HAVE_ACL_SET_FD
-#if HAVE_ACL_SET_FD_NP
-       if (fd >= 0) {
+#if HAVE_SUN_ACL || HAVE_ACL_SET_FD_NP
+       if (fd >= 0)
+#else  /* !HAVE_SUN_ACL && !HAVE_ACL_SET_FD_NP */
+       if (fd >= 0 && acl_type == ACL_TYPE_ACCESS)
+#endif
+       {
+#if HAVE_SUN_ACL
+               if (facl_set(fd, acl) == 0)
+#elif HAVE_ACL_SET_FD_NP
                if (acl_set_fd_np(fd, acl, acl_type) == 0)
-#else /* HAVE_ACL_SET_FD */
-       if (fd >= 0 && acl_type == ACL_TYPE_ACCESS) {
+#else  /* !HAVE_SUN_ACL && !HAVE_ACL_SET_FD_NP */
                if (acl_set_fd(fd, acl) == 0)
 #endif
                        ret = ARCHIVE_OK;
@@ -314,13 +553,16 @@ set_acl(struct archive *a, int fd, const char *name,
                        }
                }
        } else
-#endif /* HAVE_ACL_SET_FD_NP || HAVE_ACL_SET_FD */
-#if HAVE_ACL_SET_LINK_NP
-       if (acl_set_link_np(name, acl_type, acl) != 0) {
+#endif /* HAVE_ACL_SET_FD_NP || HAVE_ACL_SET_FD || HAVE_SUN_ACL */
+#if HAVE_SUN_ACL
+       if (acl_set(name, acl) != 0)
+#elif HAVE_ACL_SET_LINK_NP
+       if (acl_set_link_np(name, acl_type, acl) != 0)
 #else
        /* TODO: Skip this if 'name' is a symlink. */
-       if (acl_set_file(name, acl_type, acl) != 0) {
+       if (acl_set_file(name, acl_type, acl) != 0)
 #endif
+       {
                if (errno == EOPNOTSUPP) {
                        /* Filesystem doesn't support ACLs */
                        ret = ARCHIVE_OK;
@@ -334,4 +576,4 @@ exit_free:
        acl_free(acl);
        return (ret);
 }
-#endif
+#endif /* HAVE_POSIX_ACL || HAVE_SUN_ACL */
index 06b27638e9de12a5a4908cc6eadf7b7efdfa9f16..9bf19b93aa41cd7e330dcb6058fe2e7bfe849791 100644 (file)
@@ -14,6 +14,8 @@ IF(ENABLE_TEST)
     test_acl_nfs4.c
     test_acl_pax.c
     test_acl_posix1e.c
+    test_acl_solaris_nfs4.c
+    test_acl_solaris_posix1e.c
     test_acl_text.c
     test_archive_api_feature.c
     test_archive_clear_error.c
diff --git a/libarchive/test/test_acl_solaris_nfs4.c b/libarchive/test/test_acl_solaris_nfs4.c
new file mode 100644 (file)
index 0000000..6580de4
--- /dev/null
@@ -0,0 +1,515 @@
+/*-
+ * Copyright (c) 2003-2010 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+
+#if defined(__sun__)
+#include <sys/acl.h>
+
+struct myacl_t {
+       int type;
+       int permset;
+       int tag;
+       int qual; /* GID or UID of user/group, depending on tag. */
+       const char *name; /* Name of user/group, depending on tag. */
+};
+
+static struct myacl_t acls_reg[] = {
+       /* For this test, we need the file owner to be able to read and write the ACL. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW,
+         ARCHIVE_ENTRY_ACL_READ_DATA | ARCHIVE_ENTRY_ACL_READ_ACL | ARCHIVE_ENTRY_ACL_WRITE_ACL | ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS | ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES,
+         ARCHIVE_ENTRY_ACL_USER_OBJ, -1, ""},
+
+       /* An entry for each type. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_USER, 108, "user108" },
+       { ARCHIVE_ENTRY_ACL_TYPE_DENY, ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_USER, 109, "user109" },
+
+       /* An entry for each permission. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_USER, 112, "user112" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_READ_DATA,
+         ARCHIVE_ENTRY_ACL_USER, 113, "user113" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_DATA,
+         ARCHIVE_ENTRY_ACL_USER, 115, "user115" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_APPEND_DATA,
+         ARCHIVE_ENTRY_ACL_USER, 117, "user117" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS,
+         ARCHIVE_ENTRY_ACL_USER, 119, "user119" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS,
+         ARCHIVE_ENTRY_ACL_USER, 120, "user120" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES,
+         ARCHIVE_ENTRY_ACL_USER, 122, "user122" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES,
+         ARCHIVE_ENTRY_ACL_USER, 123, "user123" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_DELETE,
+         ARCHIVE_ENTRY_ACL_USER, 124, "user124" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_READ_ACL,
+         ARCHIVE_ENTRY_ACL_USER, 125, "user125" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_ACL,
+         ARCHIVE_ENTRY_ACL_USER, 126, "user126" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_OWNER,
+         ARCHIVE_ENTRY_ACL_USER, 127, "user127" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_SYNCHRONIZE,
+         ARCHIVE_ENTRY_ACL_USER, 128, "user128" },
+
+       /* One entry for each qualifier. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_USER, 135, "user135" },
+//     { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_EXECUTE,
+//       ARCHIVE_ENTRY_ACL_USER_OBJ, -1, "" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_GROUP, 136, "group136" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_GROUP_OBJ, -1, "" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_EVERYONE, -1, "" }
+};
+
+
+static struct myacl_t acls_dir[] = {
+       /* For this test, we need to be able to read and write the ACL. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_READ_DATA | ARCHIVE_ENTRY_ACL_READ_ACL,
+         ARCHIVE_ENTRY_ACL_USER_OBJ, -1, ""},
+
+       /* An entry for each type. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_LIST_DIRECTORY,
+         ARCHIVE_ENTRY_ACL_USER, 101, "user101" },
+       { ARCHIVE_ENTRY_ACL_TYPE_DENY, ARCHIVE_ENTRY_ACL_LIST_DIRECTORY,
+         ARCHIVE_ENTRY_ACL_USER, 102, "user102" },
+
+       /* An entry for each permission. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_LIST_DIRECTORY,
+         ARCHIVE_ENTRY_ACL_USER, 201, "user201" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_ADD_FILE,
+         ARCHIVE_ENTRY_ACL_USER, 202, "user202" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY,
+         ARCHIVE_ENTRY_ACL_USER, 203, "user203" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS,
+         ARCHIVE_ENTRY_ACL_USER, 204, "user204" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS,
+         ARCHIVE_ENTRY_ACL_USER, 205, "user205" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_DELETE_CHILD,
+         ARCHIVE_ENTRY_ACL_USER, 206, "user206" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES,
+         ARCHIVE_ENTRY_ACL_USER, 207, "user207" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES,
+         ARCHIVE_ENTRY_ACL_USER, 208, "user208" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_DELETE,
+         ARCHIVE_ENTRY_ACL_USER, 209, "user209" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_READ_ACL,
+         ARCHIVE_ENTRY_ACL_USER, 210, "user210" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_ACL,
+         ARCHIVE_ENTRY_ACL_USER, 211, "user211" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_WRITE_OWNER,
+         ARCHIVE_ENTRY_ACL_USER, 212, "user212" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_SYNCHRONIZE,
+         ARCHIVE_ENTRY_ACL_USER, 213, "user213" },
+
+       /* One entry with each inheritance value. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW,
+         ARCHIVE_ENTRY_ACL_READ_DATA | ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT,
+         ARCHIVE_ENTRY_ACL_USER, 301, "user301" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW,
+         ARCHIVE_ENTRY_ACL_READ_DATA | ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT,
+         ARCHIVE_ENTRY_ACL_USER, 302, "user302" },
+#if 0
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW,
+         ARCHIVE_ENTRY_ACL_READ_DATA | ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT,
+         ARCHIVE_ENTRY_ACL_USER, 303, "user303" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW,
+         ARCHIVE_ENTRY_ACL_READ_DATA | ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY,
+         ARCHIVE_ENTRY_ACL_USER, 304, "user304" },
+#endif
+
+#if 0
+       /* FreeBSD does not support audit entries. */
+       { ARCHIVE_ENTRY_ACL_TYPE_AUDIT,
+         ARCHIVE_ENTRY_ACL_READ_DATA | ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS,
+         ARCHIVE_ENTRY_ACL_USER, 401, "user401" },
+       { ARCHIVE_ENTRY_ACL_TYPE_AUDIT,
+         ARCHIVE_ENTRY_ACL_READ_DATA | ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS,
+         ARCHIVE_ENTRY_ACL_USER, 402, "user402" },
+#endif
+
+       /* One entry for each qualifier. */
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_LIST_DIRECTORY,
+         ARCHIVE_ENTRY_ACL_USER, 501, "user501" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_LIST_DIRECTORY,
+         ARCHIVE_ENTRY_ACL_GROUP, 502, "group502" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_LIST_DIRECTORY,
+         ARCHIVE_ENTRY_ACL_GROUP_OBJ, -1, "" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ALLOW, ARCHIVE_ENTRY_ACL_LIST_DIRECTORY,
+         ARCHIVE_ENTRY_ACL_EVERYONE, -1, "" }
+};
+
+static void
+set_acls(struct archive_entry *ae, struct myacl_t *acls, int start, int end)
+{
+       int i;
+
+       archive_entry_acl_clear(ae);
+       if (start > 0) {
+               assertEqualInt(ARCHIVE_OK,
+                       archive_entry_acl_add_entry(ae,
+                           acls[0].type, acls[0].permset, acls[0].tag,
+                           acls[0].qual, acls[0].name));
+       }
+       for (i = start; i < end; i++) {
+               assertEqualInt(ARCHIVE_OK,
+                   archive_entry_acl_add_entry(ae,
+                       acls[i].type, acls[i].permset, acls[i].tag,
+                       acls[i].qual, acls[i].name));
+       }
+}
+
+static int
+acl_permset_to_bitmap(uint32_t a_access_mask)
+{
+       static struct { int machine; int portable; } perms[] = {
+               {ACE_EXECUTE, ARCHIVE_ENTRY_ACL_EXECUTE},
+               {ACE_READ_DATA, ARCHIVE_ENTRY_ACL_READ_DATA},
+               {ACE_LIST_DIRECTORY, ARCHIVE_ENTRY_ACL_LIST_DIRECTORY},
+               {ACE_WRITE_DATA, ARCHIVE_ENTRY_ACL_WRITE_DATA},
+               {ACE_ADD_FILE, ARCHIVE_ENTRY_ACL_ADD_FILE},
+               {ACE_APPEND_DATA, ARCHIVE_ENTRY_ACL_APPEND_DATA},
+               {ACE_ADD_SUBDIRECTORY, ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY},
+               {ACE_READ_NAMED_ATTRS, ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS},
+               {ACE_WRITE_NAMED_ATTRS, ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS},
+               {ACE_DELETE_CHILD, ARCHIVE_ENTRY_ACL_DELETE_CHILD},
+               {ACE_READ_ATTRIBUTES, ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES},
+               {ACE_WRITE_ATTRIBUTES, ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES},
+               {ACE_DELETE, ARCHIVE_ENTRY_ACL_DELETE},
+               {ACE_READ_ACL, ARCHIVE_ENTRY_ACL_READ_ACL},
+               {ACE_WRITE_ACL, ARCHIVE_ENTRY_ACL_WRITE_ACL},
+               {ACE_WRITE_OWNER, ARCHIVE_ENTRY_ACL_WRITE_OWNER},
+               {ACE_SYNCHRONIZE, ARCHIVE_ENTRY_ACL_SYNCHRONIZE}
+       };
+       int i, permset = 0;
+
+       for (i = 0; i < (int)(sizeof(perms)/sizeof(perms[0])); ++i)
+               if (a_access_mask & perms[i].machine)
+                       permset |= perms[i].portable;
+       return permset;
+}
+
+static int
+acl_flagset_to_bitmap(uint16_t a_flags)
+{
+       static struct { int machine; int portable; } flags[] = {
+               {ACE_FILE_INHERIT_ACE, ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT},
+               {ACE_DIRECTORY_INHERIT_ACE, ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT},
+               {ACE_NO_PROPAGATE_INHERIT_ACE, ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT},
+               {ACE_INHERIT_ONLY_ACE, ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY},
+               {ACE_SUCCESSFUL_ACCESS_ACE_FLAG, ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS},
+               {ACE_FAILED_ACCESS_ACE_FLAG, ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS},
+               {ACE_INHERITED_ACE, ARCHIVE_ENTRY_ACL_ENTRY_INHERITED}
+       };
+       int i, flagset = 0;
+
+       for (i = 0; i < (int)(sizeof(flags)/sizeof(flags[0])); ++i)
+               if (a_flags & flags[i].machine)
+                       flagset |= flags[i].portable;
+       return flagset;
+}
+
+static int
+acl_match(ace_t *ace, struct myacl_t *myacl)
+{
+       int perms;
+
+       /* translate the silly opaque permset to a bitmap */
+       perms = acl_permset_to_bitmap(ace->a_access_mask) | acl_flagset_to_bitmap(ace->a_flags);
+       if (perms != myacl->permset)
+               return (0);
+
+       if (ace->a_flags & ACE_OWNER) {
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_USER_OBJ)
+                       return (0);
+       } else if (ace->a_flags & ACE_GROUP) {
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_GROUP_OBJ)
+                       return (0);
+       } else if (ace->a_flags & ACE_EVERYONE) {
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_EVERYONE)
+                       return (0);
+       } else if (ace->a_flags & ACE_IDENTIFIER_GROUP) {
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_GROUP)
+                       return (0);
+               if ((gid_t)myacl->qual != ace->a_who)
+                       return (0);
+       } else {
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_USER)
+                       return (0);
+               if ((uid_t)myacl->qual != ace->a_who)
+                       return (0);
+       }
+       return (1);
+}
+
+static void
+compare_acls(acl_t *acl, struct myacl_t *myacls, const char *filename, int start, int end)
+{
+       int *marker;
+       int matched;
+       int e, i, n;
+       ace_t *ace;
+
+       n = end - start;
+       marker = malloc(sizeof(marker[0]) * (n + 1));
+       for (i = 0; i < n; i++)
+               marker[i] = i + start;
+       /* Always include the first ACE. */
+       if (start > 0) {
+         marker[n] = 0;
+         ++n;
+       }
+
+       /*
+        * Iterate over acls in system acl object, try to match each
+        * one with an item in the myacls array.
+        */
+       for (e = 0; e < acl->acl_cnt; e++) {
+               ace = &((ace_t *)acl->acl_aclp)[e];
+               /* Search for a matching entry (tag and qualifier) */
+               for (i = 0, matched = 0; i < n && !matched; i++) {
+                       if (acl_match(ace, &myacls[marker[i]])) {
+                               /* We found a match; remove it. */
+                               marker[i] = marker[n - 1];
+                               n--;
+                               matched = 1;
+                       }
+               }
+
+               failure("ACL entry on file %s that shouldn't be there", filename);
+               assert(matched == 1);
+       }
+
+       /* Dump entries in the myacls array that weren't in the system acl. */
+       for (i = 0; i < n; ++i) {
+               failure(" ACL entry %d missing from %s: "
+                   "type=%#010x,permset=%#010x,tag=%d,qual=%d,name=``%s''\n",
+                   marker[i], filename,
+                   myacls[marker[i]].type, myacls[marker[i]].permset,
+                   myacls[marker[i]].tag, myacls[marker[i]].qual,
+                   myacls[marker[i]].name);
+               assert(0); /* Record this as a failure. */
+       }
+       free(marker);
+}
+
+static void
+compare_entry_acls(struct archive_entry *ae, struct myacl_t *myacls, const char *filename, int start, int end)
+{
+       int *marker;
+       int matched;
+       int i, n;
+       int type, permset, tag, qual;
+       const char *name;
+
+       /* Count ACL entries in myacls array and allocate an indirect array. */
+       n = end - start;
+       marker = malloc(sizeof(marker[0]) * (n + 1));
+       for (i = 0; i < n; i++)
+               marker[i] = i + start;
+       /* Always include the first ACE. */
+       if (start > 0) {
+         marker[n] = 0;
+         ++n;
+       }
+
+       /*
+        * Iterate over acls in entry, try to match each
+        * one with an item in the myacls array.
+        */
+       assertEqualInt(n, archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
+       while (ARCHIVE_OK == archive_entry_acl_next(ae,
+           ARCHIVE_ENTRY_ACL_TYPE_NFS4, &type, &permset, &tag, &qual, &name)) {
+
+               /* Search for a matching entry (tag and qualifier) */
+               for (i = 0, matched = 0; i < n && !matched; i++) {
+                       if (tag == myacls[marker[i]].tag
+                           && qual == myacls[marker[i]].qual
+                           && permset == myacls[marker[i]].permset
+                           && type == myacls[marker[i]].type) {
+                               /* We found a match; remove it. */
+                               marker[i] = marker[n - 1];
+                               n--;
+                               matched = 1;
+                       }
+               }
+
+               failure("ACL entry on file that shouldn't be there: "
+                       "type=%#010x,permset=%#010x,tag=%d,qual=%d",
+                       type,permset,tag,qual);
+               assert(matched == 1);
+       }
+
+       /* Dump entries in the myacls array that weren't in the system acl. */
+       for (i = 0; i < n; ++i) {
+               failure(" ACL entry %d missing from %s: "
+                   "type=%#010x,permset=%#010x,tag=%d,qual=%d,name=``%s''\n",
+                   marker[i], filename,
+                   myacls[marker[i]].type, myacls[marker[i]].permset,
+                   myacls[marker[i]].tag, myacls[marker[i]].qual,
+                   myacls[marker[i]].name);
+               assert(0); /* Record this as a failure. */
+       }
+       free(marker);
+}
+#endif
+
+/*
+ * Verify ACL restore-to-disk.  This test is FreeBSD-specific.
+ */
+
+DEFINE_TEST(test_acl_solaris_nfs4)
+{
+#if !defined(__sun__)
+       skipping("Solaris-specific NFS4 ACL restore test");
+#else
+       char buff[64];
+       struct stat st;
+       struct archive *a;
+       struct archive_entry *ae;
+       int i, n;
+       acl_t *acl;
+
+       /* Create a test dir and try to get trivial ACL from it. */
+       if (!assertMakeDir("pretest", 0755))
+               return;
+
+       n = acl_get("pretest", 0, &acl);
+       if (n != 0 && errno == ENOSYS) {
+               skipping("ACL tests require ACL support on the filesystem");
+               return;
+       }
+       failure("acl_get(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+
+       if (acl->acl_type != ACE_T) {
+               acl_free(acl);
+               skipping("Filesystem does not use NFSv4 ACLs");
+               return;
+       }
+
+       acl_free(acl);
+
+       /* Create a write-to-disk object. */
+       assert(NULL != (a = archive_write_disk_new()));
+       archive_write_disk_set_options(a,
+           ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL);
+
+       /* Populate an archive entry with some metadata, including ACL info */
+       ae = archive_entry_new();
+       assert(ae != NULL);
+       archive_entry_set_pathname(ae, "testall");
+       archive_entry_set_filetype(ae, AE_IFREG);
+       archive_entry_set_perm(ae, 0654);
+       archive_entry_set_mtime(ae, 123456, 7890);
+       archive_entry_set_size(ae, 0);
+       set_acls(ae, acls_reg, 0, (int)(sizeof(acls_reg)/sizeof(acls_reg[0])));
+
+       /* Write the entry to disk, including ACLs. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+
+       /* Likewise for a dir. */
+       archive_entry_set_pathname(ae, "dirall");
+       archive_entry_set_filetype(ae, AE_IFDIR);
+       archive_entry_set_perm(ae, 0654);
+       archive_entry_set_mtime(ae, 123456, 7890);
+       set_acls(ae, acls_dir, 0, (int)(sizeof(acls_dir)/sizeof(acls_dir[0])));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+
+       for (i = 0; i < (int)(sizeof(acls_dir)/sizeof(acls_dir[0])); ++i) {
+         sprintf(buff, "dir%d", i);
+         archive_entry_set_pathname(ae, buff);
+         archive_entry_set_filetype(ae, AE_IFDIR);
+         archive_entry_set_perm(ae, 0654);
+         archive_entry_set_mtime(ae, 123456 + i, 7891 + i);
+         set_acls(ae, acls_dir, i, i + 1);
+         assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       }
+
+       archive_entry_free(ae);
+
+       /* Close the archive. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+
+       /* Verify the data on disk. */
+       assertEqualInt(0, stat("testall", &st));
+       assertEqualInt(st.st_mtime, 123456);
+       n = acl_get("testall", 0, &acl);
+       failure("acl_get(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+       compare_acls(acl, acls_reg, "testall", 0, (int)(sizeof(acls_reg)/sizeof(acls_reg[0])));
+       acl_free(acl);
+
+       /* Verify single-permission dirs on disk. */
+       for (i = 0; i < (int)(sizeof(acls_dir)/sizeof(acls_dir[0])); ++i) {
+         sprintf(buff, "dir%d", i);
+         assertEqualInt(0, stat(buff, &st));
+         assertEqualInt(st.st_mtime, 123456 + i);
+         n = acl_get(buff, 0, &acl);
+         failure("acl_get(): errno = %d (%s)", errno, strerror(errno));
+         assertEqualInt(0, n);
+         compare_acls(acl, acls_dir, buff, i, i + 1);
+         acl_free(acl);
+       }
+
+       /* Verify "dirall" on disk. */
+       assertEqualInt(0, stat("dirall", &st));
+       assertEqualInt(st.st_mtime, 123456);
+       n = acl_get("dirall", 0, &acl);
+       failure("acl_get(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+       compare_acls(acl, acls_dir, "dirall", 0,  (int)(sizeof(acls_dir)/sizeof(acls_dir[0])));
+       acl_free(acl);
+
+       /* Read and compare ACL via archive_read_disk */
+       a = archive_read_disk_new();
+       assert(a != NULL);
+       ae = archive_entry_new();
+       assert(ae != NULL);
+       archive_entry_set_pathname(ae, "testall");
+       assertEqualInt(ARCHIVE_OK,
+                      archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       compare_entry_acls(ae, acls_reg, "testall", 0, (int)(sizeof(acls_reg)/sizeof(acls_reg[0])));
+       archive_entry_free(ae);
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+
+       /* Read and compare ACL via archive_read_disk */
+       a = archive_read_disk_new();
+       assert(a != NULL);
+       ae = archive_entry_new();
+       assert(ae != NULL);
+       archive_entry_set_pathname(ae, "dirall");
+       assertEqualInt(ARCHIVE_OK,
+                      archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       compare_entry_acls(ae, acls_dir, "dirall", 0,  (int)(sizeof(acls_dir)/sizeof(acls_dir[0])));
+       archive_entry_free(ae);
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+#endif
+}
diff --git a/libarchive/test/test_acl_solaris_posix1e.c b/libarchive/test/test_acl_solaris_posix1e.c
new file mode 100644 (file)
index 0000000..4f41221
--- /dev/null
@@ -0,0 +1,415 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * Copyright (c) 2017 Martin Matuska
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+
+#if defined(__sun__)
+#include <sys/acl.h>
+
+static struct archive_test_acl_t acls2[] = {
+       { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_EXECUTE | ARCHIVE_ENTRY_ACL_READ,
+         ARCHIVE_ENTRY_ACL_USER_OBJ, -1, "" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ,
+         ARCHIVE_ENTRY_ACL_USER, 77, "user77" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, 0,
+         ARCHIVE_ENTRY_ACL_USER, 78, "user78" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ,
+         ARCHIVE_ENTRY_ACL_GROUP_OBJ, -1, "" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, 0007,
+         ARCHIVE_ENTRY_ACL_GROUP, 78, "group78" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+         ARCHIVE_ENTRY_ACL_WRITE | ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_OTHER, -1, "" },
+       { ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+         ARCHIVE_ENTRY_ACL_WRITE | ARCHIVE_ENTRY_ACL_READ | ARCHIVE_ENTRY_ACL_EXECUTE,
+         ARCHIVE_ENTRY_ACL_MASK, -1, "" },
+};
+
+static int
+acl_entry_get_perm(aclent_t *aclent) {
+       int permset = 0;
+
+       if (aclent->a_perm & 1)
+               permset |= ARCHIVE_ENTRY_ACL_EXECUTE;
+       if (aclent->a_perm & 2)
+               permset |= ARCHIVE_ENTRY_ACL_WRITE;
+       if (aclent->a_perm & 4)
+               permset |= ARCHIVE_ENTRY_ACL_READ;
+
+       return permset;
+}
+
+static int
+acl_match(aclent_t *aclent, struct archive_test_acl_t *myacl)
+{
+       if (myacl->permset != acl_entry_get_perm(aclent))
+               return (0);
+
+       switch (aclent->a_type) {
+       case DEF_USER_OBJ:
+       case USER_OBJ:
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_USER_OBJ) return (0);
+               break;
+       case DEF_USER:
+       case USER:
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_USER)
+                       return (0);
+               if ((uid_t)myacl->qual != aclent->a_id)
+                       return (0);
+               break;
+       case DEF_GROUP_OBJ:
+       case GROUP_OBJ:
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_GROUP_OBJ) return (0);
+               break;
+       case DEF_GROUP:
+       case GROUP:
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_GROUP)
+                       return (0);
+               if ((gid_t)myacl->qual != aclent->a_id)
+                       return (0);
+               break;
+       case DEF_CLASS_OBJ:
+       case CLASS_OBJ:
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_MASK) return (0);
+               break;
+       case DEF_OTHER_OBJ:
+       case OTHER_OBJ:
+               if (myacl->tag != ARCHIVE_ENTRY_ACL_OTHER) return (0);
+               break;
+       }
+       return (1);
+}
+
+static void
+compare_acls(acl_t *acl, struct archive_test_acl_t *myacls, int n)
+{
+       int *marker;
+       int matched;
+       int e, i;
+       aclent_t *aclent;
+
+       /* Count ACL entries in myacls array and allocate an indirect array. */
+       marker = malloc(sizeof(marker[0]) * n);
+       if (marker == NULL)
+               return;
+       for (i = 0; i < n; i++)
+               marker[i] = i;
+
+       /*
+        * Iterate over acls in system acl object, try to match each
+        * one with an item in the myacls array.
+        */
+       for(e = 0; e < acl->acl_cnt; e++) {
+               aclent = &((aclent_t *)acl->acl_aclp)[e];
+
+               /* Search for a matching entry (tag and qualifier) */
+               for (i = 0, matched = 0; i < n && !matched; i++) {
+                       if (acl_match(aclent, &myacls[marker[i]])) {
+                               /* We found a match; remove it. */
+                               marker[i] = marker[n - 1];
+                               n--;
+                               matched = 1;
+                       }
+               }
+
+               /* TODO: Print out more details in this case. */
+               failure("ACL entry on file that shouldn't be there");
+               assert(matched == 1);
+       }
+
+       /* Dump entries in the myacls array that weren't in the system acl. */
+       for (i = 0; i < n; ++i) {
+               failure(" ACL entry missing from file: "
+                   "type=%#010x,permset=%#010x,tag=%d,qual=%d,name=``%s''\n",
+                   myacls[marker[i]].type, myacls[marker[i]].permset,
+                   myacls[marker[i]].tag, myacls[marker[i]].qual,
+                   myacls[marker[i]].name);
+               assert(0); /* Record this as a failure. */
+       }
+       free(marker);
+}
+
+#endif
+
+
+/*
+ * Verify ACL restore-to-disk.  This test is Solaris-specific.
+ */
+
+DEFINE_TEST(test_acl_solaris_posix1e_restore)
+{
+#if !defined(__sun__)
+       skipping("Solaris-specific ACL restore test");
+#else
+       struct stat st;
+       struct archive *a;
+       struct archive_entry *ae;
+       int n, fd;
+       acl_t *acl;
+
+       /*
+        * First, do a quick manual set/read of ACL data to
+        * verify that the local filesystem does support ACLs.
+        * If it doesn't, we'll simply skip the remaining tests.
+        */
+       acl = NULL;
+
+       /* Create a test file and try to set an ACL on it. */
+       fd = open("pretest", O_WRONLY | O_CREAT | O_EXCL, 0777);
+       failure("Could not create test file?!");
+       if (!assert(fd >= 0))
+               return;
+       n = facl_get(fd, 0, &acl);
+       if (n != 0)
+               close(fd);
+       if (n != 0 && errno == ENOSYS) {
+               skipping("ACL tests require ACL support on the filesystem");
+               return;
+       }
+       failure("facl_get(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+
+       if (acl->acl_type != ACLENT_T) {
+               acl_free(acl);
+               close(fd);
+               skipping("Filesystem does not use POSIX.1e ACLs");
+               return;
+       }
+
+       acl_free(acl);
+
+       n = acl_fromtext("user::rwx,user:1:rw-,group::rwx,group:15:r-x,other:rwx,mask:rwx", &acl);
+       if (n != 0)
+               close(fd);
+       failure("acl_fromtext(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+
+       n = facl_set(fd, acl);
+       acl_free(acl);
+       if (n != 0 && errno == ENOSYS) {
+               close(fd);
+               skipping("ACL tests require ACL support on the filesystem");
+               return;
+       }
+       failure("facl_set(): errno = %d (%s)",
+           errno, strerror(errno));
+       assertEqualInt(0, n);
+       close(fd);
+
+       /* Create a write-to-disk object. */
+       assert(NULL != (a = archive_write_disk_new()));
+       archive_write_disk_set_options(a,
+           ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL);
+
+       /* Populate an archive entry with some metadata, including ACL info */
+       ae = archive_entry_new();
+       assert(ae != NULL);
+       archive_entry_set_pathname(ae, "test0");
+       archive_entry_set_mtime(ae, 123456, 7890);
+       archive_entry_set_size(ae, 0);
+       archive_test_set_acls(ae, acls2, sizeof(acls2)/sizeof(acls2[0]));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+
+       /* Close the archive. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+
+       /* Verify the data on disk. */
+       assertEqualInt(0, stat("test0", &st));
+       assertEqualInt(st.st_mtime, 123456);
+       n = acl_get("test0", 0, &acl);
+       failure("acl_get(): errno = %d (%s)",
+           errno, strerror(errno));
+       assert(n == 0);
+       if (acl->acl_type != ACLENT_T) {
+               acl_free(acl);
+               skipping("Filesystem uses other than POSIX.1e ACLs");
+               return;
+       }
+       assertEqualInt(ACLENT_T, acl->acl_type);
+       compare_acls(acl, acls2, sizeof(acls2)/sizeof(acls2[0]));
+       acl_free(acl);
+#endif
+}
+
+/*
+ * Verify ACL read-from-disk.  This test is FreeBSD-specific.
+ */
+DEFINE_TEST(test_acl_solaris_posix1e_read)
+{
+#if !defined(__sun__)
+       skipping("Solaris-specific ACL read test");
+#else
+       struct archive *a;
+       struct archive_entry *ae;
+       int n, fd;
+       const char *acl1_text, *acl2_text, *acl3_text;
+       acl_t *acl;
+
+       /*
+        * Manually construct a directory and two files with
+        * different ACLs.  This also serves to verify that ACLs
+        * are supported on the local filesystem.
+        */
+
+       /* Create a test file f1 with acl1_text */
+       fd = open("f1", O_WRONLY | O_CREAT | O_EXCL, 0777);
+       failure("Could not create test file?!");
+       if (!assert(fd >= 0))
+               return;
+       n = facl_get(fd, 0, &acl);
+       if (n != 0)
+               close(fd);
+       if (n != 0 && errno == ENOSYS) {
+               skipping("ACL tests require ACL support on the filesystem");
+               return;
+       }
+       failure("facl_get(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+
+       if (acl->acl_type != ACLENT_T) {
+               acl_free(acl);
+               close(fd);
+               skipping("Filesystem does not use POSIX.1e ACLs");
+               return;
+       }
+
+       acl_free(acl);
+
+       acl1_text = "user::rwx,"
+           "group::rwx,"
+           "other:rwx,"
+           "user:1:rw-,"
+           "group:15:r-x,"
+           "mask:rwx";
+       n = acl_fromtext(acl1_text, &acl);
+       failure("acl_fromtext(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+
+       n = facl_set(fd, acl);
+       acl_free(acl);
+       if (n != 0 && errno == ENOSYS) {
+               close(fd);
+               skipping("ACL tests require ACL support on the filesystem");
+               return;
+       }
+       failure("facl_set(): errno = %d (%s)",
+           errno, strerror(errno));
+       assertEqualInt(0, n);
+       close(fd);
+
+       assertMakeDir("d1", 0700);
+
+       /*
+        * Create file d/f1 with acl2_text
+        *
+        * This differs from acl1_text in the u:1: and g:15: permissions.
+        *
+        * This file deliberately has the same name but a different ACL.
+        * Github Issue #777 explains how libarchive's directory traversal
+        * did not always correctly enter directories before attempting
+        * to read ACLs, resulting in reading the ACL from a like-named
+        * file in the wrong directory.
+        */
+       acl2_text = "user::rwx,"
+           "group::rwx,"
+           "other:---,"
+           "user:1:r--,"
+           "group:15:r--,"
+           "mask:rwx";
+       n = acl_fromtext(acl2_text, &acl);
+       failure("acl_fromtext(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+
+       fd = open("d1/f1", O_WRONLY | O_CREAT | O_EXCL, 0777);
+       failure("Could not create test file?!");
+       if (!assert(fd >= 0)) {
+               acl_free(acl);
+               return;
+       }
+       n = facl_set(fd, acl);
+       acl_free(acl);
+       if (n != 0 && errno == ENOSYS) {
+               close(fd);
+               skipping("ACL tests require ACL support on the filesystem");
+               return;
+       }
+       failure("facl_set(): errno = %d (%s)",
+           errno, strerror(errno));
+       assertEqualInt(0, n);
+       close(fd);
+
+       /* Create directory d2 with access and default ACLs */
+       assertMakeDir("d2", 0755);
+
+       acl3_text = "user::rwx,"
+           "group::r-x,"
+           "other:r-x,"
+           "user:2:r--,"
+           "group:16:-w-,"
+           "mask:rwx,"
+           "default:user::rwx,"
+            "default:user:1:r--,"
+           "default:group::r-x,"
+           "default:group:15:r--,"
+           "default:mask:rwx,"
+           "default:other:r-x";
+
+       n = acl_fromtext(acl3_text, &acl);
+       failure("acl_fromtext(): errno = %d (%s)", errno, strerror(errno));
+       assertEqualInt(0, n);
+
+       n = acl_set("d2", acl);
+       acl_free(acl);
+       if (n != 0 && errno == ENOSYS) {
+               close(fd);
+               skipping("ACL tests require ACL support on the filesystem");
+               return;
+       }
+       failure("acl_set(): errno = %d (%s)",
+           errno, strerror(errno));
+       assertEqualInt(0, n);
+
+       /* Create a read-from-disk object. */
+       assert(NULL != (a = archive_read_disk_new()));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_disk_open(a, "."));
+       assert(NULL != (ae = archive_entry_new()));
+
+       /* Walk the dir until we see both of the files */
+       while (ARCHIVE_OK == archive_read_next_header2(a, ae)) {
+               archive_read_disk_descend(a);
+               if (strcmp(archive_entry_pathname(ae), "./f1") == 0) {
+                       assertEqualString(archive_entry_acl_to_text(ae, NULL, ARCHIVE_ENTRY_ACL_TYPE_POSIX1E | ARCHIVE_ENTRY_ACL_STYLE_SEPARATOR_COMMA | ARCHIVE_ENTRY_ACL_STYLE_SOLARIS), acl1_text);
+               } else if (strcmp(archive_entry_pathname(ae), "./d1/f1") == 0) {
+                       assertEqualString(archive_entry_acl_to_text(ae, NULL, ARCHIVE_ENTRY_ACL_TYPE_POSIX1E | ARCHIVE_ENTRY_ACL_STYLE_SEPARATOR_COMMA | ARCHIVE_ENTRY_ACL_STYLE_SOLARIS), acl2_text);
+               } else if (strcmp(archive_entry_pathname(ae), "./d2") == 0) {
+                       assertEqualString(archive_entry_acl_to_text(ae, NULL, ARCHIVE_ENTRY_ACL_TYPE_POSIX1E | ARCHIVE_ENTRY_ACL_STYLE_SEPARATOR_COMMA | ARCHIVE_ENTRY_ACL_STYLE_SOLARIS), acl3_text);
+               }
+       }
+
+       archive_free(a);
+#endif
+}