NFS Clent Updates for Linux 6.16

New Features:
   * Implement the Sunrpc rfc2203 rpcsec_gss sequence number cache
   * Add support for FALLOC_FL_ZERO_RANGE on NFS v4.2
   * Add a localio sysfs attribute
 
 Stable Fixes:
   * Fix double-unlock bug in nfs_return_empty_folio()
   * Don't check for OPEN feature support in v4.1
   * Always probe for LOCALIO support asynchronously
   * Prevent hang on NFS mounts with xprtsec=[m]tls
 
 Other Bugfixes:
   * xattr handlers should check for absent nfs filehandles
   * Fix setattr caching of TIME_[MODIFY|ACCESS]_SET when timestamps are delegated
   * Fix listxattr to return selinux security labels
   * Connect to NFSv3 DS using TLS if MDS connection uses TLS
   * Clear SB_RDONLY before getting a superblock, and ignore when remounting
   * Fix incorrect handling of NFS error codes in nfs4_do_mkdir()
   * Various nfs_localio fixes from Neil Brown that include fixing an
       rcu compilation error found by older gcc versions.
   * Update stats on flexfiles pNFS DSes when receiving NFS4ERR_DELAY
 
 Cleanups:
   * Add a refcount tracker for struct net in the nfs_client
   * Allow FREE_STATEID to clean up delegations
   * Always set NLINK even if the server doesn't support it
   * Cleanups to the NFS folio writeback code
   * Remove dead code from xs_tcp_tls_setup_socket()
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEnZ5MQTpR7cLU7KEp18tUv7ClQOsFAmg/YkAACgkQ18tUv7Cl
 QOuGpQ/+OuG/xkVX6j7FerUcdbVhcZ+5jDUKC0cNe6EeFeFRjgqsdFB0uqH+AgJh
 DlxEJuXTMq+9mcptl0rjrOn0tj7dlTpgZowp3kWdK3bX1zSI2jBEJjnz3xVzjBQx
 3lbmF/UAIaHv5bPVc9aF8mioaj5DSRKWTBLTg7iOM1ol1DqgHK/M0q2D7d2n1yB4
 WYGI7LlAWSBGV4PvEkhHW6PwVPDSqECPBvIxd1obq8TSNl+YZlmVxCoJ99+zVqWf
 dvaDOwfs5x+YEQH/+N/XWdc38QiCGfu7H79qGHShWB8t/KT4axxmjVs2fT7xtUsv
 yN3fb77rlFOCJaPLRF549/4EJqHYMWmFDKIMUZ7YC1vEBCG4B1kQUqarA5eCbsAi
 s/rxBs1VNKeev/RecDaViAeH3XZoVU1rNyIBJjOuWgNlC5wnbF+An3zE0m8MAXxO
 Vh7wQSH3GZEY+VCR6ljwLhIv6+tvSVQxEZKUUjfVQXp5UuNwN3wKa+sW6li+FBl6
 uV6lJcmdUffrurNhvSghIiSQGDkerHUVhSltgtj5FnmRp/AM95Z850t5a7qqc7Cv
 duks9siLLaeC4K5W+AOcKLWXho1dJMIPWUej3ErCiHWnA20QiNXsQN4QoimkDKqf
 9SYdcl6UECqV5MzIa/L7cW96S3K0acrq+8ofJCjN3A8M0pcTGgU=
 =5DFQ
 -----END PGP SIGNATURE-----

Merge tag 'nfs-for-6.16-1' of git://git.linux-nfs.org/projects/anna/linux-nfs

Pull NFS clent updates from Anna Schumaker:
 "New Features:

   - Implement the Sunrpc rfc2203 rpcsec_gss sequence number cache

   - Add support for FALLOC_FL_ZERO_RANGE on NFS v4.2

   - Add a localio sysfs attribute

  Stable Fixes:

   - Fix double-unlock bug in nfs_return_empty_folio()

   - Don't check for OPEN feature support in v4.1

   - Always probe for LOCALIO support asynchronously

   - Prevent hang on NFS mounts with xprtsec=[m]tls

  Other Bugfixes:

   - xattr handlers should check for absent nfs filehandles

   - Fix setattr caching of TIME_[MODIFY|ACCESS]_SET when timestamps are
     delegated

   - Fix listxattr to return selinux security labels

   - Connect to NFSv3 DS using TLS if MDS connection uses TLS

   - Clear SB_RDONLY before getting a superblock, and ignore when
     remounting

   - Fix incorrect handling of NFS error codes in nfs4_do_mkdir()

   - Various nfs_localio fixes from Neil Brown that include fixing an
     rcu compilation error found by older gcc versions.

   - Update stats on flexfiles pNFS DSes when receiving NFS4ERR_DELAY

  Cleanups:

   - Add a refcount tracker for struct net in the nfs_client

   - Allow FREE_STATEID to clean up delegations

   - Always set NLINK even if the server doesn't support it

   - Cleanups to the NFS folio writeback code

   - Remove dead code from xs_tcp_tls_setup_socket()"

* tag 'nfs-for-6.16-1' of git://git.linux-nfs.org/projects/anna/linux-nfs: (30 commits)
  flexfiles/pNFS: update stats on NFS4ERR_DELAY for v4.1 DSes
  nfs_localio: change nfsd_file_put_local() to take a pointer to __rcu pointer
  nfs_localio: protect race between nfs_uuid_put() and nfs_close_local_fh()
  nfs_localio: duplicate nfs_close_local_fh()
  nfs_localio: simplify interface to nfsd for getting nfsd_file
  nfs_localio: always hold nfsd net ref with nfsd_file ref
  nfs_localio: use cmpxchg() to install new nfs_file_localio
  SUNRPC: Remove dead code from xs_tcp_tls_setup_socket()
  SUNRPC: Prevent hang on NFS mount with xprtsec=[m]tls
  nfs: fix incorrect handling of large-number NFS errors in nfs4_do_mkdir()
  nfs: ignore SB_RDONLY when remounting nfs
  nfs: clear SB_RDONLY before getting superblock
  NFS: always probe for LOCALIO support asynchronously
  pnfs/flexfiles: connect to NFSv3 DS using TLS if MDS connection uses TLS
  NFS: add localio to sysfs
  nfs: use writeback_iter directly
  nfs: refactor nfs_do_writepage
  nfs: don't return AOP_WRITEPAGE_ACTIVATE from nfs_do_writepage
  nfs: fold nfs_page_async_flush into nfs_do_writepage
  NFSv4: Always set NLINK even if the server doesn't support it
  ...
pull/1285/head
Linus Torvalds 2025-06-03 16:13:32 -07:00
commit 5abc7438f1
33 changed files with 555 additions and 227 deletions

View File

@ -180,7 +180,7 @@ struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init)
clp->cl_proto = cl_init->proto; clp->cl_proto = cl_init->proto;
clp->cl_nconnect = cl_init->nconnect; clp->cl_nconnect = cl_init->nconnect;
clp->cl_max_connect = cl_init->max_connect ? cl_init->max_connect : 1; clp->cl_max_connect = cl_init->max_connect ? cl_init->max_connect : 1;
clp->cl_net = get_net(cl_init->net); clp->cl_net = get_net_track(cl_init->net, &clp->cl_ns_tracker, GFP_KERNEL);
#if IS_ENABLED(CONFIG_NFS_LOCALIO) #if IS_ENABLED(CONFIG_NFS_LOCALIO)
seqlock_init(&clp->cl_boot_lock); seqlock_init(&clp->cl_boot_lock);
@ -250,7 +250,7 @@ void nfs_free_client(struct nfs_client *clp)
if (!IS_ERR(clp->cl_rpcclient)) if (!IS_ERR(clp->cl_rpcclient))
rpc_shutdown_client(clp->cl_rpcclient); rpc_shutdown_client(clp->cl_rpcclient);
put_net(clp->cl_net); put_net_track(clp->cl_net, &clp->cl_ns_tracker);
put_nfs_version(clp->cl_nfs_mod); put_nfs_version(clp->cl_nfs_mod);
kfree(clp->cl_hostname); kfree(clp->cl_hostname);
kfree(clp->cl_acceptor); kfree(clp->cl_acceptor);
@ -439,7 +439,7 @@ struct nfs_client *nfs_get_client(const struct nfs_client_initdata *cl_init)
spin_unlock(&nn->nfs_client_lock); spin_unlock(&nn->nfs_client_lock);
new = rpc_ops->init_client(new, cl_init); new = rpc_ops->init_client(new, cl_init);
if (!IS_ERR(new)) if (!IS_ERR(new))
nfs_local_probe(new); nfs_local_probe_async(new);
return new; return new;
} }

View File

@ -1021,13 +1021,6 @@ out:
nfs_inode_find_state_and_recover(inode, stateid); nfs_inode_find_state_and_recover(inode, stateid);
} }
void nfs_remove_bad_delegation(struct inode *inode,
const nfs4_stateid *stateid)
{
nfs_revoke_delegation(inode, stateid);
}
EXPORT_SYMBOL_GPL(nfs_remove_bad_delegation);
void nfs_delegation_mark_returned(struct inode *inode, void nfs_delegation_mark_returned(struct inode *inode,
const nfs4_stateid *stateid) const nfs4_stateid *stateid)
{ {
@ -1069,6 +1062,24 @@ out_rcu_unlock:
nfs_inode_find_state_and_recover(inode, stateid); nfs_inode_find_state_and_recover(inode, stateid);
} }
/**
* nfs_remove_bad_delegation - handle delegations that are unusable
* @inode: inode to process
* @stateid: the delegation's stateid
*
* If the server ACK-ed our FREE_STATEID then clean
* up the delegation, else mark and keep the revoked state.
*/
void nfs_remove_bad_delegation(struct inode *inode,
const nfs4_stateid *stateid)
{
if (stateid && stateid->type == NFS4_FREED_STATEID_TYPE)
nfs_delegation_mark_returned(inode, stateid);
else
nfs_revoke_delegation(inode, stateid);
}
EXPORT_SYMBOL_GPL(nfs_remove_bad_delegation);
/** /**
* nfs_expire_unused_delegation_types * nfs_expire_unused_delegation_types
* @clp: client to process * @clp: client to process

View File

@ -1129,6 +1129,8 @@ static int ff_layout_async_handle_error_v4(struct rpc_task *task,
nfs4_schedule_session_recovery(clp->cl_session, task->tk_status); nfs4_schedule_session_recovery(clp->cl_session, task->tk_status);
break; break;
case -NFS4ERR_DELAY: case -NFS4ERR_DELAY:
nfs_inc_stats(lseg->pls_layout->plh_inode, NFSIOS_DELAY);
fallthrough;
case -NFS4ERR_GRACE: case -NFS4ERR_GRACE:
rpc_delay(task, FF_LAYOUT_POLL_RETRY_MAX); rpc_delay(task, FF_LAYOUT_POLL_RETRY_MAX);
break; break;

View File

@ -400,7 +400,7 @@ nfs4_ff_layout_prepare_ds(struct pnfs_layout_segment *lseg,
* keep ds_clp even if DS is local, so that if local IO cannot * keep ds_clp even if DS is local, so that if local IO cannot
* proceed somehow, we can fall back to NFS whenever we want. * proceed somehow, we can fall back to NFS whenever we want.
*/ */
nfs_local_probe(ds->ds_clp); nfs_local_probe_async(ds->ds_clp);
max_payload = max_payload =
nfs_block_size(rpc_max_payload(ds->ds_clp->cl_rpcclient), nfs_block_size(rpc_max_payload(ds->ds_clp->cl_rpcclient),
NULL); NULL);

