nfsv4: Add support for the NFSv4.2 Clone operation

Commit 37b2cb5ecb added VFS support for block cloning.
This patch uses the VFS changes to add support for the
NFSv4.2 Clone operation, which copies ranges within one
or two files via block cloning.

The Clone operation is similar to Copy, but always
completes the "copy on write".  It is not allowed to
return partially done.  It also allows copying of bytes
ranges within the same file, which the NFSv4.2 Copy
operation does not allow.

Unless COPY_FILE_RANGE_CLONE has been specified for
copy_file_range(2), a failing Clone operation will be
redone with a Copy.

The Clone operation requires that offsets (and length,
if it does not go to EOF in the input file) be aligned
to _PC_CLONE_BLKSIZE.  This is similar to what ZFS
implements now.

At this time, ZFS is the only exportable file system
that supports block cloning.  As such, the Clone operation
is only supported for ZFS exports at this time.

Fixes:	37b2cb5ecb ("vfs: Add support for file cloning to VOP_COPY_FILE_RANGE")
This commit is contained in:
Rick Macklem 2025-08-13 12:35:59 -07:00
parent b9de52a0ec
commit cce64f2e68
16 changed files with 660 additions and 92 deletions

View file

@ -239,6 +239,7 @@ static bool nfscl_use_gss[NFSV42_NPROCS] = {
true,
true,
true,
true,
};
/*

View file

@ -187,7 +187,7 @@ struct nfsv4_opflag nfsv4_opflag[NFSV42_NOPS] = {
{ 0, 0, 0, 0, LK_EXCLUSIVE, 1, 1 }, /* Read Plus */
{ 0, 1, 0, 0, LK_SHARED, 1, 0 }, /* Seek */
{ 0, 0, 0, 0, LK_EXCLUSIVE, 1, 1 }, /* Write Same */
{ 0, 0, 0, 0, LK_EXCLUSIVE, 1, 1 }, /* Clone */
{ 2, 1, 1, 0, LK_SHARED, 1, 0 }, /* Clone */
{ 0, 1, 0, 0, LK_SHARED, 1, 1 }, /* Getxattr */
{ 0, 1, 1, 1, LK_EXCLUSIVE, 1, 1 }, /* Setxattr */
{ 0, 1, 0, 0, LK_SHARED, 1, 1 }, /* Listxattrs */
@ -219,7 +219,7 @@ NFSD_VNET_DEFINE_STATIC(u_char *, nfsrv_dnsname) = NULL;
static int nfs_bigreply[NFSV42_NPROCS] = { 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 };
/* local functions */
static int nfsrv_skipace(struct nfsrv_descript *nd, int *acesizep);
@ -310,6 +310,7 @@ static struct {
{ NFSV4OP_LAYOUTERROR, 1, "LayoutError", 11, },
{ NFSV4OP_VERIFY, 3, "AppendWrite", 11, },
{ NFSV4OP_OPENATTR, 3, "OpenAttr", 8, },
{ NFSV4OP_SAVEFH, 5, "Clone", 5, },
};
/*
@ -319,7 +320,7 @@ static int nfs_bigrequest[NFSV42_NPROCS] = {
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0
0, 1, 0, 0
};
/*
@ -648,7 +649,7 @@ nfscl_fillsattr(struct nfsrv_descript *nd, struct vattr *vap,
NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_TIMECREATE);
(void) nfsv4_fillattr(nd, vp->v_mount, vp, NULL, vap, NULL, 0,
&attrbits, NULL, NULL, 0, 0, 0, 0, (uint64_t)0, NULL,
false, false, false);
false, false, false, 0);
break;
}
}
@ -1302,7 +1303,7 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp,
struct nfsv3_pathconf *pc, struct statfs *sbp, struct nfsstatfs *sfp,
struct nfsfsinfo *fsp, NFSACL_T *aclp, int compare, int *retcmpp,
u_int32_t *leasep, u_int32_t *rderrp, bool *has_namedattrp,
NFSPROC_T *p, struct ucred *cred)
uint32_t *clone_blksizep, NFSPROC_T *p, struct ucred *cred)
{
u_int32_t *tl;
int i = 0, j, k, l = 0, m, bitpos, attrsum = 0;
@ -1437,6 +1438,13 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp,
NFSCLRBIT_ATTRBIT(&checkattrbits,
NFSATTRBIT_SYSTEM);
}
/* Some filesystems do not support block cloning */
if (vp == NULL || VOP_PATHCONF(vp,
_PC_CLONE_BLKSIZE, &has_pathconf) != 0)
has_pathconf = 0;
if (has_pathconf == 0)
NFSCLRBIT_ATTRBIT(&checkattrbits,
NFSATTRBIT_CLONEBLKSIZE);
if (!NFSEQUAL_ATTRBIT(&retattrbits, &checkattrbits)
|| retnotsup)
*retcmpp = NFSERR_NOTSAME;
@ -2374,6 +2382,23 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp,
if (compare && !(*retcmpp) && i != nfs_srvmaxio)
*retcmpp = NFSERR_NOTSAME;
break;
case NFSATTRBIT_CLONEBLKSIZE:
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
if (compare) {
if (!(*retcmpp)) {
if (vp == NULL || VOP_PATHCONF(vp,
_PC_CLONE_BLKSIZE, &has_pathconf)
!= 0)
has_pathconf = 0;
if (has_pathconf !=
fxdr_unsigned(uint32_t, *tl))
*retcmpp = NFSERR_NOTSAME;
}
} else if (clone_blksizep != NULL) {
*clone_blksizep = fxdr_unsigned(uint32_t, *tl);
}
attrsum += NFSX_UNSIGNED;
break;
case NFSATTRBIT_CHANGEATTRTYPE:
NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED);
if (compare) {
@ -2648,7 +2673,7 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp,
nfsattrbit_t *attrbitp, struct ucred *cred, NFSPROC_T *p, int isdgram,
int reterr, int supports_nfsv4acls, int at_root, uint64_t mounted_on_fileno,
struct statfs *pnfssf, bool xattrsupp, bool has_hiddensystem,
bool has_namedattr)
bool has_namedattr, uint32_t clone_blksize)
{
int bitpos, retnum = 0;
u_int32_t *tl;
@ -2771,6 +2796,9 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp,
NFSCLRBIT_ATTRBIT(&attrbits, NFSATTRBIT_HIDDEN);
NFSCLRBIT_ATTRBIT(&attrbits, NFSATTRBIT_SYSTEM);
}
if (clone_blksize == 0)
NFSCLRBIT_ATTRBIT(&attrbits,
NFSATTRBIT_CLONEBLKSIZE);
retnum += nfsrv_putattrbit(nd, &attrbits);
break;
case NFSATTRBIT_TYPE:
@ -3249,6 +3277,11 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp,
}
retnum += NFSX_UNSIGNED;
break;
case NFSATTRBIT_CLONEBLKSIZE:
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(clone_blksize);
retnum += NFSX_UNSIGNED;
break;
default:
printf("EEK! Bad V4 attribute bitpos=%d\n", bitpos);
}

View file

