X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/b0d623f7f2ae71ed96e60569f61f9a9a27016e80..15129b1c8dbb3650c63b70adb1cad9af601c6c17:/bsd/nfs/nfs4_subs.c diff --git a/bsd/nfs/nfs4_subs.c b/bsd/nfs/nfs4_subs.c index 6b1786cda..50c657b94 100644 --- a/bsd/nfs/nfs4_subs.c +++ b/bsd/nfs/nfs4_subs.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2009 Apple Inc. All rights reserved. + * Copyright (c) 2006-2011 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -69,6 +69,19 @@ #include #include +/* + * NFS_MAX_WHO is the maximum length of a string representation used + * in as an ace who, owner, or group. There is no explicit limit in the + * protocol, however the kauth routines have a limit of MAPATHLEN for + * strings including the trailing null character, so we impose that + * limit. This should be changed if kauth routines change. + * + * We also want some reasonable maximum, as 32 bits worth of string length + * is liable to cause problems. At the very least this limit must guarantee + * that any express that contains the 32 bit length from off the wire used in + * allocations does not overflow. + */ +#define NFS_MAX_WHO MAXPATHLEN /* * Create the unique client ID to use for this mount. @@ -87,7 +100,11 @@ * * In an attempt to differentiate mounts we include the mntfromname and mntonname * strings to the client ID (as long as they fit). We also make sure that the - * value does not conflict with any existing values in use. + * value does not conflict with any existing values in use (changing the unique ID). + * + * Note that info such as the server's address may change over the lifetime of the + * mount. But the client ID will not be updated because we don't want it changing + * simply because we switched to a different server address. */ int nfs4_init_clientid(struct nfsmount *nmp) @@ -120,7 +137,7 @@ nfs4_init_clientid(struct nfsmount *nmp) return (ENOMEM); vsfs = vfs_statfs(nmp->nm_mountp); - saddr = mbuf_data(nmp->nm_nam); + saddr = nmp->nm_saddr; ncip->nci_idlen = sizeof(uint32_t) + sizeof(en0addr) + saddr->sa_len + strlen(vsfs->f_mntfromname) + 1 + strlen(vsfs->f_mntonname) + 1; if (ncip->nci_idlen > NFS4_OPAQUE_LIMIT) @@ -199,10 +216,12 @@ nfs4_setclientid(struct nfsmount *nmp) thread_t thd; kauth_cred_t cred; struct nfsm_chain nmreq, nmrep; - struct sockaddr_in sin; - uint8_t *addr; - char raddr[32]; - int ralen = 0; + struct sockaddr_storage ss; + void *sinaddr = NULL; + char raddr[MAX_IPv6_STR_LEN]; + char uaddr[MAX_IPv6_STR_LEN+16]; + int ualen = 0; + in_port_t port; thd = current_thread(); cred = IS_VALID_CRED(nmp->nm_mcred) ? nmp->nm_mcred : vfs_context_ucred(vfs_context_kernel()); @@ -224,26 +243,35 @@ nfs4_setclientid(struct nfsmount *nmp) nfsm_chain_add_64(error, &nmreq, nmp->nm_mounttime); nfsm_chain_add_32(error, &nmreq, nmp->nm_longid->nci_idlen); nfsm_chain_add_opaque(error, &nmreq, nmp->nm_longid->nci_id, nmp->nm_longid->nci_idlen); + nfsmout_if(error); /* cb_client4 callback; */ - if (nmp->nm_cbid && nfs4_cb_port && - !(error = sock_getsockname(nmp->nm_so, (struct sockaddr*)&sin, sizeof(sin)))) { - /* assemble r_addr = h1.h2.h3.h4.p1.p2 */ - /* h = source address of nmp->nm_so */ - /* p = nfs4_cb_port */ - addr = (uint8_t*)&sin.sin_addr.s_addr; - ralen = snprintf(raddr, sizeof(raddr), "%d.%d.%d.%d.%d.%d", - addr[0], addr[1], addr[2], addr[3], - ((nfs4_cb_port >> 8) & 0xff), - (nfs4_cb_port & 0xff)); - /* make sure it fit, give up if it didn't */ - if (ralen >= (int)sizeof(raddr)) - ralen = 0; - } - if (ralen > 0) { + if (!NMFLAG(nmp, NOCALLBACK) && nmp->nm_cbid && nfs4_cb_port && + !sock_getsockname(nmp->nm_nso->nso_so, (struct sockaddr*)&ss, sizeof(ss))) { + if (ss.ss_family == AF_INET) { + sinaddr = &((struct sockaddr_in*)&ss)->sin_addr; + port = nfs4_cb_port; + } else if (ss.ss_family == AF_INET6) { + sinaddr = &((struct sockaddr_in6*)&ss)->sin6_addr; + port = nfs4_cb_port6; + } + if (sinaddr && port && (inet_ntop(ss.ss_family, sinaddr, raddr, sizeof(raddr)) == raddr)) { + /* assemble r_addr = universal address (nmp->nm_nso->nso_so source IP addr + port) */ + ualen = snprintf(uaddr, sizeof(uaddr), "%s.%d.%d", raddr, + ((port >> 8) & 0xff), + (port & 0xff)); + /* make sure it fit, give up if it didn't */ + if (ualen >= (int)sizeof(uaddr)) + ualen = 0; + } + } + if (ualen > 0) { /* add callback info */ nfsm_chain_add_32(error, &nmreq, NFS4_CALLBACK_PROG); /* callback program */ - nfsm_chain_add_string(error, &nmreq, "tcp", 3); /* callback r_netid */ - nfsm_chain_add_string(error, &nmreq, raddr, ralen); /* callback r_addr */ + if (ss.ss_family == AF_INET) + nfsm_chain_add_string(error, &nmreq, "tcp", 3); /* callback r_netid */ + else if (ss.ss_family == AF_INET6) + nfsm_chain_add_string(error, &nmreq, "tcp6", 4); /* callback r_netid */ + nfsm_chain_add_string(error, &nmreq, uaddr, ualen); /* callback r_addr */ nfsm_chain_add_32(error, &nmreq, nmp->nm_cbid); /* callback_ident */ } else { /* don't provide valid callback info */ @@ -255,9 +283,11 @@ nfs4_setclientid(struct nfsmount *nmp) nfsm_chain_build_done(error, &nmreq); nfsm_assert(error, (numops == 0), EPROTO); nfsmout_if(error); - error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, R_SETUP, &nmrep, &xid, &status); + error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, NULL, R_SETUP, &nmrep, &xid, &status); nfsm_chain_skip_tag(error, &nmrep); nfsm_chain_get_32(error, &nmrep, numops); + if (!error && (numops != 1) && status) + error = status; nfsm_chain_op_check(error, &nmrep, NFS_OP_SETCLIENTID); if (error == NFSERR_CLID_INUSE) printf("nfs4_setclientid: client ID in use?\n"); @@ -267,43 +297,57 @@ nfs4_setclientid(struct nfsmount *nmp) nfsm_chain_cleanup(&nmreq); nfsm_chain_cleanup(&nmrep); - // SETCLIENTID_CONFIRM, PUTFH, GETATTR(FS) - numops = nmp->nm_dnp ? 3 : 1; - nfsm_chain_build_alloc_init(error, &nmreq, 28 * NFSX_UNSIGNED); + // SETCLIENTID_CONFIRM + numops = 1; + nfsm_chain_build_alloc_init(error, &nmreq, 15 * NFSX_UNSIGNED); nfsm_chain_add_compound_header(error, &nmreq, "setclid_conf", numops); numops--; nfsm_chain_add_32(error, &nmreq, NFS_OP_SETCLIENTID_CONFIRM); nfsm_chain_add_64(error, &nmreq, nmp->nm_clientid); nfsm_chain_add_64(error, &nmreq, verifier); - if (nmp->nm_dnp) { - /* refresh fs attributes too */ - numops--; - nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH); - nfsm_chain_add_fh(error, &nmreq, nmp->nm_vers, nmp->nm_dnp->n_fhp, nmp->nm_dnp->n_fhsize); - numops--; - nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR); - NFS_CLEAR_ATTRIBUTES(bitmap); - NFS4_PER_FS_ATTRIBUTES(bitmap); - nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN); - } nfsm_chain_build_done(error, &nmreq); nfsm_assert(error, (numops == 0), EPROTO); nfsmout_if(error); - error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, R_SETUP, &nmrep, &xid, &status); + error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, NULL, R_SETUP, &nmrep, &xid, &status); nfsm_chain_skip_tag(error, &nmrep); nfsm_chain_get_32(error, &nmrep, numops); nfsm_chain_op_check(error, &nmrep, NFS_OP_SETCLIENTID_CONFIRM); if (error) printf("nfs4_setclientid: confirm error %d\n", error); - if (nmp->nm_dnp) { - nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH); - nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR); - nfsmout_if(error); - lck_mtx_lock(&nmp->nm_lock); - error = nfs4_parsefattr(&nmrep, &nmp->nm_fsattr, NULL, NULL, NULL); - lck_mtx_unlock(&nmp->nm_lock); - } + lck_mtx_lock(&nmp->nm_lock); + if (!error) + nmp->nm_state |= NFSSTA_CLIENTID; + lck_mtx_unlock(&nmp->nm_lock); + + nfsmout_if(error || !nmp->nm_dnp); + /* take the opportunity to refresh fs attributes too */ + // PUTFH, GETATTR(FS) + numops = 2; + nfsm_chain_build_alloc_init(error, &nmreq, 23 * NFSX_UNSIGNED); + nfsm_chain_add_compound_header(error, &nmreq, "setclid_attr", numops); + numops--; + nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH); + nfsm_chain_add_fh(error, &nmreq, nmp->nm_vers, nmp->nm_dnp->n_fhp, nmp->nm_dnp->n_fhsize); + numops--; + nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR); + NFS_CLEAR_ATTRIBUTES(bitmap); + NFS4_PER_FS_ATTRIBUTES(bitmap); + nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN); + nfsm_chain_build_done(error, &nmreq); + nfsm_assert(error, (numops == 0), EPROTO); + nfsmout_if(error); + error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, NULL, R_SETUP, &nmrep, &xid, &status); + nfsm_chain_skip_tag(error, &nmrep); + nfsm_chain_get_32(error, &nmrep, numops); + lck_mtx_lock(&nmp->nm_lock); + nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH); + nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR); + if (!error) + error = nfs4_parsefattr(&nmrep, &nmp->nm_fsattr, NULL, NULL, NULL, NULL); + lck_mtx_unlock(&nmp->nm_lock); + if (error) /* ignore any error from the getattr */ + error = 0; nfsmout: nfsm_chain_cleanup(&nmreq); nfsm_chain_cleanup(&nmrep); @@ -341,7 +385,7 @@ nfs4_renew(struct nfsmount *nmp, int rpcflag) nfsm_assert(error, (numops == 0), EPROTO); nfsmout_if(error); error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, - current_thread(), cred, rpcflag, &nmrep, &xid, &status); + current_thread(), cred, NULL, rpcflag, &nmrep, &xid, &status); nfsm_chain_skip_tag(error, &nmrep); nfsm_chain_get_32(error, &nmrep, numops); nfsm_chain_op_check(error, &nmrep, NFS_OP_RENEW); @@ -381,8 +425,7 @@ out: if (error && (error != ETIMEDOUT) && (nmp->nm_clientid == clientid) && !(nmp->nm_state & NFSSTA_RECOVER)) { printf("nfs4_renew_timer: error %d, initiating recovery\n", error); - nmp->nm_state |= NFSSTA_RECOVER; - nfs_mount_sock_thread_wake(nmp); + nfs_need_recover(nmp, error); } interval = nmp->nm_fsattr.nfsa_lease / (error ? 4 : 2); @@ -392,6 +435,1034 @@ out: nfs_interval_timer_start(nmp->nm_renew_timer, interval * 1000); } +/* + * get the list of supported security flavors + * + * How we get them depends on what args we are given: + * + * FH? Name? Action + * ----- ----- ------ + * YES YES Use the fh and name provided + * YES NO 4.1-only just use the fh provided + * NO YES Use the node's (or root) fh and the name provided + * NO NO Use the node's parent and the node's name (4.1 will just use node's fh) + */ +int +nfs4_secinfo_rpc(struct nfsmount *nmp, struct nfsreq_secinfo_args *siap, kauth_cred_t cred, uint32_t *sec, int *seccountp) +{ + int error = 0, status, nfsvers, numops, namelen, fhsize; + vnode_t dvp = NULLVP; + nfsnode_t np, dnp; + u_char *fhp; + const char *vname = NULL, *name; + uint64_t xid; + struct nfsm_chain nmreq, nmrep; + + *seccountp = 0; + if (!nmp) + return (ENXIO); + nfsvers = nmp->nm_vers; + np = siap->rsia_np; + + nfsm_chain_null(&nmreq); + nfsm_chain_null(&nmrep); + + fhp = siap->rsia_fh; + fhsize = fhp ? siap->rsia_fhsize : 0; + name = siap->rsia_name; + namelen = name ? siap->rsia_namelen : 0; + if (name && !namelen) + namelen = strlen(name); + if (!fhp && name) { + if (!np) /* use PUTROOTFH */ + goto gotargs; + fhp = np->n_fhp; + fhsize = np->n_fhsize; + } + if (fhp && name) + goto gotargs; + + if (!np) + return (EIO); + nfs_node_lock_force(np); + if ((vnode_vtype(NFSTOV(np)) != VDIR) && np->n_sillyrename) { + /* + * The node's been sillyrenamed, so we need to use + * the sillyrename directory/name to do the open. + */ + struct nfs_sillyrename *nsp = np->n_sillyrename; + dnp = nsp->nsr_dnp; + dvp = NFSTOV(dnp); + if ((error = vnode_get(dvp))) { + nfs_node_unlock(np); + goto nfsmout; + } + fhp = dnp->n_fhp; + fhsize = dnp->n_fhsize; + name = nsp->nsr_name; + namelen = nsp->nsr_namlen; + } else { + /* + * [sigh] We can't trust VFS to get the parent right for named + * attribute nodes. (It likes to reparent the nodes after we've + * created them.) Luckily we can probably get the right parent + * from the n_parent we have stashed away. + */ + if ((np->n_vattr.nva_flags & NFS_FFLAG_IS_ATTR) && + (((dvp = np->n_parent)) && (error = vnode_get(dvp)))) + dvp = NULL; + if (!dvp) + dvp = vnode_getparent(NFSTOV(np)); + vname = vnode_getname(NFSTOV(np)); + if (!dvp || !vname) { + if (!error) + error = EIO; + nfs_node_unlock(np); + goto nfsmout; + } + dnp = VTONFS(dvp); + fhp = dnp->n_fhp; + fhsize = dnp->n_fhsize; + name = vname; + namelen = strnlen(vname, MAXPATHLEN); + } + nfs_node_unlock(np); + +gotargs: + // PUT(ROOT)FH + SECINFO + numops = 2; + nfsm_chain_build_alloc_init(error, &nmreq, + 4 * NFSX_UNSIGNED + NFSX_FH(nfsvers) + nfsm_rndup(namelen)); + nfsm_chain_add_compound_header(error, &nmreq, "secinfo", numops); + numops--; + if (fhp) { + nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH); + nfsm_chain_add_fh(error, &nmreq, nfsvers, fhp, fhsize); + } else { + nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTROOTFH); + } + numops--; + nfsm_chain_add_32(error, &nmreq, NFS_OP_SECINFO); + nfsm_chain_add_name(error, &nmreq, name, namelen, nmp); + nfsm_chain_build_done(error, &nmreq); + nfsm_assert(error, (numops == 0), EPROTO); + nfsmout_if(error); + error = nfs_request2(np, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, + current_thread(), cred, NULL, 0, &nmrep, &xid, &status); + nfsm_chain_skip_tag(error, &nmrep); + nfsm_chain_get_32(error, &nmrep, numops); + nfsm_chain_op_check(error, &nmrep, fhp ? NFS_OP_PUTFH : NFS_OP_PUTROOTFH); + nfsm_chain_op_check(error, &nmrep, NFS_OP_SECINFO); + nfsmout_if(error); + error = nfsm_chain_get_secinfo(&nmrep, sec, seccountp); +nfsmout: + nfsm_chain_cleanup(&nmreq); + nfsm_chain_cleanup(&nmrep); + if (vname) + vnode_putname(vname); + if (dvp != NULLVP) + vnode_put(dvp); + return (error); +} + +/* + * Parse an NFSv4 SECINFO array to an array of pseudo flavors. + * (Note: also works for MOUNTv3 security arrays.) + */ +int +nfsm_chain_get_secinfo(struct nfsm_chain *nmc, uint32_t *sec, int *seccountp) +{ + int error = 0, secmax, seccount, srvcount; + uint32_t flavor, val; + u_char oid[12]; + + seccount = srvcount = 0; + secmax = *seccountp; + *seccountp = 0; + + nfsm_chain_get_32(error, nmc, srvcount); + while (!error && (srvcount > 0) && (seccount < secmax)) { + nfsm_chain_get_32(error, nmc, flavor); + nfsmout_if(error); + switch (flavor) { + case RPCAUTH_NONE: + case RPCAUTH_SYS: + case RPCAUTH_KRB5: + case RPCAUTH_KRB5I: + case RPCAUTH_KRB5P: + sec[seccount++] = flavor; + break; + case RPCSEC_GSS: + /* we only recognize KRB5, KRB5I, KRB5P */ + nfsm_chain_get_32(error, nmc, val); /* OID length */ + nfsmout_if(error); + if (val != sizeof(krb5_mech)) { + nfsm_chain_adv(error, nmc, val); + nfsm_chain_adv(error, nmc, 2*NFSX_UNSIGNED); + break; + } + nfsm_chain_get_opaque(error, nmc, val, oid); /* OID bytes */ + nfsmout_if(error); + if (bcmp(oid, krb5_mech, sizeof(krb5_mech))) { + nfsm_chain_adv(error, nmc, 2*NFSX_UNSIGNED); + break; + } + nfsm_chain_get_32(error, nmc, val); /* QOP */ + nfsm_chain_get_32(error, nmc, val); /* SERVICE */ + nfsmout_if(error); + switch (val) { + case RPCSEC_GSS_SVC_NONE: + sec[seccount++] = RPCAUTH_KRB5; + break; + case RPCSEC_GSS_SVC_INTEGRITY: + sec[seccount++] = RPCAUTH_KRB5I; + break; + case RPCSEC_GSS_SVC_PRIVACY: + sec[seccount++] = RPCAUTH_KRB5P; + break; + } + break; + } + srvcount--; + } +nfsmout: + if (!error) + *seccountp = seccount; + return (error); +} + + +/* + * Fetch the FS_LOCATIONS attribute for the node found at directory/name. + */ +int +nfs4_get_fs_locations( + struct nfsmount *nmp, + nfsnode_t dnp, + u_char *fhp, + int fhsize, + const char *name, + vfs_context_t ctx, + struct nfs_fs_locations *nfslsp) +{ + int error = 0, numops, status; + uint32_t bitmap[NFS_ATTR_BITMAP_LEN]; + struct nfsreq rq, *req = &rq; + struct nfsreq_secinfo_args si; + struct nfsm_chain nmreq, nmrep; + uint64_t xid; + + if (!fhp && dnp) { + fhp = dnp->n_fhp; + fhsize = dnp->n_fhsize; + } + if (!fhp) + return (EINVAL); + + nfsm_chain_null(&nmreq); + nfsm_chain_null(&nmrep); + + NFSREQ_SECINFO_SET(&si, NULL, fhp, fhsize, name, 0); + numops = 3; + nfsm_chain_build_alloc_init(error, &nmreq, 18 * NFSX_UNSIGNED); + nfsm_chain_add_compound_header(error, &nmreq, "fs_locations", numops); + numops--; + nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH); + nfsm_chain_add_fh(error, &nmreq, NFS_VER4, fhp, fhsize); + numops--; + nfsm_chain_add_32(error, &nmreq, NFS_OP_LOOKUP); + nfsm_chain_add_name(error, &nmreq, name, strlen(name), nmp); + numops--; + nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR); + NFS_CLEAR_ATTRIBUTES(bitmap); + NFS_BITMAP_SET(bitmap, NFS_FATTR_FS_LOCATIONS); + nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN); + nfsm_chain_build_done(error, &nmreq); + nfsm_assert(error, (numops == 0), EPROTO); + nfsmout_if(error); + error = nfs_request_async(dnp, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, + vfs_context_thread(ctx), vfs_context_ucred(ctx), &si, 0, NULL, &req); + if (!error) + error = nfs_request_async_finish(req, &nmrep, &xid, &status); + nfsm_chain_skip_tag(error, &nmrep); + nfsm_chain_get_32(error, &nmrep, numops); + nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH); + nfsm_chain_op_check(error, &nmrep, NFS_OP_LOOKUP); + nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR); + nfsmout_if(error); + error = nfs4_parsefattr(&nmrep, NULL, NULL, NULL, NULL, nfslsp); +nfsmout: + nfsm_chain_cleanup(&nmrep); + nfsm_chain_cleanup(&nmreq); + return (error); +} + +/* + * Referral trigger nodes may not have many attributes provided by the + * server, so put some default values in place. + */ +void +nfs4_default_attrs_for_referral_trigger( + nfsnode_t dnp, + char *name, + int namelen, + struct nfs_vattr *nvap, + fhandle_t *fhp) +{ + struct timeval now; + microtime(&now); + int len; + + nvap->nva_flags = NFS_FFLAG_TRIGGER | NFS_FFLAG_TRIGGER_REFERRAL; + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_TYPE)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TYPE); + nvap->nva_type = VDIR; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_FSID)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_FSID); + nvap->nva_fsid.major = 0; + nvap->nva_fsid.minor = 0; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_OWNER) && dnp) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_OWNER); + nvap->nva_uid = dnp->n_vattr.nva_uid; + nvap->nva_uuuid = dnp->n_vattr.nva_uuuid; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_OWNER_GROUP) && dnp) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_OWNER_GROUP); + nvap->nva_gid = dnp->n_vattr.nva_gid; + nvap->nva_guuid = dnp->n_vattr.nva_guuid; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_MODE)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_MODE); + nvap->nva_mode = 0777; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_SIZE)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_SIZE); + nvap->nva_size = 0; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_SPACE_USED)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_SPACE_USED); + nvap->nva_bytes = 0; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_NUMLINKS)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_NUMLINKS); + nvap->nva_nlink = 2; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_TIME_ACCESS)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_ACCESS); + nvap->nva_timesec[NFSTIME_ACCESS] = now.tv_sec; + nvap->nva_timensec[NFSTIME_ACCESS] = now.tv_usec * 1000; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_TIME_MODIFY)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_MODIFY); + nvap->nva_timesec[NFSTIME_MODIFY] = now.tv_sec; + nvap->nva_timensec[NFSTIME_MODIFY] = now.tv_usec * 1000; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_TIME_METADATA)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_METADATA); + nvap->nva_timesec[NFSTIME_CHANGE] = now.tv_sec; + nvap->nva_timensec[NFSTIME_CHANGE] = now.tv_usec * 1000; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_FILEID)) { + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_FILEID); + nvap->nva_fileid = 42; + } + if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_FILEHANDLE) && dnp && name && fhp) { + /* Build a fake filehandle made up of parent node pointer and name */ + NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_FILEHANDLE); + bcopy(&dnp, &fhp->fh_data[0], sizeof(dnp)); + len = sizeof(fhp->fh_data) - sizeof(dnp); + bcopy(name, &fhp->fh_data[0] + sizeof(dnp), MIN(len, namelen)); + fhp->fh_len = sizeof(dnp) + namelen; + if (fhp->fh_len > (int)sizeof(fhp->fh_data)) + fhp->fh_len = sizeof(fhp->fh_data); + } +} + +/* + * Set NFS bitmap according to what's set in vnode_attr (and supported by the server). + */ +void +nfs_vattr_set_bitmap(struct nfsmount *nmp, uint32_t *bitmap, struct vnode_attr *vap) +{ + int i; + + NFS_CLEAR_ATTRIBUTES(bitmap); + if (VATTR_IS_ACTIVE(vap, va_data_size)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_SIZE); + if (VATTR_IS_ACTIVE(vap, va_acl) && (nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_ACL)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_ACL); + if (VATTR_IS_ACTIVE(vap, va_flags)) { + NFS_BITMAP_SET(bitmap, NFS_FATTR_ARCHIVE); + NFS_BITMAP_SET(bitmap, NFS_FATTR_HIDDEN); + } + // NFS_BITMAP_SET(bitmap, NFS_FATTR_MIMETYPE) + if (VATTR_IS_ACTIVE(vap, va_mode) && !NMFLAG(nmp, ACLONLY)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_MODE); + if (VATTR_IS_ACTIVE(vap, va_uid) || VATTR_IS_ACTIVE(vap, va_uuuid)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_OWNER); + if (VATTR_IS_ACTIVE(vap, va_gid) || VATTR_IS_ACTIVE(vap, va_guuid)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_OWNER_GROUP); + // NFS_BITMAP_SET(bitmap, NFS_FATTR_SYSTEM) + if (vap->va_vaflags & VA_UTIMES_NULL) { + NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_ACCESS_SET); + NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_MODIFY_SET); + } else { + if (VATTR_IS_ACTIVE(vap, va_access_time)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_ACCESS_SET); + if (VATTR_IS_ACTIVE(vap, va_modify_time)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_MODIFY_SET); + } + if (VATTR_IS_ACTIVE(vap, va_backup_time)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_BACKUP); + if (VATTR_IS_ACTIVE(vap, va_create_time)) + NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_CREATE); + /* and limit to what is supported by server */ + for (i=0; i < NFS_ATTR_BITMAP_LEN; i++) + bitmap[i] &= nmp->nm_fsattr.nfsa_supp_attr[i]; +} + +/* + * Convert between NFSv4 and VFS ACE types + */ +uint32_t +nfs4_ace_nfstype_to_vfstype(uint32_t nfsacetype, int *errorp) +{ + switch (nfsacetype) { + case NFS_ACE_ACCESS_ALLOWED_ACE_TYPE: + return KAUTH_ACE_PERMIT; + case NFS_ACE_ACCESS_DENIED_ACE_TYPE: + return KAUTH_ACE_DENY; + case NFS_ACE_SYSTEM_AUDIT_ACE_TYPE: + return KAUTH_ACE_AUDIT; + case NFS_ACE_SYSTEM_ALARM_ACE_TYPE: + return KAUTH_ACE_ALARM; + } + *errorp = EBADRPC; + return 0; +} + +uint32_t +nfs4_ace_vfstype_to_nfstype(uint32_t vfstype, int *errorp) +{ + switch (vfstype) { + case KAUTH_ACE_PERMIT: + return NFS_ACE_ACCESS_ALLOWED_ACE_TYPE; + case KAUTH_ACE_DENY: + return NFS_ACE_ACCESS_DENIED_ACE_TYPE; + case KAUTH_ACE_AUDIT: + return NFS_ACE_SYSTEM_AUDIT_ACE_TYPE; + case KAUTH_ACE_ALARM: + return NFS_ACE_SYSTEM_ALARM_ACE_TYPE; + } + *errorp = EINVAL; + return 0; +} + +/* + * Convert between NFSv4 and VFS ACE flags + */ +uint32_t +nfs4_ace_nfsflags_to_vfsflags(uint32_t nfsflags) +{ + uint32_t vfsflags = 0; + + if (nfsflags & NFS_ACE_FILE_INHERIT_ACE) + vfsflags |= KAUTH_ACE_FILE_INHERIT; + if (nfsflags & NFS_ACE_DIRECTORY_INHERIT_ACE) + vfsflags |= KAUTH_ACE_DIRECTORY_INHERIT; + if (nfsflags & NFS_ACE_NO_PROPAGATE_INHERIT_ACE) + vfsflags |= KAUTH_ACE_LIMIT_INHERIT; + if (nfsflags & NFS_ACE_INHERIT_ONLY_ACE) + vfsflags |= KAUTH_ACE_ONLY_INHERIT; + if (nfsflags & NFS_ACE_SUCCESSFUL_ACCESS_ACE_FLAG) + vfsflags |= KAUTH_ACE_SUCCESS; + if (nfsflags & NFS_ACE_FAILED_ACCESS_ACE_FLAG) + vfsflags |= KAUTH_ACE_FAILURE; + if (nfsflags & NFS_ACE_INHERITED_ACE) + vfsflags |= KAUTH_ACE_INHERITED; + + return (vfsflags); +} + +uint32_t +nfs4_ace_vfsflags_to_nfsflags(uint32_t vfsflags) +{ + uint32_t nfsflags = 0; + + if (vfsflags & KAUTH_ACE_FILE_INHERIT) + nfsflags |= NFS_ACE_FILE_INHERIT_ACE; + if (vfsflags & KAUTH_ACE_DIRECTORY_INHERIT) + nfsflags |= NFS_ACE_DIRECTORY_INHERIT_ACE; + if (vfsflags & KAUTH_ACE_LIMIT_INHERIT) + nfsflags |= NFS_ACE_NO_PROPAGATE_INHERIT_ACE; + if (vfsflags & KAUTH_ACE_ONLY_INHERIT) + nfsflags |= NFS_ACE_INHERIT_ONLY_ACE; + if (vfsflags & KAUTH_ACE_SUCCESS) + nfsflags |= NFS_ACE_SUCCESSFUL_ACCESS_ACE_FLAG; + if (vfsflags & KAUTH_ACE_FAILURE) + nfsflags |= NFS_ACE_FAILED_ACCESS_ACE_FLAG; + if (vfsflags & KAUTH_ACE_INHERITED) + nfsflags |= NFS_ACE_INHERITED_ACE; + + return (nfsflags); +} + +/* + * Convert between NFSv4 ACE access masks and VFS access rights + */ +uint32_t +nfs4_ace_nfsmask_to_vfsrights(uint32_t nfsmask) +{ + uint32_t vfsrights = 0; + + if (nfsmask & NFS_ACE_READ_DATA) + vfsrights |= KAUTH_VNODE_READ_DATA; + if (nfsmask & NFS_ACE_LIST_DIRECTORY) + vfsrights |= KAUTH_VNODE_LIST_DIRECTORY; + if (nfsmask & NFS_ACE_WRITE_DATA) + vfsrights |= KAUTH_VNODE_WRITE_DATA; + if (nfsmask & NFS_ACE_ADD_FILE) + vfsrights |= KAUTH_VNODE_ADD_FILE; + if (nfsmask & NFS_ACE_APPEND_DATA) + vfsrights |= KAUTH_VNODE_APPEND_DATA; + if (nfsmask & NFS_ACE_ADD_SUBDIRECTORY) + vfsrights |= KAUTH_VNODE_ADD_SUBDIRECTORY; + if (nfsmask & NFS_ACE_READ_NAMED_ATTRS) + vfsrights |= KAUTH_VNODE_READ_EXTATTRIBUTES; + if (nfsmask & NFS_ACE_WRITE_NAMED_ATTRS) + vfsrights |= KAUTH_VNODE_WRITE_EXTATTRIBUTES; + if (nfsmask & NFS_ACE_EXECUTE) + vfsrights |= KAUTH_VNODE_EXECUTE; + if (nfsmask & NFS_ACE_DELETE_CHILD) + vfsrights |= KAUTH_VNODE_DELETE_CHILD; + if (nfsmask & NFS_ACE_READ_ATTRIBUTES) + vfsrights |= KAUTH_VNODE_READ_ATTRIBUTES; + if (nfsmask & NFS_ACE_WRITE_ATTRIBUTES) + vfsrights |= KAUTH_VNODE_WRITE_ATTRIBUTES; + if (nfsmask & NFS_ACE_DELETE) + vfsrights |= KAUTH_VNODE_DELETE; + if (nfsmask & NFS_ACE_READ_ACL) + vfsrights |= KAUTH_VNODE_READ_SECURITY; + if (nfsmask & NFS_ACE_WRITE_ACL) + vfsrights |= KAUTH_VNODE_WRITE_SECURITY; + if (nfsmask & NFS_ACE_WRITE_OWNER) + vfsrights |= KAUTH_VNODE_CHANGE_OWNER; + if (nfsmask & NFS_ACE_SYNCHRONIZE) + vfsrights |= KAUTH_VNODE_SYNCHRONIZE; + if ((nfsmask & NFS_ACE_GENERIC_READ) == NFS_ACE_GENERIC_READ) + vfsrights |= KAUTH_ACE_GENERIC_READ; + if ((nfsmask & NFS_ACE_GENERIC_WRITE) == NFS_ACE_GENERIC_WRITE) + vfsrights |= KAUTH_ACE_GENERIC_WRITE; + if ((nfsmask & NFS_ACE_GENERIC_EXECUTE) == NFS_ACE_GENERIC_EXECUTE) + vfsrights |= KAUTH_ACE_GENERIC_EXECUTE; + + return (vfsrights); +} + +uint32_t +nfs4_ace_vfsrights_to_nfsmask(uint32_t vfsrights) +{ + uint32_t nfsmask = 0; + + if (vfsrights & KAUTH_VNODE_READ_DATA) + nfsmask |= NFS_ACE_READ_DATA; + if (vfsrights & KAUTH_VNODE_LIST_DIRECTORY) + nfsmask |= NFS_ACE_LIST_DIRECTORY; + if (vfsrights & KAUTH_VNODE_WRITE_DATA) + nfsmask |= NFS_ACE_WRITE_DATA; + if (vfsrights & KAUTH_VNODE_ADD_FILE) + nfsmask |= NFS_ACE_ADD_FILE; + if (vfsrights & KAUTH_VNODE_APPEND_DATA) + nfsmask |= NFS_ACE_APPEND_DATA; + if (vfsrights & KAUTH_VNODE_ADD_SUBDIRECTORY) + nfsmask |= NFS_ACE_ADD_SUBDIRECTORY; + if (vfsrights & KAUTH_VNODE_READ_EXTATTRIBUTES) + nfsmask |= NFS_ACE_READ_NAMED_ATTRS; + if (vfsrights & KAUTH_VNODE_WRITE_EXTATTRIBUTES) + nfsmask |= NFS_ACE_WRITE_NAMED_ATTRS; + if (vfsrights & KAUTH_VNODE_EXECUTE) + nfsmask |= NFS_ACE_EXECUTE; + if (vfsrights & KAUTH_VNODE_DELETE_CHILD) + nfsmask |= NFS_ACE_DELETE_CHILD; + if (vfsrights & KAUTH_VNODE_READ_ATTRIBUTES) + nfsmask |= NFS_ACE_READ_ATTRIBUTES; + if (vfsrights & KAUTH_VNODE_WRITE_ATTRIBUTES) + nfsmask |= NFS_ACE_WRITE_ATTRIBUTES; + if (vfsrights & KAUTH_VNODE_DELETE) + nfsmask |= NFS_ACE_DELETE; + if (vfsrights & KAUTH_VNODE_READ_SECURITY) + nfsmask |= NFS_ACE_READ_ACL; + if (vfsrights & KAUTH_VNODE_WRITE_SECURITY) + nfsmask |= NFS_ACE_WRITE_ACL; + if (vfsrights & KAUTH_VNODE_CHANGE_OWNER) + nfsmask |= NFS_ACE_WRITE_OWNER; + if (vfsrights & KAUTH_VNODE_SYNCHRONIZE) + nfsmask |= NFS_ACE_SYNCHRONIZE; + if (vfsrights & KAUTH_ACE_GENERIC_READ) + nfsmask |= NFS_ACE_GENERIC_READ; + if (vfsrights & KAUTH_ACE_GENERIC_WRITE) + nfsmask |= NFS_ACE_GENERIC_WRITE; + if (vfsrights & KAUTH_ACE_GENERIC_EXECUTE) + nfsmask |= NFS_ACE_GENERIC_EXECUTE; + if (vfsrights & KAUTH_ACE_GENERIC_ALL) + nfsmask |= (KAUTH_ACE_GENERIC_READ|KAUTH_ACE_GENERIC_WRITE|NFS_ACE_GENERIC_EXECUTE); + + return (nfsmask); +} + +/* + * Map an NFSv4 ID string to a VFS guid. + * + * Try to use the ID mapping service... but we may fallback to trying to do it ourselves. + */ +int +nfs4_id2guid(/*const*/ char *id, guid_t *guidp, int isgroup) +{ + int error1 = 0, error = 0, compare; + guid_t guid1, guid2, *gp; + ntsid_t sid; + long num, unknown; + const char *p, *at; + + *guidp = kauth_null_guid; + compare = ((nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE) && + (nfs_idmap_ctrl & NFS_IDMAP_CTRL_COMPARE_RESULTS)); + unknown = (nfs_idmap_ctrl & NFS_IDMAP_CTRL_UNKNOWN_IS_99) ? 99 : -2; + + /* + * First check if it is just a simple numeric ID string or a special "XXX@" name. + * If it's a number, there's no need trying to ask the IDMAP service to map it. + * If it's a special "XXX@" name, we want to make sure to treat it as a group. + */ + num = 1; + at = NULL; + p = id; + while (*p) { + if ((*p < '0') || (*p > '9')) + num = 0; + if (*p == '@') + at = p; + p++; + } + if (at && !at[1] && !isgroup) + isgroup = 1; /* special "XXX@" names should always be treated as groups */ + if (num) { + /* must be numeric ID (or empty) */ + num = *id ? strtol(id, NULL, 10) : unknown; + gp = guidp; + goto gotnumid; + } + + if (nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE) { + /* + * Ask the ID mapping service to map the ID string to a GUID. + * + * [sigh] this isn't a "pwnam/grnam" it's an NFS ID string! + */ + gp = compare ? &guid1 : guidp; + if (isgroup) + error = kauth_cred_grnam2guid(id, gp); + else + error = kauth_cred_pwnam2guid(id, gp); + if (error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS)) + printf("nfs4_id2guid: idmap failed for %s %s error %d\n", id, isgroup ? "G" : " ", error); + if (!error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_SUCCESSFUL_MAPPINGS)) + printf("nfs4_id2guid: idmap for %s %s got guid " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x\n", + id, isgroup ? "G" : " ", + gp->g_guid[0], gp->g_guid[1], gp->g_guid[2], gp->g_guid[3], + gp->g_guid[4], gp->g_guid[5], gp->g_guid[6], gp->g_guid[7], + gp->g_guid[8], gp->g_guid[9], gp->g_guid[10], gp->g_guid[11], + gp->g_guid[12], gp->g_guid[13], gp->g_guid[14], gp->g_guid[15]); + error1 = error; + } + if (error || compare || !(nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE)) { + /* + * fallback path... see if we can come up with an answer ourselves. + */ + gp = compare ? &guid2 : guidp; + + if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_WELLKNOWN_IDS) && at && !at[1]) { + /* must be a special ACE "who" ID */ + bzero(&sid, sizeof(sid)); + sid.sid_kind = 1; + sid.sid_authcount = 1; + if (!strcmp(id, "OWNER@")) { + // S-1-3-0 + sid.sid_authority[5] = 3; + sid.sid_authorities[0] = 0; + } else if (!strcmp(id, "GROUP@")) { + // S-1-3-1 + sid.sid_authority[5] = 3; + sid.sid_authorities[0] = 1; + } else if (!strcmp(id, "EVERYONE@")) { + // S-1-1-0 + sid.sid_authority[5] = 1; + sid.sid_authorities[0] = 0; + } else if (!strcmp(id, "INTERACTIVE@")) { + // S-1-5-4 + sid.sid_authority[5] = 5; + sid.sid_authorities[0] = 4; + } else if (!strcmp(id, "NETWORK@")) { + // S-1-5-2 + sid.sid_authority[5] = 5; + sid.sid_authorities[0] = 2; + } else if (!strcmp(id, "DIALUP@")) { + // S-1-5-1 + sid.sid_authority[5] = 5; + sid.sid_authorities[0] = 1; + } else if (!strcmp(id, "BATCH@")) { + // S-1-5-3 + sid.sid_authority[5] = 5; + sid.sid_authorities[0] = 3; + } else if (!strcmp(id, "ANONYMOUS@")) { + // S-1-5-7 + sid.sid_authority[5] = 5; + sid.sid_authorities[0] = 7; + } else if (!strcmp(id, "AUTHENTICATED@")) { + // S-1-5-11 + sid.sid_authority[5] = 5; + sid.sid_authorities[0] = 11; + } else if (!strcmp(id, "SERVICE@")) { + // S-1-5-6 + sid.sid_authority[5] = 5; + sid.sid_authorities[0] = 6; + } else { + // S-1-0-0 "NOBODY" + sid.sid_authority[5] = 0; + sid.sid_authorities[0] = 0; + } + error = kauth_cred_ntsid2guid(&sid, gp); + } else { + if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_COMMON_IDS) && at) { + /* must be user@domain */ + /* try to identify some well-known IDs */ + if (!strncmp(id, "root@", 5)) + num = 0; + else if (!strncmp(id, "wheel@", 6)) + num = 0; + else if (!strncmp(id, "nobody@", 7)) + num = -2; + else if (!strncmp(id, "nfsnobody@", 10)) + num = -2; + else + num = unknown; + } else if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_COMMON_IDS) && !strcmp(id, "nobody")) { + num = -2; + } else { + num = unknown; + } +gotnumid: + if (isgroup) + error = kauth_cred_gid2guid((gid_t)num, gp); + else + error = kauth_cred_uid2guid((uid_t)num, gp); + } + if (error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS)) + printf("nfs4_id2guid: fallback map failed for %s %s error %d\n", id, isgroup ? "G" : " ", error); + if (!error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_SUCCESSFUL_MAPPINGS)) + printf("nfs4_id2guid: fallback map for %s %s got guid " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x\n", + id, isgroup ? "G" : " ", + gp->g_guid[0], gp->g_guid[1], gp->g_guid[2], gp->g_guid[3], + gp->g_guid[4], gp->g_guid[5], gp->g_guid[6], gp->g_guid[7], + gp->g_guid[8], gp->g_guid[9], gp->g_guid[10], gp->g_guid[11], + gp->g_guid[12], gp->g_guid[13], gp->g_guid[14], gp->g_guid[15]); + } + + if (compare) { + /* compare the results, log if different */ + if (!error1 && !error) { + if (!kauth_guid_equal(&guid1, &guid2)) + printf("nfs4_id2guid: idmap/fallback results differ for %s %s - " + "idmap %02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x " + "fallback %02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x\n", + id, isgroup ? "G" : " ", + guid1.g_guid[0], guid1.g_guid[1], guid1.g_guid[2], guid1.g_guid[3], + guid1.g_guid[4], guid1.g_guid[5], guid1.g_guid[6], guid1.g_guid[7], + guid1.g_guid[8], guid1.g_guid[9], guid1.g_guid[10], guid1.g_guid[11], + guid1.g_guid[12], guid1.g_guid[13], guid1.g_guid[14], guid1.g_guid[15], + guid2.g_guid[0], guid2.g_guid[1], guid2.g_guid[2], guid2.g_guid[3], + guid2.g_guid[4], guid2.g_guid[5], guid2.g_guid[6], guid2.g_guid[7], + guid2.g_guid[8], guid2.g_guid[9], guid2.g_guid[10], guid2.g_guid[11], + guid2.g_guid[12], guid2.g_guid[13], guid2.g_guid[14], guid2.g_guid[15]); + /* copy idmap result to output guid */ + *guidp = guid1; + } else if (error1 && !error) { + printf("nfs4_id2guid: idmap/fallback results differ for %s %s - " + "idmap error %d " + "fallback %02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x\n", + id, isgroup ? "G" : " ", + error1, + guid2.g_guid[0], guid2.g_guid[1], guid2.g_guid[2], guid2.g_guid[3], + guid2.g_guid[4], guid2.g_guid[5], guid2.g_guid[6], guid2.g_guid[7], + guid2.g_guid[8], guid2.g_guid[9], guid2.g_guid[10], guid2.g_guid[11], + guid2.g_guid[12], guid2.g_guid[13], guid2.g_guid[14], guid2.g_guid[15]); + /* copy fallback result to output guid */ + *guidp = guid2; + } else if (!error1 && error) { + printf("nfs4_id2guid: idmap/fallback results differ for %s %s - " + "idmap %02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x " + "fallback error %d\n", + id, isgroup ? "G" : " ", + guid1.g_guid[0], guid1.g_guid[1], guid1.g_guid[2], guid1.g_guid[3], + guid1.g_guid[4], guid1.g_guid[5], guid1.g_guid[6], guid1.g_guid[7], + guid1.g_guid[8], guid1.g_guid[9], guid1.g_guid[10], guid1.g_guid[11], + guid1.g_guid[12], guid1.g_guid[13], guid1.g_guid[14], guid1.g_guid[15], + error); + /* copy idmap result to output guid */ + *guidp = guid1; + error = 0; + } else { + if (error1 != error) + printf("nfs4_id2guid: idmap/fallback results differ for %s %s - " + "idmap error %d fallback error %d\n", + id, isgroup ? "G" : " ", error1, error); + } + } + + return (error); +} + +/* + * Map a VFS guid to an NFSv4 ID string. + * + * Try to use the ID mapping service... but we may fallback to trying to do it ourselves. + */ +int +nfs4_guid2id(guid_t *guidp, char *id, int *idlen, int isgroup) +{ + int error1 = 0, error = 0, compare; + int id1len, id2len, len; + char *id1buf, *id1; + char numbuf[32]; + const char *id2 = NULL; + + id1buf = id1 = NULL; + id1len = id2len = 0; + compare = ((nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE) && + (nfs_idmap_ctrl & NFS_IDMAP_CTRL_COMPARE_RESULTS)); + + if (nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE) { + /* + * Ask the ID mapping service to map the GUID to an ID string. + * + * [sigh] this isn't a "pwnam" it's an NFS id string! + */ + + /* + * Stupid kauth_cred_guid2pwnam() function requires that the buffer + * be at least MAXPATHLEN bytes long even though most if not all ID + * strings will be much much shorter than that. + */ + if (compare || (*idlen < MAXPATHLEN)) { + MALLOC_ZONE(id1buf, char*, MAXPATHLEN, M_NAMEI, M_WAITOK); + if (!id1buf) + return (ENOMEM); + id1 = id1buf; + id1len = MAXPATHLEN; + } else { + id1 = id; + id1len = *idlen; + } + + if (isgroup) + error = kauth_cred_guid2grnam(guidp, id1); + else + error = kauth_cred_guid2pwnam(guidp, id1); + if (error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS)) + printf("nfs4_guid2id: idmap failed for " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s " + "error %d\n", + guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3], + guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7], + guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11], + guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15], + isgroup ? "G" : " ", error); + if (!error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_SUCCESSFUL_MAPPINGS)) + printf("nfs4_guid2id: idmap for " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s " + "got ID %s\n", + guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3], + guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7], + guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11], + guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15], + isgroup ? "G" : " ", id1); + error1 = error; + if (!error) { + if (compare) { + id1len = strnlen(id1, id1len); + } else if (id1 == id1buf) { + /* copy idmap result to output buffer */ + len = strlcpy(id, id1, *idlen); + if (len >= *idlen) + error = ENOSPC; + else + *idlen = len; + } + } + } + if (error || compare || !(nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE)) { + /* + * fallback path... see if we can come up with an answer ourselves. + */ + ntsid_t sid; + uid_t uid; + + if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_WELLKNOWN_IDS)) { + error = kauth_cred_guid2ntsid(guidp, &sid); + if (!error && (sid.sid_kind == 1) && (sid.sid_authcount == 1)) { + /* check if it's one of our well-known ACE WHO names */ + if (sid.sid_authority[5] == 0) { + if (sid.sid_authorities[0] == 0) // S-1-0-0 + id2 = "nobody@localdomain"; + } else if (sid.sid_authority[5] == 1) { + if (sid.sid_authorities[0] == 0) // S-1-1-0 + id2 = "EVERYONE@"; + } else if (sid.sid_authority[5] == 3) { + if (sid.sid_authorities[0] == 0) // S-1-3-0 + id2 = "OWNER@"; + else if (sid.sid_authorities[0] == 1) // S-1-3-1 + id2 = "GROUP@"; + } else if (sid.sid_authority[5] == 5) { + if (sid.sid_authorities[0] == ntohl(1)) // S-1-5-1 + id2 = "DIALUP@"; + else if (sid.sid_authorities[0] == ntohl(2)) // S-1-5-2 + id2 = "NETWORK@"; + else if (sid.sid_authorities[0] == ntohl(3)) // S-1-5-3 + id2 = "BATCH@"; + else if (sid.sid_authorities[0] == ntohl(4)) // S-1-5-4 + id2 = "INTERACTIVE@"; + else if (sid.sid_authorities[0] == ntohl(6)) // S-1-5-6 + id2 = "SERVICE@"; + else if (sid.sid_authorities[0] == ntohl(7)) // S-1-5-7 + id2 = "ANONYMOUS@"; + else if (sid.sid_authorities[0] == ntohl(11)) // S-1-5-11 + id2 = "AUTHENTICATED@"; + } + } + } + if (!id2) { + /* OK, let's just try mapping it to a UID/GID */ + if (isgroup) + error = kauth_cred_guid2gid(guidp, (gid_t*)&uid); + else + error = kauth_cred_guid2uid(guidp, &uid); + if (!error) { + if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_COMMON_IDS)) { + /* map well known uid's to strings */ + if (uid == 0) + id2 = isgroup ? "wheel@localdomain" : "root@localdomain"; + else if (uid == (uid_t)-2) + id2 = "nobody@localdomain"; + } + if (!id2) { + /* or just use a decimal number string. */ + snprintf(numbuf, sizeof(numbuf), "%d", uid); + id2 = numbuf; + } + } + } + if (error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS)) + printf("nfs4_guid2id: fallback map failed for " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s " + "error %d\n", + guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3], + guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7], + guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11], + guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15], + isgroup ? "G" : " ", error); + if (!error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_SUCCESSFUL_MAPPINGS)) + printf("nfs4_guid2id: fallback map for " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s " + "got ID %s\n", + guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3], + guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7], + guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11], + guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15], + isgroup ? "G" : " ", id2); + if (!error && id2) { + if (compare) { + id2len = strnlen(id2, MAXPATHLEN); + } else { + /* copy fallback result to output buffer */ + len = strlcpy(id, id2, *idlen); + if (len >= *idlen) + error = ENOSPC; + else + *idlen = len; + } + } + } + + if (compare) { + /* compare the results, log if different */ + if (!error1 && !error) { + if ((id1len != id2len) || strncmp(id1, id2, id1len)) + printf("nfs4_guid2id: idmap/fallback results differ for " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s " + "idmap %s fallback %s\n", + guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3], + guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7], + guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11], + guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15], + isgroup ? "G" : " ", id1, id2); + if (id1 == id1buf) { + /* copy idmap result to output buffer */ + len = strlcpy(id, id1, *idlen); + if (len >= *idlen) + error = ENOSPC; + else + *idlen = len; + } + } else if (error1 && !error) { + printf("nfs4_guid2id: idmap/fallback results differ for " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s " + "idmap error %d fallback %s\n", + guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3], + guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7], + guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11], + guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15], + isgroup ? "G" : " ", error1, id2); + /* copy fallback result to output buffer */ + len = strlcpy(id, id2, *idlen); + if (len >= *idlen) + error = ENOSPC; + else + *idlen = len; + } else if (!error1 && error) { + printf("nfs4_guid2id: idmap/fallback results differ for " + "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s " + "idmap %s fallback error %d\n", + guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3], + guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7], + guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11], + guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15], + isgroup ? "G" : " ", id1, error); + if (id1 == id1buf) { + /* copy idmap result to output buffer */ + len = strlcpy(id, id1, *idlen); + if (len >= *idlen) + error = ENOSPC; + else + *idlen = len; + } + error = 0; + } else { + if (error1 != error) + printf("nfs4_guid2id: idmap/fallback results differ for %s %s - " + "idmap error %d fallback error %d\n", + id, isgroup ? "G" : " ", error1, error); + } + } + if (id1buf) + FREE_ZONE(id1buf, MAXPATHLEN, M_NAMEI); + return (error); +} + + /* * Set a vnode attr's supported bits according to the given bitmap */ @@ -403,11 +1474,10 @@ nfs_vattr_set_supported(uint32_t *bitmap, struct vnode_attr *vap) // if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_CHANGE)) if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SIZE)) VATTR_SET_SUPPORTED(vap, va_data_size); - // if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NAMED_ATTR)) if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FSID)) VATTR_SET_SUPPORTED(vap, va_fsid); -// if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)) -// VATTR_SET_SUPPORTED(vap, va_acl); + if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)) + VATTR_SET_SUPPORTED(vap, va_acl); if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ARCHIVE)) VATTR_SET_SUPPORTED(vap, va_flags); if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FILEID)) @@ -419,10 +1489,14 @@ nfs_vattr_set_supported(uint32_t *bitmap, struct vnode_attr *vap) VATTR_SET_SUPPORTED(vap, va_mode); if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NUMLINKS)) VATTR_SET_SUPPORTED(vap, va_nlink); - if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) + if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) { VATTR_SET_SUPPORTED(vap, va_uid); - if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP)) + VATTR_SET_SUPPORTED(vap, va_uuuid); + } + if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP)) { VATTR_SET_SUPPORTED(vap, va_gid); + VATTR_SET_SUPPORTED(vap, va_guuid); + } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_RAWDEV)) VATTR_SET_SUPPORTED(vap, va_rdev); if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SPACE_USED)) @@ -450,15 +1524,21 @@ nfs4_parsefattr( struct nfs_fsattr *nfsap, struct nfs_vattr *nvap, fhandle_t *fhp, - struct dqblk *dqbp) + struct dqblk *dqbp, + struct nfs_fs_locations *nfslsp) { - int error = 0, attrbytes; - uint32_t val, val2, val3, i, j; + int error = 0, error2, rderror = 0, attrbytes; + uint32_t val, val2, val3, i; uint32_t bitmap[NFS_ATTR_BITMAP_LEN], len; - char *s; + size_t slen; + char sbuf[64], *s; struct nfs_fsattr nfsa_dummy; struct nfs_vattr nva_dummy; struct dqblk dqb_dummy; + kauth_acl_t acl = NULL; + uint32_t ace_type, ace_flags, ace_mask; + struct nfs_fs_locations nfsls_dummy; + struct sockaddr_storage ss; /* if not interested in some values... throw 'em into a local dummy variable */ if (!nfsap) @@ -467,8 +1547,14 @@ nfs4_parsefattr( nvap = &nva_dummy; if (!dqbp) dqbp = &dqb_dummy; + if (!nfslsp) + nfslsp = &nfsls_dummy; + bzero(nfslsp, sizeof(*nfslsp)); attrbytes = val = val2 = val3 = 0; + s = sbuf; + slen = sizeof(sbuf); + NVATTR_INIT(nvap); len = NFS_ATTR_BITMAP_LEN; nfsm_chain_get_bitmap(error, nmc, bitmap, len); @@ -489,17 +1575,19 @@ nfs4_parsefattr( if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TYPE)) { nfsm_chain_get_32(error, nmc, val); nvap->nva_type = nfstov_type(val, NFS_VER4); + if ((val == NFATTRDIR) || (val == NFNAMEDATTR)) + nvap->nva_flags |= NFS_FFLAG_IS_ATTR; + else + nvap->nva_flags &= ~NFS_FFLAG_IS_ATTR; attrbytes -= NFSX_UNSIGNED; } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FH_EXPIRE_TYPE)) { nfsm_chain_get_32(error, nmc, val); nfsmout_if(error); - if (val != NFS_FH_PERSISTENT) - printf("nfs: warning: non-persistent file handles!\n"); - if (val & ~0xff) - printf("nfs: warning unknown fh type: 0x%x\n", val); nfsap->nfsa_flags &= ~NFS_FSFLAG_FHTYPE_MASK; nfsap->nfsa_flags |= val << NFS_FSFLAG_FHTYPE_SHIFT; + if (val & ~0xff) + printf("nfs: warning unknown fh type: 0x%x\n", val); attrbytes -= NFSX_UNSIGNED; } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_CHANGE)) { @@ -529,9 +1617,9 @@ nfs4_parsefattr( if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NAMED_ATTR)) { nfsm_chain_get_32(error, nmc, val); if (val) - nvap->nva_flags |= NFS_FFLAG_NAMED_ATTR; + nvap->nva_flags |= NFS_FFLAG_HAS_NAMED_ATTRS; else - nvap->nva_flags &= ~NFS_FFLAG_NAMED_ATTR; + nvap->nva_flags &= ~NFS_FFLAG_HAS_NAMED_ATTRS; attrbytes -= NFSX_UNSIGNED; } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FSID)) { @@ -552,26 +1640,82 @@ nfs4_parsefattr( attrbytes -= NFSX_UNSIGNED; } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_RDATTR_ERROR)) { - nfsm_chain_get_32(error, nmc, error); + nfsm_chain_get_32(error, nmc, rderror); attrbytes -= NFSX_UNSIGNED; - nfsmout_if(error); + if (!rderror) { /* no error */ + NFS_BITMAP_CLR(bitmap, NFS_FATTR_RDATTR_ERROR); + NFS_BITMAP_CLR(nvap->nva_bitmap, NFS_FATTR_RDATTR_ERROR); + } } - if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)) { /* skip for now */ + if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)) { + error2 = 0; + ace_type = ace_flags = ace_mask = 0; nfsm_chain_get_32(error, nmc, val); /* ACE count */ + if (!error && (val > KAUTH_ACL_MAX_ENTRIES)) + error = EOVERFLOW; + if (!error && !((acl = kauth_acl_alloc(val)))) + error = ENOMEM; + if (!error && acl) { + acl->acl_entrycount = val; + acl->acl_flags = 0; + } + attrbytes -= NFSX_UNSIGNED; + nfsm_assert(error, (attrbytes >= 0), EBADRPC); for (i=0; !error && (i < val); i++) { - nfsm_chain_adv(error, nmc, 3 * NFSX_UNSIGNED); - nfsm_chain_get_32(error, nmc, val2); /* string length */ - nfsm_chain_adv(error, nmc, nfsm_rndup(val2)); - attrbytes -= 4*NFSX_UNSIGNED + nfsm_rndup(val2); + nfsm_chain_get_32(error, nmc, ace_type); + nfsm_chain_get_32(error, nmc, ace_flags); + nfsm_chain_get_32(error, nmc, ace_mask); + nfsm_chain_get_32(error, nmc, len); + if (!error && len >= NFS_MAX_WHO) + error = EBADRPC; + acl->acl_ace[i].ace_flags = nfs4_ace_nfstype_to_vfstype(ace_type, &error); + acl->acl_ace[i].ace_flags |= nfs4_ace_nfsflags_to_vfsflags(ace_flags); + acl->acl_ace[i].ace_rights = nfs4_ace_nfsmask_to_vfsrights(ace_mask); + if (!error && !error2 && (len >= slen)) { + if (s != sbuf) { + FREE(s, M_TEMP); + s = sbuf; + slen = sizeof(sbuf); + } + /* Let's add a bit more if we can to the allocation as to try and avoid future allocations */ + MALLOC(s, char*, (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO, M_TEMP, M_WAITOK); + if (s) + slen = (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO; + else + error = ENOMEM; + } + if (error2) + nfsm_chain_adv(error, nmc, nfsm_rndup(len)); + else + nfsm_chain_get_opaque(error, nmc, len, s); + if (!error && !error2) { + s[len] = '\0'; + error2 = nfs4_id2guid(s, &acl->acl_ace[i].ace_applicable, + (ace_flags & NFS_ACE_IDENTIFIER_GROUP)); + if (error2 && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS)) + printf("nfs4_parsefattr: ACE WHO %s is no one, no guid?, error %d\n", s, error2); + } + attrbytes -= 4*NFSX_UNSIGNED + nfsm_rndup(len); nfsm_assert(error, (attrbytes >= 0), EBADRPC); } + nfsmout_if(error); + if ((nvap != &nva_dummy) && !error2) { + nvap->nva_acl = acl; + acl = NULL; + } } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACLSUPPORT)) { + /* + * Support ACLs if: the server supports DENY/ALLOC ACEs and + * (just to be safe) FATTR_ACL is in the supported list too. + */ nfsm_chain_get_32(error, nmc, val); - if (val) + if ((val & (NFS_ACL_SUPPORT_ALLOW_ACL|NFS_ACL_SUPPORT_DENY_ACL)) && + NFS_BITMAP_ISSET(nfsap->nfsa_supp_attr, NFS_FATTR_ACL)) { nfsap->nfsa_flags |= NFS_FSFLAG_ACL; - else + } else { nfsap->nfsa_flags &= ~NFS_FSFLAG_ACL; + } attrbytes -= NFSX_UNSIGNED; } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ARCHIVE)) { /* SF_ARCHIVED */ @@ -640,23 +1784,151 @@ nfs4_parsefattr( nfsm_chain_get_64(error, nmc, nfsap->nfsa_files_total); attrbytes -= 2 * NFSX_UNSIGNED; } - if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FS_LOCATIONS)) { /* skip for now */ - nfsm_chain_get_32(error, nmc, val); /* root path length */ - nfsm_chain_adv(error, nmc, nfsm_rndup(val)); /* root path */ - attrbytes -= (2 * NFSX_UNSIGNED) + nfsm_rndup(val); - nfsm_chain_get_32(error, nmc, val); /* location count */ - for (i=0; !error && (i < val); i++) { - nfsm_chain_get_32(error, nmc, val2); /* server string length */ - nfsm_chain_adv(error, nmc, nfsm_rndup(val2)); /* server string */ - attrbytes -= (2 * NFSX_UNSIGNED) + nfsm_rndup(val2); - nfsm_chain_get_32(error, nmc, val2); /* pathname component count */ - for (j=0; !error && (j < val2); j++) { - nfsm_chain_get_32(error, nmc, val3); /* component length */ - nfsm_chain_adv(error, nmc, nfsm_rndup(val3)); /* component */ - attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val3); - nfsm_assert(error, (attrbytes >= 0), EBADRPC); + if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FS_LOCATIONS)) { + uint32_t loc, serv, comp; + struct nfs_fs_location *fsl; + struct nfs_fs_server *fss; + struct nfs_fs_path *fsp; + + /* get root pathname */ + fsp = &nfslsp->nl_root; + nfsm_chain_get_32(error, nmc, fsp->np_compcount); /* component count */ + attrbytes -= NFSX_UNSIGNED; + /* sanity check component count */ + if (!error && (fsp->np_compcount > MAXPATHLEN)) + error = EBADRPC; + nfsmout_if(error); + if (fsp->np_compcount) { + MALLOC(fsp->np_components, char **, fsp->np_compcount * sizeof(char*), M_TEMP, M_WAITOK|M_ZERO); + if (!fsp->np_components) + error = ENOMEM; + } + for (comp = 0; comp < fsp->np_compcount; comp++) { + nfsm_chain_get_32(error, nmc, val); /* component length */ + /* sanity check component length */ + if (!error && (val == 0)) { + /* + * Apparently some people think a path with zero components should + * be encoded with one zero-length component. So, just ignore any + * zero length components. + */ + comp--; + fsp->np_compcount--; + if (fsp->np_compcount == 0) { + FREE(fsp->np_components, M_TEMP); + fsp->np_components = NULL; + } + attrbytes -= NFSX_UNSIGNED; + continue; + } + if (!error && ((val < 1) || (val > MAXPATHLEN))) + error = EBADRPC; + nfsmout_if(error); + MALLOC(fsp->np_components[comp], char *, val+1, M_TEMP, M_WAITOK|M_ZERO); + if (!fsp->np_components[comp]) + error = ENOMEM; + nfsmout_if(error); + nfsm_chain_get_opaque(error, nmc, val, fsp->np_components[comp]); /* component */ + attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val); + } + nfsm_chain_get_32(error, nmc, nfslsp->nl_numlocs); /* fs location count */ + attrbytes -= NFSX_UNSIGNED; + /* sanity check location count */ + if (!error && (nfslsp->nl_numlocs > 256)) + error = EBADRPC; + nfsmout_if(error); + if (nfslsp->nl_numlocs > 0) { + MALLOC(nfslsp->nl_locations, struct nfs_fs_location **, nfslsp->nl_numlocs * sizeof(struct nfs_fs_location*), M_TEMP, M_WAITOK|M_ZERO); + if (!nfslsp->nl_locations) + error = ENOMEM; + } + nfsmout_if(error); + for (loc = 0; loc < nfslsp->nl_numlocs; loc++) { + nfsmout_if(error); + MALLOC(fsl, struct nfs_fs_location *, sizeof(struct nfs_fs_location), M_TEMP, M_WAITOK|M_ZERO); + if (!fsl) + error = ENOMEM; + nfslsp->nl_locations[loc] = fsl; + nfsm_chain_get_32(error, nmc, fsl->nl_servcount); /* server count */ + attrbytes -= NFSX_UNSIGNED; + /* sanity check server count */ + if (!error && ((fsl->nl_servcount < 1) || (fsl->nl_servcount > 256))) + error = EBADRPC; + nfsmout_if(error); + MALLOC(fsl->nl_servers, struct nfs_fs_server **, fsl->nl_servcount * sizeof(struct nfs_fs_server*), M_TEMP, M_WAITOK|M_ZERO); + if (!fsl->nl_servers) + error = ENOMEM; + for (serv = 0; serv < fsl->nl_servcount; serv++) { + nfsmout_if(error); + MALLOC(fss, struct nfs_fs_server *, sizeof(struct nfs_fs_server), M_TEMP, M_WAITOK|M_ZERO); + if (!fss) + error = ENOMEM; + fsl->nl_servers[serv] = fss; + nfsm_chain_get_32(error, nmc, val); /* server name length */ + /* sanity check server name length */ + if (!error && ((val < 1) || (val > MAXPATHLEN))) + error = EINVAL; + nfsmout_if(error); + MALLOC(fss->ns_name, char *, val+1, M_TEMP, M_WAITOK|M_ZERO); + if (!fss->ns_name) + error = ENOMEM; + nfsm_chain_get_opaque(error, nmc, val, fss->ns_name); /* server name */ + attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val); + nfsmout_if(error); + /* copy name to address if it converts to a sockaddr */ + if (nfs_uaddr2sockaddr(fss->ns_name, (struct sockaddr*)&ss)) { + fss->ns_addrcount = 1; + MALLOC(fss->ns_addresses, char **, sizeof(char *), M_TEMP, M_WAITOK|M_ZERO); + if (!fss->ns_addresses) + error = ENOMEM; + nfsmout_if(error); + MALLOC(fss->ns_addresses[0], char *, val+1, M_TEMP, M_WAITOK|M_ZERO); + if (!fss->ns_addresses[0]) + error = ENOMEM; + nfsmout_if(error); + strlcpy(fss->ns_addresses[0], fss->ns_name, val+1); + } + } + /* get pathname */ + fsp = &fsl->nl_path; + nfsm_chain_get_32(error, nmc, fsp->np_compcount); /* component count */ + attrbytes -= NFSX_UNSIGNED; + /* sanity check component count */ + if (!error && (fsp->np_compcount > MAXPATHLEN)) + error = EINVAL; + nfsmout_if(error); + if (fsp->np_compcount) { + MALLOC(fsp->np_components, char **, fsp->np_compcount * sizeof(char*), M_TEMP, M_WAITOK|M_ZERO); + if (!fsp->np_components) + error = ENOMEM; + } + for (comp = 0; comp < fsp->np_compcount; comp++) { + nfsm_chain_get_32(error, nmc, val); /* component length */ + /* sanity check component length */ + if (!error && (val == 0)) { + /* + * Apparently some people think a path with zero components should + * be encoded with one zero-length component. So, just ignore any + * zero length components. + */ + comp--; + fsp->np_compcount--; + if (fsp->np_compcount == 0) { + FREE(fsp->np_components, M_TEMP); + fsp->np_components = NULL; + } + attrbytes -= NFSX_UNSIGNED; + continue; + } + if (!error && ((val < 1) || (val > MAXPATHLEN))) + error = EINVAL; + nfsmout_if(error); + MALLOC(fsp->np_components[comp], char *, val+1, M_TEMP, M_WAITOK|M_ZERO); + if (!fsp->np_components[comp]) + error = ENOMEM; + nfsm_chain_get_opaque(error, nmc, val, fsp->np_components[comp]); /* component */ + attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val); } - nfsm_assert(error, (attrbytes >= 0), EBADRPC); } nfsm_assert(error, (attrbytes >= 0), EBADRPC); } @@ -724,34 +1996,74 @@ nfs4_parsefattr( attrbytes -= NFSX_UNSIGNED; } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) { - /* XXX Need ID mapping infrastructure - use ugly hack for now */ nfsm_chain_get_32(error, nmc, len); - nfsm_chain_get_opaque_pointer(error, nmc, len, s); + if (!error && len >= NFS_MAX_WHO) + error = EBADRPC; + if (!error && (len >= slen)) { + if (s != sbuf) { + FREE(s, M_TEMP); + s = sbuf; + slen = sizeof(sbuf); + } + /* Let's add a bit more if we can to the allocation as to try and avoid future allocations */ + MALLOC(s, char*, (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO, M_TEMP, M_WAITOK); + if (s) + slen = (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO; + else + error = ENOMEM; + } + nfsm_chain_get_opaque(error, nmc, len, s); + if (!error) { + s[len] = '\0'; + error = nfs4_id2guid(s, &nvap->nva_uuuid, 0); + if (!error) + error = kauth_cred_guid2uid(&nvap->nva_uuuid, &nvap->nva_uid); + if (error) { + /* unable to get either GUID or UID, set to default */ + nvap->nva_uid = (uid_t)((nfs_idmap_ctrl & NFS_IDMAP_CTRL_UNKNOWN_IS_99) ? 99 : -2); + if (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS) + printf("nfs4_parsefattr: owner %s is no one, no %s?, error %d\n", s, + kauth_guid_equal(&nvap->nva_uuuid, &kauth_null_guid) ? "guid" : "uid", + error); + error = 0; + } + } attrbytes -= NFSX_UNSIGNED + nfsm_rndup(len); - nfsmout_if(error); - if ((*s >= '0') && (*s <= '9')) - nvap->nva_uid = strtol(s, NULL, 10); - else if (!strncmp(s, "nobody@", 7)) - nvap->nva_uid = -2; - else if (!strncmp(s, "root@", 5)) - nvap->nva_uid = 0; - else - nvap->nva_uid = 99; /* unknown */ } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP)) { - /* XXX Need ID mapping infrastructure - use ugly hack for now */ nfsm_chain_get_32(error, nmc, len); - nfsm_chain_get_opaque_pointer(error, nmc, len, s); + if (!error && len >= NFS_MAX_WHO) + error = EBADRPC; + if (!error && (len >= slen)) { + if (s != sbuf) { + FREE(s, M_TEMP); + s = sbuf; + slen = sizeof(sbuf); + } + /* Let's add a bit more if we can to the allocation as to try and avoid future allocations */ + MALLOC(s, char*, (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO, M_TEMP, M_WAITOK); + if (s) + slen = (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO; + else + error = ENOMEM; + } + nfsm_chain_get_opaque(error, nmc, len, s); + if (!error) { + s[len] = '\0'; + error = nfs4_id2guid(s, &nvap->nva_guuid, 1); + if (!error) + error = kauth_cred_guid2gid(&nvap->nva_guuid, &nvap->nva_gid); + if (error) { + /* unable to get either GUID or GID, set to default */ + nvap->nva_gid = (gid_t)((nfs_idmap_ctrl & NFS_IDMAP_CTRL_UNKNOWN_IS_99) ? 99 : -2); + if (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS) + printf("nfs4_parsefattr: group %s is no one, no %s?, error %d\n", s, + kauth_guid_equal(&nvap->nva_guuid, &kauth_null_guid) ? "guid" : "gid", + error); + error = 0; + } + } attrbytes -= NFSX_UNSIGNED + nfsm_rndup(len); - nfsmout_if(error); - if ((*s >= '0') && (*s <= '9')) - nvap->nva_gid = strtol(s, NULL, 10); - else if (!strncmp(s, "nobody@", 7)) - nvap->nva_gid = -2; - else if (!strncmp(s, "root@", 5)) - nvap->nva_uid = 0; - else - nvap->nva_gid = 99; /* unknown */ } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_QUOTA_AVAIL_HARD)) { nfsm_chain_get_64(error, nmc, dqbp->dqb_bhardlimit); @@ -828,14 +2140,32 @@ nfs4_parsefattr( nfsm_chain_adv(error, nmc, 4*NFSX_UNSIGNED); /* just skip it */ attrbytes -= 4 * NFSX_UNSIGNED; } - if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MOUNTED_ON_FILEID)) { /* skip for now */ + if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MOUNTED_ON_FILEID)) { +#if CONFIG_TRIGGERS + /* we prefer the mounted on file ID, so just replace the fileid */ + nfsm_chain_get_64(error, nmc, nvap->nva_fileid); +#else nfsm_chain_adv(error, nmc, 2*NFSX_UNSIGNED); +#endif attrbytes -= 2 * NFSX_UNSIGNED; } /* advance over any leftover attrbytes */ nfsm_assert(error, (attrbytes >= 0), EBADRPC); nfsm_chain_adv(error, nmc, nfsm_rndup(attrbytes)); nfsmout: + if (error) + nfs_fs_locations_cleanup(nfslsp); + if (!error && rderror) + error = rderror; + /* free up temporary resources */ + if (s && (s != sbuf)) + FREE(s, M_TEMP); + if (acl) + kauth_acl_free(acl); + if (error && nvap->nva_acl) { + kauth_acl_free(nvap->nva_acl); + nvap->nva_acl = NULL; + } return (error); } @@ -845,51 +2175,18 @@ nfsmout: int nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct nfsmount *nmp) { - int error = 0, attrbytes, slen, i; - uint32_t *pattrbytes; + int error = 0, attrbytes, slen, len, i, isgroup; + uint32_t *pattrbytes, val, acecount;; uint32_t bitmap[NFS_ATTR_BITMAP_LEN]; - char s[32]; + char sbuf[64], *s; + kauth_acl_t acl; + gid_t gid; - /* - * Do this in two passes. - * First calculate the bitmap, then pack - * everything together and set the size. - */ + s = sbuf; + slen = sizeof(sbuf); - NFS_CLEAR_ATTRIBUTES(bitmap); - if (VATTR_IS_ACTIVE(vap, va_data_size)) - NFS_BITMAP_SET(bitmap, NFS_FATTR_SIZE); - if (VATTR_IS_ACTIVE(vap, va_acl)) { - // NFS_BITMAP_SET(bitmap, NFS_FATTR_ACL) - } - if (VATTR_IS_ACTIVE(vap, va_flags)) { - NFS_BITMAP_SET(bitmap, NFS_FATTR_ARCHIVE); - NFS_BITMAP_SET(bitmap, NFS_FATTR_HIDDEN); - } - // NFS_BITMAP_SET(bitmap, NFS_FATTR_MIMETYPE) - if (VATTR_IS_ACTIVE(vap, va_mode)) - NFS_BITMAP_SET(bitmap, NFS_FATTR_MODE); - if (VATTR_IS_ACTIVE(vap, va_uid)) - NFS_BITMAP_SET(bitmap, NFS_FATTR_OWNER); - if (VATTR_IS_ACTIVE(vap, va_gid)) - NFS_BITMAP_SET(bitmap, NFS_FATTR_OWNER_GROUP); - // NFS_BITMAP_SET(bitmap, NFS_FATTR_SYSTEM) - if (vap->va_vaflags & VA_UTIMES_NULL) { - NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_ACCESS_SET); - NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_MODIFY_SET); - } else { - if (VATTR_IS_ACTIVE(vap, va_access_time)) - NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_ACCESS_SET); - if (VATTR_IS_ACTIVE(vap, va_modify_time)) - NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_MODIFY_SET); - } - if (VATTR_IS_ACTIVE(vap, va_backup_time)) - NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_BACKUP); - if (VATTR_IS_ACTIVE(vap, va_create_time)) - NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_CREATE); - /* and limit to what is supported by server */ - for (i=0; i < NFS_ATTR_BITMAP_LEN; i++) - bitmap[i] &= nmp->nm_fsattr.nfsa_supp_attr[i]; + /* First calculate the bitmap... */ + nfs_vattr_set_bitmap(nmp, bitmap, vap); /* * Now pack it all together: @@ -905,7 +2202,43 @@ nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct n nfsm_chain_add_64(error, nmc, vap->va_data_size); attrbytes += 2*NFSX_UNSIGNED; } - // NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL) + if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)) { + acl = vap->va_acl; + if (!acl || (acl->acl_entrycount == KAUTH_FILESEC_NOACL)) + acecount = 0; + else + acecount = acl->acl_entrycount; + nfsm_chain_add_32(error, nmc, acecount); + attrbytes += NFSX_UNSIGNED; + for (i=0; !error && (i < (int)acecount); i++) { + val = (acl->acl_ace[i].ace_flags & KAUTH_ACE_KINDMASK); + val = nfs4_ace_vfstype_to_nfstype(val, &error); + nfsm_chain_add_32(error, nmc, val); + val = nfs4_ace_vfsflags_to_nfsflags(acl->acl_ace[i].ace_flags); + nfsm_chain_add_32(error, nmc, val); + val = nfs4_ace_vfsrights_to_nfsmask(acl->acl_ace[i].ace_rights); + nfsm_chain_add_32(error, nmc, val); + len = slen; + isgroup = (kauth_cred_guid2gid(&acl->acl_ace[i].ace_applicable, &gid) == 0); + error = nfs4_guid2id(&acl->acl_ace[i].ace_applicable, s, &len, isgroup); + if (error == ENOSPC) { + if (s != sbuf) { + FREE(s, M_TEMP); + s = sbuf; + } + len += 8; + MALLOC(s, char*, len, M_TEMP, M_WAITOK); + if (s) { + slen = len; + error = nfs4_guid2id(&acl->acl_ace[i].ace_applicable, s, &len, isgroup); + } else { + error = ENOMEM; + } + } + nfsm_chain_add_name(error, nmc, s, len, nmp); + attrbytes += 4*NFSX_UNSIGNED + nfsm_rndup(len); + } + } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ARCHIVE)) { nfsm_chain_add_32(error, nmc, (vap->va_flags & SF_ARCHIVED) ? 1 : 0); attrbytes += NFSX_UNSIGNED; @@ -920,26 +2253,56 @@ nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct n attrbytes += NFSX_UNSIGNED; } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) { - /* XXX Need ID mapping infrastructure - use ugly hack for now */ - if (vap->va_uid == 0) - slen = snprintf(s, sizeof(s), "root@localdomain"); - else if (vap->va_uid == (uid_t)-2) - slen = snprintf(s, sizeof(s), "nobody@localdomain"); - else - slen = snprintf(s, sizeof(s), "%d", vap->va_uid); - nfsm_chain_add_string(error, nmc, s, slen); - attrbytes += NFSX_UNSIGNED + nfsm_rndup(slen); + nfsmout_if(error); + /* if we have va_uuuid use it, otherwise use uid */ + if (!VATTR_IS_ACTIVE(vap, va_uuuid)) { + error = kauth_cred_uid2guid(vap->va_uid, &vap->va_uuuid); + nfsmout_if(error); + } + len = slen; + error = nfs4_guid2id(&vap->va_uuuid, s, &len, 0); + if (error == ENOSPC) { + if (s != sbuf) { + FREE(s, M_TEMP); + s = sbuf; + } + len += 8; + MALLOC(s, char*, len, M_TEMP, M_WAITOK); + if (s) { + slen = len; + error = nfs4_guid2id(&vap->va_uuuid, s, &len, 0); + } else { + error = ENOMEM; + } + } + nfsm_chain_add_name(error, nmc, s, len, nmp); + attrbytes += NFSX_UNSIGNED + nfsm_rndup(len); } if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP)) { - /* XXX Need ID mapping infrastructure - use ugly hack for now */ - if (vap->va_gid == 0) - slen = snprintf(s, sizeof(s), "root@localdomain"); - else if (vap->va_gid == (gid_t)-2) - slen = snprintf(s, sizeof(s), "nobody@localdomain"); - else - slen = snprintf(s, sizeof(s), "%d", vap->va_gid); - nfsm_chain_add_string(error, nmc, s, slen); - attrbytes += NFSX_UNSIGNED + nfsm_rndup(slen); + nfsmout_if(error); + /* if we have va_guuid use it, otherwise use gid */ + if (!VATTR_IS_ACTIVE(vap, va_guuid)) { + error = kauth_cred_gid2guid(vap->va_gid, &vap->va_guuid); + nfsmout_if(error); + } + len = slen; + error = nfs4_guid2id(&vap->va_guuid, s, &len, 1); + if (error == ENOSPC) { + if (s != sbuf) { + FREE(s, M_TEMP); + s = sbuf; + } + len += 8; + MALLOC(s, char*, len, M_TEMP, M_WAITOK); + if (s) { + slen = len; + error = nfs4_guid2id(&vap->va_guuid, s, &len, 1); + } else { + error = ENOMEM; + } + } + nfsm_chain_add_name(error, nmc, s, len, nmp); + attrbytes += NFSX_UNSIGNED + nfsm_rndup(len); } // NFS_BITMAP_SET(bitmap, NFS_FATTR_SYSTEM) if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_ACCESS_SET)) { @@ -978,16 +2341,100 @@ nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct n /* Now, set the attribute data length */ *pattrbytes = txdr_unsigned(attrbytes); nfsmout: + if (s && (s != sbuf)) + FREE(s, M_TEMP); return (error); } +/* + * Got the given error and need to start recovery (if not already started). + * Note: nmp must be locked! + */ +void +nfs_need_recover(struct nfsmount *nmp, int error) +{ + int wake = !(nmp->nm_state & NFSSTA_RECOVER); + + nmp->nm_state |= NFSSTA_RECOVER; + if ((error == NFSERR_ADMIN_REVOKED) || + (error == NFSERR_EXPIRED) || + (error == NFSERR_STALE_CLIENTID)) + nmp->nm_state |= NFSSTA_RECOVER_EXPIRED; + if (wake) + nfs_mount_sock_thread_wake(nmp); +} + +/* + * After recovery due to state expiry, check each node and + * drop any lingering delegation we thought we had. + * + * If a node has an open that is not lost and is not marked + * for reopen, then we hold onto any delegation because it is + * likely newly-granted. + */ +static void +nfs4_expired_check_delegation(nfsnode_t np, vfs_context_t ctx) +{ + struct nfsmount *nmp = NFSTONMP(np); + struct nfs_open_file *nofp; + int drop = 1; + + if ((np->n_flag & NREVOKE) || !(np->n_openflags & N_DELEG_MASK)) + return; + + lck_mtx_lock(&np->n_openlock); + + TAILQ_FOREACH(nofp, &np->n_opens, nof_link) { + if (!nofp->nof_opencnt) + continue; + if (nofp->nof_flags & NFS_OPEN_FILE_LOST) + continue; + if (nofp->nof_flags & NFS_OPEN_FILE_REOPEN) + continue; + /* we have an open that is not lost and not marked for reopen */ + // XXX print out what's keeping this node from dropping the delegation. + NP(nofp->nof_np, "nfs4_expired_check_delegation: !drop: opencnt %d flags 0x%x access %d %d mmap %d %d", + nofp->nof_opencnt, nofp->nof_flags, + nofp->nof_access, nofp->nof_deny, + nofp->nof_mmap_access, nofp->nof_mmap_deny); + drop = 0; + break; + } + + if (drop) { + /* need to drop a delegation */ + if (np->n_dreturn.tqe_next != NFSNOLIST) { + /* remove this node from the delegation return list */ + lck_mtx_lock(&nmp->nm_lock); + if (np->n_dreturn.tqe_next != NFSNOLIST) { + TAILQ_REMOVE(&nmp->nm_dreturnq, np, n_dreturn); + np->n_dreturn.tqe_next = NFSNOLIST; + } + lck_mtx_unlock(&nmp->nm_lock); + } + if (np->n_openflags & N_DELEG_MASK) { + np->n_openflags &= ~N_DELEG_MASK; + lck_mtx_lock(&nmp->nm_lock); + if (np->n_dlink.tqe_next != NFSNOLIST) { + TAILQ_REMOVE(&nmp->nm_delegations, np, n_dlink); + np->n_dlink.tqe_next = NFSNOLIST; + } + lck_mtx_unlock(&nmp->nm_lock); + nfs4_delegreturn_rpc(nmp, np->n_fhp, np->n_fhsize, &np->n_dstateid, + 0, vfs_context_thread(ctx), vfs_context_ucred(ctx)); + } + } + + lck_mtx_unlock(&np->n_openlock); +} + /* * Recover state for an NFS mount. * * Iterates over all open files, reclaiming opens and lock state. */ void -nfs4_recover(struct nfsmount *nmp) +nfs_recover(struct nfsmount *nmp) { struct timespec ts = { 1, 0 }; int error, lost, reopen; @@ -996,6 +2443,8 @@ nfs4_recover(struct nfsmount *nmp) struct nfs_file_lock *nflp, *nextnflp; struct nfs_lock_owner *nlop; thread_t thd = current_thread(); + nfsnode_t np, nextnp; + struct timeval now; restart: error = 0; @@ -1020,25 +2469,36 @@ restart: } while (nmp->nm_stateinuse); if (error) { if (error == EPIPE) - printf("nfs recovery reconnecting\n"); + printf("nfs recovery reconnecting for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid); else - printf("nfs recovery aborted\n"); + printf("nfs recovery aborted for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid); lck_mtx_unlock(&nmp->nm_lock); return; } - printf("nfs recovery started\n"); + microuptime(&now); + if (now.tv_sec == nmp->nm_recover_start) { + printf("nfs recovery throttled for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid); + lck_mtx_unlock(&nmp->nm_lock); + tsleep(nfs_recover, (PZERO-1), "nfsrecoverrestart", hz); + goto restart; + } + nmp->nm_recover_start = now.tv_sec; if (++nmp->nm_stategenid == 0) ++nmp->nm_stategenid; + printf("nfs recovery started for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid); lck_mtx_unlock(&nmp->nm_lock); /* for each open owner... */ TAILQ_FOREACH(noop, &nmp->nm_open_owners, noo_link) { /* for each of its opens... */ TAILQ_FOREACH(nofp, &noop->noo_opens, nof_oolink) { - if (!nofp->nof_access || (nofp->nof_flags & NFS_OPEN_FILE_LOST)) + if (!nofp->nof_access || (nofp->nof_flags & NFS_OPEN_FILE_LOST) || (nofp->nof_np->n_flag & NREVOKE)) continue; lost = reopen = 0; + /* for NFSv2/v3, just skip straight to lock reclaim */ + if (nmp->nm_vers < NFS_VER4) + goto reclaim_locks; if (nofp->nof_rw_drw) error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_BOTH, NFS_OPEN_SHARE_DENY_BOTH); if (!error && nofp->nof_w_drw) @@ -1056,45 +2516,80 @@ restart: */ if (!error && nofp->nof_rw) { error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_BOTH, NFS_OPEN_SHARE_DENY_NONE); - if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) - reopen = 1; + if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) { + reopen = error; + error = 0; + } } - if (!error && nofp->nof_w) { + if (!error && !reopen && nofp->nof_w) { error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_WRITE, NFS_OPEN_SHARE_DENY_NONE); - if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) - reopen = 1; + if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) { + reopen = error; + error = 0; + } } - if (!error && nofp->nof_r) { + if (!error && !reopen && nofp->nof_r) { error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_READ, NFS_OPEN_SHARE_DENY_NONE); - if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) - reopen = 1; + if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) { + reopen = error; + error = 0; + } } - if (error) { + /* + * If we hold delegated state but we don't have any non-delegated opens, + * then we should attempt to claim that state now (but don't return the + * delegation unless asked to). + */ + if ((nofp->nof_d_rw_drw || nofp->nof_d_w_drw || nofp->nof_d_r_drw || + nofp->nof_d_rw_dw || nofp->nof_d_w_dw || nofp->nof_d_r_dw || + nofp->nof_d_rw || nofp->nof_d_w || nofp->nof_d_r) && + (!nofp->nof_rw_drw && !nofp->nof_w_drw && !nofp->nof_r_drw && + !nofp->nof_rw_dw && !nofp->nof_w_dw && !nofp->nof_r_dw && + !nofp->nof_rw && !nofp->nof_w && !nofp->nof_r)) { + if (!error && !nfs_open_state_set_busy(nofp->nof_np, NULL)) { + error = nfs4_claim_delegated_state_for_node(nofp->nof_np, R_RECOVER); + if (!error && (nofp->nof_flags & NFS_OPEN_FILE_REOPEN)) + reopen = EAGAIN; + nfs_open_state_clear_busy(nofp->nof_np); + /* if claim didn't go well, we may need to return delegation now */ + if (nofp->nof_np->n_openflags & N_DELEG_RETURN) { + nfs4_delegation_return(nofp->nof_np, R_RECOVER, thd, noop->noo_cred); + if (!(nmp->nm_sockflags & NMSOCK_READY)) + error = ETIMEDOUT; /* looks like we need a reconnect */ + } + } + } + + /* + * Handle any issue claiming open state. + * Potential reopens need to first confirm that there are no locks. + */ + if (error || reopen) { /* restart recovery? */ if ((error == ETIMEDOUT) || nfs_mount_state_error_should_restart(error)) { if (error == ETIMEDOUT) nfs_need_reconnect(nmp); - tsleep(&lbolt, (PZERO-1), "nfsrecoverrestart", 0); - printf("nfs recovery restarting %d\n", error); + tsleep(nfs_recover, (PZERO-1), "nfsrecoverrestart", hz); + printf("nfs recovery restarting for %s, 0x%x, error %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error); goto restart; } - if (reopen && (nfs4_check_for_locks(noop, nofp) == 0)) { + if (reopen && (nfs_check_for_locks(noop, nofp) == 0)) { /* just reopen the file on next access */ - const char *vname = vnode_getname(NFSTOV(nofp->nof_np)); - printf("nfs4_recover: %d, need reopen for %s\n", error, vname ? vname : "???"); - vnode_putname(vname); + NP(nofp->nof_np, "nfs_recover: %d, need reopen for %d %p 0x%x", reopen, + kauth_cred_getuid(noop->noo_cred), nofp->nof_np, nofp->nof_np->n_flag); lck_mtx_lock(&nofp->nof_lock); nofp->nof_flags |= NFS_OPEN_FILE_REOPEN; lck_mtx_unlock(&nofp->nof_lock); - error = 0; } else { /* open file state lost */ + if (reopen) + NP(nofp->nof_np, "nfs_recover: %d, can't reopen because of locks %d %p", reopen, + kauth_cred_getuid(noop->noo_cred), nofp->nof_np); lost = 1; error = 0; - lck_mtx_lock(&nofp->nof_lock); - nofp->nof_flags &= ~NFS_OPEN_FILE_REOPEN; - lck_mtx_unlock(&nofp->nof_lock); + reopen = 0; } } else { /* no error, so make sure the reopen flag isn't set */ @@ -1102,83 +2597,96 @@ restart: nofp->nof_flags &= ~NFS_OPEN_FILE_REOPEN; lck_mtx_unlock(&nofp->nof_lock); } + /* * Scan this node's lock owner list for entries with this open owner, * then walk the lock owner's held lock list recovering each lock. */ -rescanlocks: +reclaim_locks: TAILQ_FOREACH(nlop, &nofp->nof_np->n_lock_owners, nlo_link) { + if (lost || reopen) + break; if (nlop->nlo_open_owner != noop) continue; TAILQ_FOREACH_SAFE(nflp, &nlop->nlo_locks, nfl_lolink, nextnflp) { + /* skip dead & blocked lock requests (shouldn't be any in the held lock list) */ if (nflp->nfl_flags & (NFS_FILE_LOCK_DEAD|NFS_FILE_LOCK_BLOCKED)) continue; - if (!lost) { - error = nfs4_lock_rpc(nofp->nof_np, nofp, nflp, 1, thd, noop->noo_cred); - if (!error) - continue; - /* restart recovery? */ - if ((error == ETIMEDOUT) || nfs_mount_state_error_should_restart(error)) { - if (error == ETIMEDOUT) - nfs_need_reconnect(nmp); - tsleep(&lbolt, (PZERO-1), "nfsrecoverrestart", 0); - printf("nfs recovery restarting %d\n", error); - goto restart; - } - /* lock state lost - attempt to close file */ - lost = 1; - error = nfs4_close_rpc(nofp->nof_np, nofp, NULL, noop->noo_cred, R_RECOVER); - if ((error == ETIMEDOUT) || nfs_mount_state_error_should_restart(error)) { - if (error == ETIMEDOUT) - nfs_need_reconnect(nmp); - tsleep(&lbolt, (PZERO-1), "nfsrecoverrestart", 0); - printf("nfs recovery restarting %d\n", error); - goto restart; - } - error = 0; - /* rescan locks so we can drop them all */ - goto rescanlocks; - } - if (lost) { - /* kill/remove the lock */ - lck_mtx_lock(&nofp->nof_np->n_openlock); - nflp->nfl_flags |= NFS_FILE_LOCK_DEAD; - lck_mtx_lock(&nlop->nlo_lock); - nextnflp = TAILQ_NEXT(nflp, nfl_lolink); - TAILQ_REMOVE(&nlop->nlo_locks, nflp, nfl_lolink); - lck_mtx_unlock(&nlop->nlo_lock); - if (nflp->nfl_blockcnt) { - /* wake up anyone blocked on this lock */ - wakeup(nflp); - } else { - /* remove nflp from lock list and destroy */ - TAILQ_REMOVE(&nofp->nof_np->n_locks, nflp, nfl_link); - nfs_file_lock_destroy(nflp); - } - lck_mtx_unlock(&nofp->nof_np->n_openlock); + /* skip delegated locks */ + if (nflp->nfl_flags & NFS_FILE_LOCK_DELEGATED) + continue; + error = nmp->nm_funcs->nf_setlock_rpc(nofp->nof_np, nofp, nflp, 1, R_RECOVER, thd, noop->noo_cred); + if (error) + NP(nofp->nof_np, "nfs: lock reclaim (0x%llx, 0x%llx) %s %d", + nflp->nfl_start, nflp->nfl_end, + error ? "failed" : "succeeded", error); + if (!error) + continue; + /* restart recovery? */ + if ((error == ETIMEDOUT) || nfs_mount_state_error_should_restart(error)) { + if (error == ETIMEDOUT) + nfs_need_reconnect(nmp); + tsleep(nfs_recover, (PZERO-1), "nfsrecoverrestart", hz); + printf("nfs recovery restarting for %s, 0x%x, error %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error); + goto restart; } + /* lock state lost - attempt to close file */ + lost = 1; + error = 0; + break; + } + } + + /* + * If we've determined that we need to reopen the file then we probably + * didn't receive any delegation we think we hold. We should attempt to + * return that delegation (and claim any delegated state). + * + * If we hold a delegation that is marked for return, then we should + * return it now. + */ + if ((nofp->nof_np->n_openflags & N_DELEG_RETURN) || + (reopen && (nofp->nof_np->n_openflags & N_DELEG_MASK))) { + nfs4_delegation_return(nofp->nof_np, R_RECOVER, thd, noop->noo_cred); + if (!(nmp->nm_sockflags & NMSOCK_READY)) { + /* looks like we need a reconnect */ + tsleep(nfs_recover, (PZERO-1), "nfsrecoverrestart", hz); + printf("nfs recovery restarting for %s, 0x%x, error %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error); + goto restart; } } + if (lost) { /* revoke open file state */ - lck_mtx_lock(&nofp->nof_lock); - nofp->nof_flags |= NFS_OPEN_FILE_LOST; - lck_mtx_unlock(&nofp->nof_lock); - const char *vname = vnode_getname(NFSTOV(nofp->nof_np)); - printf("nfs4_recover: state lost for %s\n", vname ? vname : "???"); - vnode_putname(vname); + NP(nofp->nof_np, "nfs_recover: state lost for %d %p 0x%x", + kauth_cred_getuid(noop->noo_cred), nofp->nof_np, nofp->nof_np->n_flag); + nfs_revoke_open_state_for_node(nofp->nof_np); } } } if (!error) { + /* If state expired, make sure we're not holding onto any stale delegations */ lck_mtx_lock(&nmp->nm_lock); - nmp->nm_state &= ~NFSSTA_RECOVER; + if ((nmp->nm_vers >= NFS_VER4) && (nmp->nm_state & NFSSTA_RECOVER_EXPIRED)) { +recheckdeleg: + TAILQ_FOREACH_SAFE(np, &nmp->nm_delegations, n_dlink, nextnp) { + lck_mtx_unlock(&nmp->nm_lock); + nfs4_expired_check_delegation(np, vfs_context_kernel()); + lck_mtx_lock(&nmp->nm_lock); + if (nextnp == NFSNOLIST) + goto recheckdeleg; + } + } + nmp->nm_state &= ~(NFSSTA_RECOVER|NFSSTA_RECOVER_EXPIRED); wakeup(&nmp->nm_state); - printf("nfs recovery completed\n"); + printf("nfs recovery completed for %s, 0x%x\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid); lck_mtx_unlock(&nmp->nm_lock); } else { - printf("nfs recovery failed %d\n", error); + printf("nfs recovery failed for %s, 0x%x, error %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error); } } -