Commit ccc1ead2 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull NFS client updates from Trond Myklebust:
 "Highlights include:

  Stable fixes:
   - don't inherit NFS filesystem capabilities when crossing from one
     filesystem to another

  Bugfixes:
   - NFS wakeup of __nfs_lookup_revalidate() needs memory barriers
   - NFS improve bounds checking in nfs_fh_to_dentry()
   - NFS Fix allocation errors when writing to a NFS file backed
     loopback device
   - NFSv4: More listxattr fixes
   - SUNRPC: fix client handling of TLS alerts
   - pNFS block/scsi layout fix for an uninitialised pointer
     dereference
   - pNFS block/scsi layout fixes for the extent encoding, stripe
     mapping, and disk offset overflows
   - pNFS layoutcommit work around for RPC size limitations
   - pNFS/flexfiles avoid looping when handling fatal errors after
     layoutget
   - localio: fix various race conditions

  Features and cleanups:
   - Add NFSv4 support for retrieving the btime
   - NFS: Allow folio migration for the case of mode == MIGRATE_SYNC
   - NFS: Support using a kernel keyring to store TLS certificates
   - NFSv4: Speed up delegation lookup using a hash table
   - Assorted cleanups to remove unused variables and struct fields
   - Assorted new tracepoints to improve debugging"

* tag 'nfs-for-6.17-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs: (44 commits)
  NFS/localio: nfs_uuid_put() fix the wake up after unlinking the file
  NFS/localio: nfs_uuid_put() fix races with nfs_open/close_local_fh()
  NFS/localio: nfs_close_local_fh() fix check for file closed
  NFSv4: Remove duplicate lookups, capability probes and fsinfo calls
  NFS: Fix the setting of capabilities when automounting a new filesystem
  sunrpc: fix client side handling of tls alerts
  nfs/localio: use read_seqbegin() rather than read_seqbegin_or_lock()
  NFS: Fixup allocation flags for nfsiod's __GFP_NORETRY
  NFSv4.2: another fix for listxattr
  NFS: Fix filehandle bounds checking in nfs_fh_to_dentry()
  SUNRPC: Silence warnings about parameters not being described
  NFS: Clean up pnfs_put_layout_hdr()/pnfs_destroy_layout_final()
  NFS: Fix wakeup of __nfs_lookup_revalidate() in unblock_revalidate()
  NFS: use a hash table for delegation lookup
  NFS: track active delegations per-server
  NFS: move the delegation_watermark module parameter
  NFS: cleanup nfs_inode_reclaim_delegation
  NFS: cleanup error handling in nfs4_server_common_setup
  pNFS/flexfiles: don't attempt pnfs on fatal DS errors
  NFS: drop __exit from nfs_exit_keyring
  ...