@ -286,6 +286,8 @@ int nfsrvd_deallocate(struct nfsrv_descript *, int,
vnode_t, struct nfsexstuff *);
int nfsrvd_copy_file_range(struct nfsrv_descript *, int,
vnode_t, vnode_t, struct nfsexstuff *, struct nfsexstuff *);
int nfsrvd_clone(struct nfsrv_descript *, int,
vnode_t, vnode_t, struct nfsexstuff *, struct nfsexstuff *);
int nfsrvd_seek(struct nfsrv_descript *, int,
vnode_t, struct nfsexstuff *);
int nfsrvd_getxattr(struct nfsrv_descript *, int,
@ -341,7 +343,8 @@ int nfsv4_loadattr(struct nfsrv_descript *, vnode_t,
struct nfsvattr *, struct nfsfh **, fhandle_t *, int,
struct nfsv3_pathconf *, struct statfs *, struct nfsstatfs *,
struct nfsfsinfo *, NFSACL_T *,
int, int *, u_int32_t *, u_int32_t *, bool *, NFSPROC_T *, struct ucred *);
int, int *, u_int32_t *, u_int32_t *, bool *, uint32_t *, NFSPROC_T *,
struct ucred *);
int nfsv4_lock(struct nfsv4lock *, int, int *, struct mtx *, struct mount *);
void nfsv4_unlock(struct nfsv4lock *, int);
void nfsv4_relref(struct nfsv4lock *);
@ -397,7 +400,7 @@ void nfsrv_wcc(struct nfsrv_descript *, int, struct nfsvattr *, int,
int nfsv4_fillattr(struct nfsrv_descript *, struct mount *, vnode_t, NFSACL_T *,
struct vattr *, fhandle_t *, int, nfsattrbit_t *, struct ucred *,
NFSPROC_T *, int, int, int, int, uint64_t, struct statfs *, bool, bool,
bool);
bool, uint32_t);
void nfsrv_fillattr(struct nfsrv_descript *, struct nfsvattr *);
struct mbuf *nfsrv_adj(struct mbuf *, int, int);
void nfsrv_postopattr(struct nfsrv_descript *, int, struct nfsvattr *);
@ -517,10 +520,10 @@ int nfsrpc_lock(struct nfsrv_descript *, struct nfsmount *, vnode_t,
u_int8_t *, int, struct nfscllockowner *, int, int, u_int64_t,
u_int64_t, short, struct ucred *, NFSPROC_T *, int);
int nfsrpc_statfs(vnode_t, struct nfsstatfs *, struct nfsfsinfo *, uint32_t *,
struct ucred *, NFSPROC_T *, struct nfsvattr *, int *);
uint32_t *, struct ucred *, NFSPROC_T *, struct nfsvattr *, int *);
int nfsrpc_fsinfo(vnode_t, struct nfsfsinfo *, struct ucred *,
NFSPROC_T *, struct nfsvattr *, int *);
int nfsrpc_pathconf(vnode_t, struct nfsv3_pathconf *, bool *,
int nfsrpc_pathconf(vnode_t, struct nfsv3_pathconf *, bool *, uint32_t *,
struct ucred *, NFSPROC_T *, struct nfsvattr *, int *);
int nfsrpc_renew(struct nfsclclient *, struct nfsclds *, struct ucred *,
NFSPROC_T *);
@ -562,6 +565,8 @@ int nfsrpc_deallocate(vnode_t, off_t, off_t, struct nfsvattr *, int *,
int nfsrpc_copy_file_range(vnode_t, off_t *, vnode_t, off_t *, size_t *,
unsigned int, int *, struct nfsvattr *, int *, struct nfsvattr *,
struct ucred *, bool, bool *);
int nfsrpc_clone(vnode_t, off_t *, vnode_t, off_t *, size_t *, bool,
int *, struct nfsvattr *, int *, struct nfsvattr *, struct ucred *);
int nfsrpc_seek(vnode_t, off_t *, bool *, int, struct ucred *,
struct nfsvattr *, int *);
int nfsrpc_getextattr(vnode_t, const char *, struct uio *, ssize_t *,
@ -668,7 +673,7 @@ int nfscl_nget(mount_t, vnode_t, struct nfsfh *,
NFSPROC_T *nfscl_getparent(NFSPROC_T *);
void nfscl_start_renewthread(struct nfsclclient *);
void nfscl_loadsbinfo(struct nfsmount *, struct nfsstatfs *, void *);
void nfscl_loadfsinfo (struct nfsmount *, struct nfsfsinfo *);
void nfscl_loadfsinfo(struct nfsmount *, struct nfsfsinfo *, uint32_t);
void nfscl_delegreturn(struct nfscldeleg *, int, struct nfsmount *,
struct ucred *, NFSPROC_T *);
void nfsrvd_cbinit(int);
@ -737,7 +742,7 @@ int nfsvno_updfilerev(vnode_t, struct nfsvattr *, struct nfsrv_descript *,
int nfsvno_fillattr(struct nfsrv_descript *, struct mount *, vnode_t,
struct nfsvattr *, fhandle_t *, int, nfsattrbit_t *,
struct ucred *, NFSPROC_T *, int, int, int, int, uint64_t, bool, bool,
bool);
bool, uint32_t);
int nfsrv_sattr(struct nfsrv_descript *, vnode_t, struct nfsvattr *, nfsattrbit_t *,
NFSACL_T *, NFSPROC_T *);
int nfsv4_sattr(struct nfsrv_descript *, vnode_t, struct nfsvattr *, nfsattrbit_t *,

View file

@ -442,10 +442,13 @@
/* Do a NFSv4 Openattr. */
#define NFSPROC_OPENATTR 70
/* Do a NFSv4.2 Clone. */
#define NFSPROC_CLONE 71
/*
* Must be defined as one higher than the last NFSv4.2 Proc# above.
*/
#define NFSV42_NPROCS 71
#define NFSV42_NPROCS 72
/* Value of NFSV42_NPROCS for old nfsstats structure. (Always 69) */
#define NFSV42_OLDNPROCS 69
@ -477,7 +480,7 @@ struct nfsstatsv1 {
uint64_t readlink_bios;
uint64_t biocache_readdirs;
uint64_t readdir_bios;
uint64_t rpccnt[NFSV42_NPROCS + 9];
uint64_t rpccnt[NFSV42_NPROCS + 8];
uint64_t rpcretries;
uint64_t srvrpccnt[NFSV42_NOPS + NFSV4OP_FAKENOPS + 15];
uint64_t srvlayouts;

View file

@ -411,10 +411,13 @@
/* Do a NFSv4 Openattr. */
#define NFSPROC_OPENATTR 70
/* Do a NFSv4.2 Clone. */
#define NFSPROC_CLONE 71
/*
* Must be defined as one higher than the last NFSv4.2 Proc# above.
*/
#define NFSV42_NPROCS 71
#define NFSV42_NPROCS 72
/* Value of NFSV42_NPROCS for old nfsstats structure. (Always 69) */
#define NFSV42_OLDNPROCS 69
@ -1194,6 +1197,7 @@ struct nfsv3_sattr {
NFSATTRBM_LAYOUTBLKSIZE | \
NFSATTRBM_LAYOUTALIGNMENT | \
NFSATTRBM_SUPPATTREXCLCREAT | \
NFSATTRBM_CLONEBLKSIZE | \
NFSATTRBM_CHANGEATTRTYPE | \
NFSATTRBM_XATTRSUPPORT)
@ -1242,7 +1246,8 @@ struct nfsv3_sattr {
* NFSATTRBIT_NFSV42 - Attributes only supported by NFSv4.2.
*/
#define NFSATTRBIT_NFSV42_2 \
(NFSATTRBM_CHANGEATTRTYPE | \
(NFSATTRBM_CLONEBLKSIZE | \
NFSATTRBM_CHANGEATTRTYPE | \
NFSATTRBM_XATTRSUPPORT | \
NFSATTRBM_MODEUMASK)
@ -1415,7 +1420,7 @@ struct nfsv3_sattr {
/*
* NFSGETATTRBIT_STATFS2 - bits 64<->95
*/
#define NFSGETATTRBIT_STATFS2 0
#define NFSGETATTRBIT_STATFS2 (NFSATTRBM_CLONEBLKSIZE)
/*
* Set of attributes for the equivalent of an nfsv3 pathconf rpc.
@ -1438,7 +1443,7 @@ struct nfsv3_sattr {
/*
* NFSGETATTRBIT_PATHCONF2 - bits 64<->95
*/
#define NFSGETATTRBIT_PATHCONF2 0
#define NFSGETATTRBIT_PATHCONF2 (NFSATTRBM_CLONEBLKSIZE)
/*
* Sets of attributes required by readdir and readdirplus.

View file

@ -272,7 +272,7 @@ nfsm_loadattr(struct nfsrv_descript *nd, struct nfsvattr *nap)
if (nd->nd_flag & ND_NFSV4) {
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL,
NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL,
NULL);
NULL, NULL);
} else if (nd->nd_flag & ND_NFSV3) {
NFSM_DISSECT(fp, struct nfs_fattr *, NFSX_V3FATTR);
nap->na_type = nfsv34tov_type(fp->fa_type);

View file

@ -828,7 +828,7 @@ nfscl_wcc_data(struct nfsrv_descript *nd, struct vnode *vp,
== (ND_NFSV4 | ND_V4WCCATTR)) {
error = nfsv4_loadattr(nd, NULL, &nfsva, NULL,
NULL, 0, NULL, NULL, NULL, NULL, NULL, 0,
NULL, NULL, NULL, NULL, NULL, NULL);
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (error)
return (error);
/*
@ -963,7 +963,8 @@ nfscl_loadsbinfo(struct nfsmount *nmp, struct nfsstatfs *sfp, void *statfs)
* Use the fsinfo stuff to update the mount point.
*/
void
nfscl_loadfsinfo(struct nfsmount *nmp, struct nfsfsinfo *fsp)
nfscl_loadfsinfo(struct nfsmount *nmp, struct nfsfsinfo *fsp,
uint32_t clone_blksize)
{
if ((nmp->nm_wsize == 0 || fsp->fs_wtpref < nmp->nm_wsize) &&
@ -1003,6 +1004,14 @@ nfscl_loadfsinfo(struct nfsmount *nmp, struct nfsfsinfo *fsp)
fsp->fs_maxfilesize < nmp->nm_maxfilesize)
nmp->nm_maxfilesize = fsp->fs_maxfilesize;
nmp->nm_mountp->mnt_stat.f_iosize = newnfs_iosize(nmp);
/*
* Although ZFS reports a clone_blksize of 16Mbytes,
* 128Kbytes usually works, so set it to that.
*/
if (clone_blksize > 128 * 1024)
clone_blksize = 128 * 1024;
nmp->nm_cloneblksize = clone_blksize;
nmp->nm_state |= NFSSTA_GOTFSINFO;
}

View file

@ -225,6 +225,9 @@ static int nfsrpc_layoutgetres(struct nfsmount *, vnode_t, uint8_t *,
static int nfsrpc_copyrpc(vnode_t, off_t, vnode_t, off_t, size_t *,
nfsv4stateid_t *, nfsv4stateid_t *, struct nfsvattr *, int *,
struct nfsvattr *, int *, bool, int *, struct ucred *, NFSPROC_T *);
static int nfsrpc_clonerpc(vnode_t, off_t, vnode_t, off_t, size_t *, bool,
nfsv4stateid_t *, nfsv4stateid_t *, struct nfsvattr *, int *,
struct nfsvattr *, int *, struct ucred *, NFSPROC_T *);
static int nfsrpc_seekrpc(vnode_t, off_t *, nfsv4stateid_t *, bool *,
int, struct nfsvattr *, int *, struct ucred *);
static struct mbuf *nfsm_split(struct mbuf *, uint64_t);
@ -696,7 +699,7 @@ nfsrpc_openrpc(struct nfsmount *nmp, vnode_t vp, u_int8_t *nfhp, int fhlen,
("nfsrpc_openrpc: Getattr repstat"));
error = nfsv4_loadattr(nd, NULL, &nfsva, NULL,
NULL, 0, NULL, NULL, NULL, NULL, NULL, 0,
NULL, NULL, NULL, NULL, p, cred);
NULL, NULL, NULL, NULL, NULL, p, cred);
if (error)
goto nfsmout;
}
@ -1355,7 +1358,7 @@ nfsrpc_getattrnovp(struct nfsmount *nmp, u_int8_t *fhp, int fhlen, int syscred,
if ((nd->nd_flag & ND_NFSV4) != 0)
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0,
NULL, NULL, NULL, NULL, NULL, 0, NULL, leasep, NULL,
NULL, NULL, NULL);
NULL, NULL, NULL, NULL);
else
error = nfsm_loadattr(nd, nap);
} else
@ -3597,7 +3600,7 @@ nfsrpc_readdir(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep,
nfsva.na_mntonfileno = UINT64_MAX;
error = nfsv4_loadattr(nd, NULL, &nfsva, NULL,
NULL, 0, NULL, NULL, NULL, NULL, NULL, 0,
NULL, NULL, NULL, NULL, p, cred);
NULL, NULL, NULL, NULL, NULL, p, cred);
if (error) {
dotdotfileid = dotfileid;
} else if (gotmnton) {
@ -3847,7 +3850,7 @@ nfsrpc_readdir(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep,
nfsva.na_mntonfileno = UINT64_MAX;
error = nfsv4_loadattr(nd, NULL, &nfsva, NULL,
NULL, 0, NULL, NULL, NULL, NULL, NULL, 0,
NULL, NULL, &rderr, NULL, p, cred);
NULL, NULL, &rderr, NULL, NULL, p, cred);
if (error)
goto nfsmout;
NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
@ -4072,7 +4075,7 @@ nfsrpc_readdirplus(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep,
nfsva.na_mntonfileno = UINT64_MAX;
error = nfsv4_loadattr(nd, NULL, &nfsva, NULL,
NULL, 0, NULL, NULL, NULL, NULL, NULL, 0,
NULL, NULL, NULL, NULL, p, cred);
NULL, NULL, NULL, NULL, NULL, p, cred);
if (error) {
dotdotfileid = dotfileid;
} else if (gotmnton) {
@ -4346,7 +4349,7 @@ nfsrpc_readdirplus(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep,
nfsva.na_mntonfileno = 0xffffffff;
error = nfsv4_loadattr(nd, NULL, &nfsva, &nfhp,
NULL, 0, NULL, NULL, NULL, NULL, NULL, 0,
NULL, NULL, &rderr, NULL, p, cred);
NULL, NULL, &rderr, NULL, NULL, p, cred);
if (error)
goto nfsmout;
}
@ -4981,8 +4984,8 @@ nfsmout:
*/
int
nfsrpc_statfs(vnode_t vp, struct nfsstatfs *sbp, struct nfsfsinfo *fsp,
uint32_t *leasep, struct ucred *cred, NFSPROC_T *p, struct nfsvattr *nap,
int *attrflagp)
uint32_t *leasep, uint32_t *cloneblksizep, struct ucred *cred, NFSPROC_T *p,
struct nfsvattr *nap, int *attrflagp)
{
u_int32_t *tl = NULL;
struct nfsrv_descript nfsd, *nd = &nfsd;
@ -4991,6 +4994,8 @@ nfsrpc_statfs(vnode_t vp, struct nfsstatfs *sbp, struct nfsfsinfo *fsp,
int error;
*attrflagp = 0;
if (cloneblksizep != NULL)
*cloneblksizep = 0;
nmp = VFSTONFS(vp->v_mount);
if (NFSHASNFSV4(nmp)) {
/*
@ -5009,7 +5014,7 @@ nfsrpc_statfs(vnode_t vp, struct nfsstatfs *sbp, struct nfsfsinfo *fsp,
if (nd->nd_repstat == 0) {
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0,
NULL, NULL, sbp, fsp, NULL, 0, NULL, leasep, NULL,
NULL, p, cred);
NULL, cloneblksizep, p, cred);
if (!error) {
nmp->nm_fsid[0] = nap->na_filesid[0];
nmp->nm_fsid[1] = nap->na_filesid[1];
@ -5063,7 +5068,8 @@ nfsmout:
*/
int
nfsrpc_pathconf(vnode_t vp, struct nfsv3_pathconf *pc, bool *has_namedattrp,
struct ucred *cred, NFSPROC_T *p, struct nfsvattr *nap, int *attrflagp)
uint32_t *clone_blksizep, struct ucred *cred, NFSPROC_T *p,
struct nfsvattr *nap, int *attrflagp)
{
struct nfsrv_descript nfsd, *nd = &nfsd;
struct nfsmount *nmp;
@ -5074,6 +5080,7 @@ nfsrpc_pathconf(vnode_t vp, struct nfsv3_pathconf *pc, bool *has_namedattrp,
*has_namedattrp = false;
*attrflagp = 0;
*clone_blksizep = 0;
nmp = VFSTONFS(vp->v_mount);
if (NFSHASNFSV4(nmp)) {
np = VTONFS(vp);
@ -5100,7 +5107,7 @@ nfsrpc_pathconf(vnode_t vp, struct nfsv3_pathconf *pc, bool *has_namedattrp,
if (nd->nd_repstat == 0) {
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0,
pc, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL,
has_namedattrp, p, cred);
has_namedattrp, clone_blksizep, p, cred);
if (!error)
*attrflagp = 1;
} else {
@ -5395,7 +5402,8 @@ nfsrpc_getacl(vnode_t vp, struct ucred *cred, NFSPROC_T *p, struct acl *aclp)
return (error);
if (!nd->nd_repstat)
error = nfsv4_loadattr(nd, vp, NULL, NULL, NULL, 0, NULL,
NULL, NULL, NULL, aclp, 0, NULL, NULL, NULL, NULL, p, cred);
NULL, NULL, NULL, aclp, 0, NULL, NULL, NULL, NULL, NULL, p,
cred);
else
error = nd->nd_repstat;
m_freem(nd->nd_mrep);
@ -5437,7 +5445,7 @@ nfsrpc_setaclrpc(vnode_t vp, struct ucred *cred, NFSPROC_T *p,
NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_ACL);
(void) nfsv4_fillattr(nd, vp->v_mount, vp, aclp, NULL, NULL, 0,
&attrbits, NULL, NULL, 0, 0, 0, 0, (uint64_t)0, NULL, false, false,
false);
false, 0);
error = nfscl_request(nd, vp, p, cred);
if (error)
return (error);
@ -8496,7 +8504,7 @@ nfsrpc_openlayoutrpc(struct nfsmount *nmp, vnode_t vp, u_int8_t *nfhp,
if (*++tl == 0) {
error = nfsv4_loadattr(nd, NULL, &nfsva, NULL,
NULL, 0, NULL, NULL, NULL, NULL, NULL, 0,
NULL, NULL, NULL, NULL, p, cred);
NULL, NULL, NULL, NULL, NULL, p, cred);
if (error != 0)
goto nfsmout;
if (ndp != NULL) {
@ -9167,6 +9175,199 @@ nfsmout:
return (error);
}
/*
* nfs clone operation.
*/
int
nfsrpc_clone(vnode_t invp, off_t *inoffp, vnode_t outvp,
off_t *outoffp, size_t *lenp, bool toeof, int *inattrflagp,
struct nfsvattr *innap, int *outattrflagp, struct nfsvattr *outnap,
struct ucred *cred)
{
int error, expireret = 0, retrycnt;
uint32_t clidrev = 0;
struct nfsmount *nmp = VFSTONFS(invp->v_mount);
struct nfsfh *innfhp = NULL, *outnfhp = NULL;
nfsv4stateid_t instateid, outstateid;
void *inlckp, *outlckp;
if (nmp->nm_clp != NULL)
clidrev = nmp->nm_clp->nfsc_clientidrev;
innfhp = VTONFS(invp)->n_fhp;
outnfhp = VTONFS(outvp)->n_fhp;
retrycnt = 0;
do {
/* Get both stateids. */
inlckp = NULL;
nfscl_getstateid(invp, innfhp->nfh_fh, innfhp->nfh_len,
NFSV4OPEN_ACCESSREAD, 0, NULL, curthread, &instateid,
&inlckp);
outlckp = NULL;
nfscl_getstateid(outvp, outnfhp->nfh_fh, outnfhp->nfh_len,
NFSV4OPEN_ACCESSWRITE, 0, NULL, curthread, &outstateid,
&outlckp);
error = nfsrpc_clonerpc(invp, *inoffp, outvp, *outoffp, lenp,
toeof, &instateid, &outstateid, innap, inattrflagp, outnap,
outattrflagp, cred, curthread);
if (error == 0) {
*inoffp += *lenp;
*outoffp += *lenp;
} else if (error == NFSERR_STALESTATEID)
nfscl_initiate_recovery(nmp->nm_clp);
if (inlckp != NULL)
nfscl_lockderef(inlckp);
if (outlckp != NULL)
nfscl_lockderef(outlckp);
if (error == NFSERR_GRACE || error == NFSERR_STALESTATEID ||
error == NFSERR_STALEDONTRECOVER || error == NFSERR_DELAY ||
error == NFSERR_OLDSTATEID || error == NFSERR_BADSESSION) {
(void) nfs_catnap(PZERO, error, "nfs_cfr");
} else if ((error == NFSERR_EXPIRED || (!NFSHASINT(nmp) &&
error == NFSERR_BADSTATEID)) && clidrev != 0) {
expireret = nfscl_hasexpired(nmp->nm_clp, clidrev,
curthread);
} else if (error == NFSERR_BADSTATEID && NFSHASINT(nmp)) {
error = EIO;
}
retrycnt++;
} while (error == NFSERR_GRACE || error == NFSERR_DELAY ||
error == NFSERR_STALESTATEID || error == NFSERR_BADSESSION ||
error == NFSERR_STALEDONTRECOVER ||
(error == NFSERR_OLDSTATEID && retrycnt < 20) ||
((error == NFSERR_EXPIRED || error == NFSERR_BADSTATEID) &&
expireret == 0 && clidrev != 0 && retrycnt < 4));
if (error != 0 && (retrycnt >= 4 ||
error == NFSERR_STALESTATEID || error == NFSERR_BADSESSION ||
error == NFSERR_STALEDONTRECOVER))
error = EIO;
return (error);
}
/*
* The clone RPC.
*/
static int
nfsrpc_clonerpc(vnode_t invp, off_t inoff, vnode_t outvp, off_t outoff,
size_t *lenp, bool toeof, nfsv4stateid_t *instateidp,
nfsv4stateid_t *outstateidp, struct nfsvattr *innap, int *inattrflagp,
struct nfsvattr *outnap, int *outattrflagp, struct ucred *cred,
NFSPROC_T *p)
{
uint32_t *tl, *opcntp;
int error;
struct nfsrv_descript nfsd;
struct nfsrv_descript *nd = &nfsd;
struct nfsmount *nmp;
nfsattrbit_t attrbits;
struct vattr va;
uint64_t len;
nmp = VFSTONFS(invp->v_mount);
*inattrflagp = *outattrflagp = 0;
len = *lenp;
if (len == 0)
return (0);
if (toeof)
len = 0;
nfscl_reqstart(nd, NFSPROC_CLONE, nmp, VTONFS(invp)->n_fhp->nfh_fh,
VTONFS(invp)->n_fhp->nfh_len, &opcntp, NULL, 0, 0, cred);
/*
* First do a Setattr of atime to the server's clock
* time. The FreeBSD "collective" was of the opinion
* that setting atime was necessary for this syscall.
* Do the Setattr before the Clone, so that it can be
* handled well if the server replies NFSERR_DELAY to
* the Setattr operation.
*/
if ((nmp->nm_mountp->mnt_flag & MNT_NOATIME) == 0) {
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(NFSV4OP_SETATTR);
nfsm_stateidtom(nd, instateidp, NFSSTATEID_PUTSTATEID);
VATTR_NULL(&va);
va.va_atime.tv_sec = va.va_atime.tv_nsec = 0;
va.va_vaflags = VA_UTIMES_NULL;
nfscl_fillsattr(nd, &va, invp, 0, 0);
/* Bump opcnt from 7 to 8. */
*opcntp = txdr_unsigned(8);
}
/* Now Getattr the invp attributes. */
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(NFSV4OP_GETATTR);
NFSGETATTR_ATTRBIT(&attrbits);
nfsrv_putattrbit(nd, &attrbits);
/* Set outvp. */
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(NFSV4OP_PUTFH);
(void)nfsm_fhtom(nmp, nd, VTONFS(outvp)->n_fhp->nfh_fh,
VTONFS(outvp)->n_fhp->nfh_len, 0);
/* Do the Clone. */
NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
*tl = txdr_unsigned(NFSV4OP_CLONE);
nfsm_stateidtom(nd, instateidp, NFSSTATEID_PUTSTATEID);
nfsm_stateidtom(nd, outstateidp, NFSSTATEID_PUTSTATEID);
NFSM_BUILD(tl, uint32_t *, 3 * NFSX_HYPER + NFSX_UNSIGNED);
txdr_hyper(inoff, tl); tl += 2;
txdr_hyper(outoff, tl); tl += 2;
txdr_hyper(len, tl); tl += 2;
/* Get the outvp attributes. */
*tl = txdr_unsigned(NFSV4OP_GETATTR);
NFSWRITEGETATTR_ATTRBIT(&attrbits);
nfsrv_putattrbit(nd, &attrbits);
error = nfscl_request(nd, invp, p, cred);
if (error != 0)
return (error);
/* Skip over the Setattr reply. */
if ((nd->nd_flag & ND_NOMOREDATA) == 0 &&
(nmp->nm_mountp->mnt_flag & MNT_NOATIME) == 0) {
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
if (*(tl + 1) == 0) {
error = nfsrv_getattrbits(nd, &attrbits, NULL, NULL);
if (error != 0)
goto nfsmout;
} else
nd->nd_flag |= ND_NOMOREDATA;
}
if ((nd->nd_flag & ND_NOMOREDATA) == 0) {
/* Get the input file's attributes. */
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
if (*(tl + 1) == 0) {
error = nfsm_loadattr(nd, innap);
if (error != 0)
goto nfsmout;
*inattrflagp = 1;
} else
nd->nd_flag |= ND_NOMOREDATA;
}
/* Skip over return stat for PutFH. */
if ((nd->nd_flag & ND_NOMOREDATA) == 0) {
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
if (*++tl != 0)
nd->nd_flag |= ND_NOMOREDATA;
}
/* Skip over return stat for Clone. */
if ((nd->nd_flag & ND_NOMOREDATA) == 0)
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
if (nd->nd_repstat == 0) {
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
error = nfsm_loadattr(nd, outnap);
if (error == 0)
*outattrflagp = NFS_LATTR_NOSHRINK;
} else {
*lenp = 0;
}
if (error == 0)
error = nd->nd_repstat;
nfsmout:
m_freem(nd->nd_mrep);
return (error);
}
/*
* Seek operation.
*/
@ -9724,13 +9925,13 @@ nfscl_statfs(struct vnode *vp, struct ucred *cred, NFSPROC_T *td)
struct nfsstatfs sb;
struct mount *mp;
struct nfsmount *nmp;
uint32_t lease;
uint32_t clone_blksize, lease;
int attrflag, error;
mp = vp->v_mount;
nmp = VFSTONFS(mp);
error = nfsrpc_statfs(vp, &sb, &fs, &lease, cred, td, &nfsva,
&attrflag);
error = nfsrpc_statfs(vp, &sb, &fs, &lease, &clone_blksize, cred, td,
&nfsva, &attrflag);
if (attrflag != 0)
(void) nfscl_loadattrcache(&vp, &nfsva, NULL, 0, 1);
if (error == 0) {
@ -9739,7 +9940,7 @@ nfscl_statfs(struct vnode *vp, struct ucred *cred, NFSPROC_T *td)
nmp->nm_clp->nfsc_renew = NFSCL_RENEW(lease);
NFSUNLOCKCLSTATE();
mtx_lock(&nmp->nm_mtx);
nfscl_loadfsinfo(nmp, &fs);
nfscl_loadfsinfo(nmp, &fs, clone_blksize);
nfscl_loadsbinfo(nmp, &sb, &mp->mnt_stat);
mp->mnt_stat.f_iosize = newnfs_iosize(nmp);
mtx_unlock(&nmp->nm_mtx);

View file

@ -3701,7 +3701,7 @@ nfscl_docb(struct nfsrv_descript *nd, NFSPROC_T *p)
if (!error)
(void) nfsv4_fillattr(nd, NULL, NULL, NULL, &va,
NULL, 0, &rattrbits, NULL, p, 0, 0, 0, 0,
(uint64_t)0, NULL, false, false, false);
(uint64_t)0, NULL, false, false, false, 0);
break;
case NFSV4OP_CBRECALL:
NFSCL_DEBUG(4, "cbrecall\n");

View file

@ -292,8 +292,10 @@ nfs_statfs(struct mount *mp, struct statfs *sbp)
int error = 0, attrflag, gotfsinfo = 0, ret;
struct nfsnode *np;
char *fakefh;
uint32_t clone_blksize;
td = curthread;
clone_blksize = 0;
error = vfs_busy(mp, MBF_NOWAIT);
if (error)
@ -337,8 +339,8 @@ nfs_statfs(struct mount *mp, struct statfs *sbp)
} else
mtx_unlock(&nmp->nm_mtx);
if (!error)
error = nfsrpc_statfs(vp, &sb, &fs, NULL, td->td_ucred, td,
&nfsva, &attrflag);
error = nfsrpc_statfs(vp, &sb, &fs, NULL, &clone_blksize,
td->td_ucred, td, &nfsva, &attrflag);
if ((nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0 &&
error == NFSERR_WRONGSEC) {
/* Cannot get new stats, so return what is in mnt_stat. */
@ -375,7 +377,7 @@ nfs_statfs(struct mount *mp, struct statfs *sbp)
if (!error) {
mtx_lock(&nmp->nm_mtx);
if (gotfsinfo || (nmp->nm_flag & NFSMNT_NFSV4))
nfscl_loadfsinfo(nmp, &fs);
nfscl_loadfsinfo(nmp, &fs, clone_blksize);
nfscl_loadsbinfo(nmp, &sb, sbp);
sbp->f_iosize = newnfs_iosize(nmp);
mtx_unlock(&nmp->nm_mtx);
@ -408,7 +410,7 @@ ncl_fsinfo(struct nfsmount *nmp, struct vnode *vp, struct ucred *cred,
if (attrflag)
(void) nfscl_loadattrcache(&vp, &nfsva, NULL, 0, 1);
mtx_lock(&nmp->nm_mtx);
nfscl_loadfsinfo(nmp, &fs);
nfscl_loadfsinfo(nmp, &fs, 0);
mtx_unlock(&nmp->nm_mtx);
}
return (error);

View file

@ -4027,31 +4027,51 @@ nfs_copy_file_range(struct vop_copy_file_range_args *ap)
struct vattr va, *vap;
struct uio io;
struct nfsmount *nmp;
struct nfsnode *np;
size_t len, len2;
ssize_t r;
int error, inattrflag, outattrflag, ret, ret2, invp_lock;
off_t inoff, outoff;
bool consecutive, must_commit, tryoutcred;
bool consecutive, must_commit, onevp, toeof, tryclone, tryoutcred;
bool mustclone;
/*
* NFSv4.2 Copy is not permitted for infile == outfile.
* The NFSv4.2 Clone operation does work on non-overlapping
* byte ranges in the same file, but only if offsets
* (and len if not to EOF) are aligned properly.
* TODO: copy_file_range() between multiple NFS mountpoints
* --> This is not possible now, since each mount appears to
* the NFSv4.n server as a separate client.
*/
if (invp == outvp || invp->v_mount != outvp->v_mount) {
if ((invp == outvp && (ap->a_flags & COPY_FILE_RANGE_CLONE) == 0) ||
(invp != outvp && invp->v_mount != outvp->v_mount)) {
generic_copy:
return (ENOSYS);
}
invp_lock = LK_SHARED;
if (invp == outvp) {
onevp = true;
invp_lock = LK_EXCLUSIVE;
} else {
onevp = false;
invp_lock = LK_SHARED;
}
mustclone = false;
if (onevp || (ap->a_flags & COPY_FILE_RANGE_CLONE) != 0)
mustclone = true;
relock:
inoff = *ap->a_inoffp;
outoff = *ap->a_outoffp;
/* Lock both vnodes, avoiding risk of deadlock. */
/* Lock vnode(s), avoiding risk of deadlock. */
do {
mp = NULL;
error = vn_start_write(outvp, &mp, V_WAIT);
if (error == 0) {
error = vn_lock(outvp, LK_EXCLUSIVE);
if (error == 0) {
if (onevp)
break;
error = vn_lock(invp, invp_lock | LK_NOWAIT);
if (error == 0)
break;
@ -4071,16 +4091,24 @@ relock:
return (error);
/*
* More reasons to avoid nfs copy: not NFSv4.2, or explicitly
* disabled.
* More reasons to avoid nfs copy/clone: not NFSv4.2, explicitly
* disabled or requires cloning and unable to clone.
* Only clone if the clone_blksize attribute is supported
* and the clone_blksize is greater than 0.
* Alignment of offsets and length will be checked later.
*/
nmp = VFSTONFS(invp->v_mount);
np = VTONFS(invp);
mtx_lock(&nmp->nm_mtx);
if ((nmp->nm_privflag & NFSMNTP_NOCOPY) != 0)
mustclone = true;
if (!NFSHASNFSV4(nmp) || nmp->nm_minorvers < NFSV42_MINORVERSION ||
(nmp->nm_privflag & NFSMNTP_NOCOPY) != 0) {
(mustclone && (!NFSISSET_ATTRBIT(&np->n_vattr.na_suppattr,
NFSATTRBIT_CLONEBLKSIZE) || nmp->nm_cloneblksize == 0))) {
mtx_unlock(&nmp->nm_mtx);
VOP_UNLOCK(invp);
VOP_UNLOCK(outvp);
if (!onevp)
VOP_UNLOCK(outvp); /* For onevp, same as invp. */
if (mp != NULL)
vn_finished_write(mp);
goto generic_copy;
@ -4111,6 +4139,8 @@ relock:
invp_obj = invp->v_object;
if (invp_obj != NULL && vm_object_mightbedirty(invp_obj)) {
if (invp_lock != LK_EXCLUSIVE) {
KASSERT(!onevp, ("nfs_copy_file_range: "
"invp_lock LK_SHARED for onevp"));
invp_lock = LK_EXCLUSIVE;
VOP_UNLOCK(invp);
VOP_UNLOCK(outvp);
@ -4134,10 +4164,10 @@ relock:
else
consecutive = false;
mtx_unlock(&nmp->nm_mtx);
inoff = *ap->a_inoffp;
outoff = *ap->a_outoffp;
tryoutcred = true;
must_commit = false;
toeof = false;
if (error == 0) {
vap = &VTONFS(invp)->n_vattr.na_vattr;
error = VOP_GETATTR(invp, vap, ap->a_incred);
@ -4169,29 +4199,63 @@ relock:
if (error == 0 && ret != 0)
error = ret;
}
} else if (inoff + len > vap->va_size)
} else if (inoff + len >= vap->va_size) {
toeof = true;
*ap->a_lenp = len = vap->va_size - inoff;
}
} else
error = 0;
}
/*
* For cloning, the offsets must be clone blksize aligned and
* the len must be blksize aligned unless it goes to EOF on
* the input file.
*/
tryclone = false;
if (len > 0) {
if (error == 0 && NFSISSET_ATTRBIT(&np->n_vattr.na_suppattr,
NFSATTRBIT_CLONEBLKSIZE) && nmp->nm_cloneblksize != 0 &&
(inoff % nmp->nm_cloneblksize) == 0 &&
(outoff % nmp->nm_cloneblksize) == 0 &&
(toeof || (len % nmp->nm_cloneblksize) == 0))
tryclone = true;
else if (mustclone)
error = ENOSYS;
}
/*
* len will be set to 0 upon a successful Copy RPC.
* As such, this only loops when the Copy RPC needs to be retried.
* As such, this only loops when the Copy/Clone RPC needs to be retried.
*/
while (len > 0 && error == 0) {
inattrflag = outattrflag = 0;
len2 = len;
if (tryoutcred)
error = nfsrpc_copy_file_range(invp, ap->a_inoffp,
outvp, ap->a_outoffp, &len2, ap->a_flags,
&inattrflag, &innfsva, &outattrflag, &outnfsva,
ap->a_outcred, consecutive, &must_commit);
else
error = nfsrpc_copy_file_range(invp, ap->a_inoffp,
outvp, ap->a_outoffp, &len2, ap->a_flags,
&inattrflag, &innfsva, &outattrflag, &outnfsva,
ap->a_incred, consecutive, &must_commit);
if (tryclone) {
if (tryoutcred)
error = nfsrpc_clone(invp, ap->a_inoffp, outvp,
ap->a_outoffp, &len2, toeof, &inattrflag,
&innfsva, &outattrflag, &outnfsva,
ap->a_outcred);
else
error = nfsrpc_clone(invp, ap->a_inoffp, outvp,
ap->a_outoffp, &len2, toeof, &inattrflag,
&innfsva, &outattrflag, &outnfsva,
ap->a_incred);
} else {
if (tryoutcred)
error = nfsrpc_copy_file_range(invp,
ap->a_inoffp, outvp, ap->a_outoffp, &len2,
ap->a_flags, &inattrflag, &innfsva,
&outattrflag, &outnfsva,
ap->a_outcred, consecutive, &must_commit);
else
error = nfsrpc_copy_file_range(invp,
ap->a_inoffp, outvp, ap->a_outoffp, &len2,
ap->a_flags, &inattrflag, &innfsva,
&outattrflag, &outnfsva,
ap->a_incred, consecutive, &must_commit);
}
if (inattrflag != 0)
ret = nfscl_loadattrcache(&invp, &innfsva, NULL, 0, 1);
if (outattrflag != 0)
@ -4230,6 +4294,13 @@ relock:
/* Try again with incred. */
tryoutcred = false;
error = 0;
} else if (tryclone && error != 0) {
if (mustclone) {
error = ENOSYS;
} else {
tryclone = false;
error = 0;
}
}
if (error == NFSERR_STALEWRITEVERF) {
/*
@ -4243,11 +4314,12 @@ relock:
}
}
VOP_UNLOCK(invp);
VOP_UNLOCK(outvp);
if (!onevp)
VOP_UNLOCK(outvp); /* For onevp, same as invp. */
if (mp != NULL)
vn_finished_write(mp);
if (error == NFSERR_NOTSUPP || error == NFSERR_OFFLOADNOREQS ||
error == NFSERR_ACCES) {
error == NFSERR_ACCES || error == ENOSYS) {
/*
* Unlike the NFSv4.2 Copy, vn_generic_copy_file_range() can
* use a_incred for the read and a_outcred for the write, so
@ -4255,7 +4327,7 @@ relock:
* For NFSERR_NOTSUPP and NFSERR_OFFLOADNOREQS, the Copy can
* never succeed, so disable it.
*/
if (error != NFSERR_ACCES) {
if (error != NFSERR_ACCES && error != ENOSYS) {
/* Can never do Copy on this mount. */
mtx_lock(&nmp->nm_mtx);
nmp->nm_privflag |= NFSMNTP_NOCOPY;
@ -4596,6 +4668,7 @@ nfs_pathconf(struct vop_pathconf_args *ap)
struct nfsmount *nmp;
struct thread *td = curthread;
off_t off;
uint32_t clone_blksize;
bool eof, has_namedattr, named_enabled;
int attrflag, error;
struct nfsnode *np;
@ -4604,19 +4677,22 @@ nfs_pathconf(struct vop_pathconf_args *ap)
np = VTONFS(vp);
named_enabled = false;
has_namedattr = false;
clone_blksize = 0;
if ((NFS_ISV34(vp) && (ap->a_name == _PC_LINK_MAX ||
ap->a_name == _PC_NAME_MAX || ap->a_name == _PC_CHOWN_RESTRICTED ||
ap->a_name == _PC_NO_TRUNC)) ||
(NFS_ISV4(vp) && (ap->a_name == _PC_ACL_NFS4 ||
ap->a_name == _PC_HAS_NAMEDATTR))) {
ap->a_name == _PC_HAS_NAMEDATTR ||
ap->a_name == _PC_CLONE_BLKSIZE))) {
/*
* Since only the above 4 a_names are returned by the NFSv3
* Pathconf RPC, there is no point in doing it for others.
* For NFSv4, the Pathconf RPC (actually a Getattr Op.) can
* be used for _PC_ACL_NFS4 and _PC_HAS_NAMEDATTR as well.
* be used for _PC_ACL_NFS4, _PC_HAS_NAMEDATTR and
* _PC_CLONE_BLKSIZE as well.
*/
error = nfsrpc_pathconf(vp, &pc, &has_namedattr, td->td_ucred,
td, &nfsva, &attrflag);
error = nfsrpc_pathconf(vp, &pc, &has_namedattr, &clone_blksize,
td->td_ucred, td, &nfsva, &attrflag);
if (attrflag != 0)
(void) nfscl_loadattrcache(&vp, &nfsva, NULL, 0, 1);
if (error != 0)
@ -4771,6 +4847,9 @@ nfs_pathconf(struct vop_pathconf_args *ap)
else
*ap->a_retval = 0;
break;
case _PC_CLONE_BLKSIZE:
*ap->a_retval = clone_blksize;
break;
default:
error = vop_stdpathconf(ap);

View file

@ -87,6 +87,7 @@ struct nfsmount {
/* unclipped, wraps to 0 */
struct __rpc_client *nm_aconn[NFS_MAXNCONN - 1]; /* Additional nconn */
/* Locked via nm_sockreq.nr_mtx */
uint32_t nm_cloneblksize; /* Block cloning alignment */
u_int16_t nm_krbnamelen; /* Krb5 host principal, if any */
u_int16_t nm_dirpathlen; /* and mount dirpath, for V4 */
u_int16_t nm_srvkrbnamelen; /* and the server's target name */

View file

@ -2113,7 +2113,8 @@ nfsvno_fillattr(struct nfsrv_descript *nd, struct mount *mp, struct vnode *vp,
struct nfsvattr *nvap, fhandle_t *fhp, int rderror, nfsattrbit_t *attrbitp,
struct ucred *cred, struct thread *p, int isdgram, int reterr,
int supports_nfsv4acls, int at_root, uint64_t mounted_on_fileno,
bool xattrsupp, bool has_hiddensystem, bool has_namedattr)
bool xattrsupp, bool has_hiddensystem, bool has_namedattr,
uint32_t clone_blksize)
{
struct statfs *sf;
int error;
@ -2130,9 +2131,11 @@ nfsvno_fillattr(struct nfsrv_descript *nd, struct mount *mp, struct vnode *vp,
sf = NULL;
}
}
error = nfsv4_fillattr(nd, mp, vp, NULL, &nvap->na_vattr, fhp, rderror,
attrbitp, cred, p, isdgram, reterr, supports_nfsv4acls, at_root,
mounted_on_fileno, sf, xattrsupp, has_hiddensystem, has_namedattr);
mounted_on_fileno, sf, xattrsupp, has_hiddensystem, has_namedattr,
clone_blksize);
free(sf, M_TEMP);
NFSEXITCODE2(0, nd);
return (error);
@ -2441,7 +2444,7 @@ nfsrvd_readdirplus(struct nfsrv_descript *nd, int isdgram,
struct vnode *vp, struct nfsexstuff *exp)
{
struct dirent *dp;
u_int32_t *tl;
uint32_t clone_blksize, *tl;
int dirlen;
char *cpos, *cend, *rbuf;
struct vnode *nvp;
@ -2943,6 +2946,7 @@ again:
xattrsupp = false;
has_hiddensystem = false;
has_namedattr = false;
clone_blksize = 0;
if (nvp != NULL) {
supports_nfsv4acls =
nfs_supportsnfsv4acls(nvp);
@ -2966,6 +2970,11 @@ again:
&pathval) != 0)
pathval = 0;
has_namedattr = pathval > 0;
pathval = 0;
if (VOP_PATHCONF(nvp, _PC_CLONE_BLKSIZE,
&pathval) != 0)
pathval = 0;
clone_blksize = pathval;
NFSVOPUNLOCK(nvp);
} else
supports_nfsv4acls = 0;
@ -2986,14 +2995,16 @@ again:
nd->nd_cred, p, isdgram, 0,
supports_nfsv4acls, at_root,
mounted_on_fileno, xattrsupp,
has_hiddensystem, has_namedattr);
has_hiddensystem, has_namedattr,
clone_blksize);
} else {
dirlen += nfsvno_fillattr(nd, new_mp,
nvp, nvap, &nfh, r, &attrbits,
nd->nd_cred, p, isdgram, 0,
supports_nfsv4acls, at_root,
mounted_on_fileno, xattrsupp,
has_hiddensystem, has_namedattr);
has_hiddensystem, has_namedattr,
clone_blksize);
}
if (nvp != NULL)
vrele(nvp);
@ -5690,7 +5701,8 @@ nfsrv_writedsdorpc(struct nfsmount *nmp, fhandle_t *fhp, off_t off, int len,
if ((nd->nd_flag & (ND_NOMOREDATA | ND_NFSV4 | ND_V4WCCATTR)) ==
(ND_NFSV4 | ND_V4WCCATTR)) {
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL, NULL,
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL,
NULL);
NFSD_DEBUG(4, "nfsrv_writedsdorpc: wcc attr=%d\n", error);
if (error != 0)
goto nfsmout;
@ -5721,7 +5733,8 @@ nfsrv_writedsdorpc(struct nfsmount *nmp, fhandle_t *fhp, off_t off, int len,
if (error == 0) {
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL, NULL,
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL,
NULL);
}
NFSD_DEBUG(4, "nfsrv_writedsdorpc: aft loadattr=%d\n", error);
nfsmout:
@ -5887,7 +5900,8 @@ nfsrv_allocatedsdorpc(struct nfsmount *nmp, fhandle_t *fhp, off_t off,
if (nd->nd_repstat == 0) {
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL, NULL,
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL,
NULL);
} else
error = nd->nd_repstat;
NFSD_DEBUG(4, "nfsrv_allocatedsdorpc: aft loadattr=%d\n", error);
@ -6054,7 +6068,8 @@ nfsrv_deallocatedsdorpc(struct nfsmount *nmp, fhandle_t *fhp, off_t off,
if ((nd->nd_flag & (ND_NOMOREDATA | ND_NFSV4 | ND_V4WCCATTR)) ==
(ND_NFSV4 | ND_V4WCCATTR)) {
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL, NULL,
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL,
NULL);
NFSD_DEBUG(4, "nfsrv_deallocatedsdorpc: wcc attr=%d\n", error);
if (error != 0)
goto nfsmout;
@ -6068,7 +6083,8 @@ nfsrv_deallocatedsdorpc(struct nfsmount *nmp, fhandle_t *fhp, off_t off,
if (nd->nd_repstat == 0) {
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL, NULL,
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL,
NULL);
} else
error = nd->nd_repstat;
NFSD_DEBUG(4, "nfsrv_deallocatedsdorpc: aft loadattr=%d\n", error);
@ -6216,7 +6232,8 @@ nfsrv_setattrdsdorpc(fhandle_t *fhp, struct ucred *cred, NFSPROC_T *p,
if ((nd->nd_flag & (ND_NOMOREDATA | ND_NFSV4 | ND_V4WCCATTR)) ==
(ND_NFSV4 | ND_V4WCCATTR)) {
error = nfsv4_loadattr(nd, NULL, dsnap, NULL, NULL, 0, NULL,
NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL,
NULL, NULL);
NFSD_DEBUG(4, "nfsrv_setattrdsdorpc: wcc attr=%d\n", error);
if (error != 0)
goto nfsmout;
@ -6241,7 +6258,7 @@ nfsrv_setattrdsdorpc(fhandle_t *fhp, struct ucred *cred, NFSPROC_T *p,
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
error = nfsv4_loadattr(nd, NULL, dsnap, NULL, NULL, 0, NULL,
NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL,
NULL);
NULL, NULL);
}
NFSD_DEBUG(4, "nfsrv_setattrdsdorpc: aft setattr loadattr=%d\n", error);
nfsmout:
@ -6386,7 +6403,7 @@ nfsrv_setacldsdorpc(fhandle_t *fhp, struct ucred *cred, NFSPROC_T *p,
* the same type (VREG).
*/
nfsv4_fillattr(nd, NULL, vp, aclp, NULL, NULL, 0, &attrbits, NULL,
NULL, 0, 0, 0, 0, 0, NULL, false, false, false);
NULL, 0, 0, 0, 0, 0, NULL, false, false, false, 0);
error = newnfs_request(nd, nmp, NULL, &nmp->nm_sockreq, NULL, p, cred,
NFS_PROG, NFS_VER4, NULL, 1, NULL, NULL);
if (error != 0) {
@ -6530,7 +6547,7 @@ nfsrv_getattrdsrpc(fhandle_t *fhp, struct ucred *cred, NFSPROC_T *p,
if (nd->nd_repstat == 0) {
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0,
NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL,
NULL, NULL, NULL);
NULL, NULL, NULL, NULL);
/*
* We can only save the updated values in the extended
* attribute if the vp is exclusively locked.

View file

@ -253,6 +253,7 @@ nfsrvd_getattr(struct nfsrv_descript *nd, int isdgram,
size_t atsiz;
long pathval;
bool has_hiddensystem, has_namedattr, xattrsupp;
uint32_t clone_blksize;
if (nd->nd_repstat)
goto out;
@ -330,6 +331,11 @@ nfsrvd_getattr(struct nfsrv_descript *nd, int isdgram,
&pathval) != 0)
pathval = 0;
has_namedattr = pathval > 0;
pathval = 0;
if (VOP_PATHCONF(vp, _PC_CLONE_BLKSIZE,
&pathval) != 0)
pathval = 0;
clone_blksize = pathval;
mp = vp->v_mount;
if (nfsrv_enable_crossmntpt != 0 &&
vp->v_type == VDIR &&
@ -365,7 +371,7 @@ nfsrvd_getattr(struct nfsrv_descript *nd, int isdgram,
isdgram, 1, supports_nfsv4acls,
at_root, mounted_on_fileno,
xattrsupp, has_hiddensystem,
has_namedattr);
has_namedattr, clone_blksize);
vfs_unbusy(mp);
}
vrele(vp);
@ -4347,7 +4353,7 @@ nfsrvd_verify(struct nfsrv_descript *nd, int isdgram,
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, NULL, p,
sf, NULL, &fs, NULL, 1, &ret, NULL, NULL, NULL, NULL, p,
nd->nd_cred);
if (!error) {
if (nd->nd_procnum == NFSV4OP_NVERIFY) {
@ -6010,6 +6016,212 @@ nfsmout:
return (error);
}
/*
* nfs clone service
*/
int
nfsrvd_clone(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 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;
long pathval;
rl_rcookie = rl_wcookie = NULL;
pathval = 0;
if (nfsrv_maxcopyrange == 0 || nfsrv_devidcnt > 0 ||
VOP_PATHCONF(vp, _PC_CLONE_BLKSIZE, &pathval) != 0 ||
pathval == 0) {
/*
* For a pNFS server, reply NFSERR_NOTSUPP so that the client
* will not do the clone and will do I/O on the DS(s).
* If vfs.nfsd.maxcopyrange set to 0, disable Clone.
*/
nd->nd_repstat = NFSERR_NOTSUPP;
goto nfsmout;
}
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_STATEID + 3 * NFSX_HYPER);
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);
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;
}
if ((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 && vp->v_type != 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);
if (vp != tovp) {
NFSVOPUNLOCK(vp);
if (nd->nd_repstat != 0)
goto out;
error = NFSVOPLOCK(tovp, LK_SHARED);
if (error != 0)
goto out;
pathval = 0;
if (VOP_PATHCONF(tovp, _PC_CLONE_BLKSIZE, &pathval) != 0 ||
pathval == 0)
nd->nd_repstat = NFSERR_NOTSUPP;
else if (tovp->v_type != 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);
else
rl_wcookie = vn_rangelock_wlock(tovp, outoff,
outoff + len);
if (vp != tovp) {
if (len == 0)
rl_rcookie = vn_rangelock_tryrlock(vp,
inoff, OFF_MAX);
else
rl_rcookie = vn_rangelock_tryrlock(vp,
inoff, inoff + len);
if (rl_rcookie != NULL)
break;
} else {
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)
len = SSIZE_MAX; /* To EOF. */
else if (inoff + len > at.na_size)
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.nfsd.maxcopyrange.
* This size limit can be set to limit the time a copy RPC will
* take.
*/
xfer = len;
if (nd->nd_repstat == 0) {
nd->nd_repstat = vn_copy_file_range(vp, &inoff, tovp, &outoff,
&xfer, COPY_FILE_RANGE_CLONE, nd->nd_cred, nd->nd_cred,
NULL);
if (nd->nd_repstat == ENOSYS)
nd->nd_repstat = NFSERR_INVAL;
}
/* Unlock the ranges. */
if (rl_rcookie != NULL)
vn_rangelock_unlock(vp, rl_rcookie);
if (rl_wcookie != NULL)
vn_rangelock_unlock(tovp, rl_wcookie);
out:
vrele(vp);
vrele(tovp);
NFSEXITCODE2(error, nd);
return (error);
nfsmout:
vput(vp);
vrele(tovp);
NFSEXITCODE2(error, nd);
return (error);
}
/*
* nfs seek service
*/

View file

@ -371,7 +371,7 @@ int (*nfsrv4_ops2[NFSV42_NOPS])(struct nfsrv_descript *,
(int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , struct nfsexstuff *, struct nfsexstuff *))0,
(int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , struct nfsexstuff *, struct nfsexstuff *))0,
(int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , struct nfsexstuff *, struct nfsexstuff *))0,
(int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , struct nfsexstuff *, struct nfsexstuff *))0,
nfsrvd_clone,
(int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , struct nfsexstuff *, struct nfsexstuff *))0,
(int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , struct nfsexstuff *, struct nfsexstuff *))0,
(int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , struct nfsexstuff *, struct nfsexstuff *))0,

View file

@ -4675,7 +4675,7 @@ errout:
} else if (error == 0 && procnum == NFSV4OP_CBGETATTR)
error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0,
NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL,
NULL, p, NULL);
NULL, NULL, p, NULL);
m_freem(nd->nd_mrep);
}
NFSLOCKSTATE();