View File

@ -557,6 +557,8 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
set_nlink(inode, fattr->nlink); set_nlink(inode, fattr->nlink);
else if (fattr_supported & NFS_ATTR_FATTR_NLINK) else if (fattr_supported & NFS_ATTR_FATTR_NLINK)
nfs_set_cache_invalid(inode, NFS_INO_INVALID_NLINK); nfs_set_cache_invalid(inode, NFS_INO_INVALID_NLINK);
else
set_nlink(inode, 1);
if (fattr->valid & NFS_ATTR_FATTR_OWNER) if (fattr->valid & NFS_ATTR_FATTR_OWNER)
inode->i_uid = fattr->uid; inode->i_uid = fattr->uid;
else if (fattr_supported & NFS_ATTR_FATTR_OWNER) else if (fattr_supported & NFS_ATTR_FATTR_OWNER)
@ -633,6 +635,34 @@ nfs_fattr_fixup_delegated(struct inode *inode, struct nfs_fattr *fattr)
} }
} }
static void nfs_set_timestamps_to_ts(struct inode *inode, struct iattr *attr)
{
unsigned int cache_flags = 0;
if (attr->ia_valid & ATTR_MTIME_SET) {
struct timespec64 ctime = inode_get_ctime(inode);
struct timespec64 mtime = inode_get_mtime(inode);
struct timespec64 now;
int updated = 0;
now = inode_set_ctime_current(inode);
if (!timespec64_equal(&now, &ctime))
updated |= S_CTIME;
inode_set_mtime_to_ts(inode, attr->ia_mtime);
if (!timespec64_equal(&now, &mtime))
updated |= S_MTIME;
inode_maybe_inc_iversion(inode, updated);
cache_flags |= NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME;
}
if (attr->ia_valid & ATTR_ATIME_SET) {
inode_set_atime_to_ts(inode, attr->ia_atime);
cache_flags |= NFS_INO_INVALID_ATIME;
}
NFS_I(inode)->cache_validity &= ~cache_flags;
}
static void nfs_update_timestamps(struct inode *inode, unsigned int ia_valid) static void nfs_update_timestamps(struct inode *inode, unsigned int ia_valid)
{ {
enum file_time_flags time_flags = 0; enum file_time_flags time_flags = 0;
@ -701,14 +731,27 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if (nfs_have_delegated_mtime(inode) && attr->ia_valid & ATTR_MTIME) { if (nfs_have_delegated_mtime(inode) && attr->ia_valid & ATTR_MTIME) {
spin_lock(&inode->i_lock); spin_lock(&inode->i_lock);
nfs_update_timestamps(inode, attr->ia_valid); if (attr->ia_valid & ATTR_MTIME_SET) {
nfs_set_timestamps_to_ts(inode, attr);
attr->ia_valid &= ~(ATTR_MTIME|ATTR_MTIME_SET|
ATTR_ATIME|ATTR_ATIME_SET);
} else {
nfs_update_timestamps(inode, attr->ia_valid);
attr->ia_valid &= ~(ATTR_MTIME|ATTR_ATIME);
}
spin_unlock(&inode->i_lock); spin_unlock(&inode->i_lock);
attr->ia_valid &= ~(ATTR_MTIME | ATTR_ATIME);
} else if (nfs_have_delegated_atime(inode) && } else if (nfs_have_delegated_atime(inode) &&
attr->ia_valid & ATTR_ATIME && attr->ia_valid & ATTR_ATIME &&
!(attr->ia_valid & ATTR_MTIME)) { !(attr->ia_valid & ATTR_MTIME)) {
nfs_update_delegated_atime(inode); if (attr->ia_valid & ATTR_ATIME_SET) {
attr->ia_valid &= ~ATTR_ATIME; spin_lock(&inode->i_lock);
nfs_set_timestamps_to_ts(inode, attr);
spin_unlock(&inode->i_lock);
attr->ia_valid &= ~(ATTR_ATIME|ATTR_ATIME_SET);
} else {
nfs_update_delegated_atime(inode);
attr->ia_valid &= ~ATTR_ATIME;
}
} }
/* Optimization: if the end result is no change, don't RPC */ /* Optimization: if the end result is no change, don't RPC */

View File

@ -455,7 +455,6 @@ extern int nfs_wait_bit_killable(struct wait_bit_key *key, int mode);
#if IS_ENABLED(CONFIG_NFS_LOCALIO) #if IS_ENABLED(CONFIG_NFS_LOCALIO)
/* localio.c */ /* localio.c */
extern void nfs_local_probe(struct nfs_client *);
extern void nfs_local_probe_async(struct nfs_client *); extern void nfs_local_probe_async(struct nfs_client *);
extern void nfs_local_probe_async_work(struct work_struct *); extern void nfs_local_probe_async_work(struct work_struct *);
extern struct nfsd_file *nfs_local_open_fh(struct nfs_client *, extern struct nfsd_file *nfs_local_open_fh(struct nfs_client *,

View File

@ -171,7 +171,7 @@ static bool nfs_server_uuid_is_local(struct nfs_client *clp)
* - called after alloc_client and init_client (so cl_rpcclient exists) * - called after alloc_client and init_client (so cl_rpcclient exists)
* - this function is idempotent, it can be called for old or new clients * - this function is idempotent, it can be called for old or new clients
*/ */
void nfs_local_probe(struct nfs_client *clp) static void nfs_local_probe(struct nfs_client *clp)
{ {
/* Disallow localio if disabled via sysfs or AUTH_SYS isn't used */ /* Disallow localio if disabled via sysfs or AUTH_SYS isn't used */
if (!localio_enabled || if (!localio_enabled ||
@ -191,14 +191,16 @@ void nfs_local_probe(struct nfs_client *clp)
nfs_localio_enable_client(clp); nfs_localio_enable_client(clp);
nfs_uuid_end(&clp->cl_uuid); nfs_uuid_end(&clp->cl_uuid);
} }
EXPORT_SYMBOL_GPL(nfs_local_probe);
void nfs_local_probe_async_work(struct work_struct *work) void nfs_local_probe_async_work(struct work_struct *work)
{ {
struct nfs_client *clp = struct nfs_client *clp =
container_of(work, struct nfs_client, cl_local_probe_work); container_of(work, struct nfs_client, cl_local_probe_work);
if (!refcount_inc_not_zero(&clp->cl_count))
return;
nfs_local_probe(clp); nfs_local_probe(clp);
nfs_put_client(clp);
} }
void nfs_local_probe_async(struct nfs_client *clp) void nfs_local_probe_async(struct nfs_client *clp)
@ -207,14 +209,16 @@ void nfs_local_probe_async(struct nfs_client *clp)
} }
EXPORT_SYMBOL_GPL(nfs_local_probe_async); EXPORT_SYMBOL_GPL(nfs_local_probe_async);
static inline struct nfsd_file *nfs_local_file_get(struct nfsd_file *nf) static inline void nfs_local_file_put(struct nfsd_file *localio)
{ {
return nfs_to->nfsd_file_get(nf); /* nfs_to_nfsd_file_put_local() expects an __rcu pointer
} * but we have a __kernel pointer. It is always safe
* to cast a __kernel pointer to an __rcu pointer
* because the cast only weakens what is known about the pointer.
*/
struct nfsd_file __rcu *nf = (struct nfsd_file __rcu*) localio;
static inline void nfs_local_file_put(struct nfsd_file *nf) nfs_to_nfsd_file_put_local(&nf);
{
nfs_to->nfsd_file_put(nf);
} }
/* /*
@ -226,12 +230,13 @@ static inline void nfs_local_file_put(struct nfsd_file *nf)
static struct nfsd_file * static struct nfsd_file *
__nfs_local_open_fh(struct nfs_client *clp, const struct cred *cred, __nfs_local_open_fh(struct nfs_client *clp, const struct cred *cred,
struct nfs_fh *fh, struct nfs_file_localio *nfl, struct nfs_fh *fh, struct nfs_file_localio *nfl,
struct nfsd_file __rcu **pnf,
const fmode_t mode) const fmode_t mode)
{ {
struct nfsd_file *localio; struct nfsd_file *localio;
localio = nfs_open_local_fh(&clp->cl_uuid, clp->cl_rpcclient, localio = nfs_open_local_fh(&clp->cl_uuid, clp->cl_rpcclient,
cred, fh, nfl, mode); cred, fh, nfl, pnf, mode);
if (IS_ERR(localio)) { if (IS_ERR(localio)) {
int status = PTR_ERR(localio); int status = PTR_ERR(localio);
trace_nfs_local_open_fh(fh, mode, status); trace_nfs_local_open_fh(fh, mode, status);
@ -258,7 +263,7 @@ nfs_local_open_fh(struct nfs_client *clp, const struct cred *cred,
struct nfs_fh *fh, struct nfs_file_localio *nfl, struct nfs_fh *fh, struct nfs_file_localio *nfl,
const fmode_t mode) const fmode_t mode)
{ {
struct nfsd_file *nf, *new, __rcu **pnf; struct nfsd_file *nf, __rcu **pnf;
if (!nfs_server_is_local(clp)) if (!nfs_server_is_local(clp))
return NULL; return NULL;
@ -270,29 +275,9 @@ nfs_local_open_fh(struct nfs_client *clp, const struct cred *cred,
else else
pnf = &nfl->ro_file; pnf = &nfl->ro_file;
new = NULL; nf = __nfs_local_open_fh(clp, cred, fh, nfl, pnf, mode);
rcu_read_lock(); if (IS_ERR(nf))
nf = rcu_dereference(*pnf); return NULL;
if (!nf) {
rcu_read_unlock();
new = __nfs_local_open_fh(clp, cred, fh, nfl, mode);
if (IS_ERR(new))
return NULL;
rcu_read_lock();
/* try to swap in the pointer */
spin_lock(&clp->cl_uuid.lock);
nf = rcu_dereference_protected(*pnf, 1);
if (!nf) {
nf = new;
new = NULL;
rcu_assign_pointer(*pnf, nf);
}
spin_unlock(&clp->cl_uuid.lock);
}
nf = nfs_local_file_get(nf);
rcu_read_unlock();
if (new)
nfs_to_nfsd_file_put_local(new);
return nf; return nf;
} }
EXPORT_SYMBOL_GPL(nfs_local_open_fh); EXPORT_SYMBOL_GPL(nfs_local_open_fh);

View File

@ -21,6 +21,7 @@ int nfs42_proc_allocate(struct file *, loff_t, loff_t);
ssize_t nfs42_proc_copy(struct file *, loff_t, struct file *, loff_t, size_t, ssize_t nfs42_proc_copy(struct file *, loff_t, struct file *, loff_t, size_t,
struct nl4_server *, nfs4_stateid *, bool); struct nl4_server *, nfs4_stateid *, bool);
int nfs42_proc_deallocate(struct file *, loff_t, loff_t); int nfs42_proc_deallocate(struct file *, loff_t, loff_t);
int nfs42_proc_zero_range(struct file *, loff_t, loff_t);
loff_t nfs42_proc_llseek(struct file *, loff_t, int); loff_t nfs42_proc_llseek(struct file *, loff_t, int);
int nfs42_proc_layoutstats_generic(struct nfs_server *, int nfs42_proc_layoutstats_generic(struct nfs_server *,
struct nfs42_layoutstat_data *); struct nfs42_layoutstat_data *);

View File

@ -146,7 +146,8 @@ int nfs42_proc_allocate(struct file *filep, loff_t offset, loff_t len)
err = nfs42_proc_fallocate(&msg, filep, offset, len); err = nfs42_proc_fallocate(&msg, filep, offset, len);
if (err == -EOPNOTSUPP) if (err == -EOPNOTSUPP)
NFS_SERVER(inode)->caps &= ~NFS_CAP_ALLOCATE; NFS_SERVER(inode)->caps &= ~(NFS_CAP_ALLOCATE |
NFS_CAP_ZERO_RANGE);
inode_unlock(inode); inode_unlock(inode);
return err; return err;
@ -169,7 +170,31 @@ int nfs42_proc_deallocate(struct file *filep, loff_t offset, loff_t len)
if (err == 0) if (err == 0)
truncate_pagecache_range(inode, offset, (offset + len) -1); truncate_pagecache_range(inode, offset, (offset + len) -1);
if (err == -EOPNOTSUPP) if (err == -EOPNOTSUPP)
NFS_SERVER(inode)->caps &= ~NFS_CAP_DEALLOCATE; NFS_SERVER(inode)->caps &= ~(NFS_CAP_DEALLOCATE |
NFS_CAP_ZERO_RANGE);
inode_unlock(inode);
return err;
}
int nfs42_proc_zero_range(struct file *filep, loff_t offset, loff_t len)
{
struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ZERO_RANGE],
};
struct inode *inode = file_inode(filep);
int err;
if (!nfs_server_capable(inode, NFS_CAP_ZERO_RANGE))
return -EOPNOTSUPP;
inode_lock(inode);
err = nfs42_proc_fallocate(&msg, filep, offset, len);
if (err == 0)
truncate_pagecache_range(inode, offset, (offset + len) -1);
if (err == -EOPNOTSUPP)
NFS_SERVER(inode)->caps &= ~NFS_CAP_ZERO_RANGE;
inode_unlock(inode); inode_unlock(inode);
return err; return err;

