Commit 638e3e7d authored by Jeff Layton's avatar Jeff Layton

nfsd: use the getattr operation to fetch i_version

Now that we can call into vfs_getattr to get the i_version field, use
that facility to fetch it instead of doing it in nfsd4_change_attribute.

Neil also pointed out recently that IS_I_VERSION directory operations
are always logged, and so we only need to mitigate the rollback problem
on regular files. Also, we don't need to factor in the ctime when
reexporting NFS or Ceph.

Set the STATX_CHANGE_COOKIE (and BTIME) bits in the request when we're
dealing with a v4 request. Then, instead of looking at IS_I_VERSION when
generating the change attr, look at the result mask and only use it if
STATX_CHANGE_COOKIE is set.

Change nfsd4_change_attribute to only factor in the ctime if it's a
regular file and the fs doesn't advertise STATX_ATTR_CHANGE_MONOTONIC.
Acked-by: default avatarChuck Lever <chuck.lever@oracle.com>
Reviewed-by: default avatarNeilBrown <neilb@suse.de>
Signed-off-by: default avatarJeff Layton <jlayton@kernel.org>
parent 3139b1d7
...@@ -2965,7 +2965,9 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp, ...@@ -2965,7 +2965,9 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
goto out; goto out;
} }
err = vfs_getattr(&path, &stat, STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT); err = vfs_getattr(&path, &stat,
STATX_BASIC_STATS | STATX_BTIME | STATX_CHANGE_COOKIE,
AT_STATX_SYNC_AS_STAT);
if (err) if (err)
goto out_nfserr; goto out_nfserr;
if (!(stat.result_mask & STATX_BTIME)) if (!(stat.result_mask & STATX_BTIME))
......
...@@ -628,6 +628,10 @@ void fh_fill_pre_attrs(struct svc_fh *fhp) ...@@ -628,6 +628,10 @@ void fh_fill_pre_attrs(struct svc_fh *fhp)
stat.mtime = inode->i_mtime; stat.mtime = inode->i_mtime;
stat.ctime = inode->i_ctime; stat.ctime = inode->i_ctime;
stat.size = inode->i_size; stat.size = inode->i_size;
if (v4 && IS_I_VERSION(inode)) {
stat.change_cookie = inode_query_iversion(inode);
stat.result_mask |= STATX_CHANGE_COOKIE;
}
} }
if (v4) if (v4)
fhp->fh_pre_change = nfsd4_change_attribute(&stat, inode); fhp->fh_pre_change = nfsd4_change_attribute(&stat, inode);
...@@ -659,6 +663,10 @@ void fh_fill_post_attrs(struct svc_fh *fhp) ...@@ -659,6 +663,10 @@ void fh_fill_post_attrs(struct svc_fh *fhp)
if (err) { if (err) {
fhp->fh_post_saved = false; fhp->fh_post_saved = false;
fhp->fh_post_attr.ctime = inode->i_ctime; fhp->fh_post_attr.ctime = inode->i_ctime;
if (v4 && IS_I_VERSION(inode)) {
fhp->fh_post_attr.change_cookie = inode_query_iversion(inode);
fhp->fh_post_attr.result_mask |= STATX_CHANGE_COOKIE;
}
} else } else
fhp->fh_post_saved = true; fhp->fh_post_saved = true;
if (v4) if (v4)
...@@ -750,28 +758,38 @@ enum fsid_source fsid_source(const struct svc_fh *fhp) ...@@ -750,28 +758,38 @@ enum fsid_source fsid_source(const struct svc_fh *fhp)
} }
/* /*
* We could use i_version alone as the change attribute. However, * We could use i_version alone as the change attribute. However, i_version
* i_version can go backwards after a reboot. On its own that doesn't * can go backwards on a regular file after an unclean shutdown. On its own
* necessarily cause a problem, but if i_version goes backwards and then * that doesn't necessarily cause a problem, but if i_version goes backwards
* is incremented again it could reuse a value that was previously used * and then is incremented again it could reuse a value that was previously
* before boot, and a client who queried the two values might * used before boot, and a client who queried the two values might incorrectly
* incorrectly assume nothing changed. * assume nothing changed.
*
* By using both ctime and the i_version counter we guarantee that as long as
* time doesn't go backwards we never reuse an old value. If the filesystem
* advertises STATX_ATTR_CHANGE_MONOTONIC, then this mitigation is not
* needed.
* *
* By using both ctime and the i_version counter we guarantee that as * We only need to do this for regular files as well. For directories, we
* long as time doesn't go backwards we never reuse an old value. * assume that the new change attr is always logged to stable storage in some
* fashion before the results can be seen.
*/ */
u64 nfsd4_change_attribute(struct kstat *stat, struct inode *inode) u64 nfsd4_change_attribute(struct kstat *stat, struct inode *inode)
{ {
u64 chattr;
if (inode->i_sb->s_export_op->fetch_iversion) if (inode->i_sb->s_export_op->fetch_iversion)
return inode->i_sb->s_export_op->fetch_iversion(inode); return inode->i_sb->s_export_op->fetch_iversion(inode);
else if (IS_I_VERSION(inode)) { if (stat->result_mask & STATX_CHANGE_COOKIE) {
u64 chattr; chattr = stat->change_cookie;
chattr = stat->ctime.tv_sec; if (S_ISREG(inode->i_mode) &&
chattr <<= 30; !(stat->attributes & STATX_ATTR_CHANGE_MONOTONIC)) {
chattr += stat->ctime.tv_nsec; chattr += (u64)stat->ctime.tv_sec << 30;
chattr += inode_query_iversion(inode); chattr += stat->ctime.tv_nsec;
return chattr; }
} else } else {
return time_to_chattr(&stat->ctime); chattr = time_to_chattr(&stat->ctime);
}
return chattr;
} }
...@@ -170,9 +170,14 @@ static inline void fh_drop_write(struct svc_fh *fh) ...@@ -170,9 +170,14 @@ static inline void fh_drop_write(struct svc_fh *fh)
static inline __be32 fh_getattr(const struct svc_fh *fh, struct kstat *stat) static inline __be32 fh_getattr(const struct svc_fh *fh, struct kstat *stat)
{ {
u32 request_mask = STATX_BASIC_STATS;
struct path p = {.mnt = fh->fh_export->ex_path.mnt, struct path p = {.mnt = fh->fh_export->ex_path.mnt,
.dentry = fh->fh_dentry}; .dentry = fh->fh_dentry};
return nfserrno(vfs_getattr(&p, stat, STATX_BASIC_STATS,
if (fh->fh_maxsize == NFS4_FHSIZE)
request_mask |= (STATX_BTIME | STATX_CHANGE_COOKIE);
return nfserrno(vfs_getattr(&p, stat, request_mask,
AT_STATX_SYNC_AS_STAT)); AT_STATX_SYNC_AS_STAT));
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment