Commit 24c8dbbb authored by David Howells's avatar David Howells Committed by Trond Myklebust
Browse files

NFS: Generalise the nfs_client structure



Generalise the nfs_client structure by:

 (1) Moving nfs_client to a more general place (nfs_fs_sb.h).

 (2) Renaming its maintenance routines to be non-NFS4 specific.

 (3) Move those maintenance routines to a new non-NFS4 specific file (client.c)
     and move the declarations to internal.h.

 (4) Make nfs_find/get_client() take a full sockaddr_in to include the port
     number (will be required for NFS2/3).

 (5) Make nfs_find/get_client() take the NFS protocol version (again will be
     required to differentiate NFS2, 3 & 4 client records).

Also:

 (6) Make nfs_client construction proceed akin to inodes, marking them as under
     construction and providing a function to indicate completion.

 (7) Make nfs_get_client() wait interruptibly if it finds a client that it can
     share, but that client is currently being constructed.

 (8) Make nfs4_create_client() use (6) and (7) instead of locking cl_sem.

Signed-Off-By: default avatarDavid Howells <dhowells@redhat.com>
Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent e9326dca
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -4,9 +4,9 @@

obj-$(CONFIG_NFS_FS) += nfs.o

nfs-y 			:= dir.o file.o inode.o super.o nfs2xdr.o pagelist.o \
			   proc.o read.o symlink.o unlink.o write.o \
			   namespace.o
nfs-y 			:= client.o dir.o file.o inode.o super.o nfs2xdr.o \
			   pagelist.o proc.o read.o symlink.o unlink.o \
			   write.o namespace.o
nfs-$(CONFIG_ROOT_NFS)	+= nfsroot.o mount_clnt.o      
nfs-$(CONFIG_NFS_V3)	+= nfs3proc.o nfs3xdr.o
nfs-$(CONFIG_NFS_V3_ACL)	+= nfs3acl.o
+5 −4
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@

#include "nfs4_fs.h"
#include "callback.h"
#include "internal.h"

#define NFSDBG_FACILITY NFSDBG_CALLBACK

@@ -166,15 +167,15 @@ void nfs_callback_down(void)

static int nfs_callback_authenticate(struct svc_rqst *rqstp)
{
	struct in_addr *addr = &rqstp->rq_addr.sin_addr;
	struct sockaddr_in *addr = &rqstp->rq_addr;
	struct nfs_client *clp;

	/* Don't talk to strangers */
	clp = nfs4_find_client(addr);
	clp = nfs_find_client(addr, 4);
	if (clp == NULL)
		return SVC_DROP;
	dprintk("%s: %u.%u.%u.%u NFSv4 callback!\n", __FUNCTION__, NIPQUAD(addr));
	nfs4_put_client(clp);
	dprintk("%s: %u.%u.%u.%u NFSv4 callback!\n", __FUNCTION__, NIPQUAD(addr->sin_addr));
	nfs_put_client(clp);
	switch (rqstp->rq_authop->flavour) {
		case RPC_AUTH_NULL:
			if (rqstp->rq_proc != CB_NULL)
+5 −4
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#include "nfs4_fs.h"
#include "callback.h"
#include "delegation.h"
#include "internal.h"

#define NFSDBG_FACILITY NFSDBG_CALLBACK
 
@@ -22,7 +23,7 @@ unsigned nfs4_callback_getattr(struct cb_getattrargs *args, struct cb_getattrres
	
	res->bitmap[0] = res->bitmap[1] = 0;
	res->status = htonl(NFS4ERR_BADHANDLE);
	clp = nfs4_find_client(&args->addr->sin_addr);
	clp = nfs_find_client(args->addr, 4);
	if (clp == NULL)
		goto out;
	inode = nfs_delegation_find_inode(clp, &args->fh);
@@ -48,7 +49,7 @@ unsigned nfs4_callback_getattr(struct cb_getattrargs *args, struct cb_getattrres
	up_read(&nfsi->rwsem);
	iput(inode);
out_putclient:
	nfs4_put_client(clp);
	nfs_put_client(clp);
out:
	dprintk("%s: exit with status = %d\n", __FUNCTION__, ntohl(res->status));
	return res->status;
@@ -61,7 +62,7 @@ unsigned nfs4_callback_recall(struct cb_recallargs *args, void *dummy)
	unsigned res;
	
	res = htonl(NFS4ERR_BADHANDLE);
	clp = nfs4_find_client(&args->addr->sin_addr);
	clp = nfs_find_client(args->addr, 4);
	if (clp == NULL)
		goto out;
	inode = nfs_delegation_find_inode(clp, &args->fh);
@@ -80,7 +81,7 @@ unsigned nfs4_callback_recall(struct cb_recallargs *args, void *dummy)
	}
	iput(inode);
out_putclient:
	nfs4_put_client(clp);
	nfs_put_client(clp);
out:
	dprintk("%s: exit with status = %d\n", __FUNCTION__, ntohl(res));
	return res;

fs/nfs/client.c

0 → 100644
+312 −0
Original line number Diff line number Diff line
/* client.c: NFS client sharing and management code
 *
 * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */


#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>

#include <linux/time.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <linux/sunrpc/clnt.h>
#include <linux/sunrpc/stats.h>
#include <linux/sunrpc/metrics.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_mount.h>
#include <linux/nfs4_mount.h>
#include <linux/lockd/bind.h>
#include <linux/smp_lock.h>
#include <linux/seq_file.h>
#include <linux/mount.h>
#include <linux/nfs_idmap.h>
#include <linux/vfs.h>
#include <linux/inet.h>
#include <linux/nfs_xdr.h>

#include <asm/system.h>

#include "nfs4_fs.h"
#include "callback.h"
#include "delegation.h"
#include "iostat.h"
#include "internal.h"

#define NFSDBG_FACILITY		NFSDBG_CLIENT

static DEFINE_SPINLOCK(nfs_client_lock);
static LIST_HEAD(nfs_client_list);
static DECLARE_WAIT_QUEUE_HEAD(nfs_client_active_wq);

/*
 * Allocate a shared client record
 *
 * Since these are allocated/deallocated very rarely, we don't
 * bother putting them in a slab cache...
 */
static struct nfs_client *nfs_alloc_client(const char *hostname,
					   const struct sockaddr_in *addr,
					   int nfsversion)
{
	struct nfs_client *clp;
	int error;

	if ((clp = kzalloc(sizeof(*clp), GFP_KERNEL)) == NULL)
		goto error_0;

	error = rpciod_up();
	if (error < 0) {
		dprintk("%s: couldn't start rpciod! Error = %d\n",
				__FUNCTION__, error);
		__set_bit(NFS_CS_RPCIOD, &clp->cl_res_state);
		goto error_1;
	}

	if (nfsversion == 4) {
		if (nfs_callback_up() < 0)
			goto error_2;
		__set_bit(NFS_CS_CALLBACK, &clp->cl_res_state);
	}

	atomic_set(&clp->cl_count, 1);
	clp->cl_cons_state = NFS_CS_INITING;

	clp->cl_nfsversion = nfsversion;
	memcpy(&clp->cl_addr, addr, sizeof(clp->cl_addr));

	if (hostname) {
		clp->cl_hostname = kstrdup(hostname, GFP_KERNEL);
		if (!clp->cl_hostname)
			goto error_3;
	}

	INIT_LIST_HEAD(&clp->cl_superblocks);
	clp->cl_rpcclient = ERR_PTR(-EINVAL);

#ifdef CONFIG_NFS_V4
	init_rwsem(&clp->cl_sem);
	INIT_LIST_HEAD(&clp->cl_delegations);
	INIT_LIST_HEAD(&clp->cl_state_owners);
	INIT_LIST_HEAD(&clp->cl_unused);
	spin_lock_init(&clp->cl_lock);
	INIT_WORK(&clp->cl_renewd, nfs4_renew_state, clp);
	rpc_init_wait_queue(&clp->cl_rpcwaitq, "NFS client");
	clp->cl_boot_time = CURRENT_TIME;
	clp->cl_state = 1 << NFS4CLNT_LEASE_EXPIRED;
#endif

	return clp;

error_3:
	nfs_callback_down();
	__clear_bit(NFS_CS_CALLBACK, &clp->cl_res_state);
error_2:
	rpciod_down();
	__clear_bit(NFS_CS_RPCIOD, &clp->cl_res_state);
error_1:
	kfree(clp);
error_0:
	return NULL;
}

/*
 * Destroy a shared client record
 */
static void nfs_free_client(struct nfs_client *clp)
{
	dprintk("--> nfs_free_client(%d)\n", clp->cl_nfsversion);

#ifdef CONFIG_NFS_V4
	if (__test_and_clear_bit(NFS_CS_IDMAP, &clp->cl_res_state)) {
		while (!list_empty(&clp->cl_unused)) {
			struct nfs4_state_owner *sp;

			sp = list_entry(clp->cl_unused.next,
					struct nfs4_state_owner,
					so_list);
			list_del(&sp->so_list);
			kfree(sp);
		}
		BUG_ON(!list_empty(&clp->cl_state_owners));
		nfs_idmap_delete(clp);
	}
#endif

	/* -EIO all pending I/O */
	if (!IS_ERR(clp->cl_rpcclient))
		rpc_shutdown_client(clp->cl_rpcclient);

	if (__test_and_clear_bit(NFS_CS_CALLBACK, &clp->cl_res_state))
		nfs_callback_down();

	if (__test_and_clear_bit(NFS_CS_RPCIOD, &clp->cl_res_state))
	rpciod_down();

	kfree(clp->cl_hostname);
	kfree(clp);

	dprintk("<-- nfs_free_client()\n");
}

/*
 * Release a reference to a shared client record
 */
void nfs_put_client(struct nfs_client *clp)
{
	dprintk("--> nfs_put_client({%d})\n", atomic_read(&clp->cl_count));

	if (atomic_dec_and_lock(&clp->cl_count, &nfs_client_lock)) {
		list_del(&clp->cl_share_link);
		spin_unlock(&nfs_client_lock);

		BUG_ON(!list_empty(&clp->cl_superblocks));

		nfs_free_client(clp);
	}
}

/*
 * Find a client by address
 * - caller must hold nfs_client_lock
 */
static struct nfs_client *__nfs_find_client(const struct sockaddr_in *addr, int nfsversion)
{
	struct nfs_client *clp;

	list_for_each_entry(clp, &nfs_client_list, cl_share_link) {
		/* Different NFS versions cannot share the same nfs_client */
		if (clp->cl_nfsversion != nfsversion)
			continue;

		if (memcmp(&clp->cl_addr.sin_addr, &addr->sin_addr,
			   sizeof(clp->cl_addr.sin_addr)) != 0)
			continue;

		if (clp->cl_addr.sin_port == addr->sin_port)
			goto found;
	}

	return NULL;

found:
	atomic_inc(&clp->cl_count);
	return clp;
}

/*
 * Find a client by IP address and protocol version
 * - returns NULL if no such client
 */
struct nfs_client *nfs_find_client(const struct sockaddr_in *addr, int nfsversion)
{
	struct nfs_client *clp;

	spin_lock(&nfs_client_lock);
	clp = __nfs_find_client(addr, nfsversion);
	spin_unlock(&nfs_client_lock);

	BUG_ON(clp->cl_cons_state == 0);

	return clp;
}

/*
 * Look up a client by IP address and protocol version
 * - creates a new record if one doesn't yet exist
 */
struct nfs_client *nfs_get_client(const char *hostname,
				  const struct sockaddr_in *addr,
				  int nfsversion)
{
	struct nfs_client *clp, *new = NULL;
	int error;

	dprintk("--> nfs_get_client(%s,"NIPQUAD_FMT":%d,%d)\n",
		hostname ?: "", NIPQUAD(addr->sin_addr),
		addr->sin_port, nfsversion);

	/* see if the client already exists */
	do {
		spin_lock(&nfs_client_lock);

		clp = __nfs_find_client(addr, nfsversion);
		if (clp)
			goto found_client;
		if (new)
			goto install_client;

		spin_unlock(&nfs_client_lock);

		new = nfs_alloc_client(hostname, addr, nfsversion);
	} while (new);

	return ERR_PTR(-ENOMEM);

	/* install a new client and return with it unready */
install_client:
	clp = new;
	list_add(&clp->cl_share_link, &nfs_client_list);
	spin_unlock(&nfs_client_lock);
	dprintk("--> nfs_get_client() = %p [new]\n", clp);
	return clp;

	/* found an existing client
	 * - make sure it's ready before returning
	 */
found_client:
	spin_unlock(&nfs_client_lock);

	if (new)
		nfs_free_client(new);

	if (clp->cl_cons_state == NFS_CS_INITING) {
		DECLARE_WAITQUEUE(myself, current);

		add_wait_queue(&nfs_client_active_wq, &myself);

		for (;;) {
			set_current_state(TASK_INTERRUPTIBLE);
			if (signal_pending(current) ||
			    clp->cl_cons_state > NFS_CS_READY)
				break;
			schedule();
		}

		remove_wait_queue(&nfs_client_active_wq, &myself);

		if (signal_pending(current)) {
			nfs_put_client(clp);
			return ERR_PTR(-ERESTARTSYS);
		}
	}

	if (clp->cl_cons_state < NFS_CS_READY) {
		error = clp->cl_cons_state;
		nfs_put_client(clp);
		return ERR_PTR(error);
	}

	dprintk("--> nfs_get_client() = %p [share]\n", clp);
	return clp;
}

/*
 * Mark a server as ready or failed
 */
void nfs_mark_client_ready(struct nfs_client *clp, int state)
{
	clp->cl_cons_state = state;
	wake_up_all(&nfs_client_active_wq);
}
+5 −4
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

#include "nfs4_fs.h"
#include "delegation.h"
#include "internal.h"

static struct nfs_delegation *nfs_alloc_delegation(void)
{
@@ -145,7 +146,7 @@ int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct
					sizeof(delegation->stateid)) != 0 ||
				delegation->type != nfsi->delegation->type) {
			printk("%s: server %u.%u.%u.%u, handed out a duplicate delegation!\n",
					__FUNCTION__, NIPQUAD(clp->cl_addr));
					__FUNCTION__, NIPQUAD(clp->cl_addr.sin_addr));
			status = -EIO;
		}
	}
@@ -254,7 +255,7 @@ int nfs_do_expire_all_delegations(void *ptr)
	}
out:
	spin_unlock(&clp->cl_lock);
	nfs4_put_client(clp);
	nfs_put_client(clp);
	module_put_and_exit(0);
}

@@ -266,10 +267,10 @@ void nfs_expire_all_delegations(struct nfs_client *clp)
	atomic_inc(&clp->cl_count);
	task = kthread_run(nfs_do_expire_all_delegations, clp,
			"%u.%u.%u.%u-delegreturn",
			NIPQUAD(clp->cl_addr));
			NIPQUAD(clp->cl_addr.sin_addr));
	if (!IS_ERR(task))
		return;
	nfs4_put_client(clp);
	nfs_put_client(clp);
	module_put(THIS_MODULE);
}

Loading