View File

@ -174,6 +174,18 @@
decode_putfh_maxsz + \ decode_putfh_maxsz + \
decode_deallocate_maxsz + \ decode_deallocate_maxsz + \
decode_getattr_maxsz) decode_getattr_maxsz)
#define NFS4_enc_zero_range_sz (compound_encode_hdr_maxsz + \
encode_sequence_maxsz + \
encode_putfh_maxsz + \
encode_deallocate_maxsz + \
encode_allocate_maxsz + \
encode_getattr_maxsz)
#define NFS4_dec_zero_range_sz (compound_decode_hdr_maxsz + \
decode_sequence_maxsz + \
decode_putfh_maxsz + \
decode_deallocate_maxsz + \
decode_allocate_maxsz + \
decode_getattr_maxsz)
#define NFS4_enc_read_plus_sz (compound_encode_hdr_maxsz + \ #define NFS4_enc_read_plus_sz (compound_encode_hdr_maxsz + \
encode_sequence_maxsz + \ encode_sequence_maxsz + \
encode_putfh_maxsz + \ encode_putfh_maxsz + \
@ -648,6 +660,27 @@ static void nfs4_xdr_enc_deallocate(struct rpc_rqst *req,
encode_nops(&hdr); encode_nops(&hdr);
} }
/*
* Encode ZERO_RANGE request
*/
static void nfs4_xdr_enc_zero_range(struct rpc_rqst *req,
struct xdr_stream *xdr,
const void *data)
{
const struct nfs42_falloc_args *args = data;
struct compound_hdr hdr = {
.minorversion = nfs4_xdr_minorversion(&args->seq_args),
};
encode_compound_hdr(xdr, req, &hdr);
encode_sequence(xdr, &args->seq_args, &hdr);
encode_putfh(xdr, args->falloc_fh, &hdr);
encode_deallocate(xdr, args, &hdr);
encode_allocate(xdr, args, &hdr);
encode_getfattr(xdr, args->falloc_bitmask, &hdr);
encode_nops(&hdr);
}
/* /*
* Encode READ_PLUS request * Encode READ_PLUS request
*/ */
@ -1510,6 +1543,37 @@ out:
return status; return status;
} }
/*
* Decode ZERO_RANGE request
*/
static int nfs4_xdr_dec_zero_range(struct rpc_rqst *rqstp,
struct xdr_stream *xdr,
void *data)
{
struct nfs42_falloc_res *res = data;
struct compound_hdr hdr;
int status;
status = decode_compound_hdr(xdr, &hdr);
if (status)
goto out;
status = decode_sequence(xdr, &res->seq_res, rqstp);
if (status)
goto out;
status = decode_putfh(xdr);
if (status)
goto out;
status = decode_deallocate(xdr, res);
if (status)
goto out;
status = decode_allocate(xdr, res);
if (status)
goto out;
decode_getfattr(xdr, res->falloc_fattr, res->falloc_server);
out:
return status;
}
/* /*
* Decode READ_PLUS request * Decode READ_PLUS request
*/ */

View File