parents cfaf773b 4ec752ce
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -149,8 +149,8 @@ do_add_page_to_bio(struct bio *bio, int npg, enum req_op op, sector_t isect,

	/* limit length to what the device mapping allows */
	end = disk_addr + *len;
	if (end >= map->start + map->len)
		*len = map->start + map->len - disk_addr;
	if (end >= map->disk_offset + map->len)
		*len = map->disk_offset + map->len - disk_addr;

retry:
	if (!bio) {
+3 −2
Original line number Diff line number Diff line
@@ -257,10 +257,11 @@ static bool bl_map_stripe(struct pnfs_block_dev *dev, u64 offset,
	struct pnfs_block_dev *child;
	u64 chunk;
	u32 chunk_idx;
	u64 disk_chunk;
	u64 disk_offset;

	chunk = div_u64(offset, dev->chunk_size);
	div_u64_rem(chunk, dev->nr_children, &chunk_idx);
	disk_chunk = div_u64_rem(chunk, dev->nr_children, &chunk_idx);

	if (chunk_idx >= dev->nr_children) {
		dprintk("%s: invalid chunk idx %d (%lld/%lld)\n",
@@ -273,7 +274,7 @@ static bool bl_map_stripe(struct pnfs_block_dev *dev, u64 offset,
	offset = chunk * dev->chunk_size;

	/* disk offset of the stripe */
	disk_offset = div_u64(offset, dev->nr_children);
	disk_offset = disk_chunk * dev->chunk_size;

	child = &dev->children[chunk_idx];
	child->map(child, disk_offset, map);
+91 −13
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#include <linux/vmalloc.h>

#include "blocklayout.h"
#include "../nfs4trace.h"

#define NFSDBG_FACILITY		NFSDBG_PNFS_LD

@@ -520,10 +521,71 @@ static __be32 *encode_scsi_range(struct pnfs_block_extent *be, __be32 *p)
	return xdr_encode_hyper(p, be->be_length << SECTOR_SHIFT);
}

static int ext_tree_encode_commit(struct pnfs_block_layout *bl, __be32 *p,
/**
 * ext_tree_try_encode_commit - try to encode all extents into the buffer
 * @bl: pointer to the layout
 * @p: pointer to the output buffer
 * @buffer_size: size of the output buffer
 * @count: output pointer to the number of encoded extents
 * @lastbyte: output pointer to the last written byte
 *
 * Return values:
 *   %0: Success, all required extents encoded, outputs are valid
 *   %-ENOSPC: Buffer too small, nothing encoded, outputs are invalid
 */
static int
ext_tree_try_encode_commit(struct pnfs_block_layout *bl, __be32 *p,
		size_t buffer_size, size_t *count, __u64 *lastbyte)
{
	struct pnfs_block_extent *be;

	spin_lock(&bl->bl_ext_lock);
	for (be = ext_tree_first(&bl->bl_ext_rw); be; be = ext_tree_next(be)) {
		if (be->be_state != PNFS_BLOCK_INVALID_DATA ||
		    be->be_tag != EXTENT_WRITTEN)
			continue;

		(*count)++;
		if (ext_tree_layoutupdate_size(bl, *count) > buffer_size) {
			spin_unlock(&bl->bl_ext_lock);
			return -ENOSPC;
		}
	}
	for (be = ext_tree_first(&bl->bl_ext_rw); be; be = ext_tree_next(be)) {
		if (be->be_state != PNFS_BLOCK_INVALID_DATA ||
		    be->be_tag != EXTENT_WRITTEN)
			continue;

		if (bl->bl_scsi_layout)
			p = encode_scsi_range(be, p);
		else
			p = encode_block_extent(be, p);
		be->be_tag = EXTENT_COMMITTING;
	}
	*lastbyte = (bl->bl_lwb != 0) ? bl->bl_lwb - 1 : U64_MAX;
	bl->bl_lwb = 0;
	spin_unlock(&bl->bl_ext_lock);

	return 0;
}

/**
 * ext_tree_encode_commit - encode as much as possible extents into the buffer
 * @bl: pointer to the layout
 * @p: pointer to the output buffer
 * @buffer_size: size of the output buffer
 * @count: output pointer to the number of encoded extents
 * @lastbyte: output pointer to the last written byte
 *
 * Return values:
 *   %0: Success, all required extents encoded, outputs are valid
 *   %-ENOSPC: Buffer too small, some extents are encoded, outputs are valid
 */
static int
ext_tree_encode_commit(struct pnfs_block_layout *bl, __be32 *p,
		size_t buffer_size, size_t *count, __u64 *lastbyte)
{
	struct pnfs_block_extent *be, *be_prev;
	int ret = 0;

	spin_lock(&bl->bl_ext_lock);
@@ -534,9 +596,9 @@ static int ext_tree_encode_commit(struct pnfs_block_layout *bl, __be32 *p,

		(*count)++;
		if (ext_tree_layoutupdate_size(bl, *count) > buffer_size) {
			/* keep counting.. */
			(*count)--;
			ret = -ENOSPC;
			continue;
			break;
		}

		if (bl->bl_scsi_layout)
@@ -544,14 +606,30 @@ static int ext_tree_encode_commit(struct pnfs_block_layout *bl, __be32 *p,
		else
			p = encode_block_extent(be, p);
		be->be_tag = EXTENT_COMMITTING;
		be_prev = be;
	}
	*lastbyte = bl->bl_lwb - 1;
	if (!ret) {
		*lastbyte = (bl->bl_lwb != 0) ? bl->bl_lwb - 1 : U64_MAX;
		bl->bl_lwb = 0;
	} else {
		*lastbyte = be_prev->be_f_offset + be_prev->be_length;
		*lastbyte <<= SECTOR_SHIFT;
		*lastbyte -= 1;
	}
	spin_unlock(&bl->bl_ext_lock);

	return ret;
}

/**
 * ext_tree_prepare_commit - encode extents that need to be committed
 * @arg: layout commit data
 *
 * Return values:
 *   %0: Success, all required extents are encoded
 *   %-ENOSPC: Some extents are encoded, but not all, due to RPC size limit
 *   %-ENOMEM: Out of memory, extents not encoded
 */
int
ext_tree_prepare_commit(struct nfs4_layoutcommit_args *arg)
{
@@ -560,20 +638,18 @@ ext_tree_prepare_commit(struct nfs4_layoutcommit_args *arg)
	__be32 *start_p;
	int ret;

	dprintk("%s enter\n", __func__);

	arg->layoutupdate_page = alloc_page(GFP_NOFS);
	if (!arg->layoutupdate_page)
		return -ENOMEM;
	start_p = page_address(arg->layoutupdate_page);
	arg->layoutupdate_pages = &arg->layoutupdate_page;

retry:
	ret = ext_tree_encode_commit(bl, start_p + 1, buffer_size, &count, &arg->lastbytewritten);
	ret = ext_tree_try_encode_commit(bl, start_p + 1, buffer_size,
			&count, &arg->lastbytewritten);
	if (unlikely(ret)) {
		ext_tree_free_commitdata(arg, buffer_size);

		buffer_size = ext_tree_layoutupdate_size(bl, count);
		buffer_size = NFS_SERVER(arg->inode)->wsize;
		count = 0;

		arg->layoutupdate_pages =
@@ -588,7 +664,8 @@ ext_tree_prepare_commit(struct nfs4_layoutcommit_args *arg)
			return -ENOMEM;
		}

		goto retry;
		ret = ext_tree_encode_commit(bl, start_p + 1, buffer_size,
				&count, &arg->lastbytewritten);
	}

	*start_p = cpu_to_be32(count);
@@ -607,8 +684,9 @@ ext_tree_prepare_commit(struct nfs4_layoutcommit_args *arg)
		}
	}

	dprintk("%s found %zu ranges\n", __func__, count);
	return 0;
	trace_bl_ext_tree_prepare_commit(ret, count,
			arg->lastbytewritten, !!ret);
	return ret;
}

void
+43 −4
Original line number Diff line number Diff line
@@ -682,6 +682,44 @@ struct nfs_client *nfs_init_client(struct nfs_client *clp,
}
EXPORT_SYMBOL_GPL(nfs_init_client);

static void nfs4_server_set_init_caps(struct nfs_server *server)
{
#if IS_ENABLED(CONFIG_NFS_V4)
	/* Set the basic capabilities */
	server->caps = server->nfs_client->cl_mvops->init_caps;
	if (server->flags & NFS_MOUNT_NORDIRPLUS)
		server->caps &= ~NFS_CAP_READDIRPLUS;
	if (server->nfs_client->cl_proto == XPRT_TRANSPORT_RDMA)
		server->caps &= ~NFS_CAP_READ_PLUS;

	/*
	 * Don't use NFS uid/gid mapping if we're using AUTH_SYS or lower
	 * authentication.
	 */
	if (nfs4_disable_idmapping &&
	    server->client->cl_auth->au_flavor == RPC_AUTH_UNIX)
		server->caps |= NFS_CAP_UIDGID_NOMAP;
#endif
}

void nfs_server_set_init_caps(struct nfs_server *server)
{
	switch (server->nfs_client->rpc_ops->version) {
	case 2:
		server->caps = NFS_CAP_HARDLINKS | NFS_CAP_SYMLINKS;
		break;
	case 3:
		server->caps = NFS_CAP_HARDLINKS | NFS_CAP_SYMLINKS;
		if (!(server->flags & NFS_MOUNT_NORDIRPLUS))
			server->caps |= NFS_CAP_READDIRPLUS;
		break;
	default:
		nfs4_server_set_init_caps(server);
		break;
	}
}
EXPORT_SYMBOL_GPL(nfs_server_set_init_caps);

/*
 * Create a version 2 or 3 client
 */
@@ -726,7 +764,6 @@ static int nfs_init_server(struct nfs_server *server,
	/* Initialise the client representation from the mount data */
	server->flags = ctx->flags;
	server->options = ctx->options;
	server->caps |= NFS_CAP_HARDLINKS | NFS_CAP_SYMLINKS;

	switch (clp->rpc_ops->version) {
	case 2:
@@ -762,6 +799,8 @@ static int nfs_init_server(struct nfs_server *server,
	if (error < 0)
		goto error;

	nfs_server_set_init_caps(server);

	/* Preserve the values of mount_server-related mount options */
	if (ctx->mount_server.addrlen) {
		memcpy(&server->mountd_address, &ctx->mount_server.address,
@@ -814,7 +853,6 @@ static void nfs_server_set_fsinfo(struct nfs_server *server,
		server->wsize = max_rpc_payload;
	if (server->wsize > NFS_MAX_FILE_IO_SIZE)
		server->wsize = NFS_MAX_FILE_IO_SIZE;
	server->wpages = (server->wsize + PAGE_SIZE - 1) >> PAGE_SHIFT;

	server->wtmult = nfs_block_bits(fsinfo->wtmult, NULL);

@@ -831,7 +869,6 @@ static void nfs_server_set_fsinfo(struct nfs_server *server,

	server->maxfilesize = fsinfo->maxfilesize;

	server->time_delta = fsinfo->time_delta;
	server->change_attr_type = fsinfo->change_attr_type;

	server->clone_blksize = fsinfo->clone_blksize;
@@ -936,7 +973,6 @@ void nfs_server_copy_userdata(struct nfs_server *target, struct nfs_server *sour
	target->acregmax = source->acregmax;
	target->acdirmin = source->acdirmin;
	target->acdirmax = source->acdirmax;
	target->caps = source->caps;
	target->options = source->options;
	target->auth_info = source->auth_info;
	target->port = source->port;
@@ -1007,6 +1043,7 @@ struct nfs_server *nfs_alloc_server(void)
	INIT_LIST_HEAD(&server->ss_src_copies);

	atomic_set(&server->active, 0);
	atomic_long_set(&server->nr_active_delegations, 0);

	server->io_stats = nfs_alloc_iostats();
	if (!server->io_stats) {
@@ -1170,6 +1207,8 @@ struct nfs_server *nfs_clone_server(struct nfs_server *source,
	if (error < 0)
		goto out_free_server;

	nfs_server_set_init_caps(server);

	/* probe the filesystem info for this server filesystem */
	error = nfs_probe_server(server, fh);
	if (error < 0)
+73 −41
Original line number Diff line number Diff line
@@ -27,8 +27,15 @@

#define NFS_DEFAULT_DELEGATION_WATERMARK (5000U)

static atomic_long_t nfs_active_delegations;
static unsigned nfs_delegation_watermark = NFS_DEFAULT_DELEGATION_WATERMARK;
module_param_named(delegation_watermark, nfs_delegation_watermark, uint, 0644);

static struct hlist_head *nfs_delegation_hash(struct nfs_server *server,
		const struct nfs_fh *fhandle)
{
	return server->delegation_hash_table +
		(nfs_fhandle_hash(fhandle) & server->delegation_hash_mask);
}

static void __nfs_free_delegation(struct nfs_delegation *delegation)
{
@@ -37,11 +44,12 @@ static void __nfs_free_delegation(struct nfs_delegation *delegation)
	kfree_rcu(delegation, rcu);
}

static void nfs_mark_delegation_revoked(struct nfs_delegation *delegation)
static void nfs_mark_delegation_revoked(struct nfs_server *server,
		struct nfs_delegation *delegation)
{
	if (!test_and_set_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
		delegation->stateid.type = NFS4_INVALID_STATEID_TYPE;
		atomic_long_dec(&nfs_active_delegations);
		atomic_long_dec(&server->nr_active_delegations);
		if (!test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
			nfs_clear_verifier_delegated(delegation->inode);
	}
@@ -59,9 +67,10 @@ static void nfs_put_delegation(struct nfs_delegation *delegation)
		__nfs_free_delegation(delegation);
}

static void nfs_free_delegation(struct nfs_delegation *delegation)
static void nfs_free_delegation(struct nfs_server *server,
		struct nfs_delegation *delegation)
{
	nfs_mark_delegation_revoked(delegation);
	nfs_mark_delegation_revoked(server, delegation);
	nfs_put_delegation(delegation);
}

@@ -237,7 +246,13 @@ void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,

	rcu_read_lock();
	delegation = rcu_dereference(NFS_I(inode)->delegation);
	if (delegation != NULL) {
	if (!delegation) {
		rcu_read_unlock();
		nfs_inode_set_delegation(inode, cred, type, stateid,
					 pagemod_limit, deleg_type);
		return;
	}

	spin_lock(&delegation->lock);
	nfs4_stateid_copy(&delegation->stateid, stateid);
	delegation->type = type;
@@ -253,18 +268,12 @@ void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
		clear_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags);
	}
	clear_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
		if (test_and_clear_bit(NFS_DELEGATION_REVOKED,
				       &delegation->flags))
			atomic_long_inc(&nfs_active_delegations);
	if (test_and_clear_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
		atomic_long_inc(&NFS_SERVER(inode)->nr_active_delegations);
	spin_unlock(&delegation->lock);
	rcu_read_unlock();
	put_cred(oldcred);
	trace_nfs4_reclaim_delegation(inode, type);
	} else {
		rcu_read_unlock();
		nfs_inode_set_delegation(inode, cred, type, stateid,
					 pagemod_limit, deleg_type);
	}
}

static int nfs_do_return_delegation(struct inode *inode,
@@ -355,6 +364,8 @@ nfs_detach_delegation_locked(struct nfs_inode *nfsi,
		rcu_dereference_protected(nfsi->delegation,
				lockdep_is_held(&clp->cl_lock));

	trace_nfs4_detach_delegation(&nfsi->vfs_inode, delegation->type);

	if (deleg_cur == NULL || delegation != deleg_cur)
		return NULL;

@@ -363,6 +374,7 @@ nfs_detach_delegation_locked(struct nfs_inode *nfsi,
		spin_unlock(&delegation->lock);
		return NULL;
	}
	hlist_del_init_rcu(&delegation->hash);
	list_del_rcu(&delegation->super_list);
	delegation->inode = NULL;
	rcu_assign_pointer(nfsi->delegation, NULL);
@@ -410,7 +422,8 @@ nfs_update_delegation_cred(struct nfs_delegation *delegation,
}

static void
nfs_update_inplace_delegation(struct nfs_delegation *delegation,
nfs_update_inplace_delegation(struct nfs_server *server,
		struct nfs_delegation *delegation,
		const struct nfs_delegation *update)
{
	if (nfs4_stateid_is_newer(&update->stateid, &delegation->stateid)) {
@@ -423,7 +436,7 @@ nfs_update_inplace_delegation(struct nfs_delegation *delegation,
			nfs_update_delegation_cred(delegation, update->cred);
			/* smp_mb__before_atomic() is implicit due to xchg() */
			clear_bit(NFS_DELEGATION_REVOKED, &delegation->flags);
			atomic_long_inc(&nfs_active_delegations);
			atomic_long_inc(&server->nr_active_delegations);
		}
	}
}
@@ -478,7 +491,7 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
	if (nfs4_stateid_match_other(&old_delegation->stateid,
				&delegation->stateid)) {
		spin_lock(&old_delegation->lock);
		nfs_update_inplace_delegation(old_delegation,
		nfs_update_inplace_delegation(server, old_delegation,
				delegation);
		spin_unlock(&old_delegation->lock);
		goto out;
@@ -524,10 +537,12 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
	spin_unlock(&inode->i_lock);

	list_add_tail_rcu(&delegation->super_list, &server->delegations);
	hlist_add_head_rcu(&delegation->hash,
			nfs_delegation_hash(server, &NFS_I(inode)->fh));
	rcu_assign_pointer(nfsi->delegation, delegation);
	delegation = NULL;

	atomic_long_inc(&nfs_active_delegations);
	atomic_long_inc(&server->nr_active_delegations);

	trace_nfs4_set_delegation(inode, type);

@@ -541,7 +556,7 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
		__nfs_free_delegation(delegation);
	if (freeme != NULL) {
		nfs_do_return_delegation(inode, freeme, 0);
		nfs_free_delegation(freeme);
		nfs_free_delegation(server, freeme);
	}
	return status;
}
@@ -592,6 +607,8 @@ static bool nfs_delegation_need_return(struct nfs_delegation *delegation)
{
	bool ret = false;

	trace_nfs_delegation_need_return(delegation);

	if (test_and_clear_bit(NFS_DELEGATION_RETURN, &delegation->flags))
		ret = true;
	if (test_bit(NFS_DELEGATION_RETURNING, &delegation->flags) ||
@@ -751,7 +768,7 @@ void nfs_inode_evict_delegation(struct inode *inode)
		set_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
		set_bit(NFS_DELEGATION_INODE_FREEING, &delegation->flags);
		nfs_do_return_delegation(inode, delegation, 1);
		nfs_free_delegation(delegation);
		nfs_free_delegation(NFS_SERVER(inode), delegation);
	}
}

@@ -837,7 +854,8 @@ void nfs4_inode_return_delegation_on_close(struct inode *inode)
	if (!delegation)
		goto out;
	if (test_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags) ||
	    atomic_long_read(&nfs_active_delegations) >= nfs_delegation_watermark) {
	    atomic_long_read(&NFS_SERVER(inode)->nr_active_delegations) >=
	    nfs_delegation_watermark) {
		spin_lock(&delegation->lock);
		if (delegation->inode &&
		    list_empty(&NFS_I(inode)->open_files) &&
@@ -1013,7 +1031,7 @@ static void nfs_revoke_delegation(struct inode *inode,
		}
		spin_unlock(&delegation->lock);
	}
	nfs_mark_delegation_revoked(delegation);
	nfs_mark_delegation_revoked(NFS_SERVER(inode), delegation);
	ret = true;
out:
	rcu_read_unlock();
@@ -1045,7 +1063,7 @@ void nfs_delegation_mark_returned(struct inode *inode,
			delegation->stateid.seqid = stateid->seqid;
	}

	nfs_mark_delegation_revoked(delegation);
	nfs_mark_delegation_revoked(NFS_SERVER(inode), delegation);
	clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
	spin_unlock(&delegation->lock);
	if (nfs_detach_delegation(NFS_I(inode), delegation, NFS_SERVER(inode)))
@@ -1158,11 +1176,12 @@ static struct inode *
nfs_delegation_find_inode_server(struct nfs_server *server,
				 const struct nfs_fh *fhandle)
{
	struct hlist_head *head = nfs_delegation_hash(server, fhandle);
	struct nfs_delegation *delegation;
	struct super_block *freeme = NULL;
	struct inode *res = NULL;

	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
	hlist_for_each_entry_rcu(delegation, head, hash) {
		spin_lock(&delegation->lock);
		if (delegation->inode != NULL &&
		    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) &&
@@ -1265,7 +1284,7 @@ static int nfs_server_reap_unclaimed_delegations(struct nfs_server *server,
		if (delegation != NULL) {
			if (nfs_detach_delegation(NFS_I(inode), delegation,
						server) != NULL)
				nfs_free_delegation(delegation);
				nfs_free_delegation(server, delegation);
			/* Match nfs_start_delegation_return_locked */
			nfs_put_delegation(delegation);
		}
@@ -1570,4 +1589,17 @@ bool nfs4_delegation_flush_on_close(const struct inode *inode)
	return ret;
}

module_param_named(delegation_watermark, nfs_delegation_watermark, uint, 0644);
int nfs4_delegation_hash_alloc(struct nfs_server *server)
{
	int delegation_buckets, i;

	delegation_buckets = roundup_pow_of_two(nfs_delegation_watermark / 16);
	server->delegation_hash_mask = delegation_buckets - 1;
	server->delegation_hash_table = kmalloc_array(delegation_buckets,
			sizeof(*server->delegation_hash_table), GFP_KERNEL);
	if (!server->delegation_hash_table)
		return -ENOMEM;
	for (i = 0; i < delegation_buckets; i++)
		INIT_HLIST_HEAD(&server->delegation_hash_table[i]);
	return 0;
}
Loading