Pull in latest man pages as part of update-zfs.sh
[zfs.git] / module / zfs / zfs_acl.c
index 341dc4d..12ffe9f 100644 (file)
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
     ACE_WRITE_ATTRIBUTES|ACE_WRITE_NAMED_ATTRS)
 #define        OWNER_ALLOW_MASK (ACE_WRITE_ACL | ACE_WRITE_OWNER | \
     ACE_WRITE_ATTRIBUTES|ACE_WRITE_NAMED_ATTRS)
-#define        WRITE_MASK_DATA (ACE_WRITE_DATA|ACE_APPEND_DATA|ACE_WRITE_NAMED_ATTRS)
 
 #define        ZFS_CHECKED_MASKS (ACE_READ_ACL|ACE_READ_ATTRIBUTES|ACE_READ_DATA| \
     ACE_READ_NAMED_ATTRS|ACE_WRITE_DATA|ACE_WRITE_ATTRIBUTES| \
     ACE_WRITE_NAMED_ATTRS|ACE_APPEND_DATA|ACE_EXECUTE|ACE_WRITE_OWNER| \
     ACE_WRITE_ACL|ACE_DELETE|ACE_DELETE_CHILD|ACE_SYNCHRONIZE)
 
-#define        WRITE_MASK (WRITE_MASK_DATA|ACE_WRITE_ATTRIBUTES|ACE_WRITE_ACL|\
-    ACE_WRITE_OWNER|ACE_DELETE|ACE_DELETE_CHILD)
+#define        WRITE_MASK_DATA (ACE_WRITE_DATA|ACE_APPEND_DATA|ACE_WRITE_NAMED_ATTRS)
+#define        WRITE_MASK_ATTRS (ACE_WRITE_ACL|ACE_WRITE_OWNER|ACE_WRITE_ATTRIBUTES| \
+    ACE_DELETE|ACE_DELETE_CHILD)
+#define        WRITE_MASK (WRITE_MASK_DATA|WRITE_MASK_ATTRS)
 
 #define        OGE_CLEAR       (ACE_READ_DATA|ACE_LIST_DIRECTORY|ACE_WRITE_DATA| \
     ACE_ADD_FILE|ACE_APPEND_DATA|ACE_ADD_SUBDIRECTORY|ACE_EXECUTE)
@@ -92,6 +93,8 @@
 #define        ZFS_ACL_WIDE_FLAGS (V4_ACL_WIDE_FLAGS|ZFS_ACL_TRIVIAL|ZFS_INHERIT_ACE|\
     ZFS_ACL_OBJ_ACE)
 
