/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * The order and contents of this structure must be kept in sync with that of * aclreqcnt_v2_tmpl in nfs_stats.c */ char *aclnames_v2[] = { "null", "getacl", "setacl", "getattr", "access", "getxattrdir" }; /* * This table maps from NFS protocol number into call type. * Zero means a "Lookup" type call * One means a "Read" type call * Two means a "Write" type call * This is used to select a default time-out. */ uchar_t acl_call_type_v2[] = { 0, 0, 1, 0, 0, 0 }; /* * Similar table, but to determine which timer to use * (only real reads and writes!) */ uchar_t acl_timer_type_v2[] = { 0, 0, 0, 0, 0, 0 }; /* * This table maps from acl operation into a call type * for the semisoft mount option. * Zero means do not repeat operation. * One means repeat. */ uchar_t acl_ss_call_type_v2[] = { 0, 0, 1, 0, 0, 0 }; static int nfs_acl_dup_cache(vsecattr_t *, vsecattr_t *); static void nfs_acl_dup_res(rnode_t *, vsecattr_t *); /* ARGSUSED */ int acl_getacl2(vnode_t *vp, vsecattr_t *vsp, int flag, cred_t *cr) { int error; GETACL2args args; GETACL2res res; int doqueue; vattr_t va; rnode_t *rp; failinfo_t fi; hrtime_t t; rp = VTOR(vp); if (rp->r_secattr != NULL) { error = nfs_validate_caches(vp, cr); if (error) return (error); mutex_enter(&rp->r_statelock); if (rp->r_secattr != NULL) { if (nfs_acl_dup_cache(vsp, rp->r_secattr)) { mutex_exit(&rp->r_statelock); return (0); } } mutex_exit(&rp->r_statelock); } args.mask = vsp->vsa_mask; args.fh = *VTOFH(vp); fi.vp = vp; fi.fhp = (caddr_t)&args.fh; fi.copyproc = nfscopyfh; fi.lookupproc = nfslookup; fi.xattrdirproc = acl_getxattrdir2; res.resok.acl.vsa_aclentp = NULL; res.resok.acl.vsa_dfaclentp = NULL; doqueue = 1; t = gethrtime(); error = acl2call(VTOMI(vp), ACLPROC2_GETACL, xdr_GETACL2args, (caddr_t)&args, xdr_GETACL2res, (caddr_t)&res, cr, &doqueue, &res.status, 0, &fi); if (error) return (error); error = geterrno(res.status); if (!error) { (void) nfs_cache_fattr(vp, &res.resok.attr, &va, t, cr); nfs_acl_dup_res(rp, &res.resok.acl); *vsp = res.resok.acl; } else { PURGE_STALE_FH(error, vp, cr); } return (error); } /* ARGSUSED */ int acl_setacl2(vnode_t *vp, vsecattr_t *vsp, int flag, cred_t *cr) { int error; SETACL2args args; SETACL2res res; int doqueue; vattr_t va; rnode_t *rp; hrtime_t t; args.fh = *VTOFH(vp); args.acl = *vsp; doqueue = 1; t = gethrtime(); error = acl2call(VTOMI(vp), ACLPROC2_SETACL, xdr_SETACL2args, (caddr_t)&args, xdr_SETACL2res, (caddr_t)&res, cr, &doqueue, &res.status, 0, NULL); /* * On success, adding the arguments to setsecattr into the cache have * not proven adequate. On error, we cannot depend on cache. * Simply flush the cache to force the next getsecattr * to go over the wire. */ rp = VTOR(vp); mutex_enter(&rp->r_statelock); if (rp->r_secattr != NULL) { nfs_acl_free(rp->r_secattr); rp->r_secattr = NULL; } mutex_exit(&rp->r_statelock); if (error) return (error); error = geterrno(res.status); if (!error) { (void) nfs_cache_fattr(vp, &res.resok.attr, &va, t, cr); } else { PURGE_STALE_FH(error, vp, cr); } return (error); } int acl_getattr2_otw(vnode_t *vp, vattr_t *vap, cred_t *cr) { int error; GETATTR2args args; GETATTR2res res; int doqueue; failinfo_t fi; hrtime_t t; args.fh = *VTOFH(vp); fi.vp = vp; fi.fhp = (caddr_t)&args.fh; fi.copyproc = nfscopyfh; fi.lookupproc = nfslookup; fi.xattrdirproc = acl_getxattrdir2; doqueue = 1; t = gethrtime(); error = acl2call(VTOMI(vp), ACLPROC2_GETATTR, xdr_GETATTR2args, (caddr_t)&args, xdr_GETATTR2res, (caddr_t)&res, cr, &doqueue, &res.status, 0, &fi); if (error) return (error); error = geterrno(res.status); if (!error) { error = nfs_cache_fattr(vp, &res.resok.attr, vap, t, cr); } else { PURGE_STALE_FH(error, vp, cr); } return (error); } /* ARGSUSED */ int acl_access2(vnode_t *vp, int mode, int flags, cred_t *cr) { int error; ACCESS2args args; ACCESS2res res; int doqueue; uint32 acc; rnode_t *rp; cred_t *cred, *ncr; vattr_t va; failinfo_t fi; nfs_access_type_t cacc; hrtime_t t; acc = 0; if (mode & VREAD) acc |= ACCESS2_READ; if (mode & VWRITE) { if (vn_is_readonly(vp) && !IS_DEVVP(vp)) return (EROFS); if (vp->v_type == VDIR) acc |= ACCESS2_DELETE; acc |= ACCESS2_MODIFY | ACCESS2_EXTEND; } if (mode & VEXEC) { if (vp->v_type == VDIR) acc |= ACCESS2_LOOKUP; else acc |= ACCESS2_EXECUTE; } rp = VTOR(vp); if (vp->v_type == VDIR) { args.access = ACCESS2_READ | ACCESS2_DELETE | ACCESS2_MODIFY | ACCESS2_EXTEND | ACCESS2_LOOKUP; } else { args.access = ACCESS2_READ | ACCESS2_MODIFY | ACCESS2_EXTEND | ACCESS2_EXECUTE; } args.fh = *VTOFH(vp); fi.vp = vp; fi.fhp = (caddr_t)&args.fh; fi.copyproc = nfscopyfh; fi.lookupproc = nfslookup; fi.xattrdirproc = acl_getxattrdir2; cred = cr; ncr = crnetadjust(cred); tryagain: if (rp->r_acache != NULL) { cacc = nfs_access_check(rp, acc, cr); if (cacc == NFS_ACCESS_ALLOWED) return (0); if (cacc == NFS_ACCESS_DENIED) { /* * If the cred can be adjusted, try again * with the new cred. */ if (ncr != NULL) { cred = ncr; ncr = NULL; goto tryagain; } return (EACCES); } } doqueue = 1; t = gethrtime(); error = acl2call(VTOMI(vp), ACLPROC2_ACCESS, xdr_ACCESS2args, (caddr_t)&args, xdr_ACCESS2res, (caddr_t)&res, cred, &doqueue, &res.status, 0, &fi); if (error) { if (cred != cr) crfree(cred); return (error); } error = geterrno(res.status); if (!error) { (void) nfs_cache_fattr(vp, &res.resok.attr, &va, t, cr); nfs_access_cache(rp, args.access, res.resok.access, cred); if ((acc & res.resok.access) != acc) { /* * If the cred can be adjusted, try again * with the new cred. */ if (ncr != NULL) { cred = ncr; ncr = NULL; goto tryagain; } error = EACCES; } } else { PURGE_STALE_FH(error, vp, cr); } if (cred != cr) crfree(cred); return (error); } static int xattr_lookup_neg_cache = 1; /* * Look up a hidden attribute directory over the wire; the vnode * we start with could be a file or directory. We have to be * tricky in recording the name in the rnode r_path - we use the * magic name XATTR_RPATH and rely on code in failover_lookup() to * detect this and use this routine to do the same lookup on * remapping. DNLC is easier: slashes are legal, so we use * XATTR_DIR_NAME as UFS does. */ int acl_getxattrdir2(vnode_t *vp, vnode_t **vpp, bool_t create, cred_t *cr, int rfscall_flags) { int error; GETXATTRDIR2args args; GETXATTRDIR2res res; int doqueue; failinfo_t fi; hrtime_t t; args.fh = *VTOFH(vp); args.create = create; fi.vp = vp; fi.fhp = NULL; /* no need to update, filehandle not copied */ fi.copyproc = nfscopyfh; fi.lookupproc = nfslookup; fi.xattrdirproc = acl_getxattrdir2; doqueue = 1; t = gethrtime(); error = acl2call(VTOMI(vp), ACLPROC2_GETXATTRDIR, xdr_GETXATTRDIR2args, (caddr_t)&args, xdr_GETXATTRDIR2res, (caddr_t)&res, cr, &doqueue, &res.status, rfscall_flags, &fi); if (!error) { error = geterrno(res.status); if (!error) { *vpp = makenfsnode(&res.resok.fh, &res.resok.attr, vp->v_vfsp, t, cr, VTOR(vp)->r_path, XATTR_RPATH); mutex_enter(&(*vpp)->v_lock); (*vpp)->v_flag |= V_XATTRDIR; mutex_exit(&(*vpp)->v_lock); if (!(rfscall_flags & RFSCALL_SOFT)) dnlc_update(vp, XATTR_DIR_NAME, *vpp); } else { PURGE_STALE_FH(error, vp, cr); if (error == ENOENT && xattr_lookup_neg_cache) dnlc_enter(vp, XATTR_DIR_NAME, DNLC_NO_VNODE); } } return (error); } /* * The order and contents of this structure must be kept in sync with that of * aclreqcnt_v3_tmpl in nfs_stats.c */ char *aclnames_v3[] = { "null", "getacl", "setacl", "getxattrdir" }; /* * This table maps from NFS protocol number into call type. * Zero means a "Lookup" type call * One means a "Read" type call * Two means a "Write" type call * This is used to select a default time-out. */ uchar_t acl_call_type_v3[] = { 0, 0, 1, 0 }; /* * This table maps from acl operation into a call type * for the semisoft mount option. * Zero means do not repeat operation. * One means repeat. */ uchar_t acl_ss_call_type_v3[] = { 0, 0, 1, 0 }; /* * Similar table, but to determine which timer to use * (only real reads and writes!) */ uchar_t acl_timer_type_v3[] = { 0, 0, 0, 0 }; /* ARGSUSED */ int acl_getacl3(vnode_t *vp, vsecattr_t *vsp, int flag, cred_t *cr) { int error; GETACL3args args; GETACL3res res; int doqueue; rnode_t *rp; failinfo_t fi; hrtime_t t; rp = VTOR(vp); if (rp->r_secattr != NULL) { error = nfs3_validate_caches(vp, cr); if (error) return (error); mutex_enter(&rp->r_statelock); if (rp->r_secattr != NULL) { if (nfs_acl_dup_cache(vsp, rp->r_secattr)) { mutex_exit(&rp->r_statelock); return (0); } } mutex_exit(&rp->r_statelock); } args.mask = vsp->vsa_mask; args.fh = *VTOFH3(vp); fi.vp = vp; fi.fhp = (caddr_t)&args.fh; fi.copyproc = nfs3copyfh; fi.lookupproc = nfs3lookup; fi.xattrdirproc = acl_getxattrdir3; res.resok.acl.vsa_aclentp = NULL; res.resok.acl.vsa_dfaclentp = NULL; doqueue = 1; t = gethrtime(); error = acl3call(VTOMI(vp), ACLPROC3_GETACL, xdr_GETACL3args, (caddr_t)&args, xdr_GETACL3res, (caddr_t)&res, cr, &doqueue, &res.status, 0, &fi); if (error) return (error); error = geterrno3(res.status); if (!error) { nfs3_cache_post_op_attr(vp, &res.resok.attr, t, cr); nfs_acl_dup_res(rp, &res.resok.acl); *vsp = res.resok.acl; } else { nfs3_cache_post_op_attr(vp, &res.resfail.attr, t, cr); PURGE_STALE_FH(error, vp, cr); } return (error); } /* ARGSUSED */ int acl_setacl3(vnode_t *vp, vsecattr_t *vsp, int flag, cred_t *cr) { int error; SETACL3args args; SETACL3res res; rnode_t *rp; int doqueue; hrtime_t t; args.fh = *VTOFH3(vp); args.acl = *vsp; doqueue = 1; t = gethrtime(); error = acl3call(VTOMI(vp), ACLPROC3_SETACL, xdr_SETACL3args, (caddr_t)&args, xdr_SETACL3res, (caddr_t)&res, cr, &doqueue, &res.status, 0, NULL); /* * On success, adding the arguments to setsecattr into the cache have * not proven adequate. On error, we cannot depend on cache. * Simply flush the cache to force the next getsecattr * to go over the wire. */ rp = VTOR(vp); mutex_enter(&rp->r_statelock); if (rp->r_secattr != NULL) { nfs_acl_free(rp->r_secattr); rp->r_secattr = NULL; } mutex_exit(&rp->r_statelock); if (error) return (error); error = geterrno3(res.status); if (!error) { nfs3_cache_post_op_attr(vp, &res.resok.attr, t, cr); } else { nfs3_cache_post_op_attr(vp, &res.resfail.attr, t, cr); PURGE_STALE_FH(error, vp, cr); } return (error); } int acl_getxattrdir3(vnode_t *vp, vnode_t **vpp, bool_t create, cred_t *cr, int rfscall_flags) { int error; GETXATTRDIR3args args; GETXATTRDIR3res res; int doqueue; struct vattr vattr; vnode_t *nvp; failinfo_t fi; hrtime_t t; args.fh = *VTOFH3(vp); args.create = create; fi.vp = vp; fi.fhp = (caddr_t)&args.fh; fi.copyproc = nfs3copyfh; fi.lookupproc = nfs3lookup; fi.xattrdirproc = acl_getxattrdir3; doqueue = 1; t = gethrtime(); error = acl3call(VTOMI(vp), ACLPROC3_GETXATTRDIR, xdr_GETXATTRDIR3args, (caddr_t)&args, xdr_GETXATTRDIR3res, (caddr_t)&res, cr, &doqueue, &res.status, rfscall_flags, &fi); if (error) return (error); error = geterrno3(res.status); if (!error) { if (res.resok.attr.attributes) { nvp = makenfs3node(&res.resok.fh, &res.resok.attr.attr, vp->v_vfsp, t, cr, VTOR(vp)->r_path, XATTR_RPATH); } else { nvp = makenfs3node(&res.resok.fh, NULL, vp->v_vfsp, t, cr, VTOR(vp)->r_path, XATTR_RPATH); if (nvp->v_type == VNON) { vattr.va_mask = AT_TYPE; error = nfs3getattr(nvp, &vattr, cr); if (error) { VN_RELE(nvp); return (error); } nvp->v_type = vattr.va_type; } } mutex_enter(&nvp->v_lock); nvp->v_flag |= V_XATTRDIR; mutex_exit(&nvp->v_lock); if (!(rfscall_flags & RFSCALL_SOFT)) dnlc_update(vp, XATTR_DIR_NAME, nvp); *vpp = nvp; } else { PURGE_STALE_FH(error, vp, cr); if (error == ENOENT && xattr_lookup_neg_cache) dnlc_enter(vp, XATTR_DIR_NAME, DNLC_NO_VNODE); } return (error); } void nfs_acl_free(vsecattr_t *vsp) { if (vsp->vsa_aclentp != NULL) { kmem_free(vsp->vsa_aclentp, vsp->vsa_aclcnt * sizeof (aclent_t)); } if (vsp->vsa_dfaclentp != NULL) { kmem_free(vsp->vsa_dfaclentp, vsp->vsa_dfaclcnt * sizeof (aclent_t)); } kmem_free(vsp, sizeof (*vsp)); } static int nfs_acl_dup_cache(vsecattr_t *vsp, vsecattr_t *rvsp) { size_t aclsize; if ((rvsp->vsa_mask & vsp->vsa_mask) != vsp->vsa_mask) return (0); if (vsp->vsa_mask & VSA_ACL) { ASSERT(rvsp->vsa_mask & VSA_ACLCNT); aclsize = rvsp->vsa_aclcnt * sizeof (aclent_t); vsp->vsa_aclentp = kmem_alloc(aclsize, KM_SLEEP); bcopy(rvsp->vsa_aclentp, vsp->vsa_aclentp, aclsize); } if (vsp->vsa_mask & VSA_ACLCNT) vsp->vsa_aclcnt = rvsp->vsa_aclcnt; if (vsp->vsa_mask & VSA_DFACL) { ASSERT(rvsp->vsa_mask & VSA_DFACLCNT); aclsize = rvsp->vsa_dfaclcnt * sizeof (aclent_t); vsp->vsa_dfaclentp = kmem_alloc(aclsize, KM_SLEEP); bcopy(rvsp->vsa_dfaclentp, vsp->vsa_dfaclentp, aclsize); } if (vsp->vsa_mask & VSA_DFACLCNT) vsp->vsa_dfaclcnt = rvsp->vsa_dfaclcnt; return (1); } static void nfs_acl_dup_res_impl(kmutex_t *statelock, vsecattr_t **rspp, vsecattr_t *vsp) { size_t aclsize; vsecattr_t *rvsp; mutex_enter(statelock); if (*rspp != NULL) rvsp = *rspp; else { rvsp = kmem_zalloc(sizeof (*rvsp), KM_NOSLEEP); if (rvsp == NULL) { mutex_exit(statelock); return; } *rspp = rvsp; } if (vsp->vsa_mask & VSA_ACL) { if (rvsp->vsa_aclentp != NULL && rvsp->vsa_aclcnt != vsp->vsa_aclcnt) { aclsize = rvsp->vsa_aclcnt * sizeof (aclent_t); kmem_free(rvsp->vsa_aclentp, aclsize); rvsp->vsa_aclentp = NULL; } if (vsp->vsa_aclcnt > 0) { aclsize = vsp->vsa_aclcnt * sizeof (aclent_t); if (rvsp->vsa_aclentp == NULL) { rvsp->vsa_aclentp = kmem_alloc(aclsize, KM_SLEEP); } bcopy(vsp->vsa_aclentp, rvsp->vsa_aclentp, aclsize); } rvsp->vsa_aclcnt = vsp->vsa_aclcnt; rvsp->vsa_mask |= VSA_ACL | VSA_ACLCNT; } if (vsp->vsa_mask & VSA_ACLCNT) { if (rvsp->vsa_aclentp != NULL && rvsp->vsa_aclcnt != vsp->vsa_aclcnt) { aclsize = rvsp->vsa_aclcnt * sizeof (aclent_t); kmem_free(rvsp->vsa_aclentp, aclsize); rvsp->vsa_aclentp = NULL; rvsp->vsa_mask &= ~VSA_ACL; } rvsp->vsa_aclcnt = vsp->vsa_aclcnt; rvsp->vsa_mask |= VSA_ACLCNT; } if (vsp->vsa_mask & VSA_DFACL) { if (rvsp->vsa_dfaclentp != NULL && rvsp->vsa_dfaclcnt != vsp->vsa_dfaclcnt) { aclsize = rvsp->vsa_dfaclcnt * sizeof (aclent_t); kmem_free(rvsp->vsa_dfaclentp, aclsize); rvsp->vsa_dfaclentp = NULL; } if (vsp->vsa_dfaclcnt > 0) { aclsize = vsp->vsa_dfaclcnt * sizeof (aclent_t); if (rvsp->vsa_dfaclentp == NULL) { rvsp->vsa_dfaclentp = kmem_alloc(aclsize, KM_SLEEP); } bcopy(vsp->vsa_dfaclentp, rvsp->vsa_dfaclentp, aclsize); } rvsp->vsa_dfaclcnt = vsp->vsa_dfaclcnt; rvsp->vsa_mask |= VSA_DFACL | VSA_DFACLCNT; } if (vsp->vsa_mask & VSA_DFACLCNT) { if (rvsp->vsa_dfaclentp != NULL && rvsp->vsa_dfaclcnt != vsp->vsa_dfaclcnt) { aclsize = rvsp->vsa_dfaclcnt * sizeof (aclent_t); kmem_free(rvsp->vsa_dfaclentp, aclsize); rvsp->vsa_dfaclentp = NULL; rvsp->vsa_mask &= ~VSA_DFACL; } rvsp->vsa_dfaclcnt = vsp->vsa_dfaclcnt; rvsp->vsa_mask |= VSA_DFACLCNT; } mutex_exit(statelock); } static void nfs_acl_dup_res(rnode_t *rp, vsecattr_t *vsp) { nfs_acl_dup_res_impl(&rp->r_statelock, &rp->r_secattr, vsp); }