freebsd-skq/sys/fs/nfsserver/nfs_nfsdserv.c
2020-09-01 21:18:40 +00:00

5931 lines
163 KiB
C

/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Rick Macklem at The University of Guelph.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "opt_inet.h"
#include "opt_inet6.h"
/*
* nfs version 2, 3 and 4 server calls to vnode ops
* - these routines generally have 3 phases
* 1 - break down and validate rpc request in mbuf list
* 2 - do the vnode ops for the request, usually by calling a nfsvno_XXX()
* function in nfsd_port.c
* 3 - build the rpc reply in an mbuf list
* For nfsv4, these functions are called for each Op within the Compound RPC.
*/
#include <fs/nfs/nfsport.h>
#include <sys/extattr.h>
#include <sys/filio.h>
/* Global vars */
extern u_int32_t newnfs_false, newnfs_true;
extern enum vtype nv34tov_type[8];
extern struct timeval nfsboottime;
extern int nfs_rootfhset;
extern int nfsrv_enable_crossmntpt;
extern int nfsrv_statehashsize;
extern int nfsrv_layouthashsize;
extern time_t nfsdev_time;
extern volatile int nfsrv_devidcnt;
extern int nfsd_debuglevel;
extern u_long sb_max_adj;
extern int nfsrv_pnfsatime;
extern int nfsrv_maxpnfsmirror;
extern int nfs_maxcopyrange;
static int nfs_async = 0;
SYSCTL_DECL(_vfs_nfsd);
SYSCTL_INT(_vfs_nfsd, OID_AUTO, async, CTLFLAG_RW, &nfs_async, 0,
"Tell client that writes were synced even though they were not");
extern int nfsrv_doflexfile;
SYSCTL_INT(_vfs_nfsd, OID_AUTO, default_flexfile, CTLFLAG_RW,
&nfsrv_doflexfile, 0, "Make Flex File Layout the default for pNFS");
static int nfsrv_linux42server = 1;
SYSCTL_INT(_vfs_nfsd, OID_AUTO, linux42server, CTLFLAG_RW,
&nfsrv_linux42server, 0,
"Enable Linux style NFSv4.2 server (non-RFC compliant)");
static bool nfsrv_openaccess = true;
SYSCTL_BOOL(_vfs_nfsd, OID_AUTO, v4openaccess, CTLFLAG_RW,
&nfsrv_openaccess, 0,
"Enable Linux style NFSv4 Open access check");
/*
* This list defines the GSS mechanisms supported.
* (Don't ask me how you get these strings from the RFC stuff like
* iso(1), org(3)... but someone did it, so I don't need to know.)
*/
static struct nfsgss_mechlist nfsgss_mechlist[] = {
{ 9, "\052\206\110\206\367\022\001\002\002", 11 },
{ 0, "", 0 },
};
/* local functions */
static void nfsrvd_symlinksub(struct nfsrv_descript *nd, struct nameidata *ndp,
struct nfsvattr *nvap, fhandle_t *fhp, vnode_t *vpp,
vnode_t dirp, struct nfsvattr *dirforp, struct nfsvattr *diraftp,
int *diraft_retp, nfsattrbit_t *attrbitp,
NFSACL_T *aclp, NFSPROC_T *p, struct nfsexstuff *exp, char *pathcp,
int pathlen);
static void nfsrvd_mkdirsub(struct nfsrv_descript *nd, struct nameidata *ndp,
struct nfsvattr *nvap, fhandle_t *fhp, vnode_t *vpp,
vnode_t dirp, struct nfsvattr *dirforp, struct nfsvattr *diraftp,
int *diraft_retp, nfsattrbit_t *attrbitp, NFSACL_T *aclp,
NFSPROC_T *p, struct nfsexstuff *exp);
/*
* nfs access service (not a part of NFS V2)
*/
int
nfsrvd_access(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
u_int32_t *tl;
int getret, error = 0;
struct nfsvattr nva;
u_int32_t testmode, nfsmode, supported = 0;
accmode_t deletebit;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_postopattr(nd, 1, &nva);
goto out;
}
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
nfsmode = fxdr_unsigned(u_int32_t, *tl);
if ((nd->nd_flag & ND_NFSV4) &&
(nfsmode & ~(NFSACCESS_READ | NFSACCESS_LOOKUP |
NFSACCESS_MODIFY | NFSACCESS_EXTEND | NFSACCESS_DELETE |
NFSACCESS_EXECUTE | NFSACCESS_XAREAD | NFSACCESS_XAWRITE |
NFSACCESS_XALIST))) {
nd->nd_repstat = NFSERR_INVAL;
vput(vp);
goto out;
}
if (nfsmode & NFSACCESS_READ) {
supported |= NFSACCESS_READ;
if (nfsvno_accchk(vp, VREAD, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
nfsmode &= ~NFSACCESS_READ;
}
if (nfsmode & NFSACCESS_MODIFY) {
supported |= NFSACCESS_MODIFY;
if (nfsvno_accchk(vp, VWRITE, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
nfsmode &= ~NFSACCESS_MODIFY;
}
if (nfsmode & NFSACCESS_EXTEND) {
supported |= NFSACCESS_EXTEND;
if (nfsvno_accchk(vp, VWRITE | VAPPEND, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
nfsmode &= ~NFSACCESS_EXTEND;
}
if (nfsmode & NFSACCESS_XAREAD) {
supported |= NFSACCESS_XAREAD;
if (nfsvno_accchk(vp, VREAD, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
nfsmode &= ~NFSACCESS_XAREAD;
}
if (nfsmode & NFSACCESS_XAWRITE) {
supported |= NFSACCESS_XAWRITE;
if (nfsvno_accchk(vp, VWRITE, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
nfsmode &= ~NFSACCESS_XAWRITE;
}
if (nfsmode & NFSACCESS_XALIST) {
supported |= NFSACCESS_XALIST;
if (nfsvno_accchk(vp, VREAD, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
nfsmode &= ~NFSACCESS_XALIST;
}
if (nfsmode & NFSACCESS_DELETE) {
supported |= NFSACCESS_DELETE;
if (vp->v_type == VDIR)
deletebit = VDELETE_CHILD;
else
deletebit = VDELETE;
if (nfsvno_accchk(vp, deletebit, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
nfsmode &= ~NFSACCESS_DELETE;
}
if (vnode_vtype(vp) == VDIR)
testmode = NFSACCESS_LOOKUP;
else
testmode = NFSACCESS_EXECUTE;
if (nfsmode & testmode) {
supported |= (nfsmode & testmode);
if (nfsvno_accchk(vp, VEXEC, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
nfsmode &= ~testmode;
}
nfsmode &= supported;
if (nd->nd_flag & ND_NFSV3) {
getret = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
nfsrv_postopattr(nd, getret, &nva);
}
vput(vp);
if (nd->nd_flag & ND_NFSV4) {
NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(supported);
} else
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(nfsmode);
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs getattr service
*/
int
nfsrvd_getattr(struct nfsrv_descript *nd, int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
struct nfsvattr nva;
fhandle_t fh;
int at_root = 0, error = 0, supports_nfsv4acls;
struct nfsreferral *refp;
nfsattrbit_t attrbits, tmpbits;
struct mount *mp;
struct vnode *tvp = NULL;
struct vattr va;
uint64_t mounted_on_fileno = 0;
accmode_t accmode;
struct thread *p = curthread;
if (nd->nd_repstat)
goto out;
if (nd->nd_flag & ND_NFSV4) {
error = nfsrv_getattrbits(nd, &attrbits, NULL, NULL);
if (error) {
vput(vp);
goto out;
}
/*
* Check for a referral.
*/
refp = nfsv4root_getreferral(vp, NULL, 0);
if (refp != NULL) {
(void) nfsrv_putreferralattr(nd, &attrbits, refp, 1,
&nd->nd_repstat);
vput(vp);
goto out;
}
if (nd->nd_repstat == 0) {
accmode = 0;
NFSSET_ATTRBIT(&tmpbits, &attrbits);
/*
* GETATTR with write-only attr time_access_set and time_modify_set
* should return NFS4ERR_INVAL.
*/
if (NFSISSET_ATTRBIT(&tmpbits, NFSATTRBIT_TIMEACCESSSET) ||
NFSISSET_ATTRBIT(&tmpbits, NFSATTRBIT_TIMEMODIFYSET)){
error = NFSERR_INVAL;
vput(vp);
goto out;
}
if (NFSISSET_ATTRBIT(&tmpbits, NFSATTRBIT_ACL)) {
NFSCLRBIT_ATTRBIT(&tmpbits, NFSATTRBIT_ACL);
accmode |= VREAD_ACL;
}
if (NFSNONZERO_ATTRBIT(&tmpbits))
accmode |= VREAD_ATTRIBUTES;
if (accmode != 0)
nd->nd_repstat = nfsvno_accchk(vp, accmode,
nd->nd_cred, exp, p, NFSACCCHK_NOOVERRIDE,
NFSACCCHK_VPISLOCKED, NULL);
}
}
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_getattr(vp, &nva, nd, p, 1, &attrbits);
if (!nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV4) {
if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_FILEHANDLE))
nd->nd_repstat = nfsvno_getfh(vp, &fh, p);
if (!nd->nd_repstat)
nd->nd_repstat = nfsrv_checkgetattr(nd, vp,
&nva, &attrbits, p);
if (nd->nd_repstat == 0) {
supports_nfsv4acls = nfs_supportsnfsv4acls(vp);
mp = vp->v_mount;
if (nfsrv_enable_crossmntpt != 0 &&
vp->v_type == VDIR &&
(vp->v_vflag & VV_ROOT) != 0 &&
vp != rootvnode) {
tvp = mp->mnt_vnodecovered;
VREF(tvp);
at_root = 1;
} else
at_root = 0;
vfs_ref(mp);
NFSVOPUNLOCK(vp);
if (at_root != 0) {
if ((nd->nd_repstat =
NFSVOPLOCK(tvp, LK_SHARED)) == 0) {
nd->nd_repstat = VOP_GETATTR(
tvp, &va, nd->nd_cred);
vput(tvp);
} else
vrele(tvp);
if (nd->nd_repstat == 0)
mounted_on_fileno = (uint64_t)
va.va_fileid;
else
at_root = 0;
}
if (nd->nd_repstat == 0)
nd->nd_repstat = vfs_busy(mp, 0);
vfs_rel(mp);
if (nd->nd_repstat == 0) {
(void)nfsvno_fillattr(nd, mp, vp, &nva,
&fh, 0, &attrbits, nd->nd_cred, p,
isdgram, 1, supports_nfsv4acls,
at_root, mounted_on_fileno);
vfs_unbusy(mp);
}
vrele(vp);
} else
vput(vp);
} else {
nfsrv_fillattr(nd, &nva);
vput(vp);
}
} else {
vput(vp);
}
out:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs setattr service
*/
int
nfsrvd_setattr(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
struct nfsvattr nva, nva2;
u_int32_t *tl;
int preat_ret = 1, postat_ret = 1, gcheck = 0, error = 0;
int gotproxystateid;
struct timespec guard = { 0, 0 };
nfsattrbit_t attrbits, retbits;
nfsv4stateid_t stateid;
NFSACL_T *aclp = NULL;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_wcc(nd, preat_ret, &nva2, postat_ret, &nva);
goto out;
}
#ifdef NFS4_ACL_EXTATTR_NAME
aclp = acl_alloc(M_WAITOK);
aclp->acl_cnt = 0;
#endif
gotproxystateid = 0;
NFSVNO_ATTRINIT(&nva);
if (nd->nd_flag & ND_NFSV4) {
NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID);
stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
stateid.other[0] = *tl++;
stateid.other[1] = *tl++;
stateid.other[2] = *tl;
if (stateid.other[0] == 0x55555555 &&
stateid.other[1] == 0x55555555 &&
stateid.other[2] == 0x55555555 &&
stateid.seqid == 0xffffffff)
gotproxystateid = 1;
}
error = nfsrv_sattr(nd, vp, &nva, &attrbits, aclp, p);
if (error)
goto nfsmout;
/* For NFSv4, only va_uid is used from nva2. */
NFSZERO_ATTRBIT(&retbits);
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_OWNER);
preat_ret = nfsvno_getattr(vp, &nva2, nd, p, 1, &retbits);
if (!nd->nd_repstat)
nd->nd_repstat = preat_ret;
NFSZERO_ATTRBIT(&retbits);
if (nd->nd_flag & ND_NFSV3) {
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
gcheck = fxdr_unsigned(int, *tl);
if (gcheck) {
NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
fxdr_nfsv3time(tl, &guard);
}
if (!nd->nd_repstat && gcheck &&
(nva2.na_ctime.tv_sec != guard.tv_sec ||
nva2.na_ctime.tv_nsec != guard.tv_nsec))
nd->nd_repstat = NFSERR_NOT_SYNC;
if (nd->nd_repstat) {
vput(vp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
nfsrv_wcc(nd, preat_ret, &nva2, postat_ret, &nva);
goto out;
}
} else if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4))
nd->nd_repstat = nfsrv_checkuidgid(nd, &nva);
/*
* Now that we have all the fields, lets do it.
* If the size is being changed write access is required, otherwise
* just check for a read only file system.
*/
if (!nd->nd_repstat) {
if (NFSVNO_NOTSETSIZE(&nva)) {
if (NFSVNO_EXRDONLY(exp) ||
(vfs_flags(vp->v_mount) & MNT_RDONLY))
nd->nd_repstat = EROFS;
} else {
if (vnode_vtype(vp) != VREG)
nd->nd_repstat = EINVAL;
else if (nva2.na_uid != nd->nd_cred->cr_uid ||
NFSVNO_EXSTRICTACCESS(exp))
nd->nd_repstat = nfsvno_accchk(vp,
VWRITE, nd->nd_cred, exp, p,
NFSACCCHK_NOOVERRIDE,
NFSACCCHK_VPISLOCKED, NULL);
}
}
/*
* Proxy operations from the MDS are allowed via the all 0s special
* stateid.
*/
if (nd->nd_repstat == 0 && (nd->nd_flag & ND_NFSV4) != 0 &&
gotproxystateid == 0)
nd->nd_repstat = nfsrv_checksetattr(vp, nd, &stateid,
&nva, &attrbits, exp, p);
if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4)) {
/*
* For V4, try setting the attrbutes in sets, so that the
* reply bitmap will be correct for an error case.
*/
if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_OWNER) ||
NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_OWNERGROUP)) {
NFSVNO_ATTRINIT(&nva2);
NFSVNO_SETATTRVAL(&nva2, uid, nva.na_uid);
NFSVNO_SETATTRVAL(&nva2, gid, nva.na_gid);
nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p,
exp);
if (!nd->nd_repstat) {
if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_OWNER))
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_OWNER);
if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_OWNERGROUP))
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_OWNERGROUP);
}
}
if (!nd->nd_repstat &&
NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_SIZE)) {
NFSVNO_ATTRINIT(&nva2);
NFSVNO_SETATTRVAL(&nva2, size, nva.na_size);
nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p,
exp);
if (!nd->nd_repstat)
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_SIZE);
}
if (!nd->nd_repstat &&
(NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_TIMEACCESSSET) ||
NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_TIMEMODIFYSET))) {
NFSVNO_ATTRINIT(&nva2);
NFSVNO_SETATTRVAL(&nva2, atime, nva.na_atime);
NFSVNO_SETATTRVAL(&nva2, mtime, nva.na_mtime);
if (nva.na_vaflags & VA_UTIMES_NULL) {
nva2.na_vaflags |= VA_UTIMES_NULL;
NFSVNO_SETACTIVE(&nva2, vaflags);
}
nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p,
exp);
if (!nd->nd_repstat) {
if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_TIMEACCESSSET))
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_TIMEACCESSSET);
if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_TIMEMODIFYSET))
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_TIMEMODIFYSET);
}
}
if (!nd->nd_repstat &&
(NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_MODE) ||
NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_MODESETMASKED))) {
NFSVNO_ATTRINIT(&nva2);
NFSVNO_SETATTRVAL(&nva2, mode, nva.na_mode);
nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p,
exp);
if (!nd->nd_repstat) {
if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_MODE))
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_MODE);
if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_MODESETMASKED))
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_MODESETMASKED);
}
}
#ifdef NFS4_ACL_EXTATTR_NAME
if (!nd->nd_repstat && aclp->acl_cnt > 0 &&
NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_ACL)) {
nd->nd_repstat = nfsrv_setacl(vp, aclp, nd->nd_cred, p);
if (!nd->nd_repstat)
NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_ACL);
}
#endif
} else if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_setattr(vp, &nva, nd->nd_cred, p,
exp);
}
if (nd->nd_flag & (ND_NFSV2 | ND_NFSV3)) {
postat_ret = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
if (!nd->nd_repstat)
nd->nd_repstat = postat_ret;
}
vput(vp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
if (nd->nd_flag & ND_NFSV3)
nfsrv_wcc(nd, preat_ret, &nva2, postat_ret, &nva);
else if (nd->nd_flag & ND_NFSV4)
(void) nfsrv_putattrbit(nd, &retbits);
else if (!nd->nd_repstat)
nfsrv_fillattr(nd, &nva);
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
if (nd->nd_flag & ND_NFSV4) {
/*
* For all nd_repstat, the V4 reply includes a bitmap,
* even NFSERR_BADXDR, which is what this will end up
* returning.
*/
(void) nfsrv_putattrbit(nd, &retbits);
}
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs lookup rpc
* (Also performs lookup parent for v4)
*/
int
nfsrvd_lookup(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t dp, vnode_t *vpp, fhandle_t *fhp, struct nfsexstuff *exp)
{
struct nameidata named;
vnode_t vp, dirp = NULL;
int error = 0, dattr_ret = 1;
struct nfsvattr nva, dattr;
char *bufp;
u_long *hashp;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_postopattr(nd, dattr_ret, &dattr);
goto out;
}
/*
* For some reason, if dp is a symlink, the error
* returned is supposed to be NFSERR_SYMLINK and not NFSERR_NOTDIR.
*/
if (dp->v_type == VLNK && (nd->nd_flag & ND_NFSV4)) {
nd->nd_repstat = NFSERR_SYMLINK;
vrele(dp);
goto out;
}
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, LOOKUP,
LOCKLEAF | SAVESTART);
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (error) {
vrele(dp);
nfsvno_relpathbuf(&named);
goto out;
}
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp, p, &dirp);
} else {
vrele(dp);
nfsvno_relpathbuf(&named);
}
if (nd->nd_repstat) {
if (dirp) {
if (nd->nd_flag & ND_NFSV3)
dattr_ret = nfsvno_getattr(dirp, &dattr, nd, p,
0, NULL);
vrele(dirp);
}
if (nd->nd_flag & ND_NFSV3)
nfsrv_postopattr(nd, dattr_ret, &dattr);
goto out;
}
if (named.ni_startdir)
vrele(named.ni_startdir);
nfsvno_relpathbuf(&named);
vp = named.ni_vp;
if ((nd->nd_flag & ND_NFSV4) != 0 && !NFSVNO_EXPORTED(exp) &&
vp->v_type != VDIR && vp->v_type != VLNK)
/*
* Only allow lookup of VDIR and VLNK for traversal of
* non-exported volumes during NFSv4 mounting.
*/
nd->nd_repstat = ENOENT;
if (nd->nd_repstat == 0)
nd->nd_repstat = nfsvno_getfh(vp, fhp, p);
if (!(nd->nd_flag & ND_NFSV4) && !nd->nd_repstat)
nd->nd_repstat = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
if (vpp != NULL && nd->nd_repstat == 0)
*vpp = vp;
else
vput(vp);
if (dirp) {
if (nd->nd_flag & ND_NFSV3)
dattr_ret = nfsvno_getattr(dirp, &dattr, nd, p, 0,
NULL);
vrele(dirp);
}
if (nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV3)
nfsrv_postopattr(nd, dattr_ret, &dattr);
goto out;
}
if (nd->nd_flag & ND_NFSV2) {
(void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
nfsrv_fillattr(nd, &nva);
} else if (nd->nd_flag & ND_NFSV3) {
(void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
nfsrv_postopattr(nd, 0, &nva);
nfsrv_postopattr(nd, dattr_ret, &dattr);
}
out:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs readlink service
*/
int
nfsrvd_readlink(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
struct mbuf *mp = NULL, *mpend = NULL;
int getret = 1, len;
struct nfsvattr nva;
struct thread *p = curthread;
uint16_t off;
if (nd->nd_repstat) {
nfsrv_postopattr(nd, getret, &nva);
goto out;
}
if (vnode_vtype(vp) != VLNK) {
if (nd->nd_flag & ND_NFSV2)
nd->nd_repstat = ENXIO;
else
nd->nd_repstat = EINVAL;
}
if (nd->nd_repstat == 0) {
if ((nd->nd_flag & ND_EXTPG) != 0)
nd->nd_repstat = nfsvno_readlink(vp, nd->nd_cred,
nd->nd_maxextsiz, p, &mp, &mpend, &len);
else
nd->nd_repstat = nfsvno_readlink(vp, nd->nd_cred,
0, p, &mp, &mpend, &len);
}
if (nd->nd_flag & ND_NFSV3)
getret = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
vput(vp);
if (nd->nd_flag & ND_NFSV3)
nfsrv_postopattr(nd, getret, &nva);
if (nd->nd_repstat)
goto out;
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(len);
if (mp != NULL) {
nd->nd_mb->m_next = mp;
nd->nd_mb = mpend;
if ((mpend->m_flags & M_EXTPG) != 0) {
nd->nd_bextpg = mpend->m_epg_npgs - 1;
nd->nd_bpos = (char *)(void *)
PHYS_TO_DMAP(mpend->m_epg_pa[nd->nd_bextpg]);
off = (nd->nd_bextpg == 0) ? mpend->m_epg_1st_off : 0;
nd->nd_bpos += off + mpend->m_epg_last_len;
nd->nd_bextpgsiz = PAGE_SIZE - mpend->m_epg_last_len -
off;
} else
nd->nd_bpos = mtod(mpend, char *) + mpend->m_len;
}
out:
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfs read service
*/
int
nfsrvd_read(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
u_int32_t *tl;
int error = 0, cnt, getret = 1, gotproxystateid, reqlen, eof = 0;
struct mbuf *m2, *m3;
struct nfsvattr nva;
off_t off = 0x0;
struct nfsstate st, *stp = &st;
struct nfslock lo, *lop = &lo;
nfsv4stateid_t stateid;
nfsquad_t clientid;
struct thread *p = curthread;
uint16_t poff;
if (nd->nd_repstat) {
nfsrv_postopattr(nd, getret, &nva);
goto out;
}
if (nd->nd_flag & ND_NFSV2) {
NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
off = (off_t)fxdr_unsigned(u_int32_t, *tl++);
reqlen = fxdr_unsigned(int, *tl);
} else if (nd->nd_flag & ND_NFSV3) {
NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
off = fxdr_hyper(tl);
tl += 2;
reqlen = fxdr_unsigned(int, *tl);
} else {
NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + 3*NFSX_UNSIGNED);
reqlen = fxdr_unsigned(int, *(tl + 6));
}
if (reqlen > NFS_SRVMAXDATA(nd)) {
reqlen = NFS_SRVMAXDATA(nd);
} else if (reqlen < 0) {
error = EBADRPC;
goto nfsmout;
}
gotproxystateid = 0;
if (nd->nd_flag & ND_NFSV4) {
stp->ls_flags = (NFSLCK_CHECK | NFSLCK_READACCESS);
lop->lo_flags = NFSLCK_READ;
stp->ls_ownerlen = 0;
stp->ls_op = NULL;
stp->ls_uid = nd->nd_cred->cr_uid;
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
clientid.lval[0] = stp->ls_stateid.other[0] = *tl++;
clientid.lval[1] = stp->ls_stateid.other[1] = *tl++;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK1 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
stp->ls_stateid.other[2] = *tl++;
/*
* Don't allow the client to use a special stateid for a DS op.
*/
if ((nd->nd_flag & ND_DSSERVER) != 0 &&
((stp->ls_stateid.other[0] == 0x0 &&
stp->ls_stateid.other[1] == 0x0 &&
stp->ls_stateid.other[2] == 0x0) ||
(stp->ls_stateid.other[0] == 0xffffffff &&
stp->ls_stateid.other[1] == 0xffffffff &&
stp->ls_stateid.other[2] == 0xffffffff) ||
stp->ls_stateid.seqid != 0))
nd->nd_repstat = NFSERR_BADSTATEID;
/* However, allow the proxy stateid. */
if (stp->ls_stateid.seqid == 0xffffffff &&
stp->ls_stateid.other[0] == 0x55555555 &&
stp->ls_stateid.other[1] == 0x55555555 &&
stp->ls_stateid.other[2] == 0x55555555)
gotproxystateid = 1;
off = fxdr_hyper(tl);
lop->lo_first = off;
tl += 2;
lop->lo_end = off + reqlen;
/*
* Paranoia, just in case it wraps around.
*/
if (lop->lo_end < off)
lop->lo_end = NFS64BITSSET;
}
if (vnode_vtype(vp) != VREG) {
if (nd->nd_flag & ND_NFSV3)
nd->nd_repstat = EINVAL;
else
nd->nd_repstat = (vnode_vtype(vp) == VDIR) ? EISDIR :
EINVAL;
}
getret = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
if (!nd->nd_repstat)
nd->nd_repstat = getret;
if (!nd->nd_repstat &&
(nva.na_uid != nd->nd_cred->cr_uid ||
NFSVNO_EXSTRICTACCESS(exp))) {
nd->nd_repstat = nfsvno_accchk(vp, VREAD,
nd->nd_cred, exp, p,
NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED, NULL);
if (nd->nd_repstat)
nd->nd_repstat = nfsvno_accchk(vp, VEXEC,
nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
NFSACCCHK_VPISLOCKED, NULL);
}
/*
* DS reads are marked by ND_DSSERVER or use the proxy special
* stateid.
*/
if (nd->nd_repstat == 0 && (nd->nd_flag & (ND_NFSV4 | ND_DSSERVER)) ==
ND_NFSV4 && gotproxystateid == 0)
nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
&stateid, exp, nd, p);
if (nd->nd_repstat) {
vput(vp);
if (nd->nd_flag & ND_NFSV3)
nfsrv_postopattr(nd, getret, &nva);
goto out;
}
if (off >= nva.na_size) {
cnt = 0;
eof = 1;
} else if (reqlen == 0)
cnt = 0;
else if ((off + reqlen) >= nva.na_size) {
cnt = nva.na_size - off;
eof = 1;
} else
cnt = reqlen;
m3 = NULL;
if (cnt > 0) {
/*
* If cnt > MCLBYTES and the reply will not be saved, use
* ext_pgs mbufs for TLS.
* For NFSv4.0, we do not know for sure if the reply will
* be saved, so do not use ext_pgs mbufs for NFSv4.0.
* Always use ext_pgs mbufs if ND_EXTPG is set.
*/
if ((nd->nd_flag & ND_EXTPG) != 0 || (cnt > MCLBYTES &&
(nd->nd_flag & (ND_TLS | ND_SAVEREPLY)) == ND_TLS &&
(nd->nd_flag & (ND_NFSV4 | ND_NFSV41)) != ND_NFSV4))
nd->nd_repstat = nfsvno_read(vp, off, cnt, nd->nd_cred,
nd->nd_maxextsiz, p, &m3, &m2);
else
nd->nd_repstat = nfsvno_read(vp, off, cnt, nd->nd_cred,
0, p, &m3, &m2);
if (!(nd->nd_flag & ND_NFSV4)) {
getret = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
if (!nd->nd_repstat)
nd->nd_repstat = getret;
}
if (nd->nd_repstat) {
vput(vp);
if (m3)
m_freem(m3);
if (nd->nd_flag & ND_NFSV3)
nfsrv_postopattr(nd, getret, &nva);
goto out;
}
}
vput(vp);
if (nd->nd_flag & ND_NFSV2) {
nfsrv_fillattr(nd, &nva);
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
} else {
if (nd->nd_flag & ND_NFSV3) {
nfsrv_postopattr(nd, getret, &nva);
NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(cnt);
} else
NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
if (eof)
*tl++ = newnfs_true;
else
*tl++ = newnfs_false;
}
*tl = txdr_unsigned(cnt);
if (m3) {
nd->nd_mb->m_next = m3;
nd->nd_mb = m2;
if ((m2->m_flags & M_EXTPG) != 0) {
nd->nd_flag |= ND_EXTPG;
nd->nd_bextpg = m2->m_epg_npgs - 1;
nd->nd_bpos = (char *)(void *)
PHYS_TO_DMAP(m2->m_epg_pa[nd->nd_bextpg]);
poff = (nd->nd_bextpg == 0) ? m2->m_epg_1st_off : 0;
nd->nd_bpos += poff + m2->m_epg_last_len;
nd->nd_bextpgsiz = PAGE_SIZE - m2->m_epg_last_len -
poff;
} else
nd->nd_bpos = mtod(m2, char *) + m2->m_len;
}
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs write service
*/
int
nfsrvd_write(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
u_int32_t *tl;
struct nfsvattr nva, forat;
int aftat_ret = 1, retlen, len, error = 0, forat_ret = 1;
int gotproxystateid, stable = NFSWRITE_FILESYNC;
off_t off;
struct nfsstate st, *stp = &st;
struct nfslock lo, *lop = &lo;
nfsv4stateid_t stateid;
nfsquad_t clientid;
nfsattrbit_t attrbits;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_wcc(nd, forat_ret, &forat, aftat_ret, &nva);
goto out;
}
gotproxystateid = 0;
if (nd->nd_flag & ND_NFSV2) {
NFSM_DISSECT(tl, u_int32_t *, 4 * NFSX_UNSIGNED);
off = (off_t)fxdr_unsigned(u_int32_t, *++tl);
tl += 2;
retlen = len = fxdr_unsigned(int32_t, *tl);
} else if (nd->nd_flag & ND_NFSV3) {
NFSM_DISSECT(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
off = fxdr_hyper(tl);
tl += 3;
stable = fxdr_unsigned(int, *tl++);
retlen = len = fxdr_unsigned(int32_t, *tl);
} else {
NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + 4 * NFSX_UNSIGNED);
stp->ls_flags = (NFSLCK_CHECK | NFSLCK_WRITEACCESS);
lop->lo_flags = NFSLCK_WRITE;
stp->ls_ownerlen = 0;
stp->ls_op = NULL;
stp->ls_uid = nd->nd_cred->cr_uid;
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
clientid.lval[0] = stp->ls_stateid.other[0] = *tl++;
clientid.lval[1] = stp->ls_stateid.other[1] = *tl++;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK2 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
stp->ls_stateid.other[2] = *tl++;
/*
* Don't allow the client to use a special stateid for a DS op.
*/
if ((nd->nd_flag & ND_DSSERVER) != 0 &&
((stp->ls_stateid.other[0] == 0x0 &&
stp->ls_stateid.other[1] == 0x0 &&
stp->ls_stateid.other[2] == 0x0) ||
(stp->ls_stateid.other[0] == 0xffffffff &&
stp->ls_stateid.other[1] == 0xffffffff &&
stp->ls_stateid.other[2] == 0xffffffff) ||
stp->ls_stateid.seqid != 0))
nd->nd_repstat = NFSERR_BADSTATEID;
/* However, allow the proxy stateid. */
if (stp->ls_stateid.seqid == 0xffffffff &&
stp->ls_stateid.other[0] == 0x55555555 &&
stp->ls_stateid.other[1] == 0x55555555 &&
stp->ls_stateid.other[2] == 0x55555555)
gotproxystateid = 1;
off = fxdr_hyper(tl);
lop->lo_first = off;
tl += 2;
stable = fxdr_unsigned(int, *tl++);
retlen = len = fxdr_unsigned(int32_t, *tl);
lop->lo_end = off + len;
/*
* Paranoia, just in case it wraps around, which shouldn't
* ever happen anyhow.
*/
if (lop->lo_end < lop->lo_first)
lop->lo_end = NFS64BITSSET;
}
if (retlen > NFS_SRVMAXIO || retlen < 0)
nd->nd_repstat = EIO;
if (vnode_vtype(vp) != VREG && !nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV3)
nd->nd_repstat = EINVAL;
else
nd->nd_repstat = (vnode_vtype(vp) == VDIR) ? EISDIR :
EINVAL;
}
NFSZERO_ATTRBIT(&attrbits);
NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_OWNER);
forat_ret = nfsvno_getattr(vp, &forat, nd, p, 1, &attrbits);
if (!nd->nd_repstat)
nd->nd_repstat = forat_ret;
if (!nd->nd_repstat &&
(forat.na_uid != nd->nd_cred->cr_uid ||
NFSVNO_EXSTRICTACCESS(exp)))
nd->nd_repstat = nfsvno_accchk(vp, VWRITE,
nd->nd_cred, exp, p,
NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED, NULL);
/*
* DS reads are marked by ND_DSSERVER or use the proxy special
* stateid.
*/
if (nd->nd_repstat == 0 && (nd->nd_flag & (ND_NFSV4 | ND_DSSERVER)) ==
ND_NFSV4 && gotproxystateid == 0)
nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
&stateid, exp, nd, p);
if (nd->nd_repstat) {
vput(vp);
if (nd->nd_flag & ND_NFSV3)
nfsrv_wcc(nd, forat_ret, &forat, aftat_ret, &nva);
goto out;
}
/*
* For NFS Version 2, it is not obvious what a write of zero length
* should do, but I might as well be consistent with Version 3,
* which is to return ok so long as there are no permission problems.
*/
if (retlen > 0) {
nd->nd_repstat = nfsvno_write(vp, off, retlen, &stable,
nd->nd_md, nd->nd_dpos, nd->nd_cred, p);
error = nfsm_advance(nd, NFSM_RNDUP(retlen), -1);
if (error)
goto nfsmout;
}
if (nd->nd_flag & ND_NFSV4)
aftat_ret = 0;
else
aftat_ret = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
vput(vp);
if (!nd->nd_repstat)
nd->nd_repstat = aftat_ret;
if (nd->nd_flag & (ND_NFSV3 | ND_NFSV4)) {
if (nd->nd_flag & ND_NFSV3)
nfsrv_wcc(nd, forat_ret, &forat, aftat_ret, &nva);
if (nd->nd_repstat)
goto out;
NFSM_BUILD(tl, u_int32_t *, 4 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(retlen);
/*
* If nfs_async is set, then pretend the write was FILESYNC.
* Warning: Doing this violates RFC1813 and runs a risk
* of data written by a client being lost when the server
* crashes/reboots.
*/
if (stable == NFSWRITE_UNSTABLE && nfs_async == 0)
*tl++ = txdr_unsigned(stable);
else
*tl++ = txdr_unsigned(NFSWRITE_FILESYNC);
/*
* Actually, there is no need to txdr these fields,
* but it may make the values more human readable,
* for debugging purposes.
*/
*tl++ = txdr_unsigned(nfsboottime.tv_sec);
*tl = txdr_unsigned(nfsboottime.tv_usec);
} else if (!nd->nd_repstat)
nfsrv_fillattr(nd, &nva);
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs create service (creates regular files for V2 and V3. Spec. files for V2.)
* now does a truncate to 0 length via. setattr if it already exists
* The core creation routine has been extracted out into nfsrv_creatsub(),
* so it can also be used by nfsrv_open() for V4.
*/
int
nfsrvd_create(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t dp, struct nfsexstuff *exp)
{
struct nfsvattr nva, dirfor, diraft;
struct nfsv2_sattr *sp;
struct nameidata named;
u_int32_t *tl;
int error = 0, tsize, dirfor_ret = 1, diraft_ret = 1;
int how = NFSCREATE_UNCHECKED, exclusive_flag = 0;
NFSDEV_T rdev = 0;
vnode_t vp = NULL, dirp = NULL;
fhandle_t fh;
char *bufp;
u_long *hashp;
enum vtype vtyp;
int32_t cverf[2], tverf[2] = { 0, 0 };
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
goto out;
}
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE,
LOCKPARENT | LOCKLEAF | SAVESTART | NOCACHE);
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (error)
goto nfsmout;
if (!nd->nd_repstat) {
NFSVNO_ATTRINIT(&nva);
if (nd->nd_flag & ND_NFSV2) {
NFSM_DISSECT(sp, struct nfsv2_sattr *, NFSX_V2SATTR);
vtyp = IFTOVT(fxdr_unsigned(u_int32_t, sp->sa_mode));
if (vtyp == VNON)
vtyp = VREG;
NFSVNO_SETATTRVAL(&nva, type, vtyp);
NFSVNO_SETATTRVAL(&nva, mode,
nfstov_mode(sp->sa_mode));
switch (nva.na_type) {
case VREG:
tsize = fxdr_unsigned(int32_t, sp->sa_size);
if (tsize != -1)
NFSVNO_SETATTRVAL(&nva, size,
(u_quad_t)tsize);
break;
case VCHR:
case VBLK:
case VFIFO:
rdev = fxdr_unsigned(NFSDEV_T, sp->sa_size);
break;
default:
break;
}
} else {
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
how = fxdr_unsigned(int, *tl);
switch (how) {
case NFSCREATE_GUARDED:
case NFSCREATE_UNCHECKED:
error = nfsrv_sattr(nd, NULL, &nva, NULL, NULL, p);
if (error)
goto nfsmout;
break;
case NFSCREATE_EXCLUSIVE:
NFSM_DISSECT(tl, u_int32_t *, NFSX_VERF);
cverf[0] = *tl++;
cverf[1] = *tl;
exclusive_flag = 1;
break;
}
NFSVNO_SETATTRVAL(&nva, type, VREG);
}
}
if (nd->nd_repstat) {
nfsvno_relpathbuf(&named);
if (nd->nd_flag & ND_NFSV3) {
dirfor_ret = nfsvno_getattr(dp, &dirfor, nd, p, 1,
NULL);
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
&diraft);
}
vput(dp);
goto out;
}
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 1, exp, p, &dirp);
if (dirp) {
if (nd->nd_flag & ND_NFSV2) {
vrele(dirp);
dirp = NULL;
} else {
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd, p, 0,
NULL);
}
}
if (nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV3)
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
&diraft);
if (dirp)
vrele(dirp);
goto out;
}
if (!(nd->nd_flag & ND_NFSV2)) {
switch (how) {
case NFSCREATE_GUARDED:
if (named.ni_vp)
nd->nd_repstat = EEXIST;
break;
case NFSCREATE_UNCHECKED:
break;
case NFSCREATE_EXCLUSIVE:
if (named.ni_vp == NULL)
NFSVNO_SETATTRVAL(&nva, mode, 0);
break;
}
}
/*
* Iff doesn't exist, create it
* otherwise just truncate to 0 length
* should I set the mode too ?
*/
nd->nd_repstat = nfsvno_createsub(nd, &named, &vp, &nva,
&exclusive_flag, cverf, rdev, exp);
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_getfh(vp, &fh, p);
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_getattr(vp, &nva, nd, p, 1,
NULL);
vput(vp);
if (!nd->nd_repstat) {
tverf[0] = nva.na_atime.tv_sec;
tverf[1] = nva.na_atime.tv_nsec;
}
}
if (nd->nd_flag & ND_NFSV2) {
if (!nd->nd_repstat) {
(void) nfsm_fhtom(nd, (u_int8_t *)&fh, 0, 0);
nfsrv_fillattr(nd, &nva);
}
} else {
if (exclusive_flag && !nd->nd_repstat && (cverf[0] != tverf[0]
|| cverf[1] != tverf[1]))
nd->nd_repstat = EEXIST;
diraft_ret = nfsvno_getattr(dirp, &diraft, nd, p, 0, NULL);
vrele(dirp);
if (!nd->nd_repstat) {
(void) nfsm_fhtom(nd, (u_int8_t *)&fh, 0, 1);
nfsrv_postopattr(nd, 0, &nva);
}
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
}
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(dp);
nfsvno_relpathbuf(&named);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs v3 mknod service (and v4 create)
*/
int
nfsrvd_mknod(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t dp, vnode_t *vpp, fhandle_t *fhp, struct nfsexstuff *exp)
{
struct nfsvattr nva, dirfor, diraft;
u_int32_t *tl;
struct nameidata named;
int error = 0, dirfor_ret = 1, diraft_ret = 1, pathlen;
u_int32_t major, minor;
enum vtype vtyp = VNON;
nfstype nfs4type = NFNON;
vnode_t vp, dirp = NULL;
nfsattrbit_t attrbits;
char *bufp = NULL, *pathcp = NULL;
u_long *hashp, cnflags;
NFSACL_T *aclp = NULL;
struct thread *p = curthread;
NFSVNO_ATTRINIT(&nva);
cnflags = (LOCKPARENT | SAVESTART);
if (nd->nd_repstat) {
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
goto out;
}
#ifdef NFS4_ACL_EXTATTR_NAME
aclp = acl_alloc(M_WAITOK);
aclp->acl_cnt = 0;
#endif
/*
* For V4, the creation stuff is here, Yuck!
*/
if (nd->nd_flag & ND_NFSV4) {
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
vtyp = nfsv34tov_type(*tl);
nfs4type = fxdr_unsigned(nfstype, *tl);
switch (nfs4type) {
case NFLNK:
error = nfsvno_getsymlink(nd, &nva, p, &pathcp,
&pathlen);
if (error)
goto nfsmout;
break;
case NFCHR:
case NFBLK:
NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
major = fxdr_unsigned(u_int32_t, *tl++);
minor = fxdr_unsigned(u_int32_t, *tl);
nva.na_rdev = NFSMAKEDEV(major, minor);
break;
case NFSOCK:
case NFFIFO:
break;
case NFDIR:
cnflags = (LOCKPARENT | SAVENAME);
break;
default:
nd->nd_repstat = NFSERR_BADTYPE;
vrele(dp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
goto out;
}
}
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE, cnflags | NOCACHE);
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (error)
goto nfsmout;
if (!nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV3) {
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
vtyp = nfsv34tov_type(*tl);
}
error = nfsrv_sattr(nd, NULL, &nva, &attrbits, aclp, p);
if (error)
goto nfsmout;
nva.na_type = vtyp;
if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV3) &&
(vtyp == VCHR || vtyp == VBLK)) {
NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
major = fxdr_unsigned(u_int32_t, *tl++);
minor = fxdr_unsigned(u_int32_t, *tl);
nva.na_rdev = NFSMAKEDEV(major, minor);
}
}
dirfor_ret = nfsvno_getattr(dp, &dirfor, nd, p, 0, NULL);
if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4)) {
if (!dirfor_ret && NFSVNO_ISSETGID(&nva) &&
dirfor.na_gid == nva.na_gid)
NFSVNO_UNSET(&nva, gid);
nd->nd_repstat = nfsrv_checkuidgid(nd, &nva);
}
if (nd->nd_repstat) {
vrele(dp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
nfsvno_relpathbuf(&named);
if (pathcp)
free(pathcp, M_TEMP);
if (nd->nd_flag & ND_NFSV3)
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
&diraft);
goto out;
}
/*
* Yuck! For V4, mkdir and link are here and some V4 clients don't fill
* in va_mode, so we'll have to set a default here.
*/
if (NFSVNO_NOTSETMODE(&nva)) {
if (vtyp == VLNK)
nva.na_mode = 0755;
else
nva.na_mode = 0400;
}
if (vtyp == VDIR)
named.ni_cnd.cn_flags |= WILLBEDIR;
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp, p, &dirp);
if (nd->nd_repstat) {
if (dirp) {
if (nd->nd_flag & ND_NFSV3)
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd,
p, 0, NULL);
vrele(dirp);
}
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
if (nd->nd_flag & ND_NFSV3)
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
&diraft);
goto out;
}
if (dirp)
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd, p, 0, NULL);
if ((nd->nd_flag & ND_NFSV4) && (vtyp == VDIR || vtyp == VLNK)) {
if (vtyp == VDIR) {
nfsrvd_mkdirsub(nd, &named, &nva, fhp, vpp, dirp,
&dirfor, &diraft, &diraft_ret, &attrbits, aclp, p,
exp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
goto out;
} else if (vtyp == VLNK) {
nfsrvd_symlinksub(nd, &named, &nva, fhp, vpp, dirp,
&dirfor, &diraft, &diraft_ret, &attrbits,
aclp, p, exp, pathcp, pathlen);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
free(pathcp, M_TEMP);
goto out;
}
}
nd->nd_repstat = nfsvno_mknod(&named, &nva, nd->nd_cred, p);
if (!nd->nd_repstat) {
vp = named.ni_vp;
nfsrv_fixattr(nd, vp, &nva, aclp, p, &attrbits, exp);
nd->nd_repstat = nfsvno_getfh(vp, fhp, p);
if ((nd->nd_flag & ND_NFSV3) && !nd->nd_repstat)
nd->nd_repstat = nfsvno_getattr(vp, &nva, nd, p, 1,
NULL);
if (vpp != NULL && nd->nd_repstat == 0) {
NFSVOPUNLOCK(vp);
*vpp = vp;
} else
vput(vp);
}
diraft_ret = nfsvno_getattr(dirp, &diraft, nd, p, 0, NULL);
vrele(dirp);
if (!nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV3) {
(void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 1);
nfsrv_postopattr(nd, 0, &nva);
} else {
NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
*tl++ = newnfs_false;
txdr_hyper(dirfor.na_filerev, tl);
tl += 2;
txdr_hyper(diraft.na_filerev, tl);
(void) nfsrv_putattrbit(nd, &attrbits);
}
}
if (nd->nd_flag & ND_NFSV3)
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vrele(dp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
if (bufp)
nfsvno_relpathbuf(&named);
if (pathcp)
free(pathcp, M_TEMP);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs remove service
*/
int
nfsrvd_remove(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t dp, struct nfsexstuff *exp)
{
struct nameidata named;
u_int32_t *tl;
int error = 0, dirfor_ret = 1, diraft_ret = 1;
vnode_t dirp = NULL;
struct nfsvattr dirfor, diraft;
char *bufp;
u_long *hashp;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
goto out;
}
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, DELETE,
LOCKPARENT | LOCKLEAF);
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (error) {
vput(dp);
nfsvno_relpathbuf(&named);
goto out;
}
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 1, exp, p, &dirp);
} else {
vput(dp);
nfsvno_relpathbuf(&named);
}
if (dirp) {
if (!(nd->nd_flag & ND_NFSV2)) {
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd, p, 0,
NULL);
} else {
vrele(dirp);
dirp = NULL;
}
}
if (!nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV4) {
if (vnode_vtype(named.ni_vp) == VDIR)
nd->nd_repstat = nfsvno_rmdirsub(&named, 1,
nd->nd_cred, p, exp);
else
nd->nd_repstat = nfsvno_removesub(&named, 1,
nd->nd_cred, p, exp);
} else if (nd->nd_procnum == NFSPROC_RMDIR) {
nd->nd_repstat = nfsvno_rmdirsub(&named, 0,
nd->nd_cred, p, exp);
} else {
nd->nd_repstat = nfsvno_removesub(&named, 0,
nd->nd_cred, p, exp);
}
}
if (!(nd->nd_flag & ND_NFSV2)) {
if (dirp) {
diraft_ret = nfsvno_getattr(dirp, &diraft, nd, p, 0,
NULL);
vrele(dirp);
}
if (nd->nd_flag & ND_NFSV3) {
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
&diraft);
} else if (!nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
*tl++ = newnfs_false;
txdr_hyper(dirfor.na_filerev, tl);
tl += 2;
txdr_hyper(diraft.na_filerev, tl);
}
}
out:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs rename service
*/
int
nfsrvd_rename(struct nfsrv_descript *nd, int isdgram,
vnode_t dp, vnode_t todp, struct nfsexstuff *exp, struct nfsexstuff *toexp)
{
u_int32_t *tl;
int error = 0, fdirfor_ret = 1, fdiraft_ret = 1;
int tdirfor_ret = 1, tdiraft_ret = 1;
struct nameidata fromnd, tond;
vnode_t fdirp = NULL, tdirp = NULL, tdp = NULL;
struct nfsvattr fdirfor, fdiraft, tdirfor, tdiraft;
struct nfsexstuff tnes;
struct nfsrvfh tfh;
char *bufp, *tbufp = NULL;
u_long *hashp;
fhandle_t fh;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_wcc(nd, fdirfor_ret, &fdirfor, fdiraft_ret, &fdiraft);
nfsrv_wcc(nd, tdirfor_ret, &tdirfor, tdiraft_ret, &tdiraft);
goto out;
}
if (!(nd->nd_flag & ND_NFSV2))
fdirfor_ret = nfsvno_getattr(dp, &fdirfor, nd, p, 1, NULL);
tond.ni_cnd.cn_nameiop = 0;
tond.ni_startdir = NULL;
NFSNAMEICNDSET(&fromnd.ni_cnd, nd->nd_cred, DELETE, WANTPARENT | SAVESTART);
nfsvno_setpathbuf(&fromnd, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &fromnd.ni_pathlen);
if (error) {
vput(dp);
if (todp)
vrele(todp);
nfsvno_relpathbuf(&fromnd);
goto out;
}
/*
* Unlock dp in this code section, so it is unlocked before
* tdp gets locked. This avoids a potential LOR if tdp is the
* parent directory of dp.
*/
if (nd->nd_flag & ND_NFSV4) {
tdp = todp;
tnes = *toexp;
if (dp != tdp) {
NFSVOPUNLOCK(dp);
/* Might lock tdp. */
tdirfor_ret = nfsvno_getattr(tdp, &tdirfor, nd, p, 0,
NULL);
} else {
tdirfor_ret = nfsvno_getattr(tdp, &tdirfor, nd, p, 1,
NULL);
NFSVOPUNLOCK(dp);
}
} else {
tfh.nfsrvfh_len = 0;
error = nfsrv_mtofh(nd, &tfh);
if (error == 0)
error = nfsvno_getfh(dp, &fh, p);
if (error) {
vput(dp);
/* todp is always NULL except NFSv4 */
nfsvno_relpathbuf(&fromnd);
goto out;
}
/* If this is the same file handle, just VREF() the vnode. */
if (tfh.nfsrvfh_len == NFSX_MYFH &&
!NFSBCMP(tfh.nfsrvfh_data, &fh, NFSX_MYFH)) {
VREF(dp);
tdp = dp;
tnes = *exp;
tdirfor_ret = nfsvno_getattr(tdp, &tdirfor, nd, p, 1,
NULL);
NFSVOPUNLOCK(dp);
} else {
NFSVOPUNLOCK(dp);
nd->nd_cred->cr_uid = nd->nd_saveduid;
nfsd_fhtovp(nd, &tfh, LK_EXCLUSIVE, &tdp, &tnes, NULL,
0); /* Locks tdp. */
if (tdp) {
tdirfor_ret = nfsvno_getattr(tdp, &tdirfor, nd,
p, 1, NULL);
NFSVOPUNLOCK(tdp);
}
}
}
NFSNAMEICNDSET(&tond.ni_cnd, nd->nd_cred, RENAME, LOCKPARENT | LOCKLEAF | NOCACHE | SAVESTART);
nfsvno_setpathbuf(&tond, &tbufp, &hashp);
if (!nd->nd_repstat) {
error = nfsrv_parsename(nd, tbufp, hashp, &tond.ni_pathlen);
if (error) {
if (tdp)
vrele(tdp);
vrele(dp);
nfsvno_relpathbuf(&fromnd);
nfsvno_relpathbuf(&tond);
goto out;
}
}
if (nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV3) {
nfsrv_wcc(nd, fdirfor_ret, &fdirfor, fdiraft_ret,
&fdiraft);
nfsrv_wcc(nd, tdirfor_ret, &tdirfor, tdiraft_ret,
&tdiraft);
}
if (tdp)
vrele(tdp);
vrele(dp);
nfsvno_relpathbuf(&fromnd);
nfsvno_relpathbuf(&tond);
goto out;
}
/*
* Done parsing, now down to business.
*/
nd->nd_repstat = nfsvno_namei(nd, &fromnd, dp, 0, exp, p, &fdirp);
if (nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV3) {
nfsrv_wcc(nd, fdirfor_ret, &fdirfor, fdiraft_ret,
&fdiraft);
nfsrv_wcc(nd, tdirfor_ret, &tdirfor, tdiraft_ret,
&tdiraft);
}
if (fdirp)
vrele(fdirp);
if (tdp)
vrele(tdp);
nfsvno_relpathbuf(&tond);
goto out;
}
if (vnode_vtype(fromnd.ni_vp) == VDIR)
tond.ni_cnd.cn_flags |= WILLBEDIR;
nd->nd_repstat = nfsvno_namei(nd, &tond, tdp, 0, &tnes, p, &tdirp);
nd->nd_repstat = nfsvno_rename(&fromnd, &tond, nd->nd_repstat,
nd->nd_flag, nd->nd_cred, p);
if (fdirp)
fdiraft_ret = nfsvno_getattr(fdirp, &fdiraft, nd, p, 0, NULL);
if (tdirp)
tdiraft_ret = nfsvno_getattr(tdirp, &tdiraft, nd, p, 0, NULL);
if (fdirp)
vrele(fdirp);
if (tdirp)
vrele(tdirp);
if (nd->nd_flag & ND_NFSV3) {
nfsrv_wcc(nd, fdirfor_ret, &fdirfor, fdiraft_ret, &fdiraft);
nfsrv_wcc(nd, tdirfor_ret, &tdirfor, tdiraft_ret, &tdiraft);
} else if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, 10 * NFSX_UNSIGNED);
*tl++ = newnfs_false;
txdr_hyper(fdirfor.na_filerev, tl);
tl += 2;
txdr_hyper(fdiraft.na_filerev, tl);
tl += 2;
*tl++ = newnfs_false;
txdr_hyper(tdirfor.na_filerev, tl);
tl += 2;
txdr_hyper(tdiraft.na_filerev, tl);
}
out:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs link service
*/
int
nfsrvd_link(struct nfsrv_descript *nd, int isdgram,
vnode_t vp, vnode_t tovp, struct nfsexstuff *exp, struct nfsexstuff *toexp)
{
struct nameidata named;
u_int32_t *tl;
int error = 0, dirfor_ret = 1, diraft_ret = 1, getret = 1;
vnode_t dirp = NULL, dp = NULL;
struct nfsvattr dirfor, diraft, at;
struct nfsexstuff tnes;
struct nfsrvfh dfh;
char *bufp;
u_long *hashp;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_postopattr(nd, getret, &at);
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
goto out;
}
NFSVOPUNLOCK(vp);
if (vnode_vtype(vp) == VDIR) {
if (nd->nd_flag & ND_NFSV4)
nd->nd_repstat = NFSERR_ISDIR;
else
nd->nd_repstat = NFSERR_INVAL;
if (tovp)
vrele(tovp);
}
if (!nd->nd_repstat) {
if (nd->nd_flag & ND_NFSV4) {
dp = tovp;
tnes = *toexp;
} else {
error = nfsrv_mtofh(nd, &dfh);
if (error) {
vrele(vp);
/* tovp is always NULL unless NFSv4 */
goto out;
}
nfsd_fhtovp(nd, &dfh, LK_EXCLUSIVE, &dp, &tnes, NULL, 0);
if (dp)
NFSVOPUNLOCK(dp);
}
}
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE,
LOCKPARENT | SAVENAME | NOCACHE);
if (!nd->nd_repstat) {
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (error) {
vrele(vp);
if (dp)
vrele(dp);
nfsvno_relpathbuf(&named);
goto out;
}
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, &tnes,
p, &dirp);
} else {
if (dp)
vrele(dp);
nfsvno_relpathbuf(&named);
}
}
if (dirp) {
if (nd->nd_flag & ND_NFSV2) {
vrele(dirp);
dirp = NULL;
} else {
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd, p, 0,
NULL);
}
}
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_link(&named, vp, nd->nd_cred, p, exp);
if (nd->nd_flag & ND_NFSV3)
getret = nfsvno_getattr(vp, &at, nd, p, 0, NULL);
if (dirp) {
diraft_ret = nfsvno_getattr(dirp, &diraft, nd, p, 0, NULL);
vrele(dirp);
}
vrele(vp);
if (nd->nd_flag & ND_NFSV3) {
nfsrv_postopattr(nd, getret, &at);
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
} else if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
*tl++ = newnfs_false;
txdr_hyper(dirfor.na_filerev, tl);
tl += 2;
txdr_hyper(diraft.na_filerev, tl);
}
out:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs symbolic link service
*/
int
nfsrvd_symlink(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t dp, vnode_t *vpp, fhandle_t *fhp, struct nfsexstuff *exp)
{
struct nfsvattr nva, dirfor, diraft;
struct nameidata named;
int error = 0, dirfor_ret = 1, diraft_ret = 1, pathlen;
vnode_t dirp = NULL;
char *bufp, *pathcp = NULL;
u_long *hashp;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
goto out;
}
if (vpp)
*vpp = NULL;
NFSVNO_ATTRINIT(&nva);
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE,
LOCKPARENT | SAVESTART | NOCACHE);
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (!error && !nd->nd_repstat)
error = nfsvno_getsymlink(nd, &nva, p, &pathcp, &pathlen);
if (error) {
vrele(dp);
nfsvno_relpathbuf(&named);
goto out;
}
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp, p, &dirp);
} else {
vrele(dp);
nfsvno_relpathbuf(&named);
}
if (dirp != NULL && !(nd->nd_flag & ND_NFSV3)) {
vrele(dirp);
dirp = NULL;
}
/*
* And call nfsrvd_symlinksub() to do the common code. It will
* return EBADRPC upon a parsing error, 0 otherwise.
*/
if (!nd->nd_repstat) {
if (dirp != NULL)
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd, p, 0,
NULL);
nfsrvd_symlinksub(nd, &named, &nva, fhp, vpp, dirp,
&dirfor, &diraft, &diraft_ret, NULL, NULL, p, exp,
pathcp, pathlen);
} else if (dirp != NULL) {
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd, p, 0, NULL);
vrele(dirp);
}
if (pathcp)
free(pathcp, M_TEMP);
if (nd->nd_flag & ND_NFSV3) {
if (!nd->nd_repstat) {
(void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 1);
nfsrv_postopattr(nd, 0, &nva);
}
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
}
out:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* Common code for creating a symbolic link.
*/
static void
nfsrvd_symlinksub(struct nfsrv_descript *nd, struct nameidata *ndp,
struct nfsvattr *nvap, fhandle_t *fhp, vnode_t *vpp,
vnode_t dirp, struct nfsvattr *dirforp, struct nfsvattr *diraftp,
int *diraft_retp, nfsattrbit_t *attrbitp,
NFSACL_T *aclp, NFSPROC_T *p, struct nfsexstuff *exp, char *pathcp,
int pathlen)
{
u_int32_t *tl;
nd->nd_repstat = nfsvno_symlink(ndp, nvap, pathcp, pathlen,
!(nd->nd_flag & ND_NFSV2), nd->nd_saveduid, nd->nd_cred, p, exp);
if (!nd->nd_repstat && !(nd->nd_flag & ND_NFSV2)) {
nfsrv_fixattr(nd, ndp->ni_vp, nvap, aclp, p, attrbitp, exp);
if (nd->nd_flag & ND_NFSV3) {
nd->nd_repstat = nfsvno_getfh(ndp->ni_vp, fhp, p);
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_getattr(ndp->ni_vp,
nvap, nd, p, 1, NULL);
}
if (vpp != NULL && nd->nd_repstat == 0) {
NFSVOPUNLOCK(ndp->ni_vp);
*vpp = ndp->ni_vp;
} else
vput(ndp->ni_vp);
}
if (dirp) {
*diraft_retp = nfsvno_getattr(dirp, diraftp, nd, p, 0, NULL);
vrele(dirp);
}
if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
*tl++ = newnfs_false;
txdr_hyper(dirforp->na_filerev, tl);
tl += 2;
txdr_hyper(diraftp->na_filerev, tl);
(void) nfsrv_putattrbit(nd, attrbitp);
}
NFSEXITCODE2(0, nd);
}
/*
* nfs mkdir service
*/
int
nfsrvd_mkdir(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t dp, vnode_t *vpp, fhandle_t *fhp, struct nfsexstuff *exp)
{
struct nfsvattr nva, dirfor, diraft;
struct nameidata named;
u_int32_t *tl;
int error = 0, dirfor_ret = 1, diraft_ret = 1;
vnode_t dirp = NULL;
char *bufp;
u_long *hashp;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
goto out;
}
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE,
LOCKPARENT | SAVENAME | NOCACHE);
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (error)
goto nfsmout;
if (!nd->nd_repstat) {
NFSVNO_ATTRINIT(&nva);
if (nd->nd_flag & ND_NFSV3) {
error = nfsrv_sattr(nd, NULL, &nva, NULL, NULL, p);
if (error)
goto nfsmout;
} else {
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
nva.na_mode = nfstov_mode(*tl++);
}
}
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp, p, &dirp);
} else {
vrele(dp);
nfsvno_relpathbuf(&named);
}
if (dirp != NULL && !(nd->nd_flag & ND_NFSV3)) {
vrele(dirp);
dirp = NULL;
}
if (nd->nd_repstat) {
if (dirp != NULL) {
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd, p, 0,
NULL);
vrele(dirp);
}
if (nd->nd_flag & ND_NFSV3)
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
&diraft);
goto out;
}
if (dirp != NULL)
dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd, p, 0, NULL);
/*
* Call nfsrvd_mkdirsub() for the code common to V4 as well.
*/
nfsrvd_mkdirsub(nd, &named, &nva, fhp, vpp, dirp, &dirfor, &diraft,
&diraft_ret, NULL, NULL, p, exp);
if (nd->nd_flag & ND_NFSV3) {
if (!nd->nd_repstat) {
(void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 1);
nfsrv_postopattr(nd, 0, &nva);
}
nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
} else if (!nd->nd_repstat) {
(void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
nfsrv_fillattr(nd, &nva);
}
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vrele(dp);
nfsvno_relpathbuf(&named);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* Code common to mkdir for V2,3 and 4.
*/
static void
nfsrvd_mkdirsub(struct nfsrv_descript *nd, struct nameidata *ndp,
struct nfsvattr *nvap, fhandle_t *fhp, vnode_t *vpp,
vnode_t dirp, struct nfsvattr *dirforp, struct nfsvattr *diraftp,
int *diraft_retp, nfsattrbit_t *attrbitp, NFSACL_T *aclp,
NFSPROC_T *p, struct nfsexstuff *exp)
{
vnode_t vp;
u_int32_t *tl;
NFSVNO_SETATTRVAL(nvap, type, VDIR);
nd->nd_repstat = nfsvno_mkdir(ndp, nvap, nd->nd_saveduid,
nd->nd_cred, p, exp);
if (!nd->nd_repstat) {
vp = ndp->ni_vp;
nfsrv_fixattr(nd, vp, nvap, aclp, p, attrbitp, exp);
nd->nd_repstat = nfsvno_getfh(vp, fhp, p);
if (!(nd->nd_flag & ND_NFSV4) && !nd->nd_repstat)
nd->nd_repstat = nfsvno_getattr(vp, nvap, nd, p, 1,
NULL);
if (vpp && !nd->nd_repstat) {
NFSVOPUNLOCK(vp);
*vpp = vp;
} else {
vput(vp);
}
}
if (dirp) {
*diraft_retp = nfsvno_getattr(dirp, diraftp, nd, p, 0, NULL);
vrele(dirp);
}
if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
*tl++ = newnfs_false;
txdr_hyper(dirforp->na_filerev, tl);
tl += 2;
txdr_hyper(diraftp->na_filerev, tl);
(void) nfsrv_putattrbit(nd, attrbitp);
}
NFSEXITCODE2(0, nd);
}
/*
* nfs commit service
*/
int
nfsrvd_commit(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
struct nfsvattr bfor, aft;
u_int32_t *tl;
int error = 0, for_ret = 1, aft_ret = 1, cnt;
u_int64_t off;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_wcc(nd, for_ret, &bfor, aft_ret, &aft);
goto out;
}
/* Return NFSERR_ISDIR in NFSv4 when commit on a directory. */
if (vp->v_type != VREG) {
if (nd->nd_flag & ND_NFSV3)
error = NFSERR_NOTSUPP;
else
error = (vp->v_type == VDIR) ? NFSERR_ISDIR : NFSERR_INVAL;
goto nfsmout;
}
NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
/*
* XXX At this time VOP_FSYNC() does not accept offset and byte
* count parameters, so these arguments are useless (someday maybe).
*/
off = fxdr_hyper(tl);
tl += 2;
cnt = fxdr_unsigned(int, *tl);
if (nd->nd_flag & ND_NFSV3)
for_ret = nfsvno_getattr(vp, &bfor, nd, p, 1, NULL);
nd->nd_repstat = nfsvno_fsync(vp, off, cnt, nd->nd_cred, p);
if (nd->nd_flag & ND_NFSV3) {
aft_ret = nfsvno_getattr(vp, &aft, nd, p, 1, NULL);
nfsrv_wcc(nd, for_ret, &bfor, aft_ret, &aft);
}
vput(vp);
if (!nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, NFSX_VERF);
*tl++ = txdr_unsigned(nfsboottime.tv_sec);
*tl = txdr_unsigned(nfsboottime.tv_usec);
}
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs statfs service
*/
int
nfsrvd_statfs(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
struct statfs *sf;
u_int32_t *tl;
int getret = 1;
struct nfsvattr at;
u_quad_t tval;
struct thread *p = curthread;
sf = NULL;
if (nd->nd_repstat) {
nfsrv_postopattr(nd, getret, &at);
goto out;
}
sf = malloc(sizeof(struct statfs), M_STATFS, M_WAITOK);
nd->nd_repstat = nfsvno_statfs(vp, sf);
getret = nfsvno_getattr(vp, &at, nd, p, 1, NULL);
vput(vp);
if (nd->nd_flag & ND_NFSV3)
nfsrv_postopattr(nd, getret, &at);
if (nd->nd_repstat)
goto out;
if (nd->nd_flag & ND_NFSV2) {
NFSM_BUILD(tl, u_int32_t *, NFSX_V2STATFS);
*tl++ = txdr_unsigned(NFS_V2MAXDATA);
*tl++ = txdr_unsigned(sf->f_bsize);
*tl++ = txdr_unsigned(sf->f_blocks);
*tl++ = txdr_unsigned(sf->f_bfree);
*tl = txdr_unsigned(sf->f_bavail);
} else {
NFSM_BUILD(tl, u_int32_t *, NFSX_V3STATFS);
tval = (u_quad_t)sf->f_blocks;
tval *= (u_quad_t)sf->f_bsize;
txdr_hyper(tval, tl); tl += 2;
tval = (u_quad_t)sf->f_bfree;
tval *= (u_quad_t)sf->f_bsize;
txdr_hyper(tval, tl); tl += 2;
tval = (u_quad_t)sf->f_bavail;
tval *= (u_quad_t)sf->f_bsize;
txdr_hyper(tval, tl); tl += 2;
tval = (u_quad_t)sf->f_files;
txdr_hyper(tval, tl); tl += 2;
tval = (u_quad_t)sf->f_ffree;
txdr_hyper(tval, tl); tl += 2;
tval = (u_quad_t)sf->f_ffree;
txdr_hyper(tval, tl); tl += 2;
*tl = 0;
}
out:
free(sf, M_STATFS);
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfs fsinfo service
*/
int
nfsrvd_fsinfo(struct nfsrv_descript *nd, int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
struct nfsfsinfo fs;
int getret = 1;
struct nfsvattr at;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_postopattr(nd, getret, &at);
goto out;
}
getret = nfsvno_getattr(vp, &at, nd, p, 1, NULL);
nfsvno_getfs(&fs, isdgram);
vput(vp);
nfsrv_postopattr(nd, getret, &at);
NFSM_BUILD(tl, u_int32_t *, NFSX_V3FSINFO);
*tl++ = txdr_unsigned(fs.fs_rtmax);
*tl++ = txdr_unsigned(fs.fs_rtpref);
*tl++ = txdr_unsigned(fs.fs_rtmult);
*tl++ = txdr_unsigned(fs.fs_wtmax);
*tl++ = txdr_unsigned(fs.fs_wtpref);
*tl++ = txdr_unsigned(fs.fs_wtmult);
*tl++ = txdr_unsigned(fs.fs_dtpref);
txdr_hyper(fs.fs_maxfilesize, tl);
tl += 2;
txdr_nfsv3time(&fs.fs_timedelta, tl);
tl += 2;
*tl = txdr_unsigned(fs.fs_properties);
out:
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfs pathconf service
*/
int
nfsrvd_pathconf(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
struct nfsv3_pathconf *pc;
int getret = 1;
long linkmax, namemax, chownres, notrunc;
struct nfsvattr at;
struct thread *p = curthread;
if (nd->nd_repstat) {
nfsrv_postopattr(nd, getret, &at);
goto out;
}
nd->nd_repstat = nfsvno_pathconf(vp, _PC_LINK_MAX, &linkmax,
nd->nd_cred, p);
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_pathconf(vp, _PC_NAME_MAX, &namemax,
nd->nd_cred, p);
if (!nd->nd_repstat)
nd->nd_repstat=nfsvno_pathconf(vp, _PC_CHOWN_RESTRICTED,
&chownres, nd->nd_cred, p);
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_pathconf(vp, _PC_NO_TRUNC, &notrunc,
nd->nd_cred, p);
getret = nfsvno_getattr(vp, &at, nd, p, 1, NULL);
vput(vp);
nfsrv_postopattr(nd, getret, &at);
if (!nd->nd_repstat) {
NFSM_BUILD(pc, struct nfsv3_pathconf *, NFSX_V3PATHCONF);
pc->pc_linkmax = txdr_unsigned(linkmax);
pc->pc_namemax = txdr_unsigned(namemax);
pc->pc_notrunc = txdr_unsigned(notrunc);
pc->pc_chownrestricted = txdr_unsigned(chownres);
/*
* These should probably be supported by VOP_PATHCONF(), but
* until msdosfs is exportable (why would you want to?), the
* Unix defaults should be ok.
*/
pc->pc_caseinsensitive = newnfs_false;
pc->pc_casepreserving = newnfs_true;
}
out:
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfsv4 lock service
*/
int
nfsrvd_lock(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
u_int32_t *tl;
int i;
struct nfsstate *stp = NULL;
struct nfslock *lop;
struct nfslockconflict cf;
int error = 0;
u_short flags = NFSLCK_LOCK, lflags;
u_int64_t offset, len;
nfsv4stateid_t stateid;
nfsquad_t clientid;
struct thread *p = curthread;
NFSM_DISSECT(tl, u_int32_t *, 7 * NFSX_UNSIGNED);
i = fxdr_unsigned(int, *tl++);
switch (i) {
case NFSV4LOCKT_READW:
flags |= NFSLCK_BLOCKING;
case NFSV4LOCKT_READ:
lflags = NFSLCK_READ;
break;
case NFSV4LOCKT_WRITEW:
flags |= NFSLCK_BLOCKING;
case NFSV4LOCKT_WRITE:
lflags = NFSLCK_WRITE;
break;
default:
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
if (*tl++ == newnfs_true)
flags |= NFSLCK_RECLAIM;
offset = fxdr_hyper(tl);
tl += 2;
len = fxdr_hyper(tl);
tl += 2;
if (*tl == newnfs_true)
flags |= NFSLCK_OPENTOLOCK;
if (flags & NFSLCK_OPENTOLOCK) {
NFSM_DISSECT(tl, u_int32_t *, 5 * NFSX_UNSIGNED + NFSX_STATEID);
i = fxdr_unsigned(int, *(tl+4+(NFSX_STATEID / NFSX_UNSIGNED)));
if (i <= 0 || i > NFSV4_OPAQUELIMIT) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
stp = malloc(sizeof (struct nfsstate) + i,
M_NFSDSTATE, M_WAITOK);
stp->ls_ownerlen = i;
stp->ls_op = nd->nd_rp;
stp->ls_seq = fxdr_unsigned(int, *tl++);
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
/*
* For the special stateid of other all 0s and seqid == 1, set
* the stateid to the current stateid, if it is set.
*/
if ((nd->nd_flag & ND_NFSV41) != 0 &&
stp->ls_stateid.seqid == 1 &&
stp->ls_stateid.other[0] == 0 &&
stp->ls_stateid.other[1] == 0 &&
stp->ls_stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stp->ls_stateid = nd->nd_curstateid;
stp->ls_stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
stp->ls_opentolockseq = fxdr_unsigned(int, *tl++);
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl++;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK3 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
error = nfsrv_mtostr(nd, stp->ls_owner, stp->ls_ownerlen);
if (error)
goto nfsmout;
} else {
NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + NFSX_UNSIGNED);
stp = malloc(sizeof (struct nfsstate),
M_NFSDSTATE, M_WAITOK);
stp->ls_ownerlen = 0;
stp->ls_op = nd->nd_rp;
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
/*
* For the special stateid of other all 0s and seqid == 1, set
* the stateid to the current stateid, if it is set.
*/
if ((nd->nd_flag & ND_NFSV41) != 0 &&
stp->ls_stateid.seqid == 1 &&
stp->ls_stateid.other[0] == 0 &&
stp->ls_stateid.other[1] == 0 &&
stp->ls_stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stp->ls_stateid = nd->nd_curstateid;
stp->ls_stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
stp->ls_seq = fxdr_unsigned(int, *tl);
clientid.lval[0] = stp->ls_stateid.other[0];
clientid.lval[1] = stp->ls_stateid.other[1];
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK4 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
}
lop = malloc(sizeof (struct nfslock),
M_NFSDLOCK, M_WAITOK);
lop->lo_first = offset;
if (len == NFS64BITSSET) {
lop->lo_end = NFS64BITSSET;
} else {
lop->lo_end = offset + len;
if (lop->lo_end <= lop->lo_first)
nd->nd_repstat = NFSERR_INVAL;
}
lop->lo_flags = lflags;
stp->ls_flags = flags;
stp->ls_uid = nd->nd_cred->cr_uid;
/*
* Do basic access checking.
*/
if (!nd->nd_repstat && vnode_vtype(vp) != VREG) {
if (vnode_vtype(vp) == VDIR)
nd->nd_repstat = NFSERR_ISDIR;
else
nd->nd_repstat = NFSERR_INVAL;
}
if (!nd->nd_repstat) {
if (lflags & NFSLCK_WRITE) {
nd->nd_repstat = nfsvno_accchk(vp, VWRITE,
nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
NFSACCCHK_VPISLOCKED, NULL);
} else {
nd->nd_repstat = nfsvno_accchk(vp, VREAD,
nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
NFSACCCHK_VPISLOCKED, NULL);
if (nd->nd_repstat)
nd->nd_repstat = nfsvno_accchk(vp, VEXEC,
nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
NFSACCCHK_VPISLOCKED, NULL);
}
}
/*
* We call nfsrv_lockctrl() even if nd_repstat set, so that the
* seqid# gets updated. nfsrv_lockctrl() will return the value
* of nd_repstat, if it gets that far.
*/
nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, &cf, clientid,
&stateid, exp, nd, p);
if (lop)
free(lop, M_NFSDLOCK);
if (stp)
free(stp, M_NFSDSTATE);
if (!nd->nd_repstat) {
/* For NFSv4.1, set the Current StateID. */
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_curstateid = stateid;
nd->nd_flag |= ND_CURSTATEID;
}
NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
*tl++ = txdr_unsigned(stateid.seqid);
NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
} else if (nd->nd_repstat == NFSERR_DENIED) {
NFSM_BUILD(tl, u_int32_t *, 7 * NFSX_UNSIGNED);
txdr_hyper(cf.cl_first, tl);
tl += 2;
if (cf.cl_end == NFS64BITSSET)
len = NFS64BITSSET;
else
len = cf.cl_end - cf.cl_first;
txdr_hyper(len, tl);
tl += 2;
if (cf.cl_flags == NFSLCK_WRITE)
*tl++ = txdr_unsigned(NFSV4LOCKT_WRITE);
else
*tl++ = txdr_unsigned(NFSV4LOCKT_READ);
*tl++ = stateid.other[0];
*tl = stateid.other[1];
(void) nfsm_strtom(nd, cf.cl_owner, cf.cl_ownerlen);
}
vput(vp);
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
if (stp)
free(stp, M_NFSDSTATE);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 lock test service
*/
int
nfsrvd_lockt(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
u_int32_t *tl;
int i;
struct nfsstate *stp = NULL;
struct nfslock lo, *lop = &lo;
struct nfslockconflict cf;
int error = 0;
nfsv4stateid_t stateid;
nfsquad_t clientid;
u_int64_t len;
struct thread *p = curthread;
NFSM_DISSECT(tl, u_int32_t *, 8 * NFSX_UNSIGNED);
i = fxdr_unsigned(int, *(tl + 7));
if (i <= 0 || i > NFSV4_OPAQUELIMIT) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
stp = malloc(sizeof (struct nfsstate) + i,
M_NFSDSTATE, M_WAITOK);
stp->ls_ownerlen = i;
stp->ls_op = NULL;
stp->ls_flags = NFSLCK_TEST;
stp->ls_uid = nd->nd_cred->cr_uid;
i = fxdr_unsigned(int, *tl++);
switch (i) {
case NFSV4LOCKT_READW:
stp->ls_flags |= NFSLCK_BLOCKING;
case NFSV4LOCKT_READ:
lo.lo_flags = NFSLCK_READ;
break;
case NFSV4LOCKT_WRITEW:
stp->ls_flags |= NFSLCK_BLOCKING;
case NFSV4LOCKT_WRITE:
lo.lo_flags = NFSLCK_WRITE;
break;
default:
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
lo.lo_first = fxdr_hyper(tl);
tl += 2;
len = fxdr_hyper(tl);
if (len == NFS64BITSSET) {
lo.lo_end = NFS64BITSSET;
} else {
lo.lo_end = lo.lo_first + len;
if (lo.lo_end <= lo.lo_first)
nd->nd_repstat = NFSERR_INVAL;
}
tl += 2;
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK5 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
error = nfsrv_mtostr(nd, stp->ls_owner, stp->ls_ownerlen);
if (error)
goto nfsmout;
if (!nd->nd_repstat && vnode_vtype(vp) != VREG) {
if (vnode_vtype(vp) == VDIR)
nd->nd_repstat = NFSERR_ISDIR;
else
nd->nd_repstat = NFSERR_INVAL;
}
if (!nd->nd_repstat)
nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, &cf, clientid,
&stateid, exp, nd, p);
if (nd->nd_repstat) {
if (nd->nd_repstat == NFSERR_DENIED) {
NFSM_BUILD(tl, u_int32_t *, 7 * NFSX_UNSIGNED);
txdr_hyper(cf.cl_first, tl);
tl += 2;
if (cf.cl_end == NFS64BITSSET)
len = NFS64BITSSET;
else
len = cf.cl_end - cf.cl_first;
txdr_hyper(len, tl);
tl += 2;
if (cf.cl_flags == NFSLCK_WRITE)
*tl++ = txdr_unsigned(NFSV4LOCKT_WRITE);
else
*tl++ = txdr_unsigned(NFSV4LOCKT_READ);
*tl++ = stp->ls_stateid.other[0];
*tl = stp->ls_stateid.other[1];
(void) nfsm_strtom(nd, cf.cl_owner, cf.cl_ownerlen);
}
}
vput(vp);
if (stp)
free(stp, M_NFSDSTATE);
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
if (stp)
free(stp, M_NFSDSTATE);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 unlock service
*/
int
nfsrvd_locku(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
u_int32_t *tl;
int i;
struct nfsstate *stp;
struct nfslock *lop;
int error = 0;
nfsv4stateid_t stateid;
nfsquad_t clientid;
u_int64_t len;
struct thread *p = curthread;
NFSM_DISSECT(tl, u_int32_t *, 6 * NFSX_UNSIGNED + NFSX_STATEID);
stp = malloc(sizeof (struct nfsstate),
M_NFSDSTATE, M_WAITOK);
lop = malloc(sizeof (struct nfslock),
M_NFSDLOCK, M_WAITOK);
stp->ls_flags = NFSLCK_UNLOCK;
lop->lo_flags = NFSLCK_UNLOCK;
stp->ls_op = nd->nd_rp;
i = fxdr_unsigned(int, *tl++);
switch (i) {
case NFSV4LOCKT_READW:
stp->ls_flags |= NFSLCK_BLOCKING;
case NFSV4LOCKT_READ:
break;
case NFSV4LOCKT_WRITEW:
stp->ls_flags |= NFSLCK_BLOCKING;
case NFSV4LOCKT_WRITE:
break;
default:
nd->nd_repstat = NFSERR_BADXDR;
free(stp, M_NFSDSTATE);
free(lop, M_NFSDLOCK);
goto nfsmout;
}
stp->ls_ownerlen = 0;
stp->ls_uid = nd->nd_cred->cr_uid;
stp->ls_seq = fxdr_unsigned(int, *tl++);
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
/*
* For the special stateid of other all 0s and seqid == 1, set the
* stateid to the current stateid, if it is set.
*/
if ((nd->nd_flag & ND_NFSV41) != 0 && stp->ls_stateid.seqid == 1 &&
stp->ls_stateid.other[0] == 0 && stp->ls_stateid.other[1] == 0 &&
stp->ls_stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stp->ls_stateid = nd->nd_curstateid;
stp->ls_stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
free(stp, M_NFSDSTATE);
free(lop, M_NFSDLOCK);
goto nfsmout;
}
}
lop->lo_first = fxdr_hyper(tl);
tl += 2;
len = fxdr_hyper(tl);
if (len == NFS64BITSSET) {
lop->lo_end = NFS64BITSSET;
} else {
lop->lo_end = lop->lo_first + len;
if (lop->lo_end <= lop->lo_first)
nd->nd_repstat = NFSERR_INVAL;
}
clientid.lval[0] = stp->ls_stateid.other[0];
clientid.lval[1] = stp->ls_stateid.other[1];
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK6 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
if (!nd->nd_repstat && vnode_vtype(vp) != VREG) {
if (vnode_vtype(vp) == VDIR)
nd->nd_repstat = NFSERR_ISDIR;
else
nd->nd_repstat = NFSERR_INVAL;
}
/*
* Call nfsrv_lockctrl() even if nd_repstat is set, so that the
* seqid# gets incremented. nfsrv_lockctrl() will return the
* value of nd_repstat, if it gets that far.
*/
nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
&stateid, exp, nd, p);
if (stp)
free(stp, M_NFSDSTATE);
if (lop)
free(lop, M_NFSDLOCK);
if (!nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
*tl++ = txdr_unsigned(stateid.seqid);
NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
}
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 open service
*/
int
nfsrvd_open(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t dp, vnode_t *vpp, __unused fhandle_t *fhp, struct nfsexstuff *exp)
{
u_int32_t *tl;
int i, retext;
struct nfsstate *stp = NULL;
int error = 0, create, claim, exclusive_flag = 0, override;
u_int32_t rflags = NFSV4OPEN_LOCKTYPEPOSIX, acemask;
int how = NFSCREATE_UNCHECKED;
int32_t cverf[2], tverf[2] = { 0, 0 };
vnode_t vp = NULL, dirp = NULL;
struct nfsvattr nva, dirfor, diraft;
struct nameidata named;
nfsv4stateid_t stateid, delegstateid;
nfsattrbit_t attrbits;
nfsquad_t clientid;
char *bufp = NULL;
u_long *hashp;
NFSACL_T *aclp = NULL;
struct thread *p = curthread;
#ifdef NFS4_ACL_EXTATTR_NAME
aclp = acl_alloc(M_WAITOK);
aclp->acl_cnt = 0;
#endif
NFSZERO_ATTRBIT(&attrbits);
named.ni_startdir = NULL;
named.ni_cnd.cn_nameiop = 0;
NFSM_DISSECT(tl, u_int32_t *, 6 * NFSX_UNSIGNED);
i = fxdr_unsigned(int, *(tl + 5));
if (i <= 0 || i > NFSV4_OPAQUELIMIT) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
stp = malloc(sizeof (struct nfsstate) + i,
M_NFSDSTATE, M_WAITOK);
stp->ls_ownerlen = i;
stp->ls_op = nd->nd_rp;
stp->ls_flags = NFSLCK_OPEN;
stp->ls_uid = nd->nd_cred->cr_uid;
stp->ls_seq = fxdr_unsigned(u_int32_t, *tl++);
i = fxdr_unsigned(int, *tl++);
retext = 0;
if ((i & (NFSV4OPEN_WANTDELEGMASK | NFSV4OPEN_WANTSIGNALDELEG |
NFSV4OPEN_WANTPUSHDELEG)) != 0 && (nd->nd_flag & ND_NFSV41) != 0) {
retext = 1;
/* For now, ignore these. */
i &= ~(NFSV4OPEN_WANTPUSHDELEG | NFSV4OPEN_WANTSIGNALDELEG);
switch (i & NFSV4OPEN_WANTDELEGMASK) {
case NFSV4OPEN_WANTANYDELEG:
stp->ls_flags |= (NFSLCK_WANTRDELEG |
NFSLCK_WANTWDELEG);
i &= ~NFSV4OPEN_WANTDELEGMASK;
break;
case NFSV4OPEN_WANTREADDELEG:
stp->ls_flags |= NFSLCK_WANTRDELEG;
i &= ~NFSV4OPEN_WANTDELEGMASK;
break;
case NFSV4OPEN_WANTWRITEDELEG:
stp->ls_flags |= NFSLCK_WANTWDELEG;
i &= ~NFSV4OPEN_WANTDELEGMASK;
break;
case NFSV4OPEN_WANTNODELEG:
stp->ls_flags |= NFSLCK_WANTNODELEG;
i &= ~NFSV4OPEN_WANTDELEGMASK;
break;
case NFSV4OPEN_WANTCANCEL:
printf("NFSv4: ignore Open WantCancel\n");
i &= ~NFSV4OPEN_WANTDELEGMASK;
break;
default:
/* nd_repstat will be set to NFSERR_INVAL below. */
break;
}
}
switch (i) {
case NFSV4OPEN_ACCESSREAD:
stp->ls_flags |= NFSLCK_READACCESS;
break;
case NFSV4OPEN_ACCESSWRITE:
stp->ls_flags |= NFSLCK_WRITEACCESS;
break;
case NFSV4OPEN_ACCESSBOTH:
stp->ls_flags |= (NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
break;
default:
nd->nd_repstat = NFSERR_INVAL;
}
i = fxdr_unsigned(int, *tl++);
switch (i) {
case NFSV4OPEN_DENYNONE:
break;
case NFSV4OPEN_DENYREAD:
stp->ls_flags |= NFSLCK_READDENY;
break;
case NFSV4OPEN_DENYWRITE:
stp->ls_flags |= NFSLCK_WRITEDENY;
break;
case NFSV4OPEN_DENYBOTH:
stp->ls_flags |= (NFSLCK_READDENY | NFSLCK_WRITEDENY);
break;
default:
nd->nd_repstat = NFSERR_INVAL;
}
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK7 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
error = nfsrv_mtostr(nd, stp->ls_owner, stp->ls_ownerlen);
if (error)
goto nfsmout;
NFSVNO_ATTRINIT(&nva);
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
create = fxdr_unsigned(int, *tl);
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_getattr(dp, &dirfor, nd, p, 0, NULL);
if (create == NFSV4OPEN_CREATE) {
nva.na_type = VREG;
nva.na_mode = 0;
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
how = fxdr_unsigned(int, *tl);
switch (how) {
case NFSCREATE_UNCHECKED:
case NFSCREATE_GUARDED:
error = nfsv4_sattr(nd, NULL, &nva, &attrbits, aclp, p);
if (error)
goto nfsmout;
/*
* If the na_gid being set is the same as that of
* the directory it is going in, clear it, since
* that is what will be set by default. This allows
* a user that isn't in that group to do the create.
*/
if (!nd->nd_repstat && NFSVNO_ISSETGID(&nva) &&
nva.na_gid == dirfor.na_gid)
NFSVNO_UNSET(&nva, gid);
if (!nd->nd_repstat)
nd->nd_repstat = nfsrv_checkuidgid(nd, &nva);
break;
case NFSCREATE_EXCLUSIVE:
NFSM_DISSECT(tl, u_int32_t *, NFSX_VERF);
cverf[0] = *tl++;
cverf[1] = *tl;
break;
case NFSCREATE_EXCLUSIVE41:
NFSM_DISSECT(tl, u_int32_t *, NFSX_VERF);
cverf[0] = *tl++;
cverf[1] = *tl;
error = nfsv4_sattr(nd, NULL, &nva, &attrbits, aclp, p);
if (error != 0)
goto nfsmout;
if (NFSISSET_ATTRBIT(&attrbits,
NFSATTRBIT_TIMEACCESSSET))
nd->nd_repstat = NFSERR_INVAL;
/*
* If the na_gid being set is the same as that of
* the directory it is going in, clear it, since
* that is what will be set by default. This allows
* a user that isn't in that group to do the create.
*/
if (nd->nd_repstat == 0 && NFSVNO_ISSETGID(&nva) &&
nva.na_gid == dirfor.na_gid)
NFSVNO_UNSET(&nva, gid);
if (nd->nd_repstat == 0)
nd->nd_repstat = nfsrv_checkuidgid(nd, &nva);
break;
default:
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
} else if (create != NFSV4OPEN_NOCREATE) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
/*
* Now, handle the claim, which usually includes looking up a
* name in the directory referenced by dp. The exception is
* NFSV4OPEN_CLAIMPREVIOUS.
*/
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
claim = fxdr_unsigned(int, *tl);
if (claim == NFSV4OPEN_CLAIMDELEGATECUR) {
NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID);
stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
NFSBCOPY((caddr_t)tl,(caddr_t)stateid.other,NFSX_STATEIDOTHER);
stp->ls_flags |= NFSLCK_DELEGCUR;
} else if (claim == NFSV4OPEN_CLAIMDELEGATEPREV) {
stp->ls_flags |= NFSLCK_DELEGPREV;
}
if (claim == NFSV4OPEN_CLAIMNULL || claim == NFSV4OPEN_CLAIMDELEGATECUR
|| claim == NFSV4OPEN_CLAIMDELEGATEPREV) {
if (!nd->nd_repstat && create == NFSV4OPEN_CREATE &&
claim != NFSV4OPEN_CLAIMNULL)
nd->nd_repstat = NFSERR_INVAL;
if (nd->nd_repstat) {
nd->nd_repstat = nfsrv_opencheck(clientid,
&stateid, stp, NULL, nd, p, nd->nd_repstat);
goto nfsmout;
}
if (create == NFSV4OPEN_CREATE)
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE,
LOCKPARENT | LOCKLEAF | SAVESTART | NOCACHE);
else
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, LOOKUP,
LOCKLEAF | SAVESTART);
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (error) {
vrele(dp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
free(stp, M_NFSDSTATE);
nfsvno_relpathbuf(&named);
NFSEXITCODE2(error, nd);
return (error);
}
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp,
p, &dirp);
} else {
vrele(dp);
nfsvno_relpathbuf(&named);
}
if (create == NFSV4OPEN_CREATE) {
switch (how) {
case NFSCREATE_UNCHECKED:
if (named.ni_vp) {
/*
* Clear the setable attribute bits, except
* for Size, if it is being truncated.
*/
NFSZERO_ATTRBIT(&attrbits);
if (NFSVNO_ISSETSIZE(&nva))
NFSSETBIT_ATTRBIT(&attrbits,
NFSATTRBIT_SIZE);
}
break;
case NFSCREATE_GUARDED:
if (named.ni_vp && !nd->nd_repstat)
nd->nd_repstat = EEXIST;
break;
case NFSCREATE_EXCLUSIVE:
exclusive_flag = 1;
if (!named.ni_vp)
nva.na_mode = 0;
break;
case NFSCREATE_EXCLUSIVE41:
exclusive_flag = 1;
break;
}
}
nfsvno_open(nd, &named, clientid, &stateid, stp,
&exclusive_flag, &nva, cverf, create, aclp, &attrbits,
nd->nd_cred, exp, &vp);
} else if (claim == NFSV4OPEN_CLAIMPREVIOUS || claim ==
NFSV4OPEN_CLAIMFH) {
if (claim == NFSV4OPEN_CLAIMPREVIOUS) {
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
i = fxdr_unsigned(int, *tl);
switch (i) {
case NFSV4OPEN_DELEGATEREAD:
stp->ls_flags |= NFSLCK_DELEGREAD;
break;
case NFSV4OPEN_DELEGATEWRITE:
stp->ls_flags |= NFSLCK_DELEGWRITE;
case NFSV4OPEN_DELEGATENONE:
break;
default:
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
stp->ls_flags |= NFSLCK_RECLAIM;
} else {
/* CLAIM_NULL_FH */
if (nd->nd_repstat == 0 && create == NFSV4OPEN_CREATE)
nd->nd_repstat = NFSERR_INVAL;
}
vp = dp;
NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY);
if (!VN_IS_DOOMED(vp))
nd->nd_repstat = nfsrv_opencheck(clientid, &stateid,
stp, vp, nd, p, nd->nd_repstat);
else
nd->nd_repstat = NFSERR_PERM;
} else {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
/*
* Do basic access checking.
*/
if (!nd->nd_repstat && vnode_vtype(vp) != VREG) {
/*
* The IETF working group decided that this is the correct
* error return for all non-regular files.
*/
nd->nd_repstat = (vp->v_type == VDIR) ? NFSERR_ISDIR : NFSERR_SYMLINK;
}
/*
* If the Open is being done for a file that already exists, apply
* normal permission checking including for the file owner, if
* vfs.nfsd.v4openaccess is set.
* Previously, the owner was always allowed to open the file to
* be consistent with the NFS tradition of always allowing the
* owner of the file to write to the file regardless of permissions.
* It now appears that the Linux client expects the owner
* permissions to be checked for opens that are not creating the
* file. I believe the correct approach is to use the Access
* operation's results to be consistent with NFSv3, but that is
* not what the current Linux client appears to be doing.
* Since both the Linux and OpenSolaris NFSv4 servers do this check,
* I have enabled it by default.
* If this semantic change causes a problem, it can be disabled by
* setting the sysctl vfs.nfsd.v4openaccess to 0 to re-enable the
* previous semantics.
*/
if (nfsrv_openaccess && create == NFSV4OPEN_NOCREATE)
override = NFSACCCHK_NOOVERRIDE;
else
override = NFSACCCHK_ALLOWOWNER;
if (!nd->nd_repstat && (stp->ls_flags & NFSLCK_WRITEACCESS))
nd->nd_repstat = nfsvno_accchk(vp, VWRITE, nd->nd_cred,
exp, p, override, NFSACCCHK_VPISLOCKED, NULL);
if (!nd->nd_repstat && (stp->ls_flags & NFSLCK_READACCESS)) {
nd->nd_repstat = nfsvno_accchk(vp, VREAD, nd->nd_cred,
exp, p, override, NFSACCCHK_VPISLOCKED, NULL);
if (nd->nd_repstat)
nd->nd_repstat = nfsvno_accchk(vp, VEXEC,
nd->nd_cred, exp, p, override,
NFSACCCHK_VPISLOCKED, NULL);
}
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
if (!nd->nd_repstat) {
tverf[0] = nva.na_atime.tv_sec;
tverf[1] = nva.na_atime.tv_nsec;
}
}
if (!nd->nd_repstat && exclusive_flag && (cverf[0] != tverf[0] ||
cverf[1] != tverf[1]))
nd->nd_repstat = EEXIST;
/*
* Do the open locking/delegation stuff.
*/
if (!nd->nd_repstat)
nd->nd_repstat = nfsrv_openctrl(nd, vp, &stp, clientid, &stateid,
&delegstateid, &rflags, exp, p, nva.na_filerev);
/*
* vp must be unlocked before the call to nfsvno_getattr(dirp,...)
* below, to avoid a deadlock with the lookup in nfsvno_namei() above.
* (ie: Leave the NFSVOPUNLOCK() about here.)
*/
if (vp)
NFSVOPUNLOCK(vp);
if (stp)
free(stp, M_NFSDSTATE);
if (!nd->nd_repstat && dirp)
nd->nd_repstat = nfsvno_getattr(dirp, &diraft, nd, p, 0, NULL);
if (!nd->nd_repstat) {
/* For NFSv4.1, set the Current StateID. */
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_curstateid = stateid;
nd->nd_flag |= ND_CURSTATEID;
}
NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID + 6 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(stateid.seqid);
NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
if (claim == NFSV4OPEN_CLAIMPREVIOUS) {
*tl++ = newnfs_true;
*tl++ = 0;
*tl++ = 0;
*tl++ = 0;
*tl++ = 0;
} else {
*tl++ = newnfs_false; /* Since dirp is not locked */
txdr_hyper(dirfor.na_filerev, tl);
tl += 2;
txdr_hyper(diraft.na_filerev, tl);
tl += 2;
}
*tl = txdr_unsigned(rflags & NFSV4OPEN_RFLAGS);
(void) nfsrv_putattrbit(nd, &attrbits);
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
if (rflags & NFSV4OPEN_READDELEGATE)
*tl = txdr_unsigned(NFSV4OPEN_DELEGATEREAD);
else if (rflags & NFSV4OPEN_WRITEDELEGATE)
*tl = txdr_unsigned(NFSV4OPEN_DELEGATEWRITE);
else if (retext != 0) {
*tl = txdr_unsigned(NFSV4OPEN_DELEGATENONEEXT);
if ((rflags & NFSV4OPEN_WDNOTWANTED) != 0) {
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(NFSV4OPEN_NOTWANTED);
} else if ((rflags & NFSV4OPEN_WDSUPPFTYPE) != 0) {
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(NFSV4OPEN_NOTSUPPFTYPE);
} else if ((rflags & NFSV4OPEN_WDCONTENTION) != 0) {
NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(NFSV4OPEN_CONTENTION);
*tl = newnfs_false;
} else if ((rflags & NFSV4OPEN_WDRESOURCE) != 0) {
NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(NFSV4OPEN_RESOURCE);
*tl = newnfs_false;
} else {
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(NFSV4OPEN_NOTWANTED);
}
} else
*tl = txdr_unsigned(NFSV4OPEN_DELEGATENONE);
if (rflags & (NFSV4OPEN_READDELEGATE|NFSV4OPEN_WRITEDELEGATE)) {
NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID+NFSX_UNSIGNED);
*tl++ = txdr_unsigned(delegstateid.seqid);
NFSBCOPY((caddr_t)delegstateid.other, (caddr_t)tl,
NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
if (rflags & NFSV4OPEN_RECALL)
*tl = newnfs_true;
else
*tl = newnfs_false;
if (rflags & NFSV4OPEN_WRITEDELEGATE) {
NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(NFSV4OPEN_LIMITSIZE);
txdr_hyper(nva.na_size, tl);
}
NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(NFSV4ACE_ALLOWEDTYPE);
*tl++ = txdr_unsigned(0x0);
acemask = NFSV4ACE_ALLFILESMASK;
if (nva.na_mode & S_IRUSR)
acemask |= NFSV4ACE_READMASK;
if (nva.na_mode & S_IWUSR)
acemask |= NFSV4ACE_WRITEMASK;
if (nva.na_mode & S_IXUSR)
acemask |= NFSV4ACE_EXECUTEMASK;
*tl = txdr_unsigned(acemask);
(void) nfsm_strtom(nd, "OWNER@", 6);
}
*vpp = vp;
} else if (vp) {
vrele(vp);
}
if (dirp)
vrele(dirp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vrele(dp);
#ifdef NFS4_ACL_EXTATTR_NAME
acl_free(aclp);
#endif
if (stp)
free(stp, M_NFSDSTATE);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 close service
*/
int
nfsrvd_close(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
struct nfsstate st, *stp = &st;
int error = 0, writeacc;
nfsv4stateid_t stateid;
nfsquad_t clientid;
struct nfsvattr na;
struct thread *p = curthread;
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED + NFSX_STATEID);
stp->ls_seq = fxdr_unsigned(u_int32_t, *tl++);
stp->ls_ownerlen = 0;
stp->ls_op = nd->nd_rp;
stp->ls_uid = nd->nd_cred->cr_uid;
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
NFSX_STATEIDOTHER);
/*
* For the special stateid of other all 0s and seqid == 1, set the
* stateid to the current stateid, if it is set.
*/
if ((nd->nd_flag & ND_NFSV41) != 0 && stp->ls_stateid.seqid == 1 &&
stp->ls_stateid.other[0] == 0 && stp->ls_stateid.other[1] == 0 &&
stp->ls_stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0)
stp->ls_stateid = nd->nd_curstateid;
else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
stp->ls_flags = NFSLCK_CLOSE;
clientid.lval[0] = stp->ls_stateid.other[0];
clientid.lval[1] = stp->ls_stateid.other[1];
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK8 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
nd->nd_repstat = nfsrv_openupdate(vp, stp, clientid, &stateid, nd, p,
&writeacc);
/* For pNFS, update the attributes. */
if (writeacc != 0 || nfsrv_pnfsatime != 0)
nfsrv_updatemdsattr(vp, &na, p);
vput(vp);
if (!nd->nd_repstat) {
/*
* If the stateid that has been closed is the current stateid,
* unset it.
*/
if ((nd->nd_flag & ND_CURSTATEID) != 0 &&
stateid.other[0] == nd->nd_curstateid.other[0] &&
stateid.other[1] == nd->nd_curstateid.other[1] &&
stateid.other[2] == nd->nd_curstateid.other[2])
nd->nd_flag &= ~ND_CURSTATEID;
NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
*tl++ = txdr_unsigned(stateid.seqid);
NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
}
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 delegpurge service
*/
int
nfsrvd_delegpurge(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
int error = 0;
nfsquad_t clientid;
struct thread *p = curthread;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK9 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
nd->nd_repstat = nfsrv_delegupdate(nd, clientid, NULL, NULL,
NFSV4OP_DELEGPURGE, nd->nd_cred, p, NULL);
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 delegreturn service
*/
int
nfsrvd_delegreturn(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
int error = 0, writeacc;
nfsv4stateid_t stateid;
nfsquad_t clientid;
struct nfsvattr na;
struct thread *p = curthread;
NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID);
stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
NFSBCOPY((caddr_t)tl, (caddr_t)stateid.other, NFSX_STATEIDOTHER);
clientid.lval[0] = stateid.other[0];
clientid.lval[1] = stateid.other[1];
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK10 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
nd->nd_repstat = nfsrv_delegupdate(nd, clientid, &stateid, vp,
NFSV4OP_DELEGRETURN, nd->nd_cred, p, &writeacc);
/* For pNFS, update the attributes. */
if (writeacc != 0 || nfsrv_pnfsatime != 0)
nfsrv_updatemdsattr(vp, &na, p);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 get file handle service
*/
int
nfsrvd_getfh(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
fhandle_t fh;
struct thread *p = curthread;
nd->nd_repstat = nfsvno_getfh(vp, &fh, p);
vput(vp);
if (!nd->nd_repstat)
(void) nfsm_fhtom(nd, (u_int8_t *)&fh, 0, 0);
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfsv4 open confirm service
*/
int
nfsrvd_openconfirm(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
struct nfsstate st, *stp = &st;
int error = 0;
nfsv4stateid_t stateid;
nfsquad_t clientid;
struct thread *p = curthread;
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + NFSX_UNSIGNED);
stp->ls_ownerlen = 0;
stp->ls_op = nd->nd_rp;
stp->ls_uid = nd->nd_cred->cr_uid;
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
stp->ls_seq = fxdr_unsigned(u_int32_t, *tl);
stp->ls_flags = NFSLCK_CONFIRM;
clientid.lval[0] = stp->ls_stateid.other[0];
clientid.lval[1] = stp->ls_stateid.other[1];
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK11 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
nd->nd_repstat = nfsrv_openupdate(vp, stp, clientid, &stateid, nd, p,
NULL);
if (!nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
*tl++ = txdr_unsigned(stateid.seqid);
NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
}
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 open downgrade service
*/
int
nfsrvd_opendowngrade(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
int i;
struct nfsstate st, *stp = &st;
int error = 0;
nfsv4stateid_t stateid;
nfsquad_t clientid;
struct thread *p = curthread;
/* opendowngrade can only work on a file object.*/
if (vp->v_type != VREG) {
error = NFSERR_INVAL;
goto nfsmout;
}
NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + 3 * NFSX_UNSIGNED);
stp->ls_ownerlen = 0;
stp->ls_op = nd->nd_rp;
stp->ls_uid = nd->nd_cred->cr_uid;
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
/*
* For the special stateid of other all 0s and seqid == 1, set the
* stateid to the current stateid, if it is set.
*/
if ((nd->nd_flag & ND_NFSV41) != 0 && stp->ls_stateid.seqid == 1 &&
stp->ls_stateid.other[0] == 0 && stp->ls_stateid.other[1] == 0 &&
stp->ls_stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0)
stp->ls_stateid = nd->nd_curstateid;
else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
stp->ls_seq = fxdr_unsigned(u_int32_t, *tl++);
i = fxdr_unsigned(int, *tl++);
if ((nd->nd_flag & ND_NFSV41) != 0)
i &= ~NFSV4OPEN_WANTDELEGMASK;
switch (i) {
case NFSV4OPEN_ACCESSREAD:
stp->ls_flags = (NFSLCK_READACCESS | NFSLCK_DOWNGRADE);
break;
case NFSV4OPEN_ACCESSWRITE:
stp->ls_flags = (NFSLCK_WRITEACCESS | NFSLCK_DOWNGRADE);
break;
case NFSV4OPEN_ACCESSBOTH:
stp->ls_flags = (NFSLCK_READACCESS | NFSLCK_WRITEACCESS |
NFSLCK_DOWNGRADE);
break;
default:
nd->nd_repstat = NFSERR_INVAL;
}
i = fxdr_unsigned(int, *tl);
switch (i) {
case NFSV4OPEN_DENYNONE:
break;
case NFSV4OPEN_DENYREAD:
stp->ls_flags |= NFSLCK_READDENY;
break;
case NFSV4OPEN_DENYWRITE:
stp->ls_flags |= NFSLCK_WRITEDENY;
break;
case NFSV4OPEN_DENYBOTH:
stp->ls_flags |= (NFSLCK_READDENY | NFSLCK_WRITEDENY);
break;
default:
nd->nd_repstat = NFSERR_INVAL;
}
clientid.lval[0] = stp->ls_stateid.other[0];
clientid.lval[1] = stp->ls_stateid.other[1];
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK12 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
if (!nd->nd_repstat)
nd->nd_repstat = nfsrv_openupdate(vp, stp, clientid, &stateid,
nd, p, NULL);
if (!nd->nd_repstat) {
/* For NFSv4.1, set the Current StateID. */
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_curstateid = stateid;
nd->nd_flag |= ND_CURSTATEID;
}
NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
*tl++ = txdr_unsigned(stateid.seqid);
NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
}
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 renew lease service
*/
int
nfsrvd_renew(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
int error = 0;
nfsquad_t clientid;
struct thread *p = curthread;
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, u_int32_t *, NFSX_HYPER);
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK13 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
nd->nd_repstat = nfsrv_getclient(clientid, (CLOPS_RENEWOP|CLOPS_RENEW),
NULL, NULL, (nfsquad_t)((u_quad_t)0), 0, nd, p);
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 security info service
*/
int
nfsrvd_secinfo(struct nfsrv_descript *nd, int isdgram,
vnode_t dp, struct nfsexstuff *exp)
{
u_int32_t *tl;
int len;
struct nameidata named;
vnode_t dirp = NULL, vp;
struct nfsrvfh fh;
struct nfsexstuff retnes;
u_int32_t *sizp;
int error = 0, savflag, i;
char *bufp;
u_long *hashp;
struct thread *p = curthread;
/*
* All this just to get the export flags for the name.
*/
NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, LOOKUP,
LOCKLEAF | SAVESTART);
nfsvno_setpathbuf(&named, &bufp, &hashp);
error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
if (error) {
vput(dp);
nfsvno_relpathbuf(&named);
goto out;
}
if (!nd->nd_repstat) {
nd->nd_repstat = nfsvno_namei(nd, &named, dp, 1, exp, p, &dirp);
} else {
vput(dp);
nfsvno_relpathbuf(&named);
}
if (dirp)
vrele(dirp);
if (nd->nd_repstat)
goto out;
vrele(named.ni_startdir);
nfsvno_relpathbuf(&named);
fh.nfsrvfh_len = NFSX_MYFH;
vp = named.ni_vp;
nd->nd_repstat = nfsvno_getfh(vp, (fhandle_t *)fh.nfsrvfh_data, p);
vput(vp);
savflag = nd->nd_flag;
if (!nd->nd_repstat) {
nfsd_fhtovp(nd, &fh, LK_SHARED, &vp, &retnes, NULL, 0);
if (vp)
vput(vp);
}
nd->nd_flag = savflag;
if (nd->nd_repstat)
goto out;
/*
* Finally have the export flags for name, so we can create
* the security info.
*/
len = 0;
NFSM_BUILD(sizp, u_int32_t *, NFSX_UNSIGNED);
for (i = 0; i < retnes.nes_numsecflavor; i++) {
if (retnes.nes_secflavors[i] == AUTH_SYS) {
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(RPCAUTH_UNIX);
len++;
} else if (retnes.nes_secflavors[i] == RPCSEC_GSS_KRB5) {
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl++ = txdr_unsigned(RPCAUTH_GSS);
(void) nfsm_strtom(nd, nfsgss_mechlist[KERBV_MECH].str,
nfsgss_mechlist[KERBV_MECH].len);
NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(GSS_KERBV_QOP);
*tl = txdr_unsigned(RPCAUTHGSS_SVCNONE);
len++;
} else if (retnes.nes_secflavors[i] == RPCSEC_GSS_KRB5I) {
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl++ = txdr_unsigned(RPCAUTH_GSS);
(void) nfsm_strtom(nd, nfsgss_mechlist[KERBV_MECH].str,
nfsgss_mechlist[KERBV_MECH].len);
NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(GSS_KERBV_QOP);
*tl = txdr_unsigned(RPCAUTHGSS_SVCINTEGRITY);
len++;
} else if (retnes.nes_secflavors[i] == RPCSEC_GSS_KRB5P) {
NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
*tl++ = txdr_unsigned(RPCAUTH_GSS);
(void) nfsm_strtom(nd, nfsgss_mechlist[KERBV_MECH].str,
nfsgss_mechlist[KERBV_MECH].len);
NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(GSS_KERBV_QOP);
*tl = txdr_unsigned(RPCAUTHGSS_SVCPRIVACY);
len++;
}
}
*sizp = txdr_unsigned(len);
out:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 set client id service
*/
int
nfsrvd_setclientid(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
int i;
int error = 0, idlen;
struct nfsclient *clp = NULL;
#ifdef INET
struct sockaddr_in *rin;
#endif
#ifdef INET6
struct sockaddr_in6 *rin6;
#endif
#if defined(INET) || defined(INET6)
u_char *ucp, *ucp2;
#endif
u_char *verf, *addrbuf;
nfsquad_t clientid, confirm;
struct thread *p = curthread;
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto out;
}
NFSM_DISSECT(tl, u_int32_t *, NFSX_VERF + NFSX_UNSIGNED);
verf = (u_char *)tl;
tl += (NFSX_VERF / NFSX_UNSIGNED);
i = fxdr_unsigned(int, *tl);
if (i > NFSV4_OPAQUELIMIT || i <= 0) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
idlen = i;
if (nd->nd_flag & ND_GSS)
i += nd->nd_princlen;
clp = malloc(sizeof(struct nfsclient) + i, M_NFSDCLIENT, M_WAITOK |
M_ZERO);
clp->lc_stateid = malloc(sizeof(struct nfsstatehead) *
nfsrv_statehashsize, M_NFSDCLIENT, M_WAITOK);
NFSINITSOCKMUTEX(&clp->lc_req.nr_mtx);
/* Allocated large enough for an AF_INET or AF_INET6 socket. */
clp->lc_req.nr_nam = malloc(sizeof(struct sockaddr_in6), M_SONAME,
M_WAITOK | M_ZERO);
clp->lc_req.nr_cred = NULL;
NFSBCOPY(verf, clp->lc_verf, NFSX_VERF);
clp->lc_idlen = idlen;
error = nfsrv_mtostr(nd, clp->lc_id, idlen);
if (error)
goto nfsmout;
if (nd->nd_flag & ND_GSS) {
clp->lc_flags = LCL_GSS;
if (nd->nd_flag & ND_GSSINTEGRITY)
clp->lc_flags |= LCL_GSSINTEGRITY;
else if (nd->nd_flag & ND_GSSPRIVACY)
clp->lc_flags |= LCL_GSSPRIVACY;
} else {
clp->lc_flags = 0;
}
if ((nd->nd_flag & ND_GSS) && nd->nd_princlen > 0) {
clp->lc_flags |= LCL_NAME;
clp->lc_namelen = nd->nd_princlen;
clp->lc_name = &clp->lc_id[idlen];
NFSBCOPY(nd->nd_principal, clp->lc_name, clp->lc_namelen);
} else {
clp->lc_uid = nd->nd_cred->cr_uid;
clp->lc_gid = nd->nd_cred->cr_gid;
}
/* If the client is using TLS, do so for the callback connection. */
if (nd->nd_flag & ND_TLS)
clp->lc_flags |= LCL_TLSCB;
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
clp->lc_program = fxdr_unsigned(u_int32_t, *tl);
error = nfsrv_getclientipaddr(nd, clp);
if (error)
goto nfsmout;
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
clp->lc_callback = fxdr_unsigned(u_int32_t, *tl);
/*
* nfsrv_setclient() does the actual work of adding it to the
* client list. If there is no error, the structure has been
* linked into the client list and clp should no longer be used
* here. When an error is returned, it has not been linked in,
* so it should be free'd.
*/
nd->nd_repstat = nfsrv_setclient(nd, &clp, &clientid, &confirm, p);
if (nd->nd_repstat == NFSERR_CLIDINUSE) {
/*
* 8 is the maximum length of the port# string.
*/
addrbuf = malloc(INET6_ADDRSTRLEN + 8, M_TEMP, M_WAITOK);
switch (clp->lc_req.nr_nam->sa_family) {
#ifdef INET
case AF_INET:
if (clp->lc_flags & LCL_TCPCALLBACK)
(void) nfsm_strtom(nd, "tcp", 3);
else
(void) nfsm_strtom(nd, "udp", 3);
rin = (struct sockaddr_in *)clp->lc_req.nr_nam;
ucp = (u_char *)&rin->sin_addr.s_addr;
ucp2 = (u_char *)&rin->sin_port;
sprintf(addrbuf, "%d.%d.%d.%d.%d.%d", ucp[0] & 0xff,
ucp[1] & 0xff, ucp[2] & 0xff, ucp[3] & 0xff,
ucp2[0] & 0xff, ucp2[1] & 0xff);
break;
#endif
#ifdef INET6
case AF_INET6:
if (clp->lc_flags & LCL_TCPCALLBACK)
(void) nfsm_strtom(nd, "tcp6", 4);
else
(void) nfsm_strtom(nd, "udp6", 4);
rin6 = (struct sockaddr_in6 *)clp->lc_req.nr_nam;
ucp = inet_ntop(AF_INET6, &rin6->sin6_addr, addrbuf,
INET6_ADDRSTRLEN);
if (ucp != NULL)
i = strlen(ucp);
else
i = 0;
ucp2 = (u_char *)&rin6->sin6_port;
sprintf(&addrbuf[i], ".%d.%d", ucp2[0] & 0xff,
ucp2[1] & 0xff);
break;
#endif
}
(void) nfsm_strtom(nd, addrbuf, strlen(addrbuf));
free(addrbuf, M_TEMP);
}
if (clp) {
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
}
if (!nd->nd_repstat) {
NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_HYPER);
*tl++ = clientid.lval[0];
*tl++ = clientid.lval[1];
*tl++ = confirm.lval[0];
*tl = confirm.lval[1];
}
out:
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
if (clp) {
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
}
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 set client id confirm service
*/
int
nfsrvd_setclientidcfrm(struct nfsrv_descript *nd,
__unused int isdgram, __unused vnode_t vp,
__unused struct nfsexstuff *exp)
{
u_int32_t *tl;
int error = 0;
nfsquad_t clientid, confirm;
struct thread *p = curthread;
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_HYPER);
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl++;
confirm.lval[0] = *tl++;
confirm.lval[1] = *tl;
/*
* nfsrv_getclient() searches the client list for a match and
* returns the appropriate NFSERR status.
*/
nd->nd_repstat = nfsrv_getclient(clientid, (CLOPS_CONFIRM|CLOPS_RENEW),
NULL, NULL, confirm, 0, nd, p);
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 verify service
*/
int
nfsrvd_verify(struct nfsrv_descript *nd, int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
int error = 0, ret, fhsize = NFSX_MYFH;
struct nfsvattr nva;
struct statfs *sf;
struct nfsfsinfo fs;
fhandle_t fh;
struct thread *p = curthread;
sf = malloc(sizeof(struct statfs), M_STATFS, M_WAITOK);
nd->nd_repstat = nfsvno_getattr(vp, &nva, nd, p, 1, NULL);
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_statfs(vp, sf);
if (!nd->nd_repstat)
nd->nd_repstat = nfsvno_getfh(vp, &fh, p);
if (!nd->nd_repstat) {
nfsvno_getfs(&fs, isdgram);
error = nfsv4_loadattr(nd, vp, &nva, NULL, &fh, fhsize, NULL,
sf, NULL, &fs, NULL, 1, &ret, NULL, NULL, p, nd->nd_cred);
if (!error) {
if (nd->nd_procnum == NFSV4OP_NVERIFY) {
if (ret == 0)
nd->nd_repstat = NFSERR_SAME;
else if (ret != NFSERR_NOTSAME)
nd->nd_repstat = ret;
} else if (ret)
nd->nd_repstat = ret;
}
}
vput(vp);
free(sf, M_STATFS);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs openattr rpc
*/
int
nfsrvd_openattr(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t dp, __unused vnode_t *vpp, __unused fhandle_t *fhp,
__unused struct nfsexstuff *exp)
{
u_int32_t *tl;
int error = 0, createdir __unused;
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
createdir = fxdr_unsigned(int, *tl);
nd->nd_repstat = NFSERR_NOTSUPP;
nfsmout:
vrele(dp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 release lock owner service
*/
int
nfsrvd_releaselckown(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
u_int32_t *tl;
struct nfsstate *stp = NULL;
int error = 0, len;
nfsquad_t clientid;
struct thread *p = curthread;
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
len = fxdr_unsigned(int, *(tl + 2));
if (len <= 0 || len > NFSV4_OPAQUELIMIT) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
stp = malloc(sizeof (struct nfsstate) + len,
M_NFSDSTATE, M_WAITOK);
stp->ls_ownerlen = len;
stp->ls_op = NULL;
stp->ls_flags = NFSLCK_RELEASE;
stp->ls_uid = nd->nd_cred->cr_uid;
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK14 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
error = nfsrv_mtostr(nd, stp->ls_owner, len);
if (error)
goto nfsmout;
nd->nd_repstat = nfsrv_releaselckown(stp, clientid, p);
free(stp, M_NFSDSTATE);
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
if (stp)
free(stp, M_NFSDSTATE);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 exchange_id service
*/
int
nfsrvd_exchangeid(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
int error = 0, i, idlen;
struct nfsclient *clp = NULL;
nfsquad_t clientid, confirm;
uint8_t *verf;
uint32_t sp4type, v41flags;
uint64_t owner_minor;
struct timespec verstime;
#ifdef INET
struct sockaddr_in *sin, *rin;
#endif
#ifdef INET6
struct sockaddr_in6 *sin6, *rin6;
#endif
struct thread *p = curthread;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, u_int32_t *, NFSX_VERF + NFSX_UNSIGNED);
verf = (uint8_t *)tl;
tl += (NFSX_VERF / NFSX_UNSIGNED);
i = fxdr_unsigned(int, *tl);
if (i > NFSV4_OPAQUELIMIT || i <= 0) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
idlen = i;
if (nd->nd_flag & ND_GSS)
i += nd->nd_princlen;
clp = malloc(sizeof(struct nfsclient) + i, M_NFSDCLIENT, M_WAITOK |
M_ZERO);
clp->lc_stateid = malloc(sizeof(struct nfsstatehead) *
nfsrv_statehashsize, M_NFSDCLIENT, M_WAITOK);
NFSINITSOCKMUTEX(&clp->lc_req.nr_mtx);
/* Allocated large enough for an AF_INET or AF_INET6 socket. */
clp->lc_req.nr_nam = malloc(sizeof(struct sockaddr_in6), M_SONAME,
M_WAITOK | M_ZERO);
switch (nd->nd_nam->sa_family) {
#ifdef INET
case AF_INET:
rin = (struct sockaddr_in *)clp->lc_req.nr_nam;
sin = (struct sockaddr_in *)nd->nd_nam;
rin->sin_family = AF_INET;
rin->sin_len = sizeof(struct sockaddr_in);
rin->sin_port = 0;
rin->sin_addr.s_addr = sin->sin_addr.s_addr;
break;
#endif
#ifdef INET6
case AF_INET6:
rin6 = (struct sockaddr_in6 *)clp->lc_req.nr_nam;
sin6 = (struct sockaddr_in6 *)nd->nd_nam;
rin6->sin6_family = AF_INET6;
rin6->sin6_len = sizeof(struct sockaddr_in6);
rin6->sin6_port = 0;
rin6->sin6_addr = sin6->sin6_addr;
break;
#endif
}
clp->lc_req.nr_cred = NULL;
NFSBCOPY(verf, clp->lc_verf, NFSX_VERF);
clp->lc_idlen = idlen;
error = nfsrv_mtostr(nd, clp->lc_id, idlen);
if (error != 0)
goto nfsmout;
if ((nd->nd_flag & ND_GSS) != 0) {
clp->lc_flags = LCL_GSS | LCL_NFSV41;
if ((nd->nd_flag & ND_GSSINTEGRITY) != 0)
clp->lc_flags |= LCL_GSSINTEGRITY;
else if ((nd->nd_flag & ND_GSSPRIVACY) != 0)
clp->lc_flags |= LCL_GSSPRIVACY;
} else
clp->lc_flags = LCL_NFSV41;
if ((nd->nd_flag & ND_NFSV42) != 0)
clp->lc_flags |= LCL_NFSV42;
if ((nd->nd_flag & ND_GSS) != 0 && nd->nd_princlen > 0) {
clp->lc_flags |= LCL_NAME;
clp->lc_namelen = nd->nd_princlen;
clp->lc_name = &clp->lc_id[idlen];
NFSBCOPY(nd->nd_principal, clp->lc_name, clp->lc_namelen);
} else {
clp->lc_uid = nd->nd_cred->cr_uid;
clp->lc_gid = nd->nd_cred->cr_gid;
}
NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
v41flags = fxdr_unsigned(uint32_t, *tl++);
if ((v41flags & ~(NFSV4EXCH_SUPPMOVEDREFER | NFSV4EXCH_SUPPMOVEDMIGR |
NFSV4EXCH_BINDPRINCSTATEID | NFSV4EXCH_MASKPNFS |
NFSV4EXCH_UPDCONFIRMEDRECA)) != 0) {
nd->nd_repstat = NFSERR_INVAL;
goto nfsmout;
}
if ((v41flags & NFSV4EXCH_UPDCONFIRMEDRECA) != 0)
confirm.lval[1] = 1;
else
confirm.lval[1] = 0;
if (nfsrv_devidcnt == 0)
v41flags = NFSV4EXCH_USENONPNFS | NFSV4EXCH_USEPNFSDS;
else
v41flags = NFSV4EXCH_USEPNFSMDS;
sp4type = fxdr_unsigned(uint32_t, *tl);
if (sp4type != NFSV4EXCH_SP4NONE) {
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
/*
* nfsrv_setclient() does the actual work of adding it to the
* client list. If there is no error, the structure has been
* linked into the client list and clp should no longer be used
* here. When an error is returned, it has not been linked in,
* so it should be free'd.
*/
nd->nd_repstat = nfsrv_setclient(nd, &clp, &clientid, &confirm, p);
if (clp != NULL) {
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
}
if (nd->nd_repstat == 0) {
if (confirm.lval[1] != 0)
v41flags |= NFSV4EXCH_CONFIRMEDR;
NFSM_BUILD(tl, uint32_t *, 2 * NFSX_HYPER + 3 * NFSX_UNSIGNED);
*tl++ = clientid.lval[0]; /* ClientID */
*tl++ = clientid.lval[1];
*tl++ = txdr_unsigned(confirm.lval[0]); /* SequenceID */
*tl++ = txdr_unsigned(v41flags); /* Exch flags */
*tl++ = txdr_unsigned(NFSV4EXCH_SP4NONE); /* No SSV */
owner_minor = 0; /* Owner */
txdr_hyper(owner_minor, tl); /* Minor */
(void)nfsm_strtom(nd, nd->nd_cred->cr_prison->pr_hostuuid,
strlen(nd->nd_cred->cr_prison->pr_hostuuid)); /* Major */
(void)nfsm_strtom(nd, nd->nd_cred->cr_prison->pr_hostuuid,
strlen(nd->nd_cred->cr_prison->pr_hostuuid)); /* Scope */
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(1);
(void)nfsm_strtom(nd, "freebsd.org", strlen("freebsd.org"));
(void)nfsm_strtom(nd, version, strlen(version));
NFSM_BUILD(tl, uint32_t *, NFSX_V4TIME);
verstime.tv_sec = 1293840000; /* Jan 1, 2011 */
verstime.tv_nsec = 0;
txdr_nfsv4time(&verstime, tl);
}
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
if (clp != NULL) {
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
}
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 create session service
*/
int
nfsrvd_createsession(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
int error = 0;
nfsquad_t clientid, confirm;
struct nfsdsession *sep = NULL;
uint32_t rdmacnt;
struct thread *p = curthread;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
sep = (struct nfsdsession *)malloc(sizeof(struct nfsdsession),
M_NFSDSESSION, M_WAITOK | M_ZERO);
sep->sess_refcnt = 1;
mtx_init(&sep->sess_cbsess.nfsess_mtx, "nfscbsession", NULL, MTX_DEF);
NFSM_DISSECT(tl, uint32_t *, NFSX_HYPER + 2 * NFSX_UNSIGNED);
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl++;
confirm.lval[0] = fxdr_unsigned(uint32_t, *tl++);
sep->sess_crflags = fxdr_unsigned(uint32_t, *tl);
/* Persistent sessions and RDMA are not supported. */
sep->sess_crflags &= NFSV4CRSESS_CONNBACKCHAN;
/* Fore channel attributes. */
NFSM_DISSECT(tl, uint32_t *, 7 * NFSX_UNSIGNED);
tl++; /* Header pad always 0. */
sep->sess_maxreq = fxdr_unsigned(uint32_t, *tl++);
if (sep->sess_maxreq > sb_max_adj - NFS_MAXXDR) {
sep->sess_maxreq = sb_max_adj - NFS_MAXXDR;
printf("Consider increasing kern.ipc.maxsockbuf\n");
}
sep->sess_maxresp = fxdr_unsigned(uint32_t, *tl++);
if (sep->sess_maxresp > sb_max_adj - NFS_MAXXDR) {
sep->sess_maxresp = sb_max_adj - NFS_MAXXDR;
printf("Consider increasing kern.ipc.maxsockbuf\n");
}
sep->sess_maxrespcached = fxdr_unsigned(uint32_t, *tl++);
sep->sess_maxops = fxdr_unsigned(uint32_t, *tl++);
sep->sess_maxslots = fxdr_unsigned(uint32_t, *tl++);
if (sep->sess_maxslots > NFSV4_SLOTS)
sep->sess_maxslots = NFSV4_SLOTS;
rdmacnt = fxdr_unsigned(uint32_t, *tl);
if (rdmacnt > 1) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
} else if (rdmacnt == 1)
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
/* Back channel attributes. */
NFSM_DISSECT(tl, uint32_t *, 7 * NFSX_UNSIGNED);
tl++; /* Header pad always 0. */
sep->sess_cbmaxreq = fxdr_unsigned(uint32_t, *tl++);
sep->sess_cbmaxresp = fxdr_unsigned(uint32_t, *tl++);
sep->sess_cbmaxrespcached = fxdr_unsigned(uint32_t, *tl++);
sep->sess_cbmaxops = fxdr_unsigned(uint32_t, *tl++);
sep->sess_cbsess.nfsess_foreslots = fxdr_unsigned(uint32_t, *tl++);
rdmacnt = fxdr_unsigned(uint32_t, *tl);
if (rdmacnt > 1) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
} else if (rdmacnt == 1)
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
sep->sess_cbprogram = fxdr_unsigned(uint32_t, *tl);
/*
* nfsrv_getclient() searches the client list for a match and
* returns the appropriate NFSERR status.
*/
nd->nd_repstat = nfsrv_getclient(clientid, CLOPS_CONFIRM | CLOPS_RENEW,
NULL, sep, confirm, sep->sess_cbprogram, nd, p);
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_V4SESSIONID);
NFSBCOPY(sep->sess_sessionid, tl, NFSX_V4SESSIONID);
NFSM_BUILD(tl, uint32_t *, 18 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(confirm.lval[0]); /* sequenceid */
*tl++ = txdr_unsigned(sep->sess_crflags);
/* Fore channel attributes. */
*tl++ = 0;
*tl++ = txdr_unsigned(sep->sess_maxreq);
*tl++ = txdr_unsigned(sep->sess_maxresp);
*tl++ = txdr_unsigned(sep->sess_maxrespcached);
*tl++ = txdr_unsigned(sep->sess_maxops);
*tl++ = txdr_unsigned(sep->sess_maxslots);
*tl++ = txdr_unsigned(1);
*tl++ = txdr_unsigned(0); /* No RDMA. */
/* Back channel attributes. */
*tl++ = 0;
*tl++ = txdr_unsigned(sep->sess_cbmaxreq);
*tl++ = txdr_unsigned(sep->sess_cbmaxresp);
*tl++ = txdr_unsigned(sep->sess_cbmaxrespcached);
*tl++ = txdr_unsigned(sep->sess_cbmaxops);
*tl++ = txdr_unsigned(sep->sess_cbsess.nfsess_foreslots);
*tl++ = txdr_unsigned(1);
*tl = txdr_unsigned(0); /* No RDMA. */
}
nfsmout:
if (nd->nd_repstat != 0 && sep != NULL)
free(sep, M_NFSDSESSION);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 sequence service
*/
int
nfsrvd_sequence(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
uint32_t highest_slotid, sequenceid, sflags, target_highest_slotid;
int cache_this, error = 0;
struct thread *p = curthread;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_V4SESSIONID);
NFSBCOPY(tl, nd->nd_sessionid, NFSX_V4SESSIONID);
NFSM_DISSECT(tl, uint32_t *, 4 * NFSX_UNSIGNED);
sequenceid = fxdr_unsigned(uint32_t, *tl++);
nd->nd_slotid = fxdr_unsigned(uint32_t, *tl++);
highest_slotid = fxdr_unsigned(uint32_t, *tl++);
if (*tl == newnfs_true)
cache_this = 1;
else
cache_this = 0;
nd->nd_flag |= ND_HASSEQUENCE;
nd->nd_repstat = nfsrv_checksequence(nd, sequenceid, &highest_slotid,
&target_highest_slotid, cache_this, &sflags, p);
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_V4SESSIONID);
NFSBCOPY(nd->nd_sessionid, tl, NFSX_V4SESSIONID);
NFSM_BUILD(tl, uint32_t *, 5 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(sequenceid);
*tl++ = txdr_unsigned(nd->nd_slotid);
*tl++ = txdr_unsigned(highest_slotid);
*tl++ = txdr_unsigned(target_highest_slotid);
*tl = txdr_unsigned(sflags);
}
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 reclaim complete service
*/
int
nfsrvd_reclaimcomplete(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
int error = 0, onefs;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
/*
* I believe that a ReclaimComplete with rca_one_fs == TRUE is only
* to be used after a file system has been transferred to a different
* file server. However, RFC5661 is somewhat vague w.r.t. this and
* the ESXi 6.7 client does both a ReclaimComplete with rca_one_fs
* == TRUE and one with ReclaimComplete with rca_one_fs == FALSE.
* Therefore, just ignore the rca_one_fs == TRUE operation and return
* NFS_OK without doing anything.
*/
onefs = 0;
if (*tl == newnfs_true)
onefs = 1;
nd->nd_repstat = nfsrv_checkreclaimcomplete(nd, onefs);
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 destroy clientid service
*/
int
nfsrvd_destroyclientid(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
nfsquad_t clientid;
int error = 0;
struct thread *p = curthread;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
clientid.lval[0] = *tl++;
clientid.lval[1] = *tl;
nd->nd_repstat = nfsrv_destroyclient(clientid, p);
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 bind connection to session service
*/
int
nfsrvd_bindconnsess(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
uint8_t sessid[NFSX_V4SESSIONID];
int error = 0, foreaft;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_V4SESSIONID + 2 * NFSX_UNSIGNED);
NFSBCOPY(tl, sessid, NFSX_V4SESSIONID);
tl += (NFSX_V4SESSIONID / NFSX_UNSIGNED);
foreaft = fxdr_unsigned(int, *tl++);
if (*tl == newnfs_true) {
/* RDMA is not supported. */
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
nd->nd_repstat = nfsrv_bindconnsess(nd, sessid, &foreaft);
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_V4SESSIONID + 2 *
NFSX_UNSIGNED);
NFSBCOPY(sessid, tl, NFSX_V4SESSIONID);
tl += (NFSX_V4SESSIONID / NFSX_UNSIGNED);
*tl++ = txdr_unsigned(foreaft);
*tl = newnfs_false;
}
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 destroy session service
*/
int
nfsrvd_destroysession(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint8_t *cp, sessid[NFSX_V4SESSIONID];
int error = 0;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(cp, uint8_t *, NFSX_V4SESSIONID);
NFSBCOPY(cp, sessid, NFSX_V4SESSIONID);
nd->nd_repstat = nfsrv_destroysession(nd, sessid);
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 free stateid service
*/
int
nfsrvd_freestateid(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
nfsv4stateid_t stateid;
int error = 0;
struct thread *p = curthread;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_STATEID);
stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
NFSBCOPY(tl, stateid.other, NFSX_STATEIDOTHER);
/*
* For the special stateid of other all 0s and seqid == 1, set the
* stateid to the current stateid, if it is set.
*/
if (stateid.seqid == 1 && stateid.other[0] == 0 &&
stateid.other[1] == 0 && stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stateid = nd->nd_curstateid;
stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
nd->nd_repstat = nfsrv_freestateid(nd, &stateid, p);
/* If the current stateid has been free'd, unset it. */
if (nd->nd_repstat == 0 && (nd->nd_flag & ND_CURSTATEID) != 0 &&
stateid.other[0] == nd->nd_curstateid.other[0] &&
stateid.other[1] == nd->nd_curstateid.other[1] &&
stateid.other[2] == nd->nd_curstateid.other[2])
nd->nd_flag &= ~ND_CURSTATEID;
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 layoutget service
*/
int
nfsrvd_layoutget(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
uint32_t *tl;
nfsv4stateid_t stateid;
int error = 0, layoutlen, layouttype, iomode, maxcnt, retonclose;
uint64_t offset, len, minlen;
char *layp;
struct thread *p = curthread;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 4 * NFSX_UNSIGNED + 3 * NFSX_HYPER +
NFSX_STATEID);
tl++; /* Signal layout available. Ignore for now. */
layouttype = fxdr_unsigned(int, *tl++);
iomode = fxdr_unsigned(int, *tl++);
offset = fxdr_hyper(tl); tl += 2;
len = fxdr_hyper(tl); tl += 2;
minlen = fxdr_hyper(tl); tl += 2;
stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
NFSBCOPY(tl, stateid.other, NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
maxcnt = fxdr_unsigned(int, *tl);
NFSD_DEBUG(4, "layoutget ltyp=%d iom=%d off=%ju len=%ju mlen=%ju\n",
layouttype, iomode, (uintmax_t)offset, (uintmax_t)len,
(uintmax_t)minlen);
if (len < minlen ||
(minlen != UINT64_MAX && offset + minlen < offset) ||
(len != UINT64_MAX && offset + len < offset)) {
nd->nd_repstat = NFSERR_INVAL;
goto nfsmout;
}
/*
* For the special stateid of other all 0s and seqid == 1, set the
* stateid to the current stateid, if it is set.
*/
if (stateid.seqid == 1 && stateid.other[0] == 0 &&
stateid.other[1] == 0 && stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stateid = nd->nd_curstateid;
stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
layp = NULL;
if (layouttype == NFSLAYOUT_NFSV4_1_FILES && nfsrv_maxpnfsmirror == 1)
layp = malloc(NFSX_V4FILELAYOUT, M_TEMP, M_WAITOK);
else if (layouttype == NFSLAYOUT_FLEXFILE)
layp = malloc(NFSX_V4FLEXLAYOUT(nfsrv_maxpnfsmirror), M_TEMP,
M_WAITOK);
else
nd->nd_repstat = NFSERR_UNKNLAYOUTTYPE;
if (layp != NULL)
nd->nd_repstat = nfsrv_layoutget(nd, vp, exp, layouttype,
&iomode, &offset, &len, minlen, &stateid, maxcnt,
&retonclose, &layoutlen, layp, nd->nd_cred, p);
NFSD_DEBUG(4, "nfsrv_layoutget stat=%u layoutlen=%d\n", nd->nd_repstat,
layoutlen);
if (nd->nd_repstat == 0) {
/* For NFSv4.1, set the Current StateID. */
if ((nd->nd_flag & ND_NFSV41) != 0) {
nd->nd_curstateid = stateid;
nd->nd_flag |= ND_CURSTATEID;
}
NFSM_BUILD(tl, uint32_t *, 4 * NFSX_UNSIGNED + NFSX_STATEID +
2 * NFSX_HYPER);
*tl++ = txdr_unsigned(retonclose);
*tl++ = txdr_unsigned(stateid.seqid);
NFSBCOPY(stateid.other, tl, NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
*tl++ = txdr_unsigned(1); /* Only returns one layout. */
txdr_hyper(offset, tl); tl += 2;
txdr_hyper(len, tl); tl += 2;
*tl++ = txdr_unsigned(iomode);
*tl = txdr_unsigned(layouttype);
nfsm_strtom(nd, layp, layoutlen);
} else if (nd->nd_repstat == NFSERR_LAYOUTTRYLATER) {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = newnfs_false;
}
free(layp, M_TEMP);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 layoutcommit service
*/
int
nfsrvd_layoutcommit(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
uint32_t *tl;
nfsv4stateid_t stateid;
int error = 0, hasnewoff, hasnewmtime, layouttype, maxcnt, reclaim;
int hasnewsize;
uint64_t offset, len, newoff = 0, newsize;
struct timespec newmtime;
char *layp;
struct thread *p = curthread;
layp = NULL;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED + 2 * NFSX_HYPER +
NFSX_STATEID);
offset = fxdr_hyper(tl); tl += 2;
len = fxdr_hyper(tl); tl += 2;
reclaim = fxdr_unsigned(int, *tl++);
stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
NFSBCOPY(tl, stateid.other, NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
/*
* For the special stateid of other all 0s and seqid == 1, set the
* stateid to the current stateid, if it is set.
*/
if (stateid.seqid == 1 && stateid.other[0] == 0 &&
stateid.other[1] == 0 && stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stateid = nd->nd_curstateid;
stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
hasnewoff = fxdr_unsigned(int, *tl);
if (hasnewoff != 0) {
NFSM_DISSECT(tl, uint32_t *, NFSX_HYPER + NFSX_UNSIGNED);
newoff = fxdr_hyper(tl); tl += 2;
} else
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
hasnewmtime = fxdr_unsigned(int, *tl);
if (hasnewmtime != 0) {
NFSM_DISSECT(tl, uint32_t *, NFSX_V4TIME + 2 * NFSX_UNSIGNED);
fxdr_nfsv4time(tl, &newmtime);
tl += (NFSX_V4TIME / NFSX_UNSIGNED);
} else
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
layouttype = fxdr_unsigned(int, *tl++);
maxcnt = fxdr_unsigned(int, *tl);
if (maxcnt > 0) {
layp = malloc(maxcnt + 1, M_TEMP, M_WAITOK);
error = nfsrv_mtostr(nd, layp, maxcnt);
if (error != 0)
goto nfsmout;
}
nd->nd_repstat = nfsrv_layoutcommit(nd, vp, layouttype, hasnewoff,
newoff, offset, len, hasnewmtime, &newmtime, reclaim, &stateid,
maxcnt, layp, &hasnewsize, &newsize, nd->nd_cred, p);
NFSD_DEBUG(4, "nfsrv_layoutcommit stat=%u\n", nd->nd_repstat);
if (nd->nd_repstat == 0) {
if (hasnewsize != 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED + NFSX_HYPER);
*tl++ = newnfs_true;
txdr_hyper(newsize, tl);
} else {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = newnfs_false;
}
}
nfsmout:
free(layp, M_TEMP);
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 layoutreturn service
*/
int
nfsrvd_layoutreturn(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
uint32_t *tl, *layp;
nfsv4stateid_t stateid;
int error = 0, fnd, kind, layouttype, iomode, maxcnt, reclaim;
uint64_t offset, len;
struct thread *p = curthread;
layp = NULL;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 4 * NFSX_UNSIGNED);
reclaim = *tl++;
layouttype = fxdr_unsigned(int, *tl++);
iomode = fxdr_unsigned(int, *tl++);
kind = fxdr_unsigned(int, *tl);
NFSD_DEBUG(4, "layoutreturn recl=%d ltyp=%d iom=%d kind=%d\n", reclaim,
layouttype, iomode, kind);
if (kind == NFSV4LAYOUTRET_FILE) {
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_HYPER + NFSX_STATEID +
NFSX_UNSIGNED);
offset = fxdr_hyper(tl); tl += 2;
len = fxdr_hyper(tl); tl += 2;
stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
NFSBCOPY(tl, stateid.other, NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
/*
* For the special stateid of other all 0s and seqid == 1, set
* the stateid to the current stateid, if it is set.
*/
if (stateid.seqid == 1 && stateid.other[0] == 0 &&
stateid.other[1] == 0 && stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stateid = nd->nd_curstateid;
stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
maxcnt = fxdr_unsigned(int, *tl);
if (maxcnt > 0) {
layp = malloc(maxcnt + 1, M_TEMP, M_WAITOK);
error = nfsrv_mtostr(nd, (char *)layp, maxcnt);
if (error != 0)
goto nfsmout;
}
} else {
if (reclaim == newnfs_true) {
nd->nd_repstat = NFSERR_INVAL;
goto nfsmout;
}
offset = len = 0;
maxcnt = 0;
}
nd->nd_repstat = nfsrv_layoutreturn(nd, vp, layouttype, iomode,
offset, len, reclaim, kind, &stateid, maxcnt, layp, &fnd,
nd->nd_cred, p);
NFSD_DEBUG(4, "nfsrv_layoutreturn stat=%u fnd=%d\n", nd->nd_repstat,
fnd);
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
if (fnd != 0) {
*tl = newnfs_true;
NFSM_BUILD(tl, uint32_t *, NFSX_STATEID);
*tl++ = txdr_unsigned(stateid.seqid);
NFSBCOPY(stateid.other, tl, NFSX_STATEIDOTHER);
} else
*tl = newnfs_false;
}
nfsmout:
free(layp, M_TEMP);
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 layout error service
*/
int
nfsrvd_layouterror(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
uint32_t *tl;
nfsv4stateid_t stateid;
int cnt, error = 0, i, stat;
int opnum __unused;
char devid[NFSX_V4DEVICEID];
uint64_t offset, len;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_HYPER + NFSX_STATEID +
NFSX_UNSIGNED);
offset = fxdr_hyper(tl); tl += 2;
len = fxdr_hyper(tl); tl += 2;
stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
NFSBCOPY(tl, stateid.other, NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
cnt = fxdr_unsigned(int, *tl);
NFSD_DEBUG(4, "layouterror off=%ju len=%ju cnt=%d\n", (uintmax_t)offset,
(uintmax_t)len, cnt);
/*
* For the special stateid of other all 0s and seqid == 1, set
* the stateid to the current stateid, if it is set.
*/
if (stateid.seqid == 1 && stateid.other[0] == 0 &&
stateid.other[1] == 0 && stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stateid = nd->nd_curstateid;
stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
/*
* Ignore offset, len and stateid for now.
*/
for (i = 0; i < cnt; i++) {
NFSM_DISSECT(tl, uint32_t *, NFSX_V4DEVICEID + 2 *
NFSX_UNSIGNED);
NFSBCOPY(tl, devid, NFSX_V4DEVICEID);
tl += (NFSX_V4DEVICEID / NFSX_UNSIGNED);
stat = fxdr_unsigned(int, *tl++);
opnum = fxdr_unsigned(int, *tl);
NFSD_DEBUG(4, "nfsrvd_layouterr op=%d stat=%d\n", opnum, stat);
/*
* Except for NFSERR_ACCES and NFSERR_STALE errors,
* disable the mirror.
*/
if (stat != NFSERR_ACCES && stat != NFSERR_STALE)
nfsrv_delds(devid, curthread);
}
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 layout stats service
*/
int
nfsrvd_layoutstats(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
uint32_t *tl;
nfsv4stateid_t stateid;
int cnt, error = 0;
int layouttype __unused;
char devid[NFSX_V4DEVICEID] __unused;
uint64_t offset, len, readcount, readbytes, writecount, writebytes
__unused;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 6 * NFSX_HYPER + NFSX_STATEID +
NFSX_V4DEVICEID + 2 * NFSX_UNSIGNED);
offset = fxdr_hyper(tl); tl += 2;
len = fxdr_hyper(tl); tl += 2;
stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
NFSBCOPY(tl, stateid.other, NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
readcount = fxdr_hyper(tl); tl += 2;
readbytes = fxdr_hyper(tl); tl += 2;
writecount = fxdr_hyper(tl); tl += 2;
writebytes = fxdr_hyper(tl); tl += 2;
NFSBCOPY(tl, devid, NFSX_V4DEVICEID);
tl += (NFSX_V4DEVICEID / NFSX_UNSIGNED);
layouttype = fxdr_unsigned(int, *tl++);
cnt = fxdr_unsigned(int, *tl);
error = nfsm_advance(nd, NFSM_RNDUP(cnt), -1);
if (error != 0)
goto nfsmout;
NFSD_DEBUG(4, "layoutstats cnt=%d\n", cnt);
/*
* For the special stateid of other all 0s and seqid == 1, set
* the stateid to the current stateid, if it is set.
*/
if (stateid.seqid == 1 && stateid.other[0] == 0 &&
stateid.other[1] == 0 && stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stateid = nd->nd_curstateid;
stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
/*
* No use for the stats for now.
*/
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 io_advise service
*/
int
nfsrvd_ioadvise(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
uint32_t *tl;
nfsv4stateid_t stateid;
nfsattrbit_t hints;
int error = 0, ret;
off_t offset, len;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_STATEID + 2 * NFSX_HYPER);
stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
NFSBCOPY(tl, stateid.other, NFSX_STATEIDOTHER);
tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
offset = fxdr_hyper(tl); tl += 2;
len = fxdr_hyper(tl);
error = nfsrv_getattrbits(nd, &hints, NULL, NULL);
if (error != 0)
goto nfsmout;
/*
* For the special stateid of other all 0s and seqid == 1, set
* the stateid to the current stateid, if it is set.
*/
if (stateid.seqid == 1 && stateid.other[0] == 0 &&
stateid.other[1] == 0 && stateid.other[2] == 0) {
if ((nd->nd_flag & ND_CURSTATEID) != 0) {
stateid = nd->nd_curstateid;
stateid.seqid = 0;
} else {
nd->nd_repstat = NFSERR_BADSTATEID;
goto nfsmout;
}
}
if (offset < 0) {
nd->nd_repstat = NFSERR_INVAL;
goto nfsmout;
}
if (len < 0)
len = 0;
if (vp->v_type != VREG) {
if (vp->v_type == VDIR)
nd->nd_repstat = NFSERR_ISDIR;
else
nd->nd_repstat = NFSERR_WRONGTYPE;
goto nfsmout;
}
/*
* For now, we can only handle WILLNEED and DONTNEED and don't use
* the stateid.
*/
if ((NFSISSET_ATTRBIT(&hints, NFSV4IOHINT_WILLNEED) &&
!NFSISSET_ATTRBIT(&hints, NFSV4IOHINT_DONTNEED)) ||
(NFSISSET_ATTRBIT(&hints, NFSV4IOHINT_DONTNEED) &&
!NFSISSET_ATTRBIT(&hints, NFSV4IOHINT_WILLNEED))) {
NFSVOPUNLOCK(vp);
if (NFSISSET_ATTRBIT(&hints, NFSV4IOHINT_WILLNEED)) {
ret = VOP_ADVISE(vp, offset, len, POSIX_FADV_WILLNEED);
NFSZERO_ATTRBIT(&hints);
if (ret == 0)
NFSSETBIT_ATTRBIT(&hints, NFSV4IOHINT_WILLNEED);
else
NFSSETBIT_ATTRBIT(&hints, NFSV4IOHINT_NORMAL);
} else {
ret = VOP_ADVISE(vp, offset, len, POSIX_FADV_DONTNEED);
NFSZERO_ATTRBIT(&hints);
if (ret == 0)
NFSSETBIT_ATTRBIT(&hints, NFSV4IOHINT_DONTNEED);
else
NFSSETBIT_ATTRBIT(&hints, NFSV4IOHINT_NORMAL);
}
vrele(vp);
} else {
NFSZERO_ATTRBIT(&hints);
NFSSETBIT_ATTRBIT(&hints, NFSV4IOHINT_NORMAL);
vput(vp);
}
nfsrv_putattrbit(nd, &hints);
NFSEXITCODE2(error, nd);
return (error);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 getdeviceinfo service
*/
int
nfsrvd_getdevinfo(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl, maxcnt, notify[NFSV4_NOTIFYBITMAP];
int cnt, devaddrlen, error = 0, i, layouttype;
char devid[NFSX_V4DEVICEID], *devaddr;
time_t dev_time;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 3 * NFSX_UNSIGNED + NFSX_V4DEVICEID);
NFSBCOPY(tl, devid, NFSX_V4DEVICEID);
tl += (NFSX_V4DEVICEID / NFSX_UNSIGNED);
layouttype = fxdr_unsigned(int, *tl++);
maxcnt = fxdr_unsigned(uint32_t, *tl++);
cnt = fxdr_unsigned(int, *tl);
NFSD_DEBUG(4, "getdevinfo ltyp=%d maxcnt=%u bitcnt=%d\n", layouttype,
maxcnt, cnt);
if (cnt > NFSV4_NOTIFYBITMAP || cnt < 0) {
nd->nd_repstat = NFSERR_INVAL;
goto nfsmout;
}
if (cnt > 0) {
NFSM_DISSECT(tl, uint32_t *, cnt * NFSX_UNSIGNED);
for (i = 0; i < cnt; i++)
notify[i] = fxdr_unsigned(uint32_t, *tl++);
}
for (i = cnt; i < NFSV4_NOTIFYBITMAP; i++)
notify[i] = 0;
/*
* Check that the device id is not stale. Device ids are recreated
* each time the nfsd threads are restarted.
*/
NFSBCOPY(devid, &dev_time, sizeof(dev_time));
if (dev_time != nfsdev_time) {
nd->nd_repstat = NFSERR_NOENT;
goto nfsmout;
}
/* Look for the device id. */
nd->nd_repstat = nfsrv_getdevinfo(devid, layouttype, &maxcnt,
notify, &devaddrlen, &devaddr);
NFSD_DEBUG(4, "nfsrv_getdevinfo stat=%u\n", nd->nd_repstat);
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(layouttype);
nfsm_strtom(nd, devaddr, devaddrlen);
cnt = 0;
for (i = 0; i < NFSV4_NOTIFYBITMAP; i++) {
if (notify[i] != 0)
cnt = i + 1;
}
NFSM_BUILD(tl, uint32_t *, (cnt + 1) * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(cnt);
for (i = 0; i < cnt; i++)
*tl++ = txdr_unsigned(notify[i]);
} else if (nd->nd_repstat == NFSERR_TOOSMALL) {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(maxcnt);
}
nfsmout:
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfsv4 test stateid service
*/
int
nfsrvd_teststateid(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
nfsv4stateid_t *stateidp = NULL, *tstateidp;
int cnt, error = 0, i, ret;
struct thread *p = curthread;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
cnt = fxdr_unsigned(int, *tl);
if (cnt <= 0 || cnt > 1024) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
stateidp = mallocarray(cnt, sizeof(nfsv4stateid_t), M_TEMP, M_WAITOK);
tstateidp = stateidp;
for (i = 0; i < cnt; i++) {
NFSM_DISSECT(tl, uint32_t *, NFSX_STATEID);
tstateidp->seqid = fxdr_unsigned(uint32_t, *tl++);
NFSBCOPY(tl, tstateidp->other, NFSX_STATEIDOTHER);
tstateidp++;
}
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(cnt);
tstateidp = stateidp;
for (i = 0; i < cnt; i++) {
ret = nfsrv_teststateid(nd, tstateidp, p);
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(ret);
tstateidp++;
}
nfsmout:
free(stateidp, M_TEMP);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs allocate service
*/
int
nfsrvd_allocate(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
uint32_t *tl;
struct nfsvattr forat;
int error = 0, forat_ret = 1, gotproxystateid;
off_t off, len;
struct nfsstate st, *stp = &st;
struct nfslock lo, *lop = &lo;
nfsv4stateid_t stateid;
nfsquad_t clientid;
nfsattrbit_t attrbits;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
gotproxystateid = 0;
NFSM_DISSECT(tl, uint32_t *, NFSX_STATEID + 2 * NFSX_HYPER);
stp->ls_flags = (NFSLCK_CHECK | NFSLCK_WRITEACCESS);
lop->lo_flags = NFSLCK_WRITE;
stp->ls_ownerlen = 0;
stp->ls_op = NULL;
stp->ls_uid = nd->nd_cred->cr_uid;
stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
clientid.lval[0] = stp->ls_stateid.other[0] = *tl++;
clientid.lval[1] = stp->ls_stateid.other[1] = *tl++;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0) {
if ((nd->nd_flag & ND_NFSV41) != 0)
clientid.qval = nd->nd_clientid.qval;
else if (nd->nd_clientid.qval != clientid.qval)
printf("EEK2 multiple clids\n");
} else {
if ((nd->nd_flag & ND_NFSV41) != 0)
printf("EEK! no clientid from session\n");
nd->nd_flag |= ND_IMPLIEDCLID;
nd->nd_clientid.qval = clientid.qval;
}
stp->ls_stateid.other[2] = *tl++;
/*
* Don't allow this to be done for a DS.
*/
if ((nd->nd_flag & ND_DSSERVER) != 0)
nd->nd_repstat = NFSERR_NOTSUPP;
/* However, allow the proxy stateid. */
if (stp->ls_stateid.seqid == 0xffffffff &&
stp->ls_stateid.other[0] == 0x55555555 &&
stp->ls_stateid.other[1] == 0x55555555 &&
stp->ls_stateid.other[2] == 0x55555555)
gotproxystateid = 1;
off = fxdr_hyper(tl); tl += 2;
lop->lo_first = off;
len = fxdr_hyper(tl);
lop->lo_end = off + len;
/*
* Paranoia, just in case it wraps around, which shouldn't
* ever happen anyhow.
*/
if (nd->nd_repstat == 0 && (lop->lo_end < lop->lo_first || len <= 0))
nd->nd_repstat = NFSERR_INVAL;
if (nd->nd_repstat == 0 && vnode_vtype(vp) != VREG)
nd->nd_repstat = NFSERR_WRONGTYPE;
NFSZERO_ATTRBIT(&attrbits);
NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_OWNER);
forat_ret = nfsvno_getattr(vp, &forat, nd, curthread, 1, &attrbits);
if (nd->nd_repstat == 0)
nd->nd_repstat = forat_ret;
if (nd->nd_repstat == 0 && (forat.na_uid != nd->nd_cred->cr_uid ||
NFSVNO_EXSTRICTACCESS(exp)))
nd->nd_repstat = nfsvno_accchk(vp, VWRITE, nd->nd_cred, exp,
curthread, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED,
NULL);
if (nd->nd_repstat == 0 && gotproxystateid == 0)
nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
&stateid, exp, nd, curthread);
if (nd->nd_repstat == 0)
nd->nd_repstat = nfsvno_allocate(vp, off, len, nd->nd_cred,
curthread);
vput(vp);
NFSEXITCODE2(0, nd);
return (0);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs copy service
*/
int
nfsrvd_copy_file_range(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, vnode_t tovp, struct nfsexstuff *exp, struct nfsexstuff *toexp)
{
uint32_t *tl;
struct nfsvattr at;
int cnt, error = 0, ret;
off_t inoff, outoff;
uint64_t len;
size_t xfer;
struct nfsstate inst, outst, *instp = &inst, *outstp = &outst;
struct nfslock inlo, outlo, *inlop = &inlo, *outlop = &outlo;
nfsquad_t clientid;
nfsv4stateid_t stateid;
nfsattrbit_t attrbits;
void *rl_rcookie, *rl_wcookie;
rl_rcookie = rl_wcookie = NULL;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
if (nfsrv_devidcnt > 0) {
/*
* For a pNFS server, reply NFSERR_NOTSUPP so that the client
* will do the copy via I/O on the DS(s).
*/
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
if (vp == tovp) {
/* Copying a byte range within the same file is not allowed. */
nd->nd_repstat = NFSERR_INVAL;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_STATEID + 3 * NFSX_HYPER +
3 * NFSX_UNSIGNED);
instp->ls_flags = (NFSLCK_CHECK | NFSLCK_READACCESS);
inlop->lo_flags = NFSLCK_READ;
instp->ls_ownerlen = 0;
instp->ls_op = NULL;
instp->ls_uid = nd->nd_cred->cr_uid;
instp->ls_stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
clientid.lval[0] = instp->ls_stateid.other[0] = *tl++;
clientid.lval[1] = instp->ls_stateid.other[1] = *tl++;
if ((nd->nd_flag & ND_IMPLIEDCLID) != 0)
clientid.qval = nd->nd_clientid.qval;
instp->ls_stateid.other[2] = *tl++;
outstp->ls_flags = (NFSLCK_CHECK | NFSLCK_WRITEACCESS);
outlop->lo_flags = NFSLCK_WRITE;
outstp->ls_ownerlen = 0;
outstp->ls_op = NULL;
outstp->ls_uid = nd->nd_cred->cr_uid;
outstp->ls_stateid.seqid = fxdr_unsigned(uint32_t, *tl++);
outstp->ls_stateid.other[0] = *tl++;
outstp->ls_stateid.other[1] = *tl++;
outstp->ls_stateid.other[2] = *tl++;
inoff = fxdr_hyper(tl); tl += 2;
inlop->lo_first = inoff;
outoff = fxdr_hyper(tl); tl += 2;
outlop->lo_first = outoff;
len = fxdr_hyper(tl); tl += 2;
if (len == 0) {
/* len == 0 means to EOF. */
inlop->lo_end = OFF_MAX;
outlop->lo_end = OFF_MAX;
} else {
inlop->lo_end = inlop->lo_first + len;
outlop->lo_end = outlop->lo_first + len;
}
/*
* At this time only consecutive, synchronous copy is supported,
* so ca_consecutive and ca_synchronous can be ignored.
*/
tl += 2;
cnt = fxdr_unsigned(int, *tl);
if ((nd->nd_flag & ND_DSSERVER) != 0 || cnt != 0)
nd->nd_repstat = NFSERR_NOTSUPP;
if (nd->nd_repstat == 0 && (inoff > OFF_MAX || outoff > OFF_MAX ||
inlop->lo_end > OFF_MAX || outlop->lo_end > OFF_MAX ||
inlop->lo_end < inlop->lo_first || outlop->lo_end <
outlop->lo_first))
nd->nd_repstat = NFSERR_INVAL;
if (nd->nd_repstat == 0 && vnode_vtype(vp) != VREG)
nd->nd_repstat = NFSERR_WRONGTYPE;
/* Check permissions for the input file. */
NFSZERO_ATTRBIT(&attrbits);
NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_OWNER);
ret = nfsvno_getattr(vp, &at, nd, curthread, 1, &attrbits);
if (nd->nd_repstat == 0)
nd->nd_repstat = ret;
if (nd->nd_repstat == 0 && (at.na_uid != nd->nd_cred->cr_uid ||
NFSVNO_EXSTRICTACCESS(exp)))
nd->nd_repstat = nfsvno_accchk(vp, VREAD, nd->nd_cred, exp,
curthread, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED,
NULL);
if (nd->nd_repstat == 0)
nd->nd_repstat = nfsrv_lockctrl(vp, &instp, &inlop, NULL,
clientid, &stateid, exp, nd, curthread);
NFSVOPUNLOCK(vp);
if (nd->nd_repstat != 0)
goto out;
error = NFSVOPLOCK(tovp, LK_SHARED);
if (error != 0)
goto out;
if (vnode_vtype(tovp) != VREG)
nd->nd_repstat = NFSERR_WRONGTYPE;
/* For the output file, we only need the Owner attribute. */
ret = nfsvno_getattr(tovp, &at, nd, curthread, 1, &attrbits);
if (nd->nd_repstat == 0)
nd->nd_repstat = ret;
if (nd->nd_repstat == 0 && (at.na_uid != nd->nd_cred->cr_uid ||
NFSVNO_EXSTRICTACCESS(exp)))
nd->nd_repstat = nfsvno_accchk(tovp, VWRITE, nd->nd_cred, toexp,
curthread, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED,
NULL);
if (nd->nd_repstat == 0)
nd->nd_repstat = nfsrv_lockctrl(tovp, &outstp, &outlop, NULL,
clientid, &stateid, toexp, nd, curthread);
NFSVOPUNLOCK(tovp);
/* Range lock the byte ranges for both invp and outvp. */
if (nd->nd_repstat == 0) {
for (;;) {
if (len == 0) {
rl_wcookie = vn_rangelock_wlock(tovp, outoff,
OFF_MAX);
rl_rcookie = vn_rangelock_tryrlock(vp, inoff,
OFF_MAX);
} else {
rl_wcookie = vn_rangelock_wlock(tovp, outoff,
outoff + len);
rl_rcookie = vn_rangelock_tryrlock(vp, inoff,
inoff + len);
}
if (rl_rcookie != NULL)
break;
vn_rangelock_unlock(tovp, rl_wcookie);
if (len == 0)
rl_rcookie = vn_rangelock_rlock(vp, inoff,
OFF_MAX);
else
rl_rcookie = vn_rangelock_rlock(vp, inoff,
inoff + len);
vn_rangelock_unlock(vp, rl_rcookie);
}
error = NFSVOPLOCK(vp, LK_SHARED);
if (error == 0) {
ret = nfsvno_getattr(vp, &at, nd, curthread, 1, NULL);
if (ret == 0) {
/*
* Since invp is range locked, na_size should
* not change.
*/
if (len == 0 && at.na_size > inoff) {
/*
* If len == 0, set it based on invp's
* size. If offset is past EOF, just
* leave len == 0.
*/
len = at.na_size - inoff;
} else if (nfsrv_linux42server == 0 &&
inoff + len > at.na_size) {
/*
* RFC-7862 says that NFSERR_INVAL must
* be returned when inoff + len exceeds
* the file size, however the NFSv4.2
* Linux client likes to do this, so
* only check if nfsrv_linux42server
* is not set.
*/
nd->nd_repstat = NFSERR_INVAL;
}
}
NFSVOPUNLOCK(vp);
if (ret != 0 && nd->nd_repstat == 0)
nd->nd_repstat = ret;
} else if (nd->nd_repstat == 0)
nd->nd_repstat = error;
}
/*
* Do the actual copy to an upper limit of vfs.nfs.maxcopyrange.
* This limit is applied to ensure that the RPC replies in a
* reasonable time.
*/
if (len > nfs_maxcopyrange)
xfer = nfs_maxcopyrange;
else
xfer = len;
if (nd->nd_repstat == 0) {
nd->nd_repstat = vn_copy_file_range(vp, &inoff, tovp, &outoff,
&xfer, 0, nd->nd_cred, nd->nd_cred, NULL);
if (nd->nd_repstat == 0)
len = xfer;
}
/* Unlock the ranges. */
if (rl_rcookie != NULL)
vn_rangelock_unlock(vp, rl_rcookie);
if (rl_wcookie != NULL)
vn_rangelock_unlock(tovp, rl_wcookie);
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, 4 * NFSX_UNSIGNED + NFSX_HYPER +
NFSX_VERF);
*tl++ = txdr_unsigned(0); /* No callback ids. */
txdr_hyper(len, tl); tl += 2;
*tl++ = txdr_unsigned(NFSWRITE_UNSTABLE);
*tl++ = txdr_unsigned(nfsboottime.tv_sec);
*tl++ = txdr_unsigned(nfsboottime.tv_usec);
*tl++ = newnfs_true;
*tl = newnfs_true;
}
out:
vrele(vp);
vrele(tovp);
NFSEXITCODE2(error, nd);
return (error);
nfsmout:
vput(vp);
vrele(tovp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs seek service
*/
int
nfsrvd_seek(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, struct nfsexstuff *exp)
{
uint32_t *tl;
struct nfsvattr at;
int content, error = 0;
off_t off;
u_long cmd;
nfsattrbit_t attrbits;
bool eof;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_STATEID + NFSX_HYPER + NFSX_UNSIGNED);
/* Ignore the stateid for now. */
tl += (NFSX_STATEID / NFSX_UNSIGNED);
off = fxdr_hyper(tl); tl += 2;
content = fxdr_unsigned(int, *tl);
if (content == NFSV4CONTENT_DATA)
cmd = FIOSEEKDATA;
else if (content == NFSV4CONTENT_HOLE)
cmd = FIOSEEKHOLE;
else
nd->nd_repstat = NFSERR_BADXDR;
if (nd->nd_repstat == 0 && vnode_vtype(vp) == VDIR)
nd->nd_repstat = NFSERR_ISDIR;
if (nd->nd_repstat == 0 && vnode_vtype(vp) != VREG)
nd->nd_repstat = NFSERR_WRONGTYPE;
if (nd->nd_repstat == 0 && off < 0)
nd->nd_repstat = NFSERR_NXIO;
if (nd->nd_repstat == 0) {
/* Check permissions for the input file. */
NFSZERO_ATTRBIT(&attrbits);
NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_OWNER);
nd->nd_repstat = nfsvno_getattr(vp, &at, nd, curthread, 1,
&attrbits);
}
if (nd->nd_repstat == 0 && (at.na_uid != nd->nd_cred->cr_uid ||
NFSVNO_EXSTRICTACCESS(exp)))
nd->nd_repstat = nfsvno_accchk(vp, VREAD, nd->nd_cred, exp,
curthread, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED,
NULL);
if (nd->nd_repstat != 0)
goto nfsmout;
/* nfsvno_seek() unlocks and vrele()s the vp. */
nd->nd_repstat = nfsvno_seek(nd, vp, cmd, &off, content, &eof,
nd->nd_cred, curthread);
if (nd->nd_repstat == 0 && eof && content == NFSV4CONTENT_DATA &&
nfsrv_linux42server != 0)
nd->nd_repstat = NFSERR_NXIO;
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED + NFSX_HYPER);
if (eof)
*tl++ = newnfs_true;
else
*tl++ = newnfs_false;
txdr_hyper(off, tl);
}
NFSEXITCODE2(error, nd);
return (error);
nfsmout:
vput(vp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs get extended attribute service
*/
int
nfsrvd_getxattr(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
struct mbuf *mp = NULL, *mpend = NULL;
int error, len;
char *name;
struct thread *p = curthread;
uint16_t off;
error = 0;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
len = fxdr_unsigned(int, *tl);
if (len <= 0) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
if (len > EXTATTR_MAXNAMELEN) {
nd->nd_repstat = NFSERR_NOXATTR;
goto nfsmout;
}
name = malloc(len + 1, M_TEMP, M_WAITOK);
nd->nd_repstat = nfsrv_mtostr(nd, name, len);
if (nd->nd_repstat == 0)
nd->nd_repstat = nfsvno_getxattr(vp, name,
nd->nd_maxresp, nd->nd_cred, nd->nd_flag,
nd->nd_maxextsiz, p, &mp, &mpend, &len);
if (nd->nd_repstat == ENOATTR)
nd->nd_repstat = NFSERR_NOXATTR;
else if (nd->nd_repstat == EOPNOTSUPP)
nd->nd_repstat = NFSERR_NOTSUPP;
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(len);
if (len > 0) {
nd->nd_mb->m_next = mp;
nd->nd_mb = mpend;
if ((mpend->m_flags & M_EXTPG) != 0) {
nd->nd_flag |= ND_EXTPG;
nd->nd_bextpg = mpend->m_epg_npgs - 1;
nd->nd_bpos = (char *)(void *)
PHYS_TO_DMAP(mpend->m_epg_pa[nd->nd_bextpg]);
off = (nd->nd_bextpg == 0) ?
mpend->m_epg_1st_off : 0;
nd->nd_bpos += off + mpend->m_epg_last_len;
nd->nd_bextpgsiz = PAGE_SIZE -
mpend->m_epg_last_len - off;
} else
nd->nd_bpos = mtod(mpend, char *) +
mpend->m_len;
}
}
free(name, M_TEMP);
nfsmout:
if (nd->nd_repstat == 0)
nd->nd_repstat = error;
vput(vp);
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfs set extended attribute service
*/
int
nfsrvd_setxattr(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
struct nfsvattr ova, nva;
nfsattrbit_t attrbits;
int error, len, opt;
char *name;
size_t siz;
struct thread *p = curthread;
error = 0;
name = NULL;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
opt = fxdr_unsigned(int, *tl++);
len = fxdr_unsigned(int, *tl);
if (len <= 0) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
if (len > EXTATTR_MAXNAMELEN) {
nd->nd_repstat = NFSERR_NOXATTR;
goto nfsmout;
}
name = malloc(len + 1, M_TEMP, M_WAITOK);
error = nfsrv_mtostr(nd, name, len);
if (error != 0)
goto nfsmout;
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
len = fxdr_unsigned(int, *tl);
if (len < 0 || len > IOSIZE_MAX) {
nd->nd_repstat = NFSERR_XATTR2BIG;
goto nfsmout;
}
switch (opt) {
case NFSV4SXATTR_CREATE:
error = VOP_GETEXTATTR(vp, EXTATTR_NAMESPACE_USER, name, NULL,
&siz, nd->nd_cred, p);
if (error != ENOATTR)
nd->nd_repstat = NFSERR_EXIST;
error = 0;
break;
case NFSV4SXATTR_REPLACE:
error = VOP_GETEXTATTR(vp, EXTATTR_NAMESPACE_USER, name, NULL,
&siz, nd->nd_cred, p);
if (error != 0)
nd->nd_repstat = NFSERR_NOXATTR;
break;
case NFSV4SXATTR_EITHER:
break;
default:
nd->nd_repstat = NFSERR_BADXDR;
}
if (nd->nd_repstat != 0)
goto nfsmout;
/* Now, do the Set Extended attribute, with Change before and after. */
NFSZERO_ATTRBIT(&attrbits);
NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_CHANGE);
nd->nd_repstat = nfsvno_getattr(vp, &ova, nd, p, 1, &attrbits);
if (nd->nd_repstat == 0) {
nd->nd_repstat = nfsvno_setxattr(vp, name, len, nd->nd_md,
nd->nd_dpos, nd->nd_cred, p);
if (nd->nd_repstat == ENXIO)
nd->nd_repstat = NFSERR_XATTR2BIG;
}
if (nd->nd_repstat == 0 && len > 0)
nd->nd_repstat = nfsm_advance(nd, NFSM_RNDUP(len), -1);
if (nd->nd_repstat == 0)
nd->nd_repstat = nfsvno_getattr(vp, &nva, nd, p, 1, &attrbits);
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, 2 * NFSX_HYPER + NFSX_UNSIGNED);
*tl++ = newnfs_true;
txdr_hyper(ova.na_filerev, tl); tl += 2;
txdr_hyper(nva.na_filerev, tl);
}
nfsmout:
free(name, M_TEMP);
if (nd->nd_repstat == 0)
nd->nd_repstat = error;
vput(vp);
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfs remove extended attribute service
*/
int
nfsrvd_rmxattr(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t *tl;
struct nfsvattr ova, nva;
nfsattrbit_t attrbits;
int error, len;
char *name;
struct thread *p = curthread;
error = 0;
name = NULL;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
len = fxdr_unsigned(int, *tl);
if (len <= 0) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
if (len > EXTATTR_MAXNAMELEN) {
nd->nd_repstat = NFSERR_NOXATTR;
goto nfsmout;
}
name = malloc(len + 1, M_TEMP, M_WAITOK);
error = nfsrv_mtostr(nd, name, len);
if (error != 0)
goto nfsmout;
if ((nd->nd_flag & ND_IMPLIEDCLID) == 0) {
printf("EEK! nfsrvd_rmxattr: no implied clientid\n");
error = NFSERR_NOXATTR;
goto nfsmout;
}
/*
* Now, do the Remove Extended attribute, with Change before and
* after.
*/
NFSZERO_ATTRBIT(&attrbits);
NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_CHANGE);
nd->nd_repstat = nfsvno_getattr(vp, &ova, nd, p, 1, &attrbits);
if (nd->nd_repstat == 0) {
nd->nd_repstat = nfsvno_rmxattr(nd, vp, name, nd->nd_cred, p);
if (nd->nd_repstat == ENOATTR)
nd->nd_repstat = NFSERR_NOXATTR;
}
if (nd->nd_repstat == 0)
nd->nd_repstat = nfsvno_getattr(vp, &nva, nd, p, 1, &attrbits);
if (nd->nd_repstat == 0) {
NFSM_BUILD(tl, uint32_t *, 2 * NFSX_HYPER + NFSX_UNSIGNED);
*tl++ = newnfs_true;
txdr_hyper(ova.na_filerev, tl); tl += 2;
txdr_hyper(nva.na_filerev, tl);
}
nfsmout:
free(name, M_TEMP);
if (nd->nd_repstat == 0)
nd->nd_repstat = error;
vput(vp);
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfs list extended attribute service
*/
int
nfsrvd_listxattr(struct nfsrv_descript *nd, __unused int isdgram,
vnode_t vp, __unused struct nfsexstuff *exp)
{
uint32_t cnt, *tl, len, len2, i, pos, retlen;
int error;
uint64_t cookie, cookie2;
u_char *buf;
bool eof;
struct thread *p = curthread;
error = 0;
buf = NULL;
if (nfs_rootfhset == 0 || nfsd_checkrootexp(nd) != 0) {
nd->nd_repstat = NFSERR_WRONGSEC;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, NFSX_HYPER + NFSX_UNSIGNED);
/*
* The cookie doesn't need to be in net byte order, but FreeBSD
* does so to make it more readable in packet traces.
*/
cookie = fxdr_hyper(tl); tl += 2;
len = fxdr_unsigned(uint32_t, *tl);
if (len == 0 || cookie >= IOSIZE_MAX) {
nd->nd_repstat = NFSERR_BADXDR;
goto nfsmout;
}
if (len > nd->nd_maxresp - NFS_MAXXDR)
len = nd->nd_maxresp - NFS_MAXXDR;
len2 = len;
nd->nd_repstat = nfsvno_listxattr(vp, cookie, nd->nd_cred, p, &buf,
&len, &eof);
if (nd->nd_repstat == EOPNOTSUPP)
nd->nd_repstat = NFSERR_NOTSUPP;
if (nd->nd_repstat == 0) {
cookie2 = cookie + len;
if (cookie2 < cookie)
nd->nd_repstat = NFSERR_BADXDR;
}
if (nd->nd_repstat == 0) {
/* Now copy the entries out. */
retlen = NFSX_HYPER + 2 * NFSX_UNSIGNED;
if (len == 0 && retlen <= len2) {
/* The cookie was at eof. */
NFSM_BUILD(tl, uint32_t *, NFSX_HYPER + 2 *
NFSX_UNSIGNED);
txdr_hyper(cookie2, tl); tl += 2;
*tl++ = txdr_unsigned(0);
*tl = newnfs_true;
goto nfsmout;
}
/* Sanity check the cookie. */
for (pos = 0; pos < len; pos += (i + 1)) {
if (pos == cookie)
break;
i = buf[pos];
}
if (pos != cookie) {
nd->nd_repstat = NFSERR_INVAL;
goto nfsmout;
}
/* Loop around copying the entrie(s) out. */
cnt = 0;
len -= cookie;
i = buf[pos];
while (i < len && len2 >= retlen + NFSM_RNDUP(i) +
NFSX_UNSIGNED) {
if (cnt == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_HYPER +
NFSX_UNSIGNED);
txdr_hyper(cookie2, tl); tl += 2;
}
retlen += nfsm_strtom(nd, &buf[pos + 1], i);
len -= (i + 1);
pos += (i + 1);
i = buf[pos];
cnt++;
}
/*
* eof is set true/false by nfsvno_listxattr(), but if we
* can't copy all entries returned by nfsvno_listxattr(),
* we are not at eof.
*/
if (len > 0)
eof = false;
if (cnt > 0) {
/* *tl is set above. */
*tl = txdr_unsigned(cnt);
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
if (eof)
*tl = newnfs_true;
else
*tl = newnfs_false;
} else
nd->nd_repstat = NFSERR_TOOSMALL;
}
nfsmout:
free(buf, M_TEMP);
if (nd->nd_repstat == 0)
nd->nd_repstat = error;
vput(vp);
NFSEXITCODE2(0, nd);
return (0);
}
/*
* nfsv4 service not supported
*/
int
nfsrvd_notsupp(struct nfsrv_descript *nd, __unused int isdgram,
__unused vnode_t vp, __unused struct nfsexstuff *exp)
{
nd->nd_repstat = NFSERR_NOTSUPP;
NFSEXITCODE2(0, nd);
return (0);
}