+#define        ALL_MODE_EXECS (S_IXUSR | S_IXGRP | S_IXOTH)
+
 static uint16_t
 zfs_ace_v0_get_type(void *acep)
 {
@@ -538,8 +541,9 @@ zfs_acl_curr_node(zfs_acl_t *aclp)
  * ACE FUIDs will be created later.
  */
 int
-zfs_copy_ace_2_fuid(vtype_t obj_type, zfs_acl_t *aclp, void *datap,
-    zfs_ace_t *z_acl, int aclcnt, size_t *size)
+zfs_copy_ace_2_fuid(zfsvfs_t *zfsvfs, vtype_t obj_type, zfs_acl_t *aclp,
+    void *datap, zfs_ace_t *z_acl, int aclcnt, size_t *size,
+    zfs_fuid_info_t **fuidp, cred_t *cr)
 {
        int i;
        uint16_t entry_type;
@@ -555,9 +559,9 @@ zfs_copy_ace_2_fuid(vtype_t obj_type, zfs_acl_t *aclp, void *datap,
                entry_type = aceptr->z_hdr.z_flags & ACE_TYPE_FLAGS;
                if (entry_type != ACE_OWNER && entry_type != OWNING_GROUP &&
                    entry_type != ACE_EVERYONE) {
-                       if (!aclp->z_has_fuids)
-                               aclp->z_has_fuids = IS_EPHEMERAL(acep->a_who);
-                       aceptr->z_fuid = (uint64_t)acep->a_who;
+                       aceptr->z_fuid = zfs_fuid_create(zfsvfs, acep->a_who,
+                           cr, (entry_type == 0) ?
+                           ZFS_ACE_USER : ZFS_ACE_GROUP, fuidp);
                }
 
                /*
@@ -682,7 +686,7 @@ zfs_copy_ace_2_oldace(vtype_t obj_type, zfs_acl_t *aclp, ace_t *acep,
  * convert old ACL format to new
  */
 void
-zfs_acl_xform(znode_t *zp, zfs_acl_t *aclp)
+zfs_acl_xform(znode_t *zp, zfs_acl_t *aclp, cred_t *cr)
 {
        zfs_oldace_t *oldaclp;
        int i;
@@ -714,9 +718,9 @@ zfs_acl_xform(znode_t *zp, zfs_acl_t *aclp)
        newaclnode = zfs_acl_node_alloc(aclp->z_acl_count *
            sizeof (zfs_object_ace_t));
        aclp->z_ops = zfs_acl_fuid_ops;
-       VERIFY(zfs_copy_ace_2_fuid(ZTOV(zp)->v_type, aclp, oldaclp,
-           newaclnode->z_acldata, aclp->z_acl_count,
-           &newaclnode->z_size) == 0);
+       VERIFY(zfs_copy_ace_2_fuid(zp->z_zfsvfs, ZTOV(zp)->v_type, aclp,
+           oldaclp, newaclnode->z_acldata, aclp->z_acl_count,
+           &newaclnode->z_size, NULL, cr) == 0);
        newaclnode->z_ace_count = aclp->z_acl_count;
        aclp->z_version = ZFS_ACL_VERSION;
        kmem_free(oldaclp, aclp->z_acl_count * sizeof (zfs_oldace_t));
@@ -770,8 +774,7 @@ zfs_set_ace(zfs_acl_t *aclp, void *acep, uint32_t access_mask,
  * Also, create FUIDs for any User/Group ACEs
  */
 static uint64_t
-zfs_mode_fuid_compute(znode_t *zp, zfs_acl_t *aclp, cred_t *cr,
-    zfs_fuid_info_t **fuidp, dmu_tx_t *tx)
+zfs_mode_compute(znode_t *zp, zfs_acl_t *aclp)
 {
        int             entry_type;
        mode_t          mode;
@@ -780,6 +783,7 @@ zfs_mode_fuid_compute(znode_t *zp, zfs_acl_t *aclp, cred_t *cr,
        uint64_t        who;
        uint16_t        iflags, type;
        uint32_t        access_mask;
+       boolean_t       an_exec_denied = B_FALSE;
 
        mode = (zp->z_phys->zp_mode & (S_IFMT | S_ISUID | S_ISGID | S_ISVTX));
 
@@ -904,17 +908,32 @@ zfs_mode_fuid_compute(znode_t *zp, zfs_acl_t *aclp, cred_t *cr,
                                        }
                                }
                        }
-               }
-               /*
-                * Now handle FUID create for user/group ACEs
-                */
-               if (entry_type == 0 || entry_type == ACE_IDENTIFIER_GROUP) {
-                       aclp->z_ops.ace_who_set(acep,
-                           zfs_fuid_create(zp->z_zfsvfs, who, cr,
-                           (entry_type == 0) ? ZFS_ACE_USER : ZFS_ACE_GROUP,
-                           tx, fuidp));
+               } else {
+                       /*
+                        * Only care if this IDENTIFIER_GROUP or
+                        * USER ACE denies execute access to someone,
+                        * mode is not affected
+                        */
+                       if ((access_mask & ACE_EXECUTE) && type == DENY)
+                               an_exec_denied = B_TRUE;
                }
        }
+
+       /*
+        * Failure to allow is effectively a deny, so execute permission
+        * is denied if it was never mentioned or if we explicitly
+        * weren't allowed it.
+        */
+       if (!an_exec_denied &&
+           ((seen & ALL_MODE_EXECS) != ALL_MODE_EXECS ||
+           (mode & ALL_MODE_EXECS) != ALL_MODE_EXECS))
+               an_exec_denied = B_TRUE;
+
+       if (an_exec_denied)
+               zp->z_phys->zp_flags &= ~ZFS_NO_EXECS_DENIED;
+       else
+               zp->z_phys->zp_flags |= ZFS_NO_EXECS_DENIED;
+
        return (mode);
 }
 
@@ -954,7 +973,8 @@ zfs_acl_node_read_internal(znode_t *zp, boolean_t will_modify)
 }
 
 /*
- * Read an external acl object.
+ * Read an external acl object.  If the intent is to modify, always
+ * create a new acl and leave any cached acl in place.
  */
 static int
 zfs_acl_node_read(znode_t *zp, zfs_acl_t **aclpp, boolean_t will_modify)
@@ -968,8 +988,15 @@ zfs_acl_node_read(znode_t *zp, zfs_acl_t **aclpp, boolean_t will_modify)
 
        ASSERT(MUTEX_HELD(&zp->z_acl_lock));
 
+       if (zp->z_acl_cached && !will_modify) {
+               *aclpp = zp->z_acl_cached;
+               return (0);
+       }
+
        if (zp->z_phys->zp_acl.z_acl_extern_obj == 0) {
                *aclpp = zfs_acl_node_read_internal(zp, will_modify);
+               if (!will_modify)
+                       zp->z_acl_cached = *aclpp;
                return (0);
        }
 
@@ -989,7 +1016,7 @@ zfs_acl_node_read(znode_t *zp, zfs_acl_t **aclpp, boolean_t will_modify)
        aclnode = zfs_acl_node_alloc(aclsize);
        list_insert_head(&aclp->z_acl, aclnode);
        error = dmu_read(zp->z_zfsvfs->z_os, extacl, 0,
-           aclsize, aclnode->z_acldata);
+           aclsize, aclnode->z_acldata, DMU_READ_PREFETCH);
        aclnode->z_ace_count = acl_count;
        aclp->z_acl_count = acl_count;
        aclp->z_acl_bytes = aclsize;
@@ -1003,6 +1030,8 @@ zfs_acl_node_read(znode_t *zp, zfs_acl_t **aclpp, boolean_t will_modify)
        }
 
        *aclpp = aclp;
+       if (!will_modify)
+               zp->z_acl_cached = aclp;
        return (0);
 }
 
@@ -1014,8 +1043,7 @@ zfs_acl_node_read(znode_t *zp, zfs_acl_t **aclpp, boolean_t will_modify)
  * already checked the acl and knows whether to inherit.
  */
 int
-zfs_aclset_common(znode_t *zp, zfs_acl_t *aclp, cred_t *cr,
-    zfs_fuid_info_t **fuidp, dmu_tx_t *tx)
+zfs_aclset_common(znode_t *zp, zfs_acl_t *aclp, cred_t *cr, dmu_tx_t *tx)
 {
        int             error;
        znode_phys_t    *zphys = zp->z_phys;
@@ -1026,16 +1054,18 @@ zfs_aclset_common(znode_t *zp, zfs_acl_t *aclp, cred_t *cr,
        dmu_object_type_t otype;
        zfs_acl_node_t  *aclnode;
 
-       ASSERT(MUTEX_HELD(&zp->z_lock));
-       ASSERT(MUTEX_HELD(&zp->z_acl_lock));
-
        dmu_buf_will_dirty(zp->z_dbuf, tx);
 
-       zphys->zp_mode = zfs_mode_fuid_compute(zp, aclp, cr, fuidp, tx);
+       if (zp->z_acl_cached) {
+               zfs_acl_free(zp->z_acl_cached);
+               zp->z_acl_cached = NULL;
+       }
+
+       zphys->zp_mode = zfs_mode_compute(zp, aclp);
 
        /*
-        * Decide which opbject type to use.  If we are forced to
-        * use old ACL format than transform ACL into zfs_oldace_t
+        * Decide which object type to use.  If we are forced to
+        * use old ACL format then transform ACL into zfs_oldace_t
         * layout.
         */
        if (!zfsvfs->z_use_fuids) {
@@ -1043,7 +1073,7 @@ zfs_aclset_common(znode_t *zp, zfs_acl_t *aclp, cred_t *cr,
        } else {
                if ((aclp->z_version == ZFS_ACL_VERSION_INITIAL) &&
                    (zfsvfs->z_version >= ZPL_VERSION_FUID))
-                       zfs_acl_xform(zp, aclp);
+                       zfs_acl_xform(zp, aclp, cr);
                ASSERT(aclp->z_version >= ZFS_ACL_VERSION_FUID);
                otype = DMU_OT_ACL;
        }
@@ -1125,7 +1155,6 @@ zfs_aclset_common(znode_t *zp, zfs_acl_t *aclp, cred_t *cr,
        if (ace_trivial_common(aclp, 0, zfs_ace_walk) == 0)
                zp->z_phys->zp_flags |= ZFS_ACL_TRIVIAL;
 
-       zfs_time_stamper_locked(zp, STATE_CHANGED, tx);
        return (0);
 }
 
@@ -1336,7 +1365,7 @@ zfs_acl_ace_insert(zfs_acl_t *aclp, void  *acep)
  * Prepend deny ACE
  */
 static void *
-zfs_acl_prepend_deny(znode_t *zp, zfs_acl_t *aclp, void *acep,
+zfs_acl_prepend_deny(uint64_t uid, zfs_acl_t *aclp, void *acep,
     mode_t mode)
 {
        zfs_acl_node_t *aclnode;
@@ -1349,7 +1378,7 @@ zfs_acl_prepend_deny(znode_t *zp, zfs_acl_t *aclp, void *acep,
        fuid = aclp->z_ops.ace_who_get(acep);
        flags = aclp->z_ops.ace_flags_get(acep);
        zfs_set_ace(aclp, newacep, 0, DENY, fuid, (flags & ACE_TYPE_FLAGS));
-       zfs_acl_prepend_fixup(aclp, newacep, acep, mode, zp->z_phys->zp_uid);
+       zfs_acl_prepend_fixup(aclp, newacep, acep, mode, uid);
 
        return (newacep);
 }
@@ -1473,9 +1502,9 @@ zfs_fixup_group_entries(zfs_acl_t *aclp, void *acep, void *prevacep,
  * in PSARC/2002/240
  */
 static void
-zfs_acl_chmod(znode_t *zp, uint64_t mode, zfs_acl_t *aclp)
+zfs_acl_chmod(zfsvfs_t *zfsvfs, uint64_t uid,
+    uint64_t mode, zfs_acl_t *aclp)
 {
-       zfsvfs_t        *zfsvfs = zp->z_zfsvfs;
        void            *acep = NULL, *prevacep = NULL;
        uint64_t        who;
        int             i;
@@ -1485,11 +1514,6 @@ zfs_acl_chmod(znode_t *zp, uint64_t mode, zfs_acl_t *aclp)
        uint16_t        iflags, type;
        uint32_t        access_mask;
 
-       ASSERT(MUTEX_HELD(&zp->z_acl_lock));
-       ASSERT(MUTEX_HELD(&zp->z_lock));
-
-       aclp->z_hints = (zp->z_phys->zp_flags & V4_ACL_WIDE_FLAGS);
-
        /*
         * If discard then just discard all ACL nodes which
         * represent the ACEs.
@@ -1554,17 +1578,15 @@ zfs_acl_chmod(znode_t *zp, uint64_t mode, zfs_acl_t *aclp)
 
                                        if (!reuse_deny) {
                                                prevacep =
-                                                   zfs_acl_prepend_deny(zp,
+                                                   zfs_acl_prepend_deny(uid,
                                                    aclp, acep, mode);
                                        } else {
                                                zfs_acl_prepend_fixup(
                                                    aclp, prevacep,
-                                                   acep, mode,
-                                                   zp->z_phys->zp_uid);
+                                                   acep, mode, uid);
                                        }
                                        zfs_fixup_group_entries(aclp, acep,
                                            prevacep, mode);
-
                                }
                        }
                }
@@ -1623,8 +1645,10 @@ zfs_acl_chmod_setattr(znode_t *zp, zfs_acl_t **aclp, uint64_t mode)
        mutex_enter(&zp->z_acl_lock);
        *aclp = NULL;
        error = zfs_acl_node_read(zp, aclp, B_TRUE);
-       if (error == 0)
-               zfs_acl_chmod(zp, mode, *aclp);
+       if (error == 0) {
+               (*aclp)->z_hints = zp->z_phys->zp_flags & V4_ACL_WIDE_FLAGS;
+               zfs_acl_chmod(zp->z_zfsvfs, zp->z_phys->zp_uid, mode, *aclp);
+       }
        mutex_exit(&zp->z_acl_lock);
        mutex_exit(&zp->z_lock);
        return (error);
@@ -1649,9 +1673,8 @@ zfs_restricted_update(zfsvfs_t *zfsvfs, zfs_acl_t *aclp, void *acep)
  * Should ACE be inherited?
  */
 static int
-zfs_ace_can_use(znode_t *zp, uint16_t acep_flags)
+zfs_ace_can_use(vtype_t vtype, uint16_t acep_flags)
 {
-       int vtype = ZTOV(zp)->v_type;
        int     iflags = (acep_flags & 0xf);
 
        if ((vtype == VDIR) && (iflags & ACE_DIRECTORY_INHERIT_ACE))
@@ -1666,10 +1689,9 @@ zfs_ace_can_use(znode_t *zp, uint16_t acep_flags)
  * inherit inheritable ACEs from parent
  */
 static zfs_acl_t *
-zfs_acl_inherit(znode_t *zp, zfs_acl_t *paclp, uint64_t mode,
-    boolean_t *need_chmod)
+zfs_acl_inherit(zfsvfs_t *zfsvfs, vtype_t vtype, zfs_acl_t *paclp,
+    uint64_t mode, boolean_t *need_chmod)
 {
-       zfsvfs_t        *zfsvfs = zp->z_zfsvfs;
        void            *pacep;
        void            *acep, *acep2;
        zfs_acl_node_t  *aclnode, *aclnode2;
@@ -1680,8 +1702,8 @@ zfs_acl_inherit(znode_t *zp, zfs_acl_t *paclp, uint64_t mode,
        size_t          ace_size;
        void            *data1, *data2;
        size_t          data1sz, data2sz;
-       boolean_t       vdir = ZTOV(zp)->v_type == VDIR;
-       boolean_t       vreg = ZTOV(zp)->v_type == VREG;
+       boolean_t       vdir = vtype == VDIR;
+       boolean_t       vreg = vtype == VREG;
        boolean_t       passthrough, passthrough_x, noallow;
 
        passthrough_x =
@@ -1710,7 +1732,7 @@ zfs_acl_inherit(znode_t *zp, zfs_acl_t *paclp, uint64_t mode,
 
                ace_size = aclp->z_ops.ace_size(pacep);
 
-               if (!zfs_ace_can_use(zp, iflags))
+               if (!zfs_ace_can_use(vtype, iflags))
                        continue;
 
                /*
@@ -1806,55 +1828,58 @@ zfs_acl_inherit(znode_t *zp, zfs_acl_t *paclp, uint64_t mode,
  * Create file system object initial permissions
  * including inheritable ACEs.
  */
-void
-zfs_perm_init(znode_t *zp, znode_t *parent, int flag,
-    vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
-    zfs_acl_t *setaclp, zfs_fuid_info_t **fuidp)
+int
+zfs_acl_ids_create(znode_t *dzp, int flag, vattr_t *vap, cred_t *cr,
+    vsecattr_t *vsecp, zfs_acl_ids_t *acl_ids)
 {
-       uint64_t        mode, fuid, fgid;
        int             error;
-       zfsvfs_t        *zfsvfs = zp->z_zfsvfs;
-       zfs_acl_t       *aclp = NULL;
+       zfsvfs_t        *zfsvfs = dzp->z_zfsvfs;
        zfs_acl_t       *paclp;
-       xvattr_t        *xvap = (xvattr_t *)vap;
        gid_t           gid;
        boolean_t       need_chmod = B_TRUE;
 
-       if (setaclp)
-               aclp = setaclp;
+       bzero(acl_ids, sizeof (zfs_acl_ids_t));
+       acl_ids->z_mode = MAKEIMODE(vap->va_type, vap->va_mode);
 
-       mode = MAKEIMODE(vap->va_type, vap->va_mode);
+       if (vsecp)
+               if ((error = zfs_vsec_2_aclp(zfsvfs, vap->va_type, vsecp, cr,
+                   &acl_ids->z_fuidp, &acl_ids->z_aclp)) != 0)
+                       return (error);
 
        /*
         * Determine uid and gid.
         */
        if ((flag & (IS_ROOT_NODE | IS_REPLAY)) ||
            ((flag & IS_XATTR) && (vap->va_type == VDIR))) {
-               fuid = zfs_fuid_create(zfsvfs, vap->va_uid, cr,
-                   ZFS_OWNER, tx, fuidp);
-               fgid = zfs_fuid_create(zfsvfs, vap->va_gid, cr,
-                   ZFS_GROUP, tx, fuidp);
+               acl_ids->z_fuid = zfs_fuid_create(zfsvfs,
+                   (uint64_t)vap->va_uid, cr,
+                   ZFS_OWNER, &acl_ids->z_fuidp);
+               acl_ids->z_fgid = zfs_fuid_create(zfsvfs,
+                   (uint64_t)vap->va_gid, cr,
+                   ZFS_GROUP, &acl_ids->z_fuidp);
                gid = vap->va_gid;
        } else {
-               fuid = zfs_fuid_create_cred(zfsvfs, ZFS_OWNER, tx, cr, fuidp);
-               fgid = 0;
+               acl_ids->z_fuid = zfs_fuid_create_cred(zfsvfs, ZFS_OWNER,
+                   cr, &acl_ids->z_fuidp);
+               acl_ids->z_fgid = 0;
                if (vap->va_mask & AT_GID)  {
-                       fgid = zfs_fuid_create(zfsvfs, vap->va_gid, cr,
-                           ZFS_GROUP, tx, fuidp);
+                       acl_ids->z_fgid = zfs_fuid_create(zfsvfs,
+                           (uint64_t)vap->va_gid,
+                           cr, ZFS_GROUP, &acl_ids->z_fuidp);
                        gid = vap->va_gid;
-                       if (fgid != parent->z_phys->zp_gid &&
+                       if (acl_ids->z_fgid != dzp->z_phys->zp_gid &&
                            !groupmember(vap->va_gid, cr) &&
                            secpolicy_vnode_create_gid(cr) != 0)
-                               fgid = 0;
+                               acl_ids->z_fgid = 0;
                }
-               if (fgid == 0) {
-                       if (parent->z_phys->zp_mode & S_ISGID) {
-                               fgid = parent->z_phys->zp_gid;
-                               gid = zfs_fuid_map_id(zfsvfs, fgid,
+               if (acl_ids->z_fgid == 0) {
+                       if (dzp->z_phys->zp_mode & S_ISGID) {
+                               acl_ids->z_fgid = dzp->z_phys->zp_gid;
+                               gid = zfs_fuid_map_id(zfsvfs, acl_ids->z_fgid,
                                    cr, ZFS_GROUP);
                        } else {
-                               fgid = zfs_fuid_create_cred(zfsvfs,
-                                   ZFS_GROUP, tx, cr, fuidp);
+                               acl_ids->z_fgid = zfs_fuid_create_cred(zfsvfs,
+                                   ZFS_GROUP, cr, &acl_ids->z_fuidp);
                                gid = crgetgid(cr);
                        }
                }
@@ -1867,57 +1892,60 @@ zfs_perm_init(znode_t *zp, znode_t *parent, int flag,
         * file's new group, clear the file's set-GID bit.
         */
 
-       if ((parent->z_phys->zp_mode & S_ISGID) && (vap->va_type == VDIR)) {
-               mode |= S_ISGID;
+       if (!(flag & IS_ROOT_NODE) && (dzp->z_phys->zp_mode & S_ISGID) &&
+           (vap->va_type == VDIR)) {
+               acl_ids->z_mode |= S_ISGID;
        } else {
-               if ((mode & S_ISGID) &&
+               if ((acl_ids->z_mode & S_ISGID) &&
                    secpolicy_vnode_setids_setgids(cr, gid) != 0)
-                       mode &= ~S_ISGID;
-       }
-
-       zp->z_phys->zp_uid = fuid;
-       zp->z_phys->zp_gid = fgid;
-       zp->z_phys->zp_mode = mode;
-
-       if (aclp == NULL) {
-               mutex_enter(&parent->z_lock);
-               if ((ZTOV(parent)->v_type == VDIR &&
-                   (parent->z_phys->zp_flags & ZFS_INHERIT_ACE)) &&
-                   !(zp->z_phys->zp_flags & ZFS_XATTR)) {
-                       mutex_enter(&parent->z_acl_lock);
-                       VERIFY(0 == zfs_acl_node_read(parent, &paclp, B_FALSE));
-                       mutex_exit(&parent->z_acl_lock);
-                       aclp = zfs_acl_inherit(zp, paclp, mode, &need_chmod);
-                       zfs_acl_free(paclp);
+                       acl_ids->z_mode &= ~S_ISGID;
+       }
+
+       if (acl_ids->z_aclp == NULL) {
+               mutex_enter(&dzp->z_lock);
+               if (!(flag & IS_ROOT_NODE) && (ZTOV(dzp)->v_type == VDIR &&
+                   (dzp->z_phys->zp_flags & ZFS_INHERIT_ACE)) &&
+                   !(dzp->z_phys->zp_flags & ZFS_XATTR)) {
+                       mutex_enter(&dzp->z_acl_lock);
+                       VERIFY(0 == zfs_acl_node_read(dzp, &paclp, B_FALSE));
+                       mutex_exit(&dzp->z_acl_lock);
+                       acl_ids->z_aclp = zfs_acl_inherit(zfsvfs,
+                           vap->va_type, paclp, acl_ids->z_mode, &need_chmod);
                } else {
-                       aclp = zfs_acl_alloc(zfs_acl_version_zp(zp));
+                       acl_ids->z_aclp =
+                           zfs_acl_alloc(zfs_acl_version_zp(dzp));
+               }
+               mutex_exit(&dzp->z_lock);
+               if (need_chmod) {
+                       acl_ids->z_aclp->z_hints = (vap->va_type == VDIR) ?
+                           ZFS_ACL_AUTO_INHERIT : 0;
+                       zfs_acl_chmod(zfsvfs, acl_ids->z_fuid,
+                           acl_ids->z_mode, acl_ids->z_aclp);
                }
-               mutex_exit(&parent->z_lock);
-               mutex_enter(&zp->z_lock);
-               mutex_enter(&zp->z_acl_lock);
-               if (need_chmod)
-                       zfs_acl_chmod(zp, mode, aclp);
-       } else {
-               mutex_enter(&zp->z_lock);
-               mutex_enter(&zp->z_acl_lock);
        }
 
-       /* Force auto_inherit on all new directory objects */
-       if (vap->va_type == VDIR)
-               aclp->z_hints |= ZFS_ACL_AUTO_INHERIT;
-
-       error = zfs_aclset_common(zp, aclp, cr, fuidp, tx);
-
-       /* Set optional attributes if any */
-       if (vap->va_mask & AT_XVATTR)
-               zfs_xvattr_set(zp, xvap);
+       return (0);
+}
 
-       mutex_exit(&zp->z_lock);
-       mutex_exit(&zp->z_acl_lock);
-       ASSERT3U(error, ==, 0);
+/*
+ * Free ACL and fuid_infop, but not the acl_ids structure
+ */
+void
+zfs_acl_ids_free(zfs_acl_ids_t *acl_ids)
+{
+       if (acl_ids->z_aclp)
+               zfs_acl_free(acl_ids->z_aclp);
+       if (acl_ids->z_fuidp)
+               zfs_fuid_info_free(acl_ids->z_fuidp);
+       acl_ids->z_aclp = NULL;
+       acl_ids->z_fuidp = NULL;
+}
 
-       if (aclp != setaclp)
-               zfs_acl_free(aclp);
+boolean_t
+zfs_acl_ids_overquota(zfsvfs_t *zfsvfs, zfs_acl_ids_t *acl_ids)
+{
+       return (zfs_usergroup_overquota(zfsvfs, B_FALSE, acl_ids->z_fuid) ||
+           zfs_usergroup_overquota(zfsvfs, B_TRUE, acl_ids->z_fgid));
 }
 
 /*
@@ -2011,14 +2039,12 @@ zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
 
        mutex_exit(&zp->z_acl_lock);
 
-       zfs_acl_free(aclp);
-
        return (0);
 }
 
 int
 zfs_vsec_2_aclp(zfsvfs_t *zfsvfs, vtype_t obj_type,
-    vsecattr_t *vsecp, zfs_acl_t **zaclp)
+    vsecattr_t *vsecp, cred_t *cr, zfs_fuid_info_t **fuidp, zfs_acl_t **zaclp)
 {
        zfs_acl_t *aclp;
        zfs_acl_node_t *aclnode;
@@ -2041,9 +2067,9 @@ zfs_vsec_2_aclp(zfsvfs_t *zfsvfs, vtype_t obj_type,
                        return (error);
                }
        } else {
-               if ((error = zfs_copy_ace_2_fuid(obj_type, aclp,
+               if ((error = zfs_copy_ace_2_fuid(zfsvfs, obj_type, aclp,
                    vsecp->vsa_aclentp, aclnode->z_acldata, aclcnt,
-                   &aclnode->z_size)) != 0) {
+                   &aclnode->z_size, fuidp, cr)) != 0) {
                        zfs_acl_free(aclp);
                        zfs_acl_node_free(aclnode);
                        return (error);
@@ -2084,6 +2110,7 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
        int             error;
        zfs_acl_t       *aclp;
        zfs_fuid_info_t *fuidp = NULL;
+       boolean_t       fuid_dirtied;
 
        if (mask == 0)
                return (ENOSYS);
@@ -2094,7 +2121,8 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
        if (error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, skipaclchk, cr))
                return (error);
 
-       error = zfs_vsec_2_aclp(zfsvfs, ZTOV(zp)->v_type, vsecp, &aclp);
+       error = zfs_vsec_2_aclp(zfsvfs, ZTOV(zp)->v_type, vsecp, cr, &fuidp,
+           &aclp);
        if (error)
                return (error);
 
@@ -2106,11 +2134,6 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
                aclp->z_hints |= (zp->z_phys->zp_flags & V4_ACL_WIDE_FLAGS);
        }
 top:
-       if (error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, skipaclchk, cr)) {
-               zfs_acl_free(aclp);
-               return (error);
-       }
-
        mutex_enter(&zp->z_lock);
        mutex_enter(&zp->z_acl_lock);
 
@@ -2135,25 +2158,16 @@ top:
        } else if (aclp->z_acl_bytes > ZFS_ACE_SPACE) {
                dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0, aclp->z_acl_bytes);
        }
-       if (aclp->z_has_fuids) {
-               if (zfsvfs->z_fuid_obj == 0) {
-                       dmu_tx_hold_bonus(tx, DMU_NEW_OBJECT);
-                       dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0,
-                           FUID_SIZE_ESTIMATE(zfsvfs));
-                       dmu_tx_hold_zap(tx, MASTER_NODE_OBJ, FALSE, NULL);
-               } else {
-                       dmu_tx_hold_bonus(tx, zfsvfs->z_fuid_obj);
-                       dmu_tx_hold_write(tx, zfsvfs->z_fuid_obj, 0,
-                           FUID_SIZE_ESTIMATE(zfsvfs));
-               }
-       }
+       fuid_dirtied = zfsvfs->z_fuid_dirty;
+       if (fuid_dirtied)
+               zfs_fuid_txhold(zfsvfs, tx);
 
-       error = dmu_tx_assign(tx, zfsvfs->z_assign);
+       error = dmu_tx_assign(tx, TXG_NOWAIT);
        if (error) {
                mutex_exit(&zp->z_acl_lock);
                mutex_exit(&zp->z_lock);
 
-               if (error == ERESTART && zfsvfs->z_assign == TXG_NOWAIT) {
+               if (error == ERESTART) {
                        dmu_tx_wait(tx);
                        dmu_tx_abort(tx);
                        goto top;
@@ -2163,14 +2177,18 @@ top:
                return (error);
        }
 
-       error = zfs_aclset_common(zp, aclp, cr, &fuidp, tx);
+       error = zfs_aclset_common(zp, aclp, cr, tx);
        ASSERT(error == 0);
+       zp->z_acl_cached = aclp;
+
+       if (fuid_dirtied)
+               zfs_fuid_sync(zfsvfs, tx);
 
+       zfs_time_stamper_locked(zp, STATE_CHANGED, tx);
        zfs_log_acl(zilog, tx, zp, vsecp, fuidp);
 
        if (fuidp)
                zfs_fuid_info_free(fuidp);
-       zfs_acl_free(aclp);
        dmu_tx_commit(tx);
 done:
        mutex_exit(&zp->z_acl_lock);
@@ -2180,45 +2198,17 @@ done:
 }
 
 /*
- * working_mode returns the permissions that were not granted
+ * Check accesses of interest (AoI) against attributes of the dataset
+ * such as read-only.  Returns zero if no AoI conflict with dataset
+ * attributes, otherwise an appropriate errno is returned.
  */
 static int
-zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode,
-    boolean_t *check_privs, boolean_t skipaclchk, cred_t *cr)
+zfs_zaccess_dataset_check(znode_t *zp, uint32_t v4_mode)
 {
-       zfs_acl_t       *aclp;
-       zfsvfs_t        *zfsvfs = zp->z_zfsvfs;
-       int             error;
-       uid_t           uid = crgetuid(cr);
-       uint64_t        who;
-       uint16_t        type, iflags;
-       uint16_t        entry_type;
-       uint32_t        access_mask;
-       uint32_t        deny_mask = 0;
-       zfs_ace_hdr_t   *acep = NULL;
-       boolean_t       checkit;
-       uid_t           fowner;
-       uid_t           gowner;
-
-       /*
-        * Short circuit empty requests
-        */
-       if (v4_mode == 0)
-               return (0);
-
-       *check_privs = B_TRUE;
-
-       if (zfsvfs->z_assign >= TXG_INITIAL) {          /* ZIL replay */
-               *working_mode = 0;
-               return (0);
-       }
-
-       *working_mode = v4_mode;
-
        if ((v4_mode & WRITE_MASK) &&
            (zp->z_zfsvfs->z_vfs->vfs_flag & VFS_RDONLY) &&
-           (!IS_DEVVP(ZTOV(zp)))) {
-               *check_privs = B_FALSE;
+           (!IS_DEVVP(ZTOV(zp)) ||
+           (IS_DEVVP(ZTOV(zp)) && (v4_mode & WRITE_MASK_ATTRS)))) {
                return (EROFS);
        }
 
@@ -2230,31 +2220,64 @@ zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode,
            (zp->z_phys->zp_flags & (ZFS_READONLY | ZFS_IMMUTABLE))) ||
            (ZTOV(zp)->v_type == VDIR &&
            (zp->z_phys->zp_flags & ZFS_IMMUTABLE)))) {
-               *check_privs = B_FALSE;
                return (EPERM);
        }
 
        if ((v4_mode & (ACE_DELETE | ACE_DELETE_CHILD)) &&
            (zp->z_phys->zp_flags & ZFS_NOUNLINK)) {
-               *check_privs = B_FALSE;
                return (EPERM);
        }
 
        if (((v4_mode & (ACE_READ_DATA|ACE_EXECUTE)) &&
            (zp->z_phys->zp_flags & ZFS_AV_QUARANTINED))) {
-               *check_privs = B_FALSE;
                return (EACCES);
        }
 
-       /*
-        * The caller requested that the ACL check be skipped.  This
-        * would only happen if the caller checked VOP_ACCESS() with a
-        * 32 bit ACE mask and already had the appropriate permissions.
-        */
-       if (skipaclchk) {
-               *working_mode = 0;
-               return (0);
-       }
+       return (0);
+}
+
+/*
+ * The primary usage of this function is to loop through all of the
+ * ACEs in the znode, determining what accesses of interest (AoI) to
+ * the caller are allowed or denied.  The AoI are expressed as bits in
+ * the working_mode parameter.  As each ACE is processed, bits covered
+ * by that ACE are removed from the working_mode.  This removal
+ * facilitates two things.  The first is that when the working mode is
+ * empty (= 0), we know we've looked at all the AoI. The second is
+ * that the ACE interpretation rules don't allow a later ACE to undo
+ * something granted or denied by an earlier ACE.  Removing the
+ * discovered access or denial enforces this rule.  At the end of
+ * processing the ACEs, all AoI that were found to be denied are
+ * placed into the working_mode, giving the caller a mask of denied
+ * accesses.  Returns:
+ *     0               if all AoI granted
+ *     EACCESS         if the denied mask is non-zero
+ *     other error     if abnormal failure (e.g., IO error)
+ *
+ * A secondary usage of the function is to determine if any of the
+ * AoI are granted.  If an ACE grants any access in
+ * the working_mode, we immediately short circuit out of the function.
+ * This mode is chosen by setting anyaccess to B_TRUE.  The
+ * working_mode is not a denied access mask upon exit if the function
+ * is used in this manner.
+ */
+static int
+zfs_zaccess_aces_check(znode_t *zp, uint32_t *working_mode,
+    boolean_t anyaccess, cred_t *cr)
+{
+       zfsvfs_t        *zfsvfs = zp->z_zfsvfs;
+       zfs_acl_t       *aclp;
+       int             error;
+       uid_t           uid = crgetuid(cr);
+       uint64_t        who;
+       uint16_t        type, iflags;
+       uint16_t        entry_type;
+       uint32_t        access_mask;
+       uint32_t        deny_mask = 0;
+       zfs_ace_hdr_t   *acep = NULL;
+       boolean_t       checkit;
+       uid_t           fowner;
+       uid_t           gowner;
 
        zfs_fuid_map_ids(zp, cr, &fowner, &gowner);
 
@@ -2268,6 +2291,7 @@ zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode,
 
        while (acep = zfs_acl_next_ace(aclp, acep, &who, &access_mask,
            &iflags, &type)) {
+               uint32_t mask_matched;
 
                if (!zfs_acl_valid_ace_type(type, iflags))
                        continue;
@@ -2275,6 +2299,11 @@ zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode,
                if (ZTOV(zp)->v_type == VDIR && (iflags & ACE_INHERIT_ONLY_ACE))
                        continue;
 
+               /* Skip ACE if it does not affect any AoI */
+               mask_matched = (access_mask & *working_mode);
+               if (!mask_matched)
+                       continue;
+
                entry_type = (iflags & ACE_TYPE_FLAGS);
 
                checkit = B_FALSE;
@@ -2306,21 +2335,29 @@ zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode,
                                        checkit = B_TRUE;
                                break;
                        } else {
-                               zfs_acl_free(aclp);
                                mutex_exit(&zp->z_acl_lock);
                                return (EIO);
                        }
                }
 
                if (checkit) {
-                       uint32_t mask_matched = (access_mask & *working_mode);
-
-                       if (mask_matched) {
-                               if (type == DENY)
-                                       deny_mask |= mask_matched;
-
-                               *working_mode &= ~mask_matched;
+                       if (type == DENY) {
+                               DTRACE_PROBE3(zfs__ace__denies,
+                                   znode_t *, zp,
+                                   zfs_ace_hdr_t *, acep,
+                                   uint32_t, mask_matched);
+                               deny_mask |= mask_matched;
+                       } else {
+                               DTRACE_PROBE3(zfs__ace__allows,
+                                   znode_t *, zp,
+                                   zfs_ace_hdr_t *, acep,
+                                   uint32_t, mask_matched);
+                               if (anyaccess) {
+                                       mutex_exit(&zp->z_acl_lock);
+                                       return (0);
+                               }
                        }
+                       *working_mode &= ~mask_matched;
                }
 
                /* Are we done? */
@@ -2329,7 +2366,6 @@ zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode,
        }
 
        mutex_exit(&zp->z_acl_lock);
-       zfs_acl_free(aclp);
 
        /* Put the found 'denies' back on the working mode */
        if (deny_mask) {
@@ -2342,6 +2378,68 @@ zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode,
        return (0);
 }
 
+/*
+ * Return true if any access whatsoever granted, we don't actually
+ * care what access is granted.
+ */
+boolean_t
+zfs_has_access(znode_t *zp, cred_t *cr)
+{
+       uint32_t have = ACE_ALL_PERMS;
+
+       if (zfs_zaccess_aces_check(zp, &have, B_TRUE, cr) != 0) {
+               uid_t           owner;
+
+               owner = zfs_fuid_map_id(zp->z_zfsvfs,
+                   zp->z_phys->zp_uid, cr, ZFS_OWNER);
+
+               return (
+                   secpolicy_vnode_access(cr, ZTOV(zp), owner, VREAD) == 0 ||
+                   secpolicy_vnode_access(cr, ZTOV(zp), owner, VWRITE) == 0 ||
+                   secpolicy_vnode_access(cr, ZTOV(zp), owner, VEXEC) == 0 ||
+                   secpolicy_vnode_chown(cr, owner) == 0 ||
+                   secpolicy_vnode_setdac(cr, owner) == 0 ||
+                   secpolicy_vnode_remove(cr) == 0);
+       }
+       return (B_TRUE);
+}
+
+static int
+zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode,
+    boolean_t *check_privs, boolean_t skipaclchk, cred_t *cr)
+{
+       zfsvfs_t *zfsvfs = zp->z_zfsvfs;
+       int err;
+
+       *working_mode = v4_mode;
+       *check_privs = B_TRUE;
+
+       /*
+        * Short circuit empty requests
+        */
+       if (v4_mode == 0 || zfsvfs->z_replay) {
+               *working_mode = 0;
+               return (0);
+       }
+
+       if ((err = zfs_zaccess_dataset_check(zp, v4_mode)) != 0) {
+               *check_privs = B_FALSE;
+               return (err);
+       }
+
+       /*
+        * The caller requested that the ACL check be skipped.  This
+        * would only happen if the caller checked VOP_ACCESS() with a
+        * 32 bit ACE mask and already had the appropriate permissions.
+        */
+       if (skipaclchk) {
+               *working_mode = 0;
+               return (0);
+       }
+
+       return (zfs_zaccess_aces_check(zp, working_mode, B_FALSE, cr));
+}
+
 static int
 zfs_zaccess_append(znode_t *zp, uint32_t *working_mode, boolean_t *check_privs,
     cred_t *cr)
@@ -2353,6 +2451,78 @@ zfs_zaccess_append(znode_t *zp, uint32_t *working_mode, boolean_t *check_privs,
            check_privs, B_FALSE, cr));
 }
 
+int
+zfs_fastaccesschk_execute(znode_t *zdp, cred_t *cr)
+{
+       boolean_t owner = B_FALSE;
+       boolean_t groupmbr = B_FALSE;
+       boolean_t is_attr;
+       uid_t fowner;
+       uid_t gowner;
+       uid_t uid = crgetuid(cr);
+       int error;
+
+       if (zdp->z_phys->zp_flags & ZFS_AV_QUARANTINED)
+               return (EACCES);
+
+       is_attr = ((zdp->z_phys->zp_flags & ZFS_XATTR) &&
+           (ZTOV(zdp)->v_type == VDIR));
+       if (is_attr)
+               goto slow;
+
+       mutex_enter(&zdp->z_acl_lock);
+
+       if (zdp->z_phys->zp_flags & ZFS_NO_EXECS_DENIED) {
+               mutex_exit(&zdp->z_acl_lock);
+               return (0);
+       }
+
+       if (FUID_INDEX(zdp->z_phys->zp_uid) != 0 ||
+           FUID_INDEX(zdp->z_phys->zp_gid) != 0) {
+               mutex_exit(&zdp->z_acl_lock);
+               goto slow;
+       }
+
+       fowner = (uid_t)zdp->z_phys->zp_uid;
+       gowner = (uid_t)zdp->z_phys->zp_gid;
+
+       if (uid == fowner) {
+               owner = B_TRUE;
+               if (zdp->z_phys->zp_mode & S_IXUSR) {
+                       mutex_exit(&zdp->z_acl_lock);
+                       return (0);
+               } else {
+                       mutex_exit(&zdp->z_acl_lock);
+                       goto slow;
+               }
+       }
+       if (groupmember(gowner, cr)) {
+               groupmbr = B_TRUE;
+               if (zdp->z_phys->zp_mode & S_IXGRP) {
+                       mutex_exit(&zdp->z_acl_lock);
+                       return (0);
+               } else {
+                       mutex_exit(&zdp->z_acl_lock);
+                       goto slow;
+               }
+       }
+       if (!owner && !groupmbr) {
+               if (zdp->z_phys->zp_mode & S_IXOTH) {
+                       mutex_exit(&zdp->z_acl_lock);
+                       return (0);
+               }
+       }
+
+       mutex_exit(&zdp->z_acl_lock);
+
+slow:
+       DTRACE_PROBE(zfs__fastpath__execute__access__miss);
+       ZFS_ENTER(zdp->z_zfsvfs);
+       error = zfs_zaccess(zdp, ACE_EXECUTE, 0, B_FALSE, cr);
+       ZFS_EXIT(zdp->z_zfsvfs);
+       return (error);
+}
+
 /*
  * Determine whether Access should be granted/denied, invoking least
  * priv subsytem when a deny is determined.
@@ -2447,7 +2617,7 @@ zfs_zaccess(znode_t *zp, int mode, int flags, boolean_t skipaclchk, cred_t *cr)
                            owner, checkmode);
 
                if (error == 0 && (working_mode & ACE_WRITE_OWNER))
-                       error = secpolicy_vnode_chown(cr, B_TRUE);
+                       error = secpolicy_vnode_chown(cr, owner);
                if (error == 0 && (working_mode & ACE_WRITE_ACL))
                        error = secpolicy_vnode_setdac(cr, owner);
 
@@ -2456,7 +2626,7 @@ zfs_zaccess(znode_t *zp, int mode, int flags, boolean_t skipaclchk, cred_t *cr)
                        error = secpolicy_vnode_remove(cr);
 
                if (error == 0 && (working_mode & ACE_SYNCHRONIZE)) {
-                       error = secpolicy_vnode_chown(cr, B_FALSE);
+                       error = secpolicy_vnode_chown(cr, owner);
                }
                if (error == 0) {
                        /*