@ -67,8 +67,7 @@ struct nfs4_minor_version_ops {
void (*free_lock_state)(struct nfs_server *, void (*free_lock_state)(struct nfs_server *,
struct nfs4_lock_state *); struct nfs4_lock_state *);
int (*test_and_free_expired)(struct nfs_server *, int (*test_and_free_expired)(struct nfs_server *,
const nfs4_stateid *, nfs4_stateid *, const struct cred *);
const struct cred *);
struct nfs_seqid * struct nfs_seqid *
(*alloc_seqid)(struct nfs_seqid_counter *, gfp_t); (*alloc_seqid)(struct nfs_seqid_counter *, gfp_t);
void (*session_trunk)(struct rpc_clnt *clnt, void (*session_trunk)(struct rpc_clnt *clnt,

View File

@ -225,8 +225,14 @@ static long nfs42_fallocate(struct file *filep, int mode, loff_t offset, loff_t
if (!S_ISREG(inode->i_mode)) if (!S_ISREG(inode->i_mode))
return -EOPNOTSUPP; return -EOPNOTSUPP;
if ((mode != 0) && (mode != (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE))) switch (mode) {
case 0:
case FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE:
case FALLOC_FL_ZERO_RANGE:
break;
default:
return -EOPNOTSUPP; return -EOPNOTSUPP;
}
ret = inode_newsize_ok(inode, offset + len); ret = inode_newsize_ok(inode, offset + len);
if (ret < 0) if (ret < 0)
@ -234,6 +240,8 @@ static long nfs42_fallocate(struct file *filep, int mode, loff_t offset, loff_t
if (mode & FALLOC_FL_PUNCH_HOLE) if (mode & FALLOC_FL_PUNCH_HOLE)
return nfs42_proc_deallocate(filep, offset, len); return nfs42_proc_deallocate(filep, offset, len);
else if (mode & FALLOC_FL_ZERO_RANGE)
return nfs42_proc_zero_range(filep, offset ,len);
return nfs42_proc_allocate(filep, offset, len); return nfs42_proc_allocate(filep, offset, len);
} }

View File

@ -105,7 +105,7 @@ static struct rpc_task *_nfs41_proc_sequence(struct nfs_client *clp,
bool is_privileged); bool is_privileged);
static int nfs41_test_stateid(struct nfs_server *, const nfs4_stateid *, static int nfs41_test_stateid(struct nfs_server *, const nfs4_stateid *,
const struct cred *); const struct cred *);
static int nfs41_free_stateid(struct nfs_server *, const nfs4_stateid *, static int nfs41_free_stateid(struct nfs_server *, nfs4_stateid *,
const struct cred *, bool); const struct cred *, bool);
#endif #endif
@ -325,14 +325,14 @@ static void nfs4_bitmap_copy_adjust(__u32 *dst, const __u32 *src,
if (nfs_have_delegated_mtime(inode)) { if (nfs_have_delegated_mtime(inode)) {
if (!(cache_validity & NFS_INO_INVALID_ATIME)) if (!(cache_validity & NFS_INO_INVALID_ATIME))
dst[1] &= ~FATTR4_WORD1_TIME_ACCESS; dst[1] &= ~(FATTR4_WORD1_TIME_ACCESS|FATTR4_WORD1_TIME_ACCESS_SET);
if (!(cache_validity & NFS_INO_INVALID_MTIME)) if (!(cache_validity & NFS_INO_INVALID_MTIME))
dst[1] &= ~FATTR4_WORD1_TIME_MODIFY; dst[1] &= ~(FATTR4_WORD1_TIME_MODIFY|FATTR4_WORD1_TIME_MODIFY_SET);
if (!(cache_validity & NFS_INO_INVALID_CTIME)) if (!(cache_validity & NFS_INO_INVALID_CTIME))
dst[1] &= ~FATTR4_WORD1_TIME_METADATA; dst[1] &= ~(FATTR4_WORD1_TIME_METADATA|FATTR4_WORD1_TIME_MODIFY_SET);
} else if (nfs_have_delegated_atime(inode)) { } else if (nfs_have_delegated_atime(inode)) {
if (!(cache_validity & NFS_INO_INVALID_ATIME)) if (!(cache_validity & NFS_INO_INVALID_ATIME))
dst[1] &= ~FATTR4_WORD1_TIME_ACCESS; dst[1] &= ~(FATTR4_WORD1_TIME_ACCESS|FATTR4_WORD1_TIME_ACCESS_SET);
} }
} }
@ -2903,16 +2903,14 @@ static int nfs40_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *st
} }
static int nfs40_test_and_free_expired_stateid(struct nfs_server *server, static int nfs40_test_and_free_expired_stateid(struct nfs_server *server,
const nfs4_stateid *stateid, nfs4_stateid *stateid, const struct cred *cred)
const struct cred *cred)
{ {
return -NFS4ERR_BAD_STATEID; return -NFS4ERR_BAD_STATEID;
} }
#if defined(CONFIG_NFS_V4_1) #if defined(CONFIG_NFS_V4_1)
static int nfs41_test_and_free_expired_stateid(struct nfs_server *server, static int nfs41_test_and_free_expired_stateid(struct nfs_server *server,
const nfs4_stateid *stateid, nfs4_stateid *stateid, const struct cred *cred)
const struct cred *cred)
{ {
int status; int status;
@ -2921,6 +2919,7 @@ static int nfs41_test_and_free_expired_stateid(struct nfs_server *server,
break; break;
case NFS4_INVALID_STATEID_TYPE: case NFS4_INVALID_STATEID_TYPE:
case NFS4_SPECIAL_STATEID_TYPE: case NFS4_SPECIAL_STATEID_TYPE:
case NFS4_FREED_STATEID_TYPE:
return -NFS4ERR_BAD_STATEID; return -NFS4ERR_BAD_STATEID;
case NFS4_REVOKED_STATEID_TYPE: case NFS4_REVOKED_STATEID_TYPE:
goto out_free; goto out_free;
@ -3976,8 +3975,9 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
FATTR4_WORD0_CASE_INSENSITIVE | FATTR4_WORD0_CASE_INSENSITIVE |
FATTR4_WORD0_CASE_PRESERVING; FATTR4_WORD0_CASE_PRESERVING;
if (minorversion) if (minorversion)
bitmask[2] = FATTR4_WORD2_SUPPATTR_EXCLCREAT | bitmask[2] = FATTR4_WORD2_SUPPATTR_EXCLCREAT;
FATTR4_WORD2_OPEN_ARGUMENTS; if (minorversion > 1)
bitmask[2] |= FATTR4_WORD2_OPEN_ARGUMENTS;
status = nfs4_call_sync(server->client, server, &msg, &args.seq_args, &res.seq_res, 0); status = nfs4_call_sync(server->client, server, &msg, &args.seq_args, &res.seq_res, 0);
if (status == 0) { if (status == 0) {
@ -5164,13 +5164,15 @@ static int nfs4_do_create(struct inode *dir, struct dentry *dentry, struct nfs4_
} }
static struct dentry *nfs4_do_mkdir(struct inode *dir, struct dentry *dentry, static struct dentry *nfs4_do_mkdir(struct inode *dir, struct dentry *dentry,
struct nfs4_createdata *data) struct nfs4_createdata *data, int *statusp)
{ {
int status = nfs4_call_sync(NFS_SERVER(dir)->client, NFS_SERVER(dir), &data->msg, struct dentry *ret;
*statusp = nfs4_call_sync(NFS_SERVER(dir)->client, NFS_SERVER(dir), &data->msg,
&data->arg.seq_args, &data->res.seq_res, 1); &data->arg.seq_args, &data->res.seq_res, 1);
if (status) if (*statusp)
return ERR_PTR(status); return NULL;
spin_lock(&dir->i_lock); spin_lock(&dir->i_lock);
/* Creating a directory bumps nlink in the parent */ /* Creating a directory bumps nlink in the parent */
@ -5179,7 +5181,11 @@ static struct dentry *nfs4_do_mkdir(struct inode *dir, struct dentry *dentry,
data->res.fattr->time_start, data->res.fattr->time_start,
NFS_INO_INVALID_DATA); NFS_INO_INVALID_DATA);
spin_unlock(&dir->i_lock); spin_unlock(&dir->i_lock);
return nfs_add_or_obtain(dentry, data->res.fh, data->res.fattr); ret = nfs_add_or_obtain(dentry, data->res.fh, data->res.fattr);
if (!IS_ERR(ret))
return ret;
*statusp = PTR_ERR(ret);
return NULL;
} }
static void nfs4_free_createdata(struct nfs4_createdata *data) static void nfs4_free_createdata(struct nfs4_createdata *data)
@ -5240,17 +5246,18 @@ static int nfs4_proc_symlink(struct inode *dir, struct dentry *dentry,
static struct dentry *_nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry, static struct dentry *_nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry,
struct iattr *sattr, struct iattr *sattr,
struct nfs4_label *label) struct nfs4_label *label, int *statusp)
{ {
struct nfs4_createdata *data; struct nfs4_createdata *data;
struct dentry *ret = ERR_PTR(-ENOMEM); struct dentry *ret = NULL;
*statusp = -ENOMEM;
data = nfs4_alloc_createdata(dir, &dentry->d_name, sattr, NF4DIR); data = nfs4_alloc_createdata(dir, &dentry->d_name, sattr, NF4DIR);
if (data == NULL) if (data == NULL)
goto out; goto out;
data->arg.label = label; data->arg.label = label;
ret = nfs4_do_mkdir(dir, dentry, data); ret = nfs4_do_mkdir(dir, dentry, data, statusp);
nfs4_free_createdata(data); nfs4_free_createdata(data);
out: out:
@ -5273,11 +5280,12 @@ static struct dentry *nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry,
if (!(server->attr_bitmask[2] & FATTR4_WORD2_MODE_UMASK)) if (!(server->attr_bitmask[2] & FATTR4_WORD2_MODE_UMASK))
sattr->ia_mode &= ~current_umask(); sattr->ia_mode &= ~current_umask();
do { do {
alias = _nfs4_proc_mkdir(dir, dentry, sattr, label); alias = _nfs4_proc_mkdir(dir, dentry, sattr, label, &err);
err = PTR_ERR_OR_ZERO(alias);
trace_nfs4_mkdir(dir, &dentry->d_name, err); trace_nfs4_mkdir(dir, &dentry->d_name, err);
err = nfs4_handle_exception(NFS_SERVER(dir), err, if (err)
&exception); alias = ERR_PTR(nfs4_handle_exception(NFS_SERVER(dir),
err,
&exception));
} while (exception.retry); } while (exception.retry);
nfs4_label_release_security(label); nfs4_label_release_security(label);
@ -6211,6 +6219,8 @@ static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen,
struct nfs_server *server = NFS_SERVER(inode); struct nfs_server *server = NFS_SERVER(inode);
int ret; int ret;
if (unlikely(NFS_FH(inode)->size == 0))
return -ENODATA;
if (!nfs4_server_supports_acls(server, type)) if (!nfs4_server_supports_acls(server, type))
return -EOPNOTSUPP; return -EOPNOTSUPP;
ret = nfs_revalidate_inode(inode, NFS_INO_INVALID_CHANGE); ret = nfs_revalidate_inode(inode, NFS_INO_INVALID_CHANGE);
@ -6285,6 +6295,9 @@ static int nfs4_proc_set_acl(struct inode *inode, const void *buf,
{ {
struct nfs4_exception exception = { }; struct nfs4_exception exception = { };
int err; int err;
if (unlikely(NFS_FH(inode)->size == 0))
return -ENODATA;
do { do {
err = __nfs4_proc_set_acl(inode, buf, buflen, type); err = __nfs4_proc_set_acl(inode, buf, buflen, type);
trace_nfs4_set_acl(inode, err); trace_nfs4_set_acl(inode, err);
@ -10611,7 +10624,7 @@ static const struct rpc_call_ops nfs41_free_stateid_ops = {
* Note: this function is always asynchronous. * Note: this function is always asynchronous.
*/ */
static int nfs41_free_stateid(struct nfs_server *server, static int nfs41_free_stateid(struct nfs_server *server,
const nfs4_stateid *stateid, nfs4_stateid *stateid,
const struct cred *cred, const struct cred *cred,
bool privileged) bool privileged)
{ {
@ -10651,6 +10664,7 @@ static int nfs41_free_stateid(struct nfs_server *server,
if (IS_ERR(task)) if (IS_ERR(task))
return PTR_ERR(task); return PTR_ERR(task);
rpc_put_task(task); rpc_put_task(task);
stateid->type = NFS4_FREED_STATEID_TYPE;
return 0; return 0;
} }
@ -10817,6 +10831,7 @@ static const struct nfs4_minor_version_ops nfs_v4_2_minor_ops = {
| NFS_CAP_OFFLOAD_CANCEL | NFS_CAP_OFFLOAD_CANCEL
| NFS_CAP_COPY_NOTIFY | NFS_CAP_COPY_NOTIFY
| NFS_CAP_DEALLOCATE | NFS_CAP_DEALLOCATE
| NFS_CAP_ZERO_RANGE
| NFS_CAP_SEEK | NFS_CAP_SEEK
| NFS_CAP_LAYOUTSTATS | NFS_CAP_LAYOUTSTATS
| NFS_CAP_CLONE | NFS_CAP_CLONE
@ -10852,7 +10867,7 @@ const struct nfs4_minor_version_ops *nfs_v4_minor_ops[] = {
static ssize_t nfs4_listxattr(struct dentry *dentry, char *list, size_t size) static ssize_t nfs4_listxattr(struct dentry *dentry, char *list, size_t size)
{ {
ssize_t error, error2, error3; ssize_t error, error2, error3, error4;
size_t left = size; size_t left = size;
error = generic_listxattr(dentry, list, left); error = generic_listxattr(dentry, list, left);
@ -10875,8 +10890,16 @@ static ssize_t nfs4_listxattr(struct dentry *dentry, char *list, size_t size)
error3 = nfs4_listxattr_nfs4_user(d_inode(dentry), list, left); error3 = nfs4_listxattr_nfs4_user(d_inode(dentry), list, left);
if (error3 < 0) if (error3 < 0)
return error3; return error3;
if (list) {
list += error3;
left -= error3;
}
error += error2 + error3; error4 = security_inode_listsecurity(d_inode(dentry), list, left);
if (error4 < 0)
return error4;
error += error2 + error3 + error4;
if (size && error > size) if (size && error > size)
return -ERANGE; return -ERANGE;
return error; return error;

View File

@ -7711,6 +7711,7 @@ const struct rpc_procinfo nfs4_procedures[] = {
PROC42(LISTXATTRS, enc_listxattrs, dec_listxattrs), PROC42(LISTXATTRS, enc_listxattrs, dec_listxattrs),
PROC42(REMOVEXATTR, enc_removexattr, dec_removexattr), PROC42(REMOVEXATTR, enc_removexattr, dec_removexattr),
PROC42(READ_PLUS, enc_read_plus, dec_read_plus), PROC42(READ_PLUS, enc_read_plus, dec_read_plus),
PROC42(ZERO_RANGE, enc_zero_range, dec_zero_range),
}; };
static unsigned int nfs_version4_counts[ARRAY_SIZE(nfs4_procedures)]; static unsigned int nfs_version4_counts[ARRAY_SIZE(nfs4_procedures)];

View File

@ -830,10 +830,16 @@ static int _nfs4_pnfs_v3_ds_connect(struct nfs_server *mds_srv,
.servername = clp->cl_hostname, .servername = clp->cl_hostname,
.connect_timeout = connect_timeout, .connect_timeout = connect_timeout,
.reconnect_timeout = connect_timeout, .reconnect_timeout = connect_timeout,
.xprtsec = clp->cl_xprtsec,
}; };
if (da->da_transport != clp->cl_proto) if (da->da_transport != clp->cl_proto &&
clp->cl_proto != XPRT_TRANSPORT_TCP_TLS)
continue; continue;
if (da->da_transport == XPRT_TRANSPORT_TCP &&
mds_srv->nfs_client->cl_proto == XPRT_TRANSPORT_TCP_TLS)
xprt_args.ident = XPRT_TRANSPORT_TCP_TLS;
if (da->da_addr.ss_family != clp->cl_addr.ss_family) if (da->da_addr.ss_family != clp->cl_addr.ss_family)
continue; continue;
/* Add this address as an alias */ /* Add this address as an alias */
@ -841,6 +847,9 @@ static int _nfs4_pnfs_v3_ds_connect(struct nfs_server *mds_srv,
rpc_clnt_test_and_add_xprt, NULL); rpc_clnt_test_and_add_xprt, NULL);
continue; continue;
} }
if (da->da_transport == XPRT_TRANSPORT_TCP &&
mds_srv->nfs_client->cl_proto == XPRT_TRANSPORT_TCP_TLS)
da->da_transport = XPRT_TRANSPORT_TCP_TLS;
clp = get_v3_ds_connect(mds_srv, clp = get_v3_ds_connect(mds_srv,
&da->da_addr, &da->da_addr,
da->da_addrlen, da->da_transport, da->da_addrlen, da->da_transport,

View File

@ -56,7 +56,8 @@ static int nfs_return_empty_folio(struct folio *folio)
{ {
folio_zero_segment(folio, 0, folio_size(folio)); folio_zero_segment(folio, 0, folio_size(folio));
folio_mark_uptodate(folio); folio_mark_uptodate(folio);
folio_unlock(folio); if (nfs_netfs_folio_unlock(folio))
folio_unlock(folio);
return 0; return 0;
} }

View File

@ -1051,6 +1051,16 @@ int nfs_reconfigure(struct fs_context *fc)
sync_filesystem(sb); sync_filesystem(sb);
/*
* The SB_RDONLY flag has been removed from the superblock during
* mounts to prevent interference between different filesystems.
* Similarly, it is also necessary to ignore the SB_RDONLY flag
* during reconfiguration; otherwise, it may also result in the
* creation of redundant superblocks when mounting a directory with
* different rw and ro flags multiple times.
*/
fc->sb_flags_mask &= ~SB_RDONLY;
/* /*
* Userspace mount programs that send binary options generally send * Userspace mount programs that send binary options generally send
* them populated with default values. We have no way to know which * them populated with default values. We have no way to know which
@ -1308,8 +1318,17 @@ int nfs_get_tree_common(struct fs_context *fc)
if (IS_ERR(server)) if (IS_ERR(server))
return PTR_ERR(server); return PTR_ERR(server);
/*
* When NFS_MOUNT_UNSHARED is not set, NFS forces the sharing of a
* superblock among each filesystem that mounts sub-directories
* belonging to a single exported root path.
* To prevent interference between different filesystems, the
* SB_RDONLY flag should be removed from the superblock.
*/
if (server->flags & NFS_MOUNT_UNSHARED) if (server->flags & NFS_MOUNT_UNSHARED)
compare_super = NULL; compare_super = NULL;
else
fc->sb_flags &= ~SB_RDONLY;
/* -o noac implies -o sync */ /* -o noac implies -o sync */
if (server->flags & NFS_MOUNT_NOAC) if (server->flags & NFS_MOUNT_NOAC)

View File

@ -387,6 +387,33 @@ static inline void nfs_sysfs_add_nfsv41_server(struct nfs_server *server)
} }
#endif /* CONFIG_NFS_V4_1 */ #endif /* CONFIG_NFS_V4_1 */
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
static ssize_t
localio_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct nfs_server *server = container_of(kobj, struct nfs_server, kobj);
bool localio = nfs_server_is_local(server->nfs_client);
return sysfs_emit(buf, "%d\n", localio);
}
static struct kobj_attribute nfs_sysfs_attr_localio = __ATTR_RO(localio);
static void nfs_sysfs_add_nfs_localio_server(struct nfs_server *server)
{
int ret = sysfs_create_file_ns(&server->kobj, &nfs_sysfs_attr_localio.attr,
nfs_netns_server_namespace(&server->kobj));
if (ret < 0)
pr_warn("NFS: sysfs_create_file_ns for server-%d failed (%d)\n",
server->s_sysfs_id, ret);
}
#else
static inline void nfs_sysfs_add_nfs_localio_server(struct nfs_server *server)
{
}
#endif /* IS_ENABLED(CONFIG_NFS_LOCALIO) */
void nfs_sysfs_add_server(struct nfs_server *server) void nfs_sysfs_add_server(struct nfs_server *server)
{ {
int ret; int ret;
@ -405,6 +432,7 @@ void nfs_sysfs_add_server(struct nfs_server *server)
server->s_sysfs_id, ret); server->s_sysfs_id, ret);
nfs_sysfs_add_nfsv41_server(server); nfs_sysfs_add_nfsv41_server(server);
nfs_sysfs_add_nfs_localio_server(server);
} }
EXPORT_SYMBOL_GPL(nfs_sysfs_add_server); EXPORT_SYMBOL_GPL(nfs_sysfs_add_server);

View File

@ -632,19 +632,19 @@ static void nfs_write_error(struct nfs_page *req, int error)
* Find an associated nfs write request, and prepare to flush it out * Find an associated nfs write request, and prepare to flush it out
* May return an error if the user signalled nfs_wait_on_request(). * May return an error if the user signalled nfs_wait_on_request().
*/ */
static int nfs_page_async_flush(struct folio *folio, static int nfs_do_writepage(struct folio *folio, struct writeback_control *wbc,
struct writeback_control *wbc, struct nfs_pageio_descriptor *pgio)
struct nfs_pageio_descriptor *pgio)
{ {
struct nfs_page *req; struct nfs_page *req;
int ret = 0; int ret;
nfs_pageio_cond_complete(pgio, folio->index);
req = nfs_lock_and_join_requests(folio); req = nfs_lock_and_join_requests(folio);
if (!req) if (!req)
goto out; return 0;
ret = PTR_ERR(req);
if (IS_ERR(req)) if (IS_ERR(req))
goto out; return PTR_ERR(req);
nfs_folio_set_writeback(folio); nfs_folio_set_writeback(folio);
WARN_ON_ONCE(test_bit(PG_CLEAN, &req->wb_flags)); WARN_ON_ONCE(test_bit(PG_CLEAN, &req->wb_flags));
@ -654,7 +654,6 @@ static int nfs_page_async_flush(struct folio *folio,
if (nfs_error_is_fatal_on_server(ret)) if (nfs_error_is_fatal_on_server(ret))
goto out_launder; goto out_launder;
ret = 0;
if (!nfs_pageio_add_request(pgio, req)) { if (!nfs_pageio_add_request(pgio, req)) {
ret = pgio->pg_error; ret = pgio->pg_error;
/* /*
@ -662,28 +661,20 @@ static int nfs_page_async_flush(struct folio *folio,
*/ */
if (nfs_error_is_fatal_on_server(ret)) if (nfs_error_is_fatal_on_server(ret))
goto out_launder; goto out_launder;
if (wbc->sync_mode == WB_SYNC_NONE)
ret = AOP_WRITEPAGE_ACTIVATE;
folio_redirty_for_writepage(wbc, folio); folio_redirty_for_writepage(wbc, folio);
nfs_redirty_request(req); nfs_redirty_request(req);
pgio->pg_error = 0; pgio->pg_error = 0;
} else return ret;
nfs_add_stats(folio->mapping->host, }
NFSIOS_WRITEPAGES, 1);
out: nfs_add_stats(folio->mapping->host, NFSIOS_WRITEPAGES, 1);
return ret; return 0;
out_launder: out_launder:
nfs_write_error(req, ret); nfs_write_error(req, ret);
return 0; return 0;
} }
static int nfs_do_writepage(struct folio *folio, struct writeback_control *wbc,
struct nfs_pageio_descriptor *pgio)
{
nfs_pageio_cond_complete(pgio, folio->index);
return nfs_page_async_flush(folio, wbc, pgio);
}
/* /*
* Write an mmapped page to the server. * Write an mmapped page to the server.
*/ */
@ -703,17 +694,6 @@ static int nfs_writepage_locked(struct folio *folio,
return err; return err;
} }
static int nfs_writepages_callback(struct folio *folio,
struct writeback_control *wbc, void *data)
{
int ret;
ret = nfs_do_writepage(folio, wbc, data);
if (ret != AOP_WRITEPAGE_ACTIVATE)
folio_unlock(folio);
return ret;
}
static void nfs_io_completion_commit(void *inode) static void nfs_io_completion_commit(void *inode)
{ {
nfs_commit_inode(inode, 0); nfs_commit_inode(inode, 0);
@ -749,11 +729,15 @@ int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
} }
do { do {
struct folio *folio = NULL;
nfs_pageio_init_write(&pgio, inode, priority, false, nfs_pageio_init_write(&pgio, inode, priority, false,
&nfs_async_write_completion_ops); &nfs_async_write_completion_ops);
pgio.pg_io_completion = ioc; pgio.pg_io_completion = ioc;
err = write_cache_pages(mapping, wbc, nfs_writepages_callback, while ((folio = writeback_iter(mapping, wbc, folio, &err))) {
&pgio); err = nfs_do_writepage(folio, wbc, &pgio);
folio_unlock(folio);
}
pgio.pg_error = 0; pgio.pg_error = 0;
nfs_pageio_complete(&pgio); nfs_pageio_complete(&pgio);
if (err == -EAGAIN && mntflags & NFS_MOUNT_SOFTERR) if (err == -EAGAIN && mntflags & NFS_MOUNT_SOFTERR)

View File

@ -151,8 +151,7 @@ EXPORT_SYMBOL_GPL(nfs_localio_enable_client);
*/ */
static bool nfs_uuid_put(nfs_uuid_t *nfs_uuid) static bool nfs_uuid_put(nfs_uuid_t *nfs_uuid)
{ {
LIST_HEAD(local_files); struct nfs_file_localio *nfl;
struct nfs_file_localio *nfl, *tmp;
spin_lock(&nfs_uuid->lock); spin_lock(&nfs_uuid->lock);
if (unlikely(!rcu_access_pointer(nfs_uuid->net))) { if (unlikely(!rcu_access_pointer(nfs_uuid->net))) {
@ -166,17 +165,42 @@ static bool nfs_uuid_put(nfs_uuid_t *nfs_uuid)
nfs_uuid->dom = NULL; nfs_uuid->dom = NULL;
} }
list_splice_init(&nfs_uuid->files, &local_files);
spin_unlock(&nfs_uuid->lock);
/* Walk list of files and ensure their last references dropped */ /* Walk list of files and ensure their last references dropped */
list_for_each_entry_safe(nfl, tmp, &local_files, list) {
nfs_close_local_fh(nfl);
cond_resched();
}
spin_lock(&nfs_uuid->lock); while ((nfl = list_first_entry_or_null(&nfs_uuid->files,
BUG_ON(!list_empty(&nfs_uuid->files)); struct nfs_file_localio,
list)) != NULL) {
/* If nfs_uuid is already NULL, nfs_close_local_fh is
* closing and we must wait, else we unlink and close.
*/
if (rcu_access_pointer(nfl->nfs_uuid) == NULL) {
/* nfs_close_local_fh() is doing the
* close and we must wait. until it unlinks
*/
wait_var_event_spinlock(nfl,
list_first_entry_or_null(
&nfs_uuid->files,
struct nfs_file_localio,
list) != nfl,
&nfs_uuid->lock);
continue;
}
/* Remove nfl from nfs_uuid->files list */
list_del_init(&nfl->list);
spin_unlock(&nfs_uuid->lock);
nfs_to_nfsd_file_put_local(&nfl->ro_file);
nfs_to_nfsd_file_put_local(&nfl->rw_file);
cond_resched();
spin_lock(&nfs_uuid->lock);
/* Now we can allow racing nfs_close_local_fh() to
* skip the locking.
*/
RCU_INIT_POINTER(nfl->nfs_uuid, NULL);
wake_up_var_locked(&nfl->nfs_uuid, &nfs_uuid->lock);
}
/* Remove client from nn->local_clients */ /* Remove client from nn->local_clients */
if (nfs_uuid->list_lock) { if (nfs_uuid->list_lock) {
@ -237,6 +261,7 @@ static void nfs_uuid_add_file(nfs_uuid_t *nfs_uuid, struct nfs_file_localio *nfl
struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid, struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid,
struct rpc_clnt *rpc_clnt, const struct cred *cred, struct rpc_clnt *rpc_clnt, const struct cred *cred,
const struct nfs_fh *nfs_fh, struct nfs_file_localio *nfl, const struct nfs_fh *nfs_fh, struct nfs_file_localio *nfl,
struct nfsd_file __rcu **pnf,
const fmode_t fmode) const fmode_t fmode)
{ {
struct net *net; struct net *net;
@ -261,10 +286,9 @@ struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid,
rcu_read_unlock(); rcu_read_unlock();
/* We have an implied reference to net thanks to nfsd_net_try_get */ /* We have an implied reference to net thanks to nfsd_net_try_get */
localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt, localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt,
cred, nfs_fh, fmode); cred, nfs_fh, pnf, fmode);
if (IS_ERR(localio)) nfs_to_nfsd_net_put(net);
nfs_to_nfsd_net_put(net); if (!IS_ERR(localio))
else
nfs_uuid_add_file(uuid, nfl); nfs_uuid_add_file(uuid, nfl);
return localio; return localio;
@ -273,8 +297,6 @@ EXPORT_SYMBOL_GPL(nfs_open_local_fh);
void nfs_close_local_fh(struct nfs_file_localio *nfl) void nfs_close_local_fh(struct nfs_file_localio *nfl)
{ {
struct nfsd_file *ro_nf = NULL;
struct nfsd_file *rw_nf = NULL;
nfs_uuid_t *nfs_uuid; nfs_uuid_t *nfs_uuid;
rcu_read_lock(); rcu_read_lock();
@ -285,28 +307,39 @@ void nfs_close_local_fh(struct nfs_file_localio *nfl)
return; return;
} }
ro_nf = rcu_access_pointer(nfl->ro_file); spin_lock(&nfs_uuid->lock);
rw_nf = rcu_access_pointer(nfl->rw_file); if (!rcu_access_pointer(nfl->nfs_uuid)) {
if (ro_nf || rw_nf) { /* nfs_uuid_put has finished here */
spin_lock(&nfs_uuid->lock);
if (ro_nf)
ro_nf = rcu_dereference_protected(xchg(&nfl->ro_file, NULL), 1);
if (rw_nf)
rw_nf = rcu_dereference_protected(xchg(&nfl->rw_file, NULL), 1);
/* Remove nfl from nfs_uuid->files list */
RCU_INIT_POINTER(nfl->nfs_uuid, NULL);
list_del_init(&nfl->list);
spin_unlock(&nfs_uuid->lock); spin_unlock(&nfs_uuid->lock);
rcu_read_unlock(); rcu_read_unlock();
if (ro_nf)
nfs_to_nfsd_file_put_local(ro_nf);
if (rw_nf)
nfs_to_nfsd_file_put_local(rw_nf);
return; return;
} }
if (list_empty(&nfs_uuid->files)) {
/* nfs_uuid_put() has started closing files, wait for it
* to finished
*/
spin_unlock(&nfs_uuid->lock);
rcu_read_unlock();
wait_var_event(&nfl->nfs_uuid,
rcu_access_pointer(nfl->nfs_uuid) == NULL);
return;
}
/* tell nfs_uuid_put() to wait for us */
RCU_INIT_POINTER(nfl->nfs_uuid, NULL);
spin_unlock(&nfs_uuid->lock);
rcu_read_unlock(); rcu_read_unlock();
nfs_to_nfsd_file_put_local(&nfl->ro_file);
nfs_to_nfsd_file_put_local(&nfl->rw_file);
/* Remove nfl from nfs_uuid->files list and signal nfs_uuid_put()
* that we are done. The moment we drop the spinlock the
* nfs_uuid could be freed.
*/
spin_lock(&nfs_uuid->lock);
list_del_init(&nfl->list);
wake_up_var_locked(&nfl->nfs_uuid, &nfs_uuid->lock);
spin_unlock(&nfs_uuid->lock);
} }
EXPORT_SYMBOL_GPL(nfs_close_local_fh); EXPORT_SYMBOL_GPL(nfs_close_local_fh);

View File

@ -378,12 +378,38 @@ nfsd_file_put(struct nfsd_file *nf)
* the reference of the nfsd_file. * the reference of the nfsd_file.
*/ */
struct net * struct net *
nfsd_file_put_local(struct nfsd_file *nf) nfsd_file_put_local(struct nfsd_file __rcu **pnf)
{
struct nfsd_file *nf;
struct net *net = NULL;
nf = unrcu_pointer(xchg(pnf, NULL));
if (nf) {
net = nf->nf_net;
nfsd_file_put(nf);
}
return net;
}
/**
* nfsd_file_get_local - get nfsd_file reference and reference to net
* @nf: nfsd_file of which to put the reference
*
* Get reference to both the nfsd_file and nf->nf_net.
*/
struct nfsd_file *
nfsd_file_get_local(struct nfsd_file *nf)
{ {
struct net *net = nf->nf_net; struct net *net = nf->nf_net;
nfsd_file_put(nf); if (nfsd_net_try_get(net)) {
return net; nf = nfsd_file_get(nf);
if (!nf)
nfsd_net_put(net);
} else {
nf = NULL;
}
return nf;
} }
/** /**

View File

@ -62,7 +62,8 @@ void nfsd_file_cache_shutdown(void);
int nfsd_file_cache_start_net(struct net *net); int nfsd_file_cache_start_net(struct net *net);
void nfsd_file_cache_shutdown_net(struct net *net); void nfsd_file_cache_shutdown_net(struct net *net);
void nfsd_file_put(struct nfsd_file *nf); void nfsd_file_put(struct nfsd_file *nf);
struct net *nfsd_file_put_local(struct nfsd_file *nf); struct net *nfsd_file_put_local(struct nfsd_file __rcu **nf);
struct nfsd_file *nfsd_file_get_local(struct nfsd_file *nf);
struct nfsd_file *nfsd_file_get(struct nfsd_file *nf); struct nfsd_file *nfsd_file_get(struct nfsd_file *nf);
struct file *nfsd_file_file(struct nfsd_file *nf); struct file *nfsd_file_file(struct nfsd_file *nf);
void nfsd_file_close_inode_sync(struct inode *inode); void nfsd_file_close_inode_sync(struct inode *inode);

View File

@ -24,21 +24,6 @@
#include "filecache.h" #include "filecache.h"
#include "cache.h" #include "cache.h"
static const struct nfsd_localio_operations nfsd_localio_ops = {
.nfsd_net_try_get = nfsd_net_try_get,
.nfsd_net_put = nfsd_net_put,
.nfsd_open_local_fh = nfsd_open_local_fh,
.nfsd_file_put_local = nfsd_file_put_local,
.nfsd_file_get = nfsd_file_get,
.nfsd_file_put = nfsd_file_put,
.nfsd_file_file = nfsd_file_file,
};
void nfsd_localio_ops_init(void)
{
nfs_to = &nfsd_localio_ops;
}
/** /**
* nfsd_open_local_fh - lookup a local filehandle @nfs_fh and map to nfsd_file * nfsd_open_local_fh - lookup a local filehandle @nfs_fh and map to nfsd_file
* *
@ -47,6 +32,7 @@ void nfsd_localio_ops_init(void)
* @rpc_clnt: rpc_clnt that the client established * @rpc_clnt: rpc_clnt that the client established
* @cred: cred that the client established * @cred: cred that the client established
* @nfs_fh: filehandle to lookup * @nfs_fh: filehandle to lookup
* @nfp: place to find the nfsd_file, or store it if it was non-NULL
* @fmode: fmode_t to use for open * @fmode: fmode_t to use for open
* *
* This function maps a local fh to a path on a local filesystem. * This function maps a local fh to a path on a local filesystem.
@ -57,10 +43,11 @@ void nfsd_localio_ops_init(void)
* set. Caller (NFS client) is responsible for calling nfsd_net_put and * set. Caller (NFS client) is responsible for calling nfsd_net_put and
* nfsd_file_put (via nfs_to_nfsd_file_put_local). * nfsd_file_put (via nfs_to_nfsd_file_put_local).
*/ */
struct nfsd_file * static struct nfsd_file *
nfsd_open_local_fh(struct net *net, struct auth_domain *dom, nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
struct rpc_clnt *rpc_clnt, const struct cred *cred, struct rpc_clnt *rpc_clnt, const struct cred *cred,
const struct nfs_fh *nfs_fh, const fmode_t fmode) const struct nfs_fh *nfs_fh, struct nfsd_file __rcu **pnf,
const fmode_t fmode)
{ {
int mayflags = NFSD_MAY_LOCALIO; int mayflags = NFSD_MAY_LOCALIO;
struct svc_cred rq_cred; struct svc_cred rq_cred;
@ -71,6 +58,15 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
if (nfs_fh->size > NFS4_FHSIZE) if (nfs_fh->size > NFS4_FHSIZE)
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
if (!nfsd_net_try_get(net))
return ERR_PTR(-ENXIO);
rcu_read_lock();
localio = nfsd_file_get(rcu_dereference(*pnf));
rcu_read_unlock();
if (localio)
return localio;
/* nfs_fh -> svc_fh */ /* nfs_fh -> svc_fh */
fh_init(&fh, NFS4_FHSIZE); fh_init(&fh, NFS4_FHSIZE);
fh.fh_handle.fh_size = nfs_fh->size; fh.fh_handle.fh_size = nfs_fh->size;
@ -92,9 +88,47 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
if (rq_cred.cr_group_info) if (rq_cred.cr_group_info)
put_group_info(rq_cred.cr_group_info); put_group_info(rq_cred.cr_group_info);
if (!IS_ERR(localio)) {
struct nfsd_file *new;
if (!nfsd_net_try_get(net)) {
nfsd_file_put(localio);
nfsd_net_put(net);
return ERR_PTR(-ENXIO);
}
nfsd_file_get(localio);
again:
new = unrcu_pointer(cmpxchg(pnf, NULL, RCU_INITIALIZER(localio)));
if (new) {
/* Some other thread installed an nfsd_file */
if (nfsd_file_get(new) == NULL)
goto again;
/*
* Drop the ref we were going to install and the
* one we were going to return.
*/
nfsd_file_put(localio);
nfsd_file_put(localio);
localio = new;
}
} else
nfsd_net_put(net);
return localio; return localio;
} }
EXPORT_SYMBOL_GPL(nfsd_open_local_fh);
static const struct nfsd_localio_operations nfsd_localio_ops = {
.nfsd_net_try_get = nfsd_net_try_get,
.nfsd_net_put = nfsd_net_put,
.nfsd_open_local_fh = nfsd_open_local_fh,
.nfsd_file_put_local = nfsd_file_put_local,
.nfsd_file_get_local = nfsd_file_get_local,
.nfsd_file_file = nfsd_file_file,
};
void nfsd_localio_ops_init(void)
{
nfs_to = &nfsd_localio_ops;
}
/* /*
* UUID_IS_LOCAL XDR functions * UUID_IS_LOCAL XDR functions

View File

@ -72,6 +72,7 @@ struct nfs4_stateid_struct {
NFS4_LAYOUT_STATEID_TYPE, NFS4_LAYOUT_STATEID_TYPE,
NFS4_PNFS_DS_STATEID_TYPE, NFS4_PNFS_DS_STATEID_TYPE,
NFS4_REVOKED_STATEID_TYPE, NFS4_REVOKED_STATEID_TYPE,
NFS4_FREED_STATEID_TYPE,
} type; } type;
}; };
@ -678,6 +679,7 @@ enum {
NFSPROC4_CLNT_SEEK, NFSPROC4_CLNT_SEEK,
NFSPROC4_CLNT_ALLOCATE, NFSPROC4_CLNT_ALLOCATE,
NFSPROC4_CLNT_DEALLOCATE, NFSPROC4_CLNT_DEALLOCATE,
NFSPROC4_CLNT_ZERO_RANGE,
NFSPROC4_CLNT_LAYOUTSTATS, NFSPROC4_CLNT_LAYOUTSTATS,
NFSPROC4_CLNT_CLONE, NFSPROC4_CLNT_CLONE,
NFSPROC4_CLNT_COPY, NFSPROC4_CLNT_COPY,

View File

@ -125,6 +125,7 @@ struct nfs_client {
*/ */
char cl_ipaddr[48]; char cl_ipaddr[48];
struct net *cl_net; struct net *cl_net;
netns_tracker cl_ns_tracker;
struct list_head pending_cb_stateids; struct list_head pending_cb_stateids;
struct rcu_head rcu; struct rcu_head rcu;
@ -303,6 +304,7 @@ struct nfs_server {
#define NFS_CAP_CASE_PRESERVING (1U << 7) #define NFS_CAP_CASE_PRESERVING (1U << 7)
#define NFS_CAP_REBOOT_LAYOUTRETURN (1U << 8) #define NFS_CAP_REBOOT_LAYOUTRETURN (1U << 8)
#define NFS_CAP_OFFLOAD_STATUS (1U << 9) #define NFS_CAP_OFFLOAD_STATUS (1U << 9)
#define NFS_CAP_ZERO_RANGE (1U << 10)
#define NFS_CAP_OPEN_XOR (1U << 12) #define NFS_CAP_OPEN_XOR (1U << 12)
#define NFS_CAP_DELEGTIME (1U << 13) #define NFS_CAP_DELEGTIME (1U << 13)
#define NFS_CAP_POSIX_LOCK (1U << 14) #define NFS_CAP_POSIX_LOCK (1U << 14)

View File

@ -50,10 +50,6 @@ void nfs_localio_invalidate_clients(struct list_head *nn_local_clients,
spinlock_t *nn_local_clients_lock); spinlock_t *nn_local_clients_lock);
/* localio needs to map filehandle -> struct nfsd_file */ /* localio needs to map filehandle -> struct nfsd_file */
extern struct nfsd_file *
nfsd_open_local_fh(struct net *, struct auth_domain *, struct rpc_clnt *,
const struct cred *, const struct nfs_fh *,
const fmode_t) __must_hold(rcu);
void nfs_close_local_fh(struct nfs_file_localio *); void nfs_close_local_fh(struct nfs_file_localio *);
struct nfsd_localio_operations { struct nfsd_localio_operations {
@ -64,10 +60,10 @@ struct nfsd_localio_operations {
struct rpc_clnt *, struct rpc_clnt *,
const struct cred *, const struct cred *,
const struct nfs_fh *, const struct nfs_fh *,
struct nfsd_file __rcu **pnf,
const fmode_t); const fmode_t);
struct net *(*nfsd_file_put_local)(struct nfsd_file *); struct net *(*nfsd_file_put_local)(struct nfsd_file __rcu **);
struct nfsd_file *(*nfsd_file_get)(struct nfsd_file *); struct nfsd_file *(*nfsd_file_get_local)(struct nfsd_file *);
void (*nfsd_file_put)(struct nfsd_file *);
struct file *(*nfsd_file_file)(struct nfsd_file *); struct file *(*nfsd_file_file)(struct nfsd_file *);
} ____cacheline_aligned; } ____cacheline_aligned;
@ -77,6 +73,7 @@ extern const struct nfsd_localio_operations *nfs_to;
struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *, struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *,
struct rpc_clnt *, const struct cred *, struct rpc_clnt *, const struct cred *,
const struct nfs_fh *, struct nfs_file_localio *, const struct nfs_fh *, struct nfs_file_localio *,
struct nfsd_file __rcu **pnf,
const fmode_t); const fmode_t);
static inline void nfs_to_nfsd_net_put(struct net *net) static inline void nfs_to_nfsd_net_put(struct net *net)
@ -91,16 +88,19 @@ static inline void nfs_to_nfsd_net_put(struct net *net)
rcu_read_unlock(); rcu_read_unlock();
} }
static inline void nfs_to_nfsd_file_put_local(struct nfsd_file *localio) static inline void nfs_to_nfsd_file_put_local(struct nfsd_file __rcu **localio)
{ {
/* /*
* Must not hold RCU otherwise nfsd_file_put() can easily trigger: * Either *localio must be guaranteed to be non-NULL, or caller
* "Voluntary context switch within RCU read-side critical section!" * must prevent nfsd shutdown from completing as nfs_close_local_fh()
* by scheduling deep in underlying filesystem (e.g. XFS). * does by blocking the nfs_uuid from being finally put.
*/ */
struct net *net = nfs_to->nfsd_file_put_local(localio); struct net *net;
nfs_to_nfsd_net_put(net); net = nfs_to->nfsd_file_put_local(localio);
if (net)
nfs_to_nfsd_net_put(net);
} }
#else /* CONFIG_NFS_LOCALIO */ #else /* CONFIG_NFS_LOCALIO */

View File

@ -30,6 +30,8 @@
#define RPC_MAXCWND(xprt) ((xprt)->max_reqs << RPC_CWNDSHIFT) #define RPC_MAXCWND(xprt) ((xprt)->max_reqs << RPC_CWNDSHIFT)
#define RPCXPRT_CONGESTED(xprt) ((xprt)->cong >= (xprt)->cwnd) #define RPCXPRT_CONGESTED(xprt) ((xprt)->cong >= (xprt)->cwnd)
#define RPC_GSS_SEQNO_ARRAY_SIZE 3U
enum rpc_display_format_t { enum rpc_display_format_t {
RPC_DISPLAY_ADDR = 0, RPC_DISPLAY_ADDR = 0,
RPC_DISPLAY_PORT, RPC_DISPLAY_PORT,
@ -66,7 +68,8 @@ struct rpc_rqst {
struct rpc_cred * rq_cred; /* Bound cred */ struct rpc_cred * rq_cred; /* Bound cred */
__be32 rq_xid; /* request XID */ __be32 rq_xid; /* request XID */
int rq_cong; /* has incremented xprt->cong */ int rq_cong; /* has incremented xprt->cong */
u32 rq_seqno; /* gss seq no. used on req. */ u32 rq_seqnos[RPC_GSS_SEQNO_ARRAY_SIZE]; /* past gss req seq nos. */
unsigned int rq_seqno_count; /* number of entries in rq_seqnos */
int rq_enc_pages_num; int rq_enc_pages_num;
struct page **rq_enc_pages; /* scratch pages for use by struct page **rq_enc_pages; /* scratch pages for use by
gss privacy code */ gss privacy code */
@ -119,6 +122,18 @@ struct rpc_rqst {
#define rq_svec rq_snd_buf.head #define rq_svec rq_snd_buf.head
#define rq_slen rq_snd_buf.len #define rq_slen rq_snd_buf.len
static inline int xprt_rqst_add_seqno(struct rpc_rqst *req, u32 seqno)
{
if (likely(req->rq_seqno_count < RPC_GSS_SEQNO_ARRAY_SIZE))
req->rq_seqno_count++;
/* Shift array to make room for the newest element at the beginning */
memmove(&req->rq_seqnos[1], &req->rq_seqnos[0],
(RPC_GSS_SEQNO_ARRAY_SIZE - 1) * sizeof(req->rq_seqnos[0]));
req->rq_seqnos[0] = seqno;
return 0;
}
/* RPC transport layer security policies */ /* RPC transport layer security policies */
enum xprtsec_policies { enum xprtsec_policies {
RPC_XPRTSEC_NONE = 0, RPC_XPRTSEC_NONE = 0,

View File

@ -409,7 +409,7 @@ TRACE_EVENT(rpcgss_seqno,
__entry->task_id = task->tk_pid; __entry->task_id = task->tk_pid;
__entry->client_id = task->tk_client->cl_clid; __entry->client_id = task->tk_client->cl_clid;
__entry->xid = be32_to_cpu(rqst->rq_xid); __entry->xid = be32_to_cpu(rqst->rq_xid);
__entry->seqno = rqst->rq_seqno; __entry->seqno = *rqst->rq_seqnos;
), ),
TP_printk(SUNRPC_TRACE_TASK_SPECIFIER " xid=0x%08x seqno=%u", TP_printk(SUNRPC_TRACE_TASK_SPECIFIER " xid=0x%08x seqno=%u",
@ -440,7 +440,7 @@ TRACE_EVENT(rpcgss_need_reencode,
__entry->client_id = task->tk_client->cl_clid; __entry->client_id = task->tk_client->cl_clid;
__entry->xid = be32_to_cpu(task->tk_rqstp->rq_xid); __entry->xid = be32_to_cpu(task->tk_rqstp->rq_xid);
__entry->seq_xmit = seq_xmit; __entry->seq_xmit = seq_xmit;
__entry->seqno = task->tk_rqstp->rq_seqno; __entry->seqno = *task->tk_rqstp->rq_seqnos;
__entry->ret = ret; __entry->ret = ret;
), ),

View File

@ -1098,7 +1098,7 @@ TRACE_EVENT(xprt_transmit,
__entry->client_id = rqst->rq_task->tk_client ? __entry->client_id = rqst->rq_task->tk_client ?
rqst->rq_task->tk_client->cl_clid : -1; rqst->rq_task->tk_client->cl_clid : -1;
__entry->xid = be32_to_cpu(rqst->rq_xid); __entry->xid = be32_to_cpu(rqst->rq_xid);
__entry->seqno = rqst->rq_seqno; __entry->seqno = *rqst->rq_seqnos;
__entry->status = status; __entry->status = status;
), ),

View File

@ -1545,6 +1545,7 @@ static int gss_marshal(struct rpc_task *task, struct xdr_stream *xdr)
struct kvec iov; struct kvec iov;
struct xdr_buf verf_buf; struct xdr_buf verf_buf;
int status; int status;
u32 seqno;
/* Credential */ /* Credential */
@ -1556,15 +1557,16 @@ static int gss_marshal(struct rpc_task *task, struct xdr_stream *xdr)
cred_len = p++; cred_len = p++;
spin_lock(&ctx->gc_seq_lock); spin_lock(&ctx->gc_seq_lock);
req->rq_seqno = (ctx->gc_seq < MAXSEQ) ? ctx->gc_seq++ : MAXSEQ; seqno = (ctx->gc_seq < MAXSEQ) ? ctx->gc_seq++ : MAXSEQ;
xprt_rqst_add_seqno(req, seqno);
spin_unlock(&ctx->gc_seq_lock); spin_unlock(&ctx->gc_seq_lock);
if (req->rq_seqno == MAXSEQ) if (*req->rq_seqnos == MAXSEQ)
goto expired; goto expired;
trace_rpcgss_seqno(task); trace_rpcgss_seqno(task);
*p++ = cpu_to_be32(RPC_GSS_VERSION); *p++ = cpu_to_be32(RPC_GSS_VERSION);
*p++ = cpu_to_be32(ctx->gc_proc); *p++ = cpu_to_be32(ctx->gc_proc);
*p++ = cpu_to_be32(req->rq_seqno); *p++ = cpu_to_be32(*req->rq_seqnos);
*p++ = cpu_to_be32(gss_cred->gc_service); *p++ = cpu_to_be32(gss_cred->gc_service);
p = xdr_encode_netobj(p, &ctx->gc_wire_ctx); p = xdr_encode_netobj(p, &ctx->gc_wire_ctx);
*cred_len = cpu_to_be32((p - (cred_len + 1)) << 2); *cred_len = cpu_to_be32((p - (cred_len + 1)) << 2);
@ -1678,17 +1680,31 @@ gss_refresh_null(struct rpc_task *task)
return 0; return 0;
} }
static u32
gss_validate_seqno_mic(struct gss_cl_ctx *ctx, u32 seqno, __be32 *seq, __be32 *p, u32 len)
{
struct kvec iov;
struct xdr_buf verf_buf;
struct xdr_netobj mic;
*seq = cpu_to_be32(seqno);
iov.iov_base = seq;
iov.iov_len = 4;
xdr_buf_from_iov(&iov, &verf_buf);
mic.data = (u8 *)p;
mic.len = len;
return gss_verify_mic(ctx->gc_gss_ctx, &verf_buf, &mic);
}
static int static int
gss_validate(struct rpc_task *task, struct xdr_stream *xdr) gss_validate(struct rpc_task *task, struct xdr_stream *xdr)
{ {
struct rpc_cred *cred = task->tk_rqstp->rq_cred; struct rpc_cred *cred = task->tk_rqstp->rq_cred;
struct gss_cl_ctx *ctx = gss_cred_get_ctx(cred); struct gss_cl_ctx *ctx = gss_cred_get_ctx(cred);
__be32 *p, *seq = NULL; __be32 *p, *seq = NULL;
struct kvec iov;
struct xdr_buf verf_buf;
struct xdr_netobj mic;
u32 len, maj_stat; u32 len, maj_stat;
int status; int status;
int i = 1; /* don't recheck the first item */
p = xdr_inline_decode(xdr, 2 * sizeof(*p)); p = xdr_inline_decode(xdr, 2 * sizeof(*p));
if (!p) if (!p)
@ -1705,13 +1721,10 @@ gss_validate(struct rpc_task *task, struct xdr_stream *xdr)
seq = kmalloc(4, GFP_KERNEL); seq = kmalloc(4, GFP_KERNEL);
if (!seq) if (!seq)
goto validate_failed; goto validate_failed;
*seq = cpu_to_be32(task->tk_rqstp->rq_seqno); maj_stat = gss_validate_seqno_mic(ctx, task->tk_rqstp->rq_seqnos[0], seq, p, len);
iov.iov_base = seq; /* RFC 2203 5.3.3.1 - compute the checksum of each sequence number in the cache */
iov.iov_len = 4; while (unlikely(maj_stat == GSS_S_BAD_SIG && i < task->tk_rqstp->rq_seqno_count))
xdr_buf_from_iov(&iov, &verf_buf); maj_stat = gss_validate_seqno_mic(ctx, task->tk_rqstp->rq_seqnos[i], seq, p, len);
mic.data = (u8 *)p;
mic.len = len;
maj_stat = gss_verify_mic(ctx->gc_gss_ctx, &verf_buf, &mic);
if (maj_stat == GSS_S_CONTEXT_EXPIRED) if (maj_stat == GSS_S_CONTEXT_EXPIRED)
clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags); clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
if (maj_stat) if (maj_stat)
@ -1750,7 +1763,7 @@ gss_wrap_req_integ(struct rpc_cred *cred, struct gss_cl_ctx *ctx,
if (!p) if (!p)
goto wrap_failed; goto wrap_failed;
integ_len = p++; integ_len = p++;
*p = cpu_to_be32(rqstp->rq_seqno); *p = cpu_to_be32(*rqstp->rq_seqnos);
if (rpcauth_wrap_req_encode(task, xdr)) if (rpcauth_wrap_req_encode(task, xdr))
goto wrap_failed; goto wrap_failed;
@ -1847,7 +1860,7 @@ gss_wrap_req_priv(struct rpc_cred *cred, struct gss_cl_ctx *ctx,
if (!p) if (!p)
goto wrap_failed; goto wrap_failed;
opaque_len = p++; opaque_len = p++;
*p = cpu_to_be32(rqstp->rq_seqno); *p = cpu_to_be32(*rqstp->rq_seqnos);
if (rpcauth_wrap_req_encode(task, xdr)) if (rpcauth_wrap_req_encode(task, xdr))
goto wrap_failed; goto wrap_failed;
@ -2001,7 +2014,7 @@ gss_unwrap_resp_integ(struct rpc_task *task, struct rpc_cred *cred,
offset = rcv_buf->len - xdr_stream_remaining(xdr); offset = rcv_buf->len - xdr_stream_remaining(xdr);
if (xdr_stream_decode_u32(xdr, &seqno)) if (xdr_stream_decode_u32(xdr, &seqno))
goto unwrap_failed; goto unwrap_failed;
if (seqno != rqstp->rq_seqno) if (seqno != *rqstp->rq_seqnos)
goto bad_seqno; goto bad_seqno;
if (xdr_buf_subsegment(rcv_buf, &gss_data, offset, len)) if (xdr_buf_subsegment(rcv_buf, &gss_data, offset, len))
goto unwrap_failed; goto unwrap_failed;
@ -2045,7 +2058,7 @@ unwrap_failed:
trace_rpcgss_unwrap_failed(task); trace_rpcgss_unwrap_failed(task);
goto out; goto out;
bad_seqno: bad_seqno:
trace_rpcgss_bad_seqno(task, rqstp->rq_seqno, seqno); trace_rpcgss_bad_seqno(task, *rqstp->rq_seqnos, seqno);
goto out; goto out;
bad_mic: bad_mic:
trace_rpcgss_verify_mic(task, maj_stat); trace_rpcgss_verify_mic(task, maj_stat);
@ -2077,7 +2090,7 @@ gss_unwrap_resp_priv(struct rpc_task *task, struct rpc_cred *cred,
if (maj_stat != GSS_S_COMPLETE) if (maj_stat != GSS_S_COMPLETE)
goto bad_unwrap; goto bad_unwrap;
/* gss_unwrap decrypted the sequence number */ /* gss_unwrap decrypted the sequence number */
if (be32_to_cpup(p++) != rqstp->rq_seqno) if (be32_to_cpup(p++) != *rqstp->rq_seqnos)
goto bad_seqno; goto bad_seqno;
/* gss_unwrap redacts the opaque blob from the head iovec. /* gss_unwrap redacts the opaque blob from the head iovec.
@ -2093,7 +2106,7 @@ unwrap_failed:
trace_rpcgss_unwrap_failed(task); trace_rpcgss_unwrap_failed(task);
return -EIO; return -EIO;
bad_seqno: bad_seqno:
trace_rpcgss_bad_seqno(task, rqstp->rq_seqno, be32_to_cpup(--p)); trace_rpcgss_bad_seqno(task, *rqstp->rq_seqnos, be32_to_cpup(--p));
return -EIO; return -EIO;
bad_unwrap: bad_unwrap:
trace_rpcgss_unwrap(task, maj_stat); trace_rpcgss_unwrap(task, maj_stat);
@ -2118,14 +2131,14 @@ gss_xmit_need_reencode(struct rpc_task *task)
if (!ctx) if (!ctx)
goto out; goto out;
if (gss_seq_is_newer(req->rq_seqno, READ_ONCE(ctx->gc_seq))) if (gss_seq_is_newer(*req->rq_seqnos, READ_ONCE(ctx->gc_seq)))
goto out_ctx; goto out_ctx;
seq_xmit = READ_ONCE(ctx->gc_seq_xmit); seq_xmit = READ_ONCE(ctx->gc_seq_xmit);
while (gss_seq_is_newer(req->rq_seqno, seq_xmit)) { while (gss_seq_is_newer(*req->rq_seqnos, seq_xmit)) {
u32 tmp = seq_xmit; u32 tmp = seq_xmit;
seq_xmit = cmpxchg(&ctx->gc_seq_xmit, tmp, req->rq_seqno); seq_xmit = cmpxchg(&ctx->gc_seq_xmit, tmp, *req->rq_seqnos);
if (seq_xmit == tmp) { if (seq_xmit == tmp) {
ret = false; ret = false;
goto out_ctx; goto out_ctx;
@ -2134,7 +2147,7 @@ gss_xmit_need_reencode(struct rpc_task *task)
win = ctx->gc_win; win = ctx->gc_win;
if (win > 0) if (win > 0)
ret = !gss_seq_is_newer(req->rq_seqno, seq_xmit - win); ret = !gss_seq_is_newer(*req->rq_seqnos, seq_xmit - win);
out_ctx: out_ctx:
gss_put_ctx(ctx); gss_put_ctx(ctx);

View File

@ -2771,8 +2771,13 @@ out_verifier:
case -EPROTONOSUPPORT: case -EPROTONOSUPPORT:
goto out_err; goto out_err;
case -EACCES: case -EACCES:
/* Re-encode with a fresh cred */ /* possible RPCSEC_GSS out-of-sequence event (RFC2203),
fallthrough; * reset recv state and keep waiting, don't retransmit
*/
task->tk_rqstp->rq_reply_bytes_recvd = 0;
task->tk_status = xprt_request_enqueue_receive(task);
task->tk_action = call_transmit_status;
return -EBADMSG;
default: default:
goto out_garbage; goto out_garbage;
} }

View File

@ -1365,7 +1365,7 @@ xprt_request_enqueue_transmit(struct rpc_task *task)
INIT_LIST_HEAD(&req->rq_xmit2); INIT_LIST_HEAD(&req->rq_xmit2);
goto out; goto out;
} }
} else if (!req->rq_seqno) { } else if (req->rq_seqno_count == 0) {
list_for_each_entry(pos, &xprt->xmit_queue, rq_xmit) { list_for_each_entry(pos, &xprt->xmit_queue, rq_xmit) {
if (pos->rq_task->tk_owner != task->tk_owner) if (pos->rq_task->tk_owner != task->tk_owner)
continue; continue;
@ -1898,6 +1898,7 @@ xprt_request_init(struct rpc_task *task)
req->rq_snd_buf.bvec = NULL; req->rq_snd_buf.bvec = NULL;
req->rq_rcv_buf.bvec = NULL; req->rq_rcv_buf.bvec = NULL;
req->rq_release_snd_buf = NULL; req->rq_release_snd_buf = NULL;
req->rq_seqno_count = 0;
xprt_init_majortimeo(task, req, task->tk_client->cl_timeout); xprt_init_majortimeo(task, req, task->tk_client->cl_timeout);
trace_xprt_reserve(req); trace_xprt_reserve(req);

View File

@ -2726,20 +2726,14 @@ static void xs_tcp_tls_setup_socket(struct work_struct *work)
if (status) if (status)
goto out_close; goto out_close;
xprt_release_write(lower_xprt, NULL); xprt_release_write(lower_xprt, NULL);
trace_rpc_socket_connect(upper_xprt, upper_transport->sock, 0); trace_rpc_socket_connect(upper_xprt, upper_transport->sock, 0);
if (!xprt_test_and_set_connected(upper_xprt)) {
upper_xprt->connect_cookie++;
clear_bit(XPRT_SOCK_CONNECTING, &upper_transport->sock_state);
xprt_clear_connecting(upper_xprt);
upper_xprt->stat.connect_count++;
upper_xprt->stat.connect_time += (long)jiffies -
upper_xprt->stat.connect_start;
xs_run_error_worker(upper_transport, XPRT_SOCK_WAKE_PENDING);
}
rpc_shutdown_client(lower_clnt); rpc_shutdown_client(lower_clnt);
/* Check for ingress data that arrived before the socket's
* ->data_ready callback was set up.
*/
xs_poll_check_readable(upper_transport);
out_unlock: out_unlock:
current_restore_flags(pflags, PF_MEMALLOC); current_restore_flags(pflags, PF_MEMALLOC);
upper_transport->clnt = NULL; upper_transport->clnt = NULL;