diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index da09faba1..6ec154504 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -2581,8 +2581,19 @@ top: if (fuid_dirtied) zfs_fuid_sync(zfsvfs, tx); - if (mask != 0) + if (mask != 0) { zfs_log_setattr(zilog, tx, TX_SETATTR, zp, vap, mask, fuidp); + /* + * Ensure that the z_seq is always incremented on setattr + * operation. This is required for change accounting for + * NFS clients. + * + * ATTR_MODE already increments via zfs_acl_chmod_setattr. + * ATTR_SIZE already increments via zfs_freesp. + */ + if (!(mask & (ATTR_MODE | ATTR_SIZE))) + zp->z_seq++; + } mutex_exit(&zp->z_lock); if (mask & (ATTR_UID|ATTR_GID|ATTR_MODE)) diff --git a/module/os/linux/zfs/zpl_inode.c b/module/os/linux/zfs/zpl_inode.c index f97662d05..e4e15c824 100644 --- a/module/os/linux/zfs/zpl_inode.c +++ b/module/os/linux/zfs/zpl_inode.c @@ -506,6 +506,32 @@ zpl_getattr_impl(const struct path *path, struct kstat *stat, u32 request_mask, } #endif +#ifdef STATX_CHANGE_COOKIE + if (request_mask & STATX_CHANGE_COOKIE) { + /* + * knfsd uses the STATX_CHANGE_COOKIE to surface to clients + * change_info4 data, which is used to implement NFS client + * name caching (see RFC 8881 Section 10.8). This number + * should always increase with changes and should not be + * reused. We cannot simply present ctime here because + * ZFS uses a coarse timer to set them, which may cause + * clients to fail to detect changes and invalidate cache. + * + * ZFS always increments znode z_seq number, but this is + * uint_t and so we mask in ctime to upper bits. + * + * STATX_ATTR_CHANGE_MONOTONIC is advertised + * to prevent knfsd from generating the change cookie + * based on ctime. C.f. nfsd4_change_attribute in + * fs/nfsd/nfsfh.c. + */ + stat->change_cookie = + ((u64)stat->ctime.tv_sec << 32) | zp->z_seq; + stat->attributes |= STATX_ATTR_CHANGE_MONOTONIC; + stat->result_mask |= STATX_CHANGE_COOKIE; + } +#endif + #ifdef STATX_DIOALIGN if (request_mask & STATX_DIOALIGN) { uint64_t align;