2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2001 The Regents of the University of Michigan.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Kendrick Smith <kmsmith@umich.edu>
|
|
|
|
* Andy Adamson <kandros@umich.edu>
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
*
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* 3. Neither the name of the University nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived
|
|
|
|
* from this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2008-02-16 05:37:31 +07:00
|
|
|
#include <linux/file.h>
|
2010-09-18 20:09:31 +07:00
|
|
|
#include <linux/fs.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2005-06-24 12:04:32 +07:00
|
|
|
#include <linux/namei.h>
|
2007-07-17 18:04:39 +07:00
|
|
|
#include <linux/swap.h>
|
2011-07-13 21:50:48 +07:00
|
|
|
#include <linux/pagemap.h>
|
2012-05-30 00:56:37 +07:00
|
|
|
#include <linux/ratelimit.h>
|
2008-12-24 04:17:15 +07:00
|
|
|
#include <linux/sunrpc/svcauth_gss.h>
|
2013-02-05 00:50:00 +07:00
|
|
|
#include <linux/sunrpc/addr.h>
|
2014-12-10 22:33:11 +07:00
|
|
|
#include <linux/jhash.h>
|
2009-12-04 01:30:56 +07:00
|
|
|
#include "xdr4.h"
|
2013-04-09 22:34:36 +07:00
|
|
|
#include "xdr4cb.h"
|
2009-11-05 06:12:35 +07:00
|
|
|
#include "vfs.h"
|
2012-04-26 03:49:18 +07:00
|
|
|
#include "current_stateid.h"
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-07-25 19:56:58 +07:00
|
|
|
#include "netns.h"
|
nfsd: implement pNFS operations
Add support for the GETDEVICEINFO, LAYOUTGET, LAYOUTCOMMIT and
LAYOUTRETURN NFSv4.1 operations, as well as backing code to manage
outstanding layouts and devices.
Layout management is very straight forward, with a nfs4_layout_stateid
structure that extends nfs4_stid to manage layout stateids as the
top-level structure. It is linked into the nfs4_file and nfs4_client
structures like the other stateids, and contains a linked list of
layouts that hang of the stateid. The actual layout operations are
implemented in layout drivers that are not part of this commit, but
will be added later.
The worst part of this commit is the management of the pNFS device IDs,
which suffers from a specification that is not sanely implementable due
to the fact that the device-IDs are global and not bound to an export,
and have a small enough size so that we can't store the fsid portion of
a file handle, and must never be reused. As we still do need perform all
export authentication and validation checks on a device ID passed to
GETDEVICEINFO we are caught between a rock and a hard place. To work
around this issue we add a new hash that maps from a 64-bit integer to a
fsid so that we can look up the export to authenticate against it,
a 32-bit integer as a generation that we can bump when changing the device,
and a currently unused 32-bit integer that could be used in the future
to handle more than a single device per export. Entries in this hash
table are never deleted as we can't reuse the ids anyway, and would have
a severe lifetime problem anyway as Linux export structures are temporary
structures that can go away under load.
Parts of the XDR data, structures and marshaling/unmarshaling code, as
well as many concepts are derived from the old pNFS server implementation
from Andy Adamson, Benny Halevy, Dean Hildebrand, Marc Eshel, Fred Isaman,
Mike Sager, Ricardo Labiaga and many others.
Signed-off-by: Christoph Hellwig <hch@lst.de>
2014-05-05 18:11:59 +07:00
|
|
|
#include "pnfs.h"
|
2012-07-25 19:56:58 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
#define NFSDDBG_FACILITY NFSDDBG_PROC
|
|
|
|
|
2011-12-13 03:00:35 +07:00
|
|
|
#define all_ones {{~0,~0},~0}
|
|
|
|
static const stateid_t one_stateid = {
|
|
|
|
.si_generation = ~0,
|
|
|
|
.si_opaque = all_ones,
|
|
|
|
};
|
|
|
|
static const stateid_t zero_stateid = {
|
|
|
|
/* all fields zero */
|
|
|
|
};
|
2012-02-14 04:55:23 +07:00
|
|
|
static const stateid_t currentstateid = {
|
|
|
|
.si_generation = 1,
|
|
|
|
};
|
2011-12-13 03:00:35 +07:00
|
|
|
|
2009-04-03 12:28:28 +07:00
|
|
|
static u64 current_sessionid = 1;
|
2005-06-24 12:04:03 +07:00
|
|
|
|
2011-12-13 03:00:35 +07:00
|
|
|
#define ZERO_STATEID(stateid) (!memcmp((stateid), &zero_stateid, sizeof(stateid_t)))
|
|
|
|
#define ONE_STATEID(stateid) (!memcmp((stateid), &one_stateid, sizeof(stateid_t)))
|
2012-02-14 04:55:23 +07:00
|
|
|
#define CURRENT_STATEID(stateid) (!memcmp((stateid), ¤tstateid, sizeof(stateid_t)))
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* forward declarations */
|
2014-07-24 03:17:41 +07:00
|
|
|
static bool check_for_locks(struct nfs4_file *fp, struct nfs4_lockowner *lowner);
|
2014-07-30 08:34:06 +07:00
|
|
|
static void nfs4_free_ol_stateid(struct nfs4_stid *stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-02-23 05:51:34 +07:00
|
|
|
/* Locking: */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Currently used for the del_recall_lru and file hash table. In an
|
|
|
|
* effort to decrease the scope of the client_mutex, this spinlock may
|
|
|
|
* eventually cover more:
|
|
|
|
*/
|
2014-05-30 20:09:30 +07:00
|
|
|
static DEFINE_SPINLOCK(state_lock);
|
2009-02-23 05:51:34 +07:00
|
|
|
|
2014-07-30 08:34:33 +07:00
|
|
|
/*
|
|
|
|
* A waitqueue for all in-progress 4.0 CLOSE operations that are waiting for
|
|
|
|
* the refcount on the open stateid to drop.
|
|
|
|
*/
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(close_wq);
|
|
|
|
|
2014-05-21 21:43:03 +07:00
|
|
|
static struct kmem_cache *openowner_slab;
|
|
|
|
static struct kmem_cache *lockowner_slab;
|
|
|
|
static struct kmem_cache *file_slab;
|
|
|
|
static struct kmem_cache *stateid_slab;
|
|
|
|
static struct kmem_cache *deleg_slab;
|
2015-04-27 19:50:14 +07:00
|
|
|
static struct kmem_cache *odstate_slab;
|
2005-06-24 12:03:01 +07:00
|
|
|
|
2013-03-19 23:05:39 +07:00
|
|
|
static void free_session(struct nfsd4_session *);
|
2012-02-24 08:40:52 +07:00
|
|
|
|
2015-11-22 04:57:39 +07:00
|
|
|
static const struct nfsd4_callback_ops nfsd4_cb_recall_ops;
|
2016-09-17 03:28:24 +07:00
|
|
|
static const struct nfsd4_callback_ops nfsd4_cb_notify_lock_ops;
|
2014-09-24 17:19:19 +07:00
|
|
|
|
2013-06-19 01:26:02 +07:00
|
|
|
static bool is_session_dead(struct nfsd4_session *ses)
|
2013-03-19 23:05:39 +07:00
|
|
|
{
|
2013-06-19 01:26:02 +07:00
|
|
|
return ses->se_flags & NFS4_SESSION_DEAD;
|
2013-03-19 23:05:39 +07:00
|
|
|
}
|
|
|
|
|
2013-06-19 01:26:02 +07:00
|
|
|
static __be32 mark_session_dead_locked(struct nfsd4_session *ses, int ref_held_by_me)
|
2012-02-24 08:40:52 +07:00
|
|
|
{
|
2013-06-19 01:26:02 +07:00
|
|
|
if (atomic_read(&ses->se_ref) > ref_held_by_me)
|
2013-03-19 23:05:39 +07:00
|
|
|
return nfserr_jukebox;
|
|
|
|
ses->se_flags |= NFS4_SESSION_DEAD;
|
|
|
|
return nfs_ok;
|
2012-02-24 08:40:52 +07:00
|
|
|
}
|
|
|
|
|
2013-04-02 09:23:49 +07:00
|
|
|
static bool is_client_expired(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
return clp->cl_time == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static __be32 get_client_locked(struct nfs4_client *clp)
|
|
|
|
{
|
2014-07-30 19:27:10 +07:00
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
|
|
|
|
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
2013-04-02 09:23:49 +07:00
|
|
|
if (is_client_expired(clp))
|
|
|
|
return nfserr_expired;
|
|
|
|
atomic_inc(&clp->cl_refcount);
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* must be called under the client_lock */
|
|
|
|
static inline void
|
|
|
|
renew_client_locked(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
|
|
|
|
|
|
|
if (is_client_expired(clp)) {
|
|
|
|
WARN_ON(1);
|
|
|
|
printk("%s: client (clientid %08x/%08x) already expired\n",
|
|
|
|
__func__,
|
|
|
|
clp->cl_clientid.cl_boot,
|
|
|
|
clp->cl_clientid.cl_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dprintk("renewing client (clientid %08x/%08x)\n",
|
|
|
|
clp->cl_clientid.cl_boot,
|
|
|
|
clp->cl_clientid.cl_id);
|
|
|
|
list_move_tail(&clp->cl_lru, &nn->client_lru);
|
|
|
|
clp->cl_time = get_seconds();
|
|
|
|
}
|
|
|
|
|
2013-04-17 09:14:15 +07:00
|
|
|
static void put_client_renew_locked(struct nfs4_client *clp)
|
2013-04-02 09:23:49 +07:00
|
|
|
{
|
2014-07-30 19:27:10 +07:00
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
|
|
|
|
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
2013-04-02 09:23:49 +07:00
|
|
|
if (!atomic_dec_and_test(&clp->cl_refcount))
|
|
|
|
return;
|
|
|
|
if (!is_client_expired(clp))
|
|
|
|
renew_client_locked(clp);
|
|
|
|
}
|
|
|
|
|
2014-06-30 22:48:44 +07:00
|
|
|
static void put_client_renew(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
|
|
|
|
2014-07-09 01:02:50 +07:00
|
|
|
if (!atomic_dec_and_lock(&clp->cl_refcount, &nn->client_lock))
|
|
|
|
return;
|
|
|
|
if (!is_client_expired(clp))
|
|
|
|
renew_client_locked(clp);
|
2014-06-30 22:48:44 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
}
|
|
|
|
|
2014-06-30 22:48:42 +07:00
|
|
|
static __be32 nfsd4_get_session_locked(struct nfsd4_session *ses)
|
|
|
|
{
|
|
|
|
__be32 status;
|
|
|
|
|
|
|
|
if (is_session_dead(ses))
|
|
|
|
return nfserr_badsession;
|
|
|
|
status = get_client_locked(ses->se_client);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
atomic_inc(&ses->se_ref);
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nfsd4_put_session_locked(struct nfsd4_session *ses)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp = ses->se_client;
|
2014-07-30 19:27:10 +07:00
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
|
|
|
|
|
|
|
lockdep_assert_held(&nn->client_lock);
|
2014-06-30 22:48:42 +07:00
|
|
|
|
|
|
|
if (atomic_dec_and_test(&ses->se_ref) && is_session_dead(ses))
|
|
|
|
free_session(ses);
|
|
|
|
put_client_renew_locked(clp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nfsd4_put_session(struct nfsd4_session *ses)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp = ses->se_client;
|
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
nfsd4_put_session_locked(ses);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
}
|
|
|
|
|
2016-09-17 03:28:24 +07:00
|
|
|
static struct nfsd4_blocked_lock *
|
|
|
|
find_blocked_lock(struct nfs4_lockowner *lo, struct knfsd_fh *fh,
|
|
|
|
struct nfsd_net *nn)
|
|
|
|
{
|
|
|
|
struct nfsd4_blocked_lock *cur, *found = NULL;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry(cur, &lo->lo_blocked, nbl_list) {
|
|
|
|
if (fh_match(fh, &cur->nbl_fh)) {
|
|
|
|
list_del_init(&cur->nbl_list);
|
2016-09-17 03:28:25 +07:00
|
|
|
list_del_init(&cur->nbl_lru);
|
2016-09-17 03:28:24 +07:00
|
|
|
found = cur;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
if (found)
|
|
|
|
posix_unblock_lock(&found->nbl_lock);
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfsd4_blocked_lock *
|
|
|
|
find_or_allocate_block(struct nfs4_lockowner *lo, struct knfsd_fh *fh,
|
|
|
|
struct nfsd_net *nn)
|
|
|
|
{
|
|
|
|
struct nfsd4_blocked_lock *nbl;
|
|
|
|
|
|
|
|
nbl = find_blocked_lock(lo, fh, nn);
|
|
|
|
if (!nbl) {
|
|
|
|
nbl= kmalloc(sizeof(*nbl), GFP_KERNEL);
|
|
|
|
if (nbl) {
|
|
|
|
fh_copy_shallow(&nbl->nbl_fh, fh);
|
|
|
|
locks_init_lock(&nbl->nbl_lock);
|
|
|
|
nfsd4_init_cb(&nbl->nbl_cb, lo->lo_owner.so_client,
|
|
|
|
&nfsd4_cb_notify_lock_ops,
|
|
|
|
NFSPROC4_CLNT_CB_NOTIFY_LOCK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nbl;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
free_blocked_lock(struct nfsd4_blocked_lock *nbl)
|
|
|
|
{
|
|
|
|
locks_release_private(&nbl->nbl_lock);
|
|
|
|
kfree(nbl);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
nfsd4_cb_notify_lock_done(struct nfsd4_callback *cb, struct rpc_task *task)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Since this is just an optimization, we don't try very hard if it
|
|
|
|
* turns out not to succeed. We'll requeue it on NFS4ERR_DELAY, and
|
|
|
|
* just quit trying on anything else.
|
|
|
|
*/
|
|
|
|
switch (task->tk_status) {
|
|
|
|
case -NFS4ERR_DELAY:
|
|
|
|
rpc_delay(task, 1 * HZ);
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd4_cb_notify_lock_release(struct nfsd4_callback *cb)
|
|
|
|
{
|
|
|
|
struct nfsd4_blocked_lock *nbl = container_of(cb,
|
|
|
|
struct nfsd4_blocked_lock, nbl_cb);
|
|
|
|
|
|
|
|
free_blocked_lock(nbl);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct nfsd4_callback_ops nfsd4_cb_notify_lock_ops = {
|
|
|
|
.done = nfsd4_cb_notify_lock_done,
|
|
|
|
.release = nfsd4_cb_notify_lock_release,
|
|
|
|
};
|
|
|
|
|
2014-08-22 21:18:43 +07:00
|
|
|
static inline struct nfs4_stateowner *
|
|
|
|
nfs4_get_stateowner(struct nfs4_stateowner *sop)
|
|
|
|
{
|
|
|
|
atomic_inc(&sop->so_count);
|
|
|
|
return sop;
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:34 +07:00
|
|
|
static int
|
2014-07-30 08:34:36 +07:00
|
|
|
same_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner)
|
2014-07-30 08:34:34 +07:00
|
|
|
{
|
|
|
|
return (sop->so_owner.len == owner->len) &&
|
2014-07-30 08:34:36 +07:00
|
|
|
0 == memcmp(sop->so_owner.data, owner->data, owner->len);
|
2014-07-30 08:34:34 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfs4_openowner *
|
|
|
|
find_openstateowner_str_locked(unsigned int hashval, struct nfsd4_open *open,
|
2014-07-30 08:34:36 +07:00
|
|
|
struct nfs4_client *clp)
|
2014-07-30 08:34:34 +07:00
|
|
|
{
|
|
|
|
struct nfs4_stateowner *so;
|
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
lockdep_assert_held(&clp->cl_lock);
|
2014-07-30 08:34:34 +07:00
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
list_for_each_entry(so, &clp->cl_ownerstr_hashtbl[hashval],
|
|
|
|
so_strhash) {
|
2014-07-30 08:34:34 +07:00
|
|
|
if (!so->so_is_open_owner)
|
|
|
|
continue;
|
2014-08-22 21:18:43 +07:00
|
|
|
if (same_owner_str(so, &open->op_owner))
|
|
|
|
return openowner(nfs4_get_stateowner(so));
|
2014-07-30 08:34:34 +07:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfs4_openowner *
|
|
|
|
find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open,
|
2014-07-30 08:34:36 +07:00
|
|
|
struct nfs4_client *clp)
|
2014-07-30 08:34:34 +07:00
|
|
|
{
|
|
|
|
struct nfs4_openowner *oo;
|
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
oo = find_openstateowner_str_locked(hashval, open, clp);
|
|
|
|
spin_unlock(&clp->cl_lock);
|
2014-07-30 08:34:34 +07:00
|
|
|
return oo;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static inline u32
|
|
|
|
opaque_hashval(const void *ptr, int nbytes)
|
|
|
|
{
|
|
|
|
unsigned char *cptr = (unsigned char *) ptr;
|
|
|
|
|
|
|
|
u32 x = 0;
|
|
|
|
while (nbytes--) {
|
|
|
|
x *= 37;
|
|
|
|
x += *cptr++;
|
|
|
|
}
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
2014-10-23 19:01:02 +07:00
|
|
|
static void nfsd4_free_file_rcu(struct rcu_head *rcu)
|
2011-10-14 03:00:16 +07:00
|
|
|
{
|
2014-10-23 19:01:02 +07:00
|
|
|
struct nfs4_file *fp = container_of(rcu, struct nfs4_file, fi_rcu);
|
|
|
|
|
|
|
|
kmem_cache_free(file_slab, fp);
|
2011-10-14 03:00:16 +07:00
|
|
|
}
|
|
|
|
|
2014-08-14 13:50:16 +07:00
|
|
|
void
|
2005-06-24 12:03:10 +07:00
|
|
|
put_nfs4_file(struct nfs4_file *fi)
|
|
|
|
{
|
2014-07-16 21:31:57 +07:00
|
|
|
might_lock(&state_lock);
|
|
|
|
|
2014-05-30 20:09:30 +07:00
|
|
|
if (atomic_dec_and_lock(&fi->fi_ref, &state_lock)) {
|
2014-10-23 19:01:02 +07:00
|
|
|
hlist_del_rcu(&fi->fi_hash);
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_unlock(&state_lock);
|
2015-04-27 19:50:14 +07:00
|
|
|
WARN_ON_ONCE(!list_empty(&fi->fi_clnt_odstate));
|
2014-10-23 19:01:02 +07:00
|
|
|
WARN_ON_ONCE(!list_empty(&fi->fi_delegations));
|
|
|
|
call_rcu(&fi->fi_rcu, nfsd4_free_file_rcu);
|
2009-02-23 05:51:34 +07:00
|
|
|
}
|
2005-06-24 12:03:10 +07:00
|
|
|
}
|
|
|
|
|
2014-07-11 01:07:26 +07:00
|
|
|
static struct file *
|
|
|
|
__nfs4_get_fd(struct nfs4_file *f, int oflag)
|
|
|
|
{
|
|
|
|
if (f->fi_fds[oflag])
|
|
|
|
return get_file(f->fi_fds[oflag]);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct file *
|
|
|
|
find_writeable_file_locked(struct nfs4_file *f)
|
|
|
|
{
|
|
|
|
struct file *ret;
|
|
|
|
|
|
|
|
lockdep_assert_held(&f->fi_lock);
|
|
|
|
|
|
|
|
ret = __nfs4_get_fd(f, O_WRONLY);
|
|
|
|
if (!ret)
|
|
|
|
ret = __nfs4_get_fd(f, O_RDWR);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct file *
|
|
|
|
find_writeable_file(struct nfs4_file *f)
|
|
|
|
{
|
|
|
|
struct file *ret;
|
|
|
|
|
|
|
|
spin_lock(&f->fi_lock);
|
|
|
|
ret = find_writeable_file_locked(f);
|
|
|
|
spin_unlock(&f->fi_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct file *find_readable_file_locked(struct nfs4_file *f)
|
|
|
|
{
|
|
|
|
struct file *ret;
|
|
|
|
|
|
|
|
lockdep_assert_held(&f->fi_lock);
|
|
|
|
|
|
|
|
ret = __nfs4_get_fd(f, O_RDONLY);
|
|
|
|
if (!ret)
|
|
|
|
ret = __nfs4_get_fd(f, O_RDWR);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct file *
|
|
|
|
find_readable_file(struct nfs4_file *f)
|
|
|
|
{
|
|
|
|
struct file *ret;
|
|
|
|
|
|
|
|
spin_lock(&f->fi_lock);
|
|
|
|
ret = find_readable_file_locked(f);
|
|
|
|
spin_unlock(&f->fi_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-08-17 19:40:00 +07:00
|
|
|
struct file *
|
2014-07-11 01:07:26 +07:00
|
|
|
find_any_file(struct nfs4_file *f)
|
|
|
|
{
|
|
|
|
struct file *ret;
|
|
|
|
|
|
|
|
spin_lock(&f->fi_lock);
|
|
|
|
ret = __nfs4_get_fd(f, O_RDWR);
|
|
|
|
if (!ret) {
|
|
|
|
ret = __nfs4_get_fd(f, O_WRONLY);
|
|
|
|
if (!ret)
|
|
|
|
ret = __nfs4_get_fd(f, O_RDONLY);
|
|
|
|
}
|
|
|
|
spin_unlock(&f->fi_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-07-25 18:34:22 +07:00
|
|
|
static atomic_long_t num_delegations;
|
2013-02-23 07:35:47 +07:00
|
|
|
unsigned long max_delegations;
|
2006-04-11 12:55:41 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Open owner state (share locks)
|
|
|
|
*/
|
|
|
|
|
2011-11-08 05:23:30 +07:00
|
|
|
/* hash tables for lock and open owners */
|
|
|
|
#define OWNER_HASH_BITS 8
|
|
|
|
#define OWNER_HASH_SIZE (1 << OWNER_HASH_BITS)
|
|
|
|
#define OWNER_HASH_MASK (OWNER_HASH_SIZE - 1)
|
2006-04-11 12:55:41 +07:00
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
static unsigned int ownerstr_hashval(struct xdr_netobj *ownername)
|
2011-07-31 10:46:29 +07:00
|
|
|
{
|
|
|
|
unsigned int ret;
|
|
|
|
|
|
|
|
ret = opaque_hashval(ownername->data, ownername->len);
|
2011-11-08 05:23:30 +07:00
|
|
|
return ret & OWNER_HASH_MASK;
|
2011-07-31 10:46:29 +07:00
|
|
|
}
|
2006-04-11 12:55:41 +07:00
|
|
|
|
|
|
|
/* hash table for nfs4_file */
|
|
|
|
#define FILE_HASH_BITS 8
|
|
|
|
#define FILE_HASH_SIZE (1 << FILE_HASH_BITS)
|
2011-01-14 16:35:59 +07:00
|
|
|
|
2014-07-24 03:17:39 +07:00
|
|
|
static unsigned int nfsd_fh_hashval(struct knfsd_fh *fh)
|
2011-07-31 10:46:29 +07:00
|
|
|
{
|
2014-07-24 03:17:39 +07:00
|
|
|
return jhash2(fh->fh_base.fh_pad, XDR_QUADLEN(fh->fh_size), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int file_hashval(struct knfsd_fh *fh)
|
|
|
|
{
|
|
|
|
return nfsd_fh_hashval(fh) & (FILE_HASH_SIZE - 1);
|
|
|
|
}
|
|
|
|
|
2013-04-02 20:01:59 +07:00
|
|
|
static struct hlist_head file_hashtbl[FILE_HASH_SIZE];
|
2006-04-11 12:55:41 +07:00
|
|
|
|
2014-07-11 01:07:28 +07:00
|
|
|
static void
|
|
|
|
__nfs4_file_get_access(struct nfs4_file *fp, u32 access)
|
2013-08-24 04:55:18 +07:00
|
|
|
{
|
2014-07-11 01:07:33 +07:00
|
|
|
lockdep_assert_held(&fp->fi_lock);
|
|
|
|
|
2014-07-11 01:07:28 +07:00
|
|
|
if (access & NFS4_SHARE_ACCESS_WRITE)
|
|
|
|
atomic_inc(&fp->fi_access[O_WRONLY]);
|
|
|
|
if (access & NFS4_SHARE_ACCESS_READ)
|
|
|
|
atomic_inc(&fp->fi_access[O_RDONLY]);
|
2013-08-24 04:55:18 +07:00
|
|
|
}
|
|
|
|
|
2014-07-11 01:07:28 +07:00
|
|
|
static __be32
|
|
|
|
nfs4_file_get_access(struct nfs4_file *fp, u32 access)
|
2010-08-07 20:21:41 +07:00
|
|
|
{
|
2014-07-11 01:07:33 +07:00
|
|
|
lockdep_assert_held(&fp->fi_lock);
|
|
|
|
|
2014-07-11 01:07:28 +07:00
|
|
|
/* Does this access mode make sense? */
|
|
|
|
if (access & ~NFS4_SHARE_ACCESS_BOTH)
|
|
|
|
return nfserr_inval;
|
|
|
|
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
/* Does it conflict with a deny mode already set? */
|
|
|
|
if ((access & fp->fi_share_deny) != 0)
|
|
|
|
return nfserr_share_denied;
|
|
|
|
|
2014-07-11 01:07:28 +07:00
|
|
|
__nfs4_file_get_access(fp, access);
|
|
|
|
return nfs_ok;
|
2010-08-07 20:21:41 +07:00
|
|
|
}
|
|
|
|
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
static __be32 nfs4_file_check_deny(struct nfs4_file *fp, u32 deny)
|
|
|
|
{
|
|
|
|
/* Common case is that there is no deny mode. */
|
|
|
|
if (deny) {
|
|
|
|
/* Does this deny mode make sense? */
|
|
|
|
if (deny & ~NFS4_SHARE_DENY_BOTH)
|
|
|
|
return nfserr_inval;
|
|
|
|
|
|
|
|
if ((deny & NFS4_SHARE_DENY_READ) &&
|
|
|
|
atomic_read(&fp->fi_access[O_RDONLY]))
|
|
|
|
return nfserr_share_denied;
|
|
|
|
|
|
|
|
if ((deny & NFS4_SHARE_DENY_WRITE) &&
|
|
|
|
atomic_read(&fp->fi_access[O_WRONLY]))
|
|
|
|
return nfserr_share_denied;
|
|
|
|
}
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
2010-08-07 20:21:41 +07:00
|
|
|
static void __nfs4_file_put_access(struct nfs4_file *fp, int oflag)
|
2010-07-08 22:02:09 +07:00
|
|
|
{
|
2014-07-11 01:07:26 +07:00
|
|
|
might_lock(&fp->fi_lock);
|
|
|
|
|
|
|
|
if (atomic_dec_and_lock(&fp->fi_access[oflag], &fp->fi_lock)) {
|
|
|
|
struct file *f1 = NULL;
|
|
|
|
struct file *f2 = NULL;
|
|
|
|
|
2014-07-11 01:07:29 +07:00
|
|
|
swap(f1, fp->fi_fds[oflag]);
|
2013-03-29 07:37:14 +07:00
|
|
|
if (atomic_read(&fp->fi_access[1 - oflag]) == 0)
|
2014-07-11 01:07:29 +07:00
|
|
|
swap(f2, fp->fi_fds[O_RDWR]);
|
2014-07-11 01:07:26 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
if (f1)
|
|
|
|
fput(f1);
|
|
|
|
if (f2)
|
|
|
|
fput(f2);
|
2010-07-08 22:02:09 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-11 01:07:28 +07:00
|
|
|
static void nfs4_file_put_access(struct nfs4_file *fp, u32 access)
|
2010-08-07 20:21:41 +07:00
|
|
|
{
|
2014-07-11 01:07:28 +07:00
|
|
|
WARN_ON_ONCE(access & ~NFS4_SHARE_ACCESS_BOTH);
|
|
|
|
|
|
|
|
if (access & NFS4_SHARE_ACCESS_WRITE)
|
2010-08-07 20:21:41 +07:00
|
|
|
__nfs4_file_put_access(fp, O_WRONLY);
|
2014-07-11 01:07:28 +07:00
|
|
|
if (access & NFS4_SHARE_ACCESS_READ)
|
|
|
|
__nfs4_file_put_access(fp, O_RDONLY);
|
2010-08-07 20:21:41 +07:00
|
|
|
}
|
|
|
|
|
2015-04-27 19:50:14 +07:00
|
|
|
/*
|
|
|
|
* Allocate a new open/delegation state counter. This is needed for
|
|
|
|
* pNFS for proper return on close semantics.
|
|
|
|
*
|
|
|
|
* Note that we only allocate it for pNFS-enabled exports, otherwise
|
|
|
|
* all pointers to struct nfs4_clnt_odstate are always NULL.
|
|
|
|
*/
|
|
|
|
static struct nfs4_clnt_odstate *
|
|
|
|
alloc_clnt_odstate(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
struct nfs4_clnt_odstate *co;
|
|
|
|
|
|
|
|
co = kmem_cache_zalloc(odstate_slab, GFP_KERNEL);
|
|
|
|
if (co) {
|
|
|
|
co->co_client = clp;
|
|
|
|
atomic_set(&co->co_odcount, 1);
|
|
|
|
}
|
|
|
|
return co;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
hash_clnt_odstate_locked(struct nfs4_clnt_odstate *co)
|
|
|
|
{
|
|
|
|
struct nfs4_file *fp = co->co_file;
|
|
|
|
|
|
|
|
lockdep_assert_held(&fp->fi_lock);
|
|
|
|
list_add(&co->co_perfile, &fp->fi_clnt_odstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
get_clnt_odstate(struct nfs4_clnt_odstate *co)
|
|
|
|
{
|
|
|
|
if (co)
|
|
|
|
atomic_inc(&co->co_odcount);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
put_clnt_odstate(struct nfs4_clnt_odstate *co)
|
|
|
|
{
|
|
|
|
struct nfs4_file *fp;
|
|
|
|
|
|
|
|
if (!co)
|
|
|
|
return;
|
|
|
|
|
|
|
|
fp = co->co_file;
|
|
|
|
if (atomic_dec_and_lock(&co->co_odcount, &fp->fi_lock)) {
|
|
|
|
list_del(&co->co_perfile);
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
|
|
|
|
nfsd4_return_all_file_layouts(co->co_client, fp);
|
|
|
|
kmem_cache_free(odstate_slab, co);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfs4_clnt_odstate *
|
|
|
|
find_or_hash_clnt_odstate(struct nfs4_file *fp, struct nfs4_clnt_odstate *new)
|
|
|
|
{
|
|
|
|
struct nfs4_clnt_odstate *co;
|
|
|
|
struct nfs4_client *cl;
|
|
|
|
|
|
|
|
if (!new)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
cl = new->co_client;
|
|
|
|
|
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
list_for_each_entry(co, &fp->fi_clnt_odstate, co_perfile) {
|
|
|
|
if (co->co_client == cl) {
|
|
|
|
get_clnt_odstate(co);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
co = new;
|
|
|
|
co->co_file = fp;
|
|
|
|
hash_clnt_odstate_locked(new);
|
|
|
|
out:
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
return co;
|
|
|
|
}
|
|
|
|
|
2014-08-14 13:44:57 +07:00
|
|
|
struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl,
|
2014-07-30 08:34:06 +07:00
|
|
|
struct kmem_cache *slab)
|
2011-09-24 04:20:02 +07:00
|
|
|
{
|
2013-02-04 00:23:01 +07:00
|
|
|
struct nfs4_stid *stid;
|
2011-09-24 03:21:15 +07:00
|
|
|
int new_id;
|
2011-09-24 04:20:02 +07:00
|
|
|
|
2014-07-25 18:34:19 +07:00
|
|
|
stid = kmem_cache_zalloc(slab, GFP_KERNEL);
|
2013-02-04 00:23:01 +07:00
|
|
|
if (!stid)
|
|
|
|
return NULL;
|
|
|
|
|
2014-07-30 08:34:10 +07:00
|
|
|
idr_preload(GFP_KERNEL);
|
|
|
|
spin_lock(&cl->cl_lock);
|
|
|
|
new_id = idr_alloc_cyclic(&cl->cl_stateids, stid, 0, 0, GFP_NOWAIT);
|
|
|
|
spin_unlock(&cl->cl_lock);
|
|
|
|
idr_preload_end();
|
2013-03-14 04:59:37 +07:00
|
|
|
if (new_id < 0)
|
2013-02-04 00:23:01 +07:00
|
|
|
goto out_free;
|
2011-09-24 04:20:02 +07:00
|
|
|
stid->sc_client = cl;
|
2013-02-04 00:23:01 +07:00
|
|
|
stid->sc_stateid.si_opaque.so_id = new_id;
|
|
|
|
stid->sc_stateid.si_opaque.so_clid = cl->cl_clientid;
|
2011-09-24 04:20:02 +07:00
|
|
|
/* Will be incremented before return to client: */
|
2014-07-21 20:34:58 +07:00
|
|
|
atomic_set(&stid->sc_count, 1);
|
2015-10-01 20:05:50 +07:00
|
|
|
spin_lock_init(&stid->sc_lock);
|
2011-10-17 22:14:48 +07:00
|
|
|
|
|
|
|
/*
|
2013-02-04 00:23:01 +07:00
|
|
|
* It shouldn't be a problem to reuse an opaque stateid value.
|
|
|
|
* I don't think it is for 4.1. But with 4.0 I worry that, for
|
|
|
|
* example, a stray write retransmission could be accepted by
|
|
|
|
* the server when it should have been rejected. Therefore,
|
|
|
|
* adopt a trick from the sctp code to attempt to maximize the
|
|
|
|
* amount of time until an id is reused, by ensuring they always
|
|
|
|
* "increase" (mod INT_MAX):
|
2011-10-17 22:14:48 +07:00
|
|
|
*/
|
2013-02-04 00:23:01 +07:00
|
|
|
return stid;
|
|
|
|
out_free:
|
2013-04-09 13:15:31 +07:00
|
|
|
kmem_cache_free(slab, stid);
|
2013-02-04 00:23:01 +07:00
|
|
|
return NULL;
|
2011-09-24 04:20:02 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:11 +07:00
|
|
|
static struct nfs4_ol_stateid * nfs4_alloc_open_stateid(struct nfs4_client *clp)
|
2011-10-18 02:57:47 +07:00
|
|
|
{
|
2014-07-30 08:34:06 +07:00
|
|
|
struct nfs4_stid *stid;
|
|
|
|
struct nfs4_ol_stateid *stp;
|
|
|
|
|
|
|
|
stid = nfs4_alloc_stid(clp, stateid_slab);
|
|
|
|
if (!stid)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
stp = openlockstateid(stid);
|
|
|
|
stp->st_stid.sc_free = nfs4_free_ol_stateid;
|
|
|
|
return stp;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nfs4_free_deleg(struct nfs4_stid *stid)
|
|
|
|
{
|
|
|
|
kmem_cache_free(deleg_slab, stid);
|
|
|
|
atomic_long_dec(&num_delegations);
|
2011-10-18 02:57:47 +07:00
|
|
|
}
|
|
|
|
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
/*
|
|
|
|
* When we recall a delegation, we should be careful not to hand it
|
|
|
|
* out again straight away.
|
|
|
|
* To ensure this we keep a pair of bloom filters ('new' and 'old')
|
|
|
|
* in which the filehandles of recalled delegations are "stored".
|
|
|
|
* If a filehandle appear in either filter, a delegation is blocked.
|
|
|
|
* When a delegation is recalled, the filehandle is stored in the "new"
|
|
|
|
* filter.
|
|
|
|
* Every 30 seconds we swap the filters and clear the "new" one,
|
|
|
|
* unless both are empty of course.
|
|
|
|
*
|
|
|
|
* Each filter is 256 bits. We hash the filehandle to 32bit and use the
|
|
|
|
* low 3 bytes as hash-table indices.
|
|
|
|
*
|
2014-07-25 18:34:26 +07:00
|
|
|
* 'blocked_delegations_lock', which is always taken in block_delegations(),
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
* is used to manage concurrent access. Testing does not need the lock
|
|
|
|
* except when swapping the two filters.
|
|
|
|
*/
|
2014-07-25 18:34:26 +07:00
|
|
|
static DEFINE_SPINLOCK(blocked_delegations_lock);
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
static struct bloom_pair {
|
|
|
|
int entries, old_entries;
|
|
|
|
time_t swap_time;
|
|
|
|
int new; /* index into 'set' */
|
|
|
|
DECLARE_BITMAP(set[2], 256);
|
|
|
|
} blocked_delegations;
|
|
|
|
|
|
|
|
static int delegation_blocked(struct knfsd_fh *fh)
|
|
|
|
{
|
|
|
|
u32 hash;
|
|
|
|
struct bloom_pair *bd = &blocked_delegations;
|
|
|
|
|
|
|
|
if (bd->entries == 0)
|
|
|
|
return 0;
|
|
|
|
if (seconds_since_boot() - bd->swap_time > 30) {
|
2014-07-25 18:34:26 +07:00
|
|
|
spin_lock(&blocked_delegations_lock);
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
if (seconds_since_boot() - bd->swap_time > 30) {
|
|
|
|
bd->entries -= bd->old_entries;
|
|
|
|
bd->old_entries = bd->entries;
|
|
|
|
memset(bd->set[bd->new], 0,
|
|
|
|
sizeof(bd->set[0]));
|
|
|
|
bd->new = 1-bd->new;
|
|
|
|
bd->swap_time = seconds_since_boot();
|
|
|
|
}
|
2014-07-25 18:34:26 +07:00
|
|
|
spin_unlock(&blocked_delegations_lock);
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
}
|
2014-12-10 22:33:11 +07:00
|
|
|
hash = jhash(&fh->fh_base, fh->fh_size, 0);
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
if (test_bit(hash&255, bd->set[0]) &&
|
|
|
|
test_bit((hash>>8)&255, bd->set[0]) &&
|
|
|
|
test_bit((hash>>16)&255, bd->set[0]))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (test_bit(hash&255, bd->set[1]) &&
|
|
|
|
test_bit((hash>>8)&255, bd->set[1]) &&
|
|
|
|
test_bit((hash>>16)&255, bd->set[1]))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void block_delegations(struct knfsd_fh *fh)
|
|
|
|
{
|
|
|
|
u32 hash;
|
|
|
|
struct bloom_pair *bd = &blocked_delegations;
|
|
|
|
|
2014-12-10 22:33:11 +07:00
|
|
|
hash = jhash(&fh->fh_base, fh->fh_size, 0);
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
|
2014-07-25 18:34:26 +07:00
|
|
|
spin_lock(&blocked_delegations_lock);
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
__set_bit(hash&255, bd->set[bd->new]);
|
|
|
|
__set_bit((hash>>8)&255, bd->set[bd->new]);
|
|
|
|
__set_bit((hash>>16)&255, bd->set[bd->new]);
|
|
|
|
if (bd->entries == 0)
|
|
|
|
bd->swap_time = seconds_since_boot();
|
|
|
|
bd->entries += 1;
|
2014-07-25 18:34:26 +07:00
|
|
|
spin_unlock(&blocked_delegations_lock);
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static struct nfs4_delegation *
|
2015-04-27 19:50:14 +07:00
|
|
|
alloc_init_deleg(struct nfs4_client *clp, struct svc_fh *current_fh,
|
|
|
|
struct nfs4_clnt_odstate *odstate)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_delegation *dp;
|
2014-07-25 18:34:22 +07:00
|
|
|
long n;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
dprintk("NFSD alloc_init_deleg\n");
|
2014-07-25 18:34:22 +07:00
|
|
|
n = atomic_long_inc_return(&num_delegations);
|
|
|
|
if (n < 0 || n > max_delegations)
|
|
|
|
goto out_dec;
|
NFSD: Don't hand out delegations for 30 seconds after recalling them.
If nfsd needs to recall a delegation for some reason it implies that there is
contention on the file, so further delegations should not be handed out.
The current code fails to do so, and the result is effectively a
live-lock under some workloads: a client attempting a conflicting
operation on a read-delegated file receives NFS4ERR_DELAY and retries
the operation, but by the time it retries the server may already have
given out another delegation.
We could simply avoid delegations for (say) 30 seconds after any recall, but
this is probably too heavy handed.
We could keep a list of inodes (or inode numbers or filehandles) for recalled
delegations, but that requires memory allocation and searching.
The approach taken here is to use a bloom filter to record the filehandles
which are currently blocked from delegation, and to accept the cost of a few
false positives.
We have 2 bloom filters, each of which is valid for 30 seconds. When a
delegation is recalled the filehandle is added to one filter and will remain
disabled for between 30 and 60 seconds.
We keep a count of the number of filehandles that have been added, so when
that count is zero we can bypass all other tests.
The bloom filters have 256 bits and 3 hash functions. This should allow a
couple of dozen blocked filehandles with minimal false positives. If many
more filehandles are all blocked at once, behaviour will degrade towards
rejecting all delegations for between 30 and 60 seconds, then resetting and
allowing new delegations.
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-06-04 14:39:26 +07:00
|
|
|
if (delegation_blocked(¤t_fh->fh_handle))
|
2014-07-25 18:34:22 +07:00
|
|
|
goto out_dec;
|
2011-10-17 22:14:48 +07:00
|
|
|
dp = delegstateid(nfs4_alloc_stid(clp, deleg_slab));
|
2005-06-24 12:03:04 +07:00
|
|
|
if (dp == NULL)
|
2014-07-25 18:34:22 +07:00
|
|
|
goto out_dec;
|
2014-07-30 08:34:06 +07:00
|
|
|
|
|
|
|
dp->dl_stid.sc_free = nfs4_free_deleg;
|
2011-09-24 04:20:02 +07:00
|
|
|
/*
|
|
|
|
* delegation seqid's are never incremented. The 4.1 special
|
2011-09-24 03:21:15 +07:00
|
|
|
* meaning of seqid 0 isn't meaningful, really, but let's avoid
|
|
|
|
* 0 anyway just for consistency and use 1:
|
2011-09-24 04:20:02 +07:00
|
|
|
*/
|
|
|
|
dp->dl_stid.sc_stateid.si_generation = 1;
|
2005-06-24 12:04:17 +07:00
|
|
|
INIT_LIST_HEAD(&dp->dl_perfile);
|
|
|
|
INIT_LIST_HEAD(&dp->dl_perclnt);
|
2005-04-17 05:20:36 +07:00
|
|
|
INIT_LIST_HEAD(&dp->dl_recall_lru);
|
2015-04-27 19:50:14 +07:00
|
|
|
dp->dl_clnt_odstate = odstate;
|
|
|
|
get_clnt_odstate(odstate);
|
2013-05-22 03:21:25 +07:00
|
|
|
dp->dl_type = NFS4_OPEN_DELEGATE_READ;
|
2014-09-24 17:19:18 +07:00
|
|
|
dp->dl_retries = 1;
|
|
|
|
nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client,
|
2014-09-24 17:19:19 +07:00
|
|
|
&nfsd4_cb_recall_ops, NFSPROC4_CLNT_CB_RECALL);
|
2005-04-17 05:20:36 +07:00
|
|
|
return dp;
|
2014-07-25 18:34:22 +07:00
|
|
|
out_dec:
|
|
|
|
atomic_long_dec(&num_delegations);
|
|
|
|
return NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-07-30 08:34:06 +07:00
|
|
|
nfs4_put_stid(struct nfs4_stid *s)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-07-30 08:34:08 +07:00
|
|
|
struct nfs4_file *fp = s->sc_file;
|
2014-07-30 08:34:06 +07:00
|
|
|
struct nfs4_client *clp = s->sc_client;
|
|
|
|
|
2014-07-30 08:34:10 +07:00
|
|
|
might_lock(&clp->cl_lock);
|
|
|
|
|
2014-07-30 08:34:33 +07:00
|
|
|
if (!atomic_dec_and_lock(&s->sc_count, &clp->cl_lock)) {
|
|
|
|
wake_up_all(&close_wq);
|
2014-07-30 08:34:06 +07:00
|
|
|
return;
|
2014-07-30 08:34:33 +07:00
|
|
|
}
|
2014-07-30 08:34:06 +07:00
|
|
|
idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
|
2014-07-30 08:34:10 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2014-07-30 08:34:06 +07:00
|
|
|
s->sc_free(s);
|
2014-07-30 08:34:08 +07:00
|
|
|
if (fp)
|
|
|
|
put_nfs4_file(fp);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2015-10-01 20:05:50 +07:00
|
|
|
void
|
|
|
|
nfs4_inc_and_copy_stateid(stateid_t *dst, struct nfs4_stid *stid)
|
|
|
|
{
|
|
|
|
stateid_t *src = &stid->sc_stateid;
|
|
|
|
|
|
|
|
spin_lock(&stid->sc_lock);
|
|
|
|
if (unlikely(++src->si_generation == 0))
|
|
|
|
src->si_generation = 1;
|
|
|
|
memcpy(dst, src, sizeof(*dst));
|
|
|
|
spin_unlock(&stid->sc_lock);
|
|
|
|
}
|
|
|
|
|
2011-02-01 07:20:39 +07:00
|
|
|
static void nfs4_put_deleg_lease(struct nfs4_file *fp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-08-09 21:22:40 +07:00
|
|
|
struct file *filp = NULL;
|
2014-07-21 20:34:57 +07:00
|
|
|
|
2014-08-09 21:22:40 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
2014-12-13 21:11:40 +07:00
|
|
|
if (fp->fi_deleg_file && --fp->fi_delegees == 0)
|
2014-08-09 21:22:40 +07:00
|
|
|
swap(filp, fp->fi_deleg_file);
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
|
|
|
|
if (filp) {
|
2015-01-22 01:14:02 +07:00
|
|
|
vfs_setlease(filp, F_UNLCK, NULL, (void **)&fp);
|
2014-08-09 21:22:40 +07:00
|
|
|
fput(filp);
|
2011-02-01 07:20:39 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-08-14 13:44:57 +07:00
|
|
|
void nfs4_unhash_stid(struct nfs4_stid *s)
|
2011-09-24 03:21:15 +07:00
|
|
|
{
|
2013-02-04 00:23:01 +07:00
|
|
|
s->sc_type = 0;
|
2011-09-24 03:21:15 +07:00
|
|
|
}
|
|
|
|
|
2015-10-15 23:07:28 +07:00
|
|
|
/**
|
|
|
|
* nfs4_get_existing_delegation - Discover if this delegation already exists
|
|
|
|
* @clp: a pointer to the nfs4_client we're granting a delegation to
|
|
|
|
* @fp: a pointer to the nfs4_file we're granting a delegation on
|
|
|
|
*
|
|
|
|
* Return:
|
|
|
|
* On success: NULL if an existing delegation was not found.
|
|
|
|
*
|
|
|
|
* On error: -EAGAIN if one was previously granted to this nfs4_client
|
|
|
|
* for this nfs4_file.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
nfs4_get_existing_delegation(struct nfs4_client *clp, struct nfs4_file *fp)
|
|
|
|
{
|
|
|
|
struct nfs4_delegation *searchdp = NULL;
|
|
|
|
struct nfs4_client *searchclp = NULL;
|
|
|
|
|
|
|
|
lockdep_assert_held(&state_lock);
|
|
|
|
lockdep_assert_held(&fp->fi_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(searchdp, &fp->fi_delegations, dl_perfile) {
|
|
|
|
searchclp = searchdp->dl_stid.sc_client;
|
|
|
|
if (clp == searchclp) {
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* hash_delegation_locked - Add a delegation to the appropriate lists
|
|
|
|
* @dp: a pointer to the nfs4_delegation we are adding.
|
|
|
|
* @fp: a pointer to the nfs4_file we're granting a delegation on
|
|
|
|
*
|
|
|
|
* Return:
|
|
|
|
* On success: NULL if the delegation was successfully hashed.
|
|
|
|
*
|
|
|
|
* On error: -EAGAIN if one was previously granted to this
|
|
|
|
* nfs4_client for this nfs4_file. Delegation is not hashed.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
2014-05-30 20:09:27 +07:00
|
|
|
hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp)
|
|
|
|
{
|
2015-10-15 23:07:28 +07:00
|
|
|
int status;
|
|
|
|
struct nfs4_client *clp = dp->dl_stid.sc_client;
|
|
|
|
|
2014-05-30 20:09:30 +07:00
|
|
|
lockdep_assert_held(&state_lock);
|
2014-07-21 20:34:57 +07:00
|
|
|
lockdep_assert_held(&fp->fi_lock);
|
2014-05-30 20:09:27 +07:00
|
|
|
|
2015-10-15 23:07:28 +07:00
|
|
|
status = nfs4_get_existing_delegation(clp, fp);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
++fp->fi_delegees;
|
2014-07-30 08:34:17 +07:00
|
|
|
atomic_inc(&dp->dl_stid.sc_count);
|
2014-05-30 20:09:31 +07:00
|
|
|
dp->dl_stid.sc_type = NFS4_DELEG_STID;
|
2014-05-30 20:09:27 +07:00
|
|
|
list_add(&dp->dl_perfile, &fp->fi_delegations);
|
2015-10-15 23:07:28 +07:00
|
|
|
list_add(&dp->dl_perclnt, &clp->cl_delegations);
|
|
|
|
return 0;
|
2014-05-30 20:09:27 +07:00
|
|
|
}
|
|
|
|
|
2015-08-24 23:41:48 +07:00
|
|
|
static bool
|
2014-07-25 18:34:20 +07:00
|
|
|
unhash_delegation_locked(struct nfs4_delegation *dp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-07-30 08:34:08 +07:00
|
|
|
struct nfs4_file *fp = dp->dl_stid.sc_file;
|
2014-07-16 21:31:57 +07:00
|
|
|
|
2014-07-25 18:34:20 +07:00
|
|
|
lockdep_assert_held(&state_lock);
|
|
|
|
|
2015-08-24 23:41:48 +07:00
|
|
|
if (list_empty(&dp->dl_perfile))
|
|
|
|
return false;
|
|
|
|
|
2014-07-16 21:31:59 +07:00
|
|
|
dp->dl_stid.sc_type = NFS4_CLOSED_DELEG_STID;
|
nfsd: bump dl_time when unhashing delegation
There's a potential race between a lease break and DELEGRETURN call.
Suppose a lease break comes in and queues the workqueue job for a
delegation, but it doesn't run just yet. Then, a DELEGRETURN comes in
finds the delegation and calls destroy_delegation on it to unhash it and
put its primary reference.
Next, the workqueue job runs and queues the delegation back onto the
del_recall_lru list, issues the CB_RECALL and puts the final reference.
With that, the final reference to the delegation is put, but it's still
on the LRU list.
When we go to unhash a delegation, it's because we intend to get rid of
it soon afterward, so we don't want lease breaks to mess with it once
that occurs. Fix this by bumping the dl_time whenever we unhash a
delegation, to ensure that lease breaks don't monkey with it.
I believe this is a regression due to commit 02e1215f9f7 (nfsd: Avoid
taking state_lock while holding inode lock in nfsd_break_one_deleg).
Prior to that, the state_lock was held in the lm_break callback itself,
and that would have prevented this race.
Cc: Trond Myklebust <trond.myklebust@primarydata.com>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-23 00:52:06 +07:00
|
|
|
/* Ensure that deleg break won't try to requeue it */
|
|
|
|
++dp->dl_time;
|
2014-07-21 20:34:57 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
2014-05-30 20:09:27 +07:00
|
|
|
list_del_init(&dp->dl_perclnt);
|
2005-04-17 05:20:36 +07:00
|
|
|
list_del_init(&dp->dl_recall_lru);
|
2014-07-16 21:31:57 +07:00
|
|
|
list_del_init(&dp->dl_perfile);
|
|
|
|
spin_unlock(&fp->fi_lock);
|
2015-08-24 23:41:48 +07:00
|
|
|
return true;
|
2013-04-10 04:02:51 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void destroy_delegation(struct nfs4_delegation *dp)
|
|
|
|
{
|
2015-08-24 23:41:48 +07:00
|
|
|
bool unhashed;
|
|
|
|
|
2014-07-25 18:34:20 +07:00
|
|
|
spin_lock(&state_lock);
|
2015-08-24 23:41:48 +07:00
|
|
|
unhashed = unhash_delegation_locked(dp);
|
2014-07-25 18:34:20 +07:00
|
|
|
spin_unlock(&state_lock);
|
2015-08-24 23:41:48 +07:00
|
|
|
if (unhashed) {
|
|
|
|
put_clnt_odstate(dp->dl_clnt_odstate);
|
|
|
|
nfs4_put_deleg_lease(dp->dl_stid.sc_file);
|
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
|
|
|
}
|
2013-04-10 04:02:51 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void revoke_delegation(struct nfs4_delegation *dp)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp = dp->dl_stid.sc_client;
|
|
|
|
|
2014-07-25 18:34:21 +07:00
|
|
|
WARN_ON(!list_empty(&dp->dl_recall_lru));
|
|
|
|
|
2015-04-27 19:50:14 +07:00
|
|
|
put_clnt_odstate(dp->dl_clnt_odstate);
|
2014-08-09 21:22:41 +07:00
|
|
|
nfs4_put_deleg_lease(dp->dl_stid.sc_file);
|
|
|
|
|
2013-04-10 04:02:51 +07:00
|
|
|
if (clp->cl_minorversion == 0)
|
2014-07-30 08:34:06 +07:00
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
2013-04-10 04:02:51 +07:00
|
|
|
else {
|
|
|
|
dp->dl_stid.sc_type = NFS4_REVOKED_DELEG_STID;
|
2014-07-25 18:34:21 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
list_add(&dp->dl_recall_lru, &clp->cl_revoked);
|
|
|
|
spin_unlock(&clp->cl_lock);
|
2013-04-10 04:02:51 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* SETCLIENTID state
|
|
|
|
*/
|
|
|
|
|
2011-07-31 10:46:29 +07:00
|
|
|
static unsigned int clientid_hashval(u32 id)
|
|
|
|
{
|
|
|
|
return id & CLIENT_HASH_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int clientstr_hashval(const char *name)
|
|
|
|
{
|
|
|
|
return opaque_hashval(name, 8) & CLIENT_HASH_MASK;
|
|
|
|
}
|
|
|
|
|
2010-07-08 22:02:09 +07:00
|
|
|
/*
|
|
|
|
* We store the NONE, READ, WRITE, and BOTH bits separately in the
|
|
|
|
* st_{access,deny}_bmap field of the stateid, in order to track not
|
|
|
|
* only what share bits are currently in force, but also what
|
|
|
|
* combinations of share bits previous opens have used. This allows us
|
|
|
|
* to enforce the recommendation of rfc 3530 14.2.19 that the server
|
|
|
|
* return an error if the client attempt to downgrade to a combination
|
|
|
|
* of share bits not explicable by closing some of its previous opens.
|
|
|
|
*
|
|
|
|
* XXX: This enforcement is actually incomplete, since we don't keep
|
|
|
|
* track of access/deny bit combinations; so, e.g., we allow:
|
|
|
|
*
|
|
|
|
* OPEN allow read, deny write
|
|
|
|
* OPEN allow both, deny none
|
|
|
|
* DOWNGRADE allow read, deny none
|
|
|
|
*
|
|
|
|
* which we should reject.
|
|
|
|
*/
|
2012-05-11 20:45:11 +07:00
|
|
|
static unsigned int
|
|
|
|
bmap_to_share_mode(unsigned long bmap) {
|
2010-07-08 22:02:09 +07:00
|
|
|
int i;
|
2012-05-11 20:45:11 +07:00
|
|
|
unsigned int access = 0;
|
2010-07-08 22:02:09 +07:00
|
|
|
|
|
|
|
for (i = 1; i < 4; i++) {
|
|
|
|
if (test_bit(i, &bmap))
|
2012-05-11 20:45:11 +07:00
|
|
|
access |= i;
|
2010-07-08 22:02:09 +07:00
|
|
|
}
|
2012-05-11 20:45:11 +07:00
|
|
|
return access;
|
2010-07-08 22:02:09 +07:00
|
|
|
}
|
|
|
|
|
2012-05-11 20:45:13 +07:00
|
|
|
/* set share access for a given stateid */
|
|
|
|
static inline void
|
|
|
|
set_access(u32 access, struct nfs4_ol_stateid *stp)
|
|
|
|
{
|
2014-07-11 01:07:30 +07:00
|
|
|
unsigned char mask = 1 << access;
|
|
|
|
|
|
|
|
WARN_ON_ONCE(access > NFS4_SHARE_ACCESS_BOTH);
|
|
|
|
stp->st_access_bmap |= mask;
|
2012-05-11 20:45:13 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* clear share access for a given stateid */
|
|
|
|
static inline void
|
|
|
|
clear_access(u32 access, struct nfs4_ol_stateid *stp)
|
|
|
|
{
|
2014-07-11 01:07:30 +07:00
|
|
|
unsigned char mask = 1 << access;
|
|
|
|
|
|
|
|
WARN_ON_ONCE(access > NFS4_SHARE_ACCESS_BOTH);
|
|
|
|
stp->st_access_bmap &= ~mask;
|
2012-05-11 20:45:13 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* test whether a given stateid has access */
|
|
|
|
static inline bool
|
|
|
|
test_access(u32 access, struct nfs4_ol_stateid *stp)
|
|
|
|
{
|
2014-07-11 01:07:30 +07:00
|
|
|
unsigned char mask = 1 << access;
|
|
|
|
|
|
|
|
return (bool)(stp->st_access_bmap & mask);
|
2012-05-11 20:45:13 +07:00
|
|
|
}
|
|
|
|
|
2012-05-11 20:45:14 +07:00
|
|
|
/* set share deny for a given stateid */
|
|
|
|
static inline void
|
2014-07-11 01:07:30 +07:00
|
|
|
set_deny(u32 deny, struct nfs4_ol_stateid *stp)
|
2012-05-11 20:45:14 +07:00
|
|
|
{
|
2014-07-11 01:07:30 +07:00
|
|
|
unsigned char mask = 1 << deny;
|
|
|
|
|
|
|
|
WARN_ON_ONCE(deny > NFS4_SHARE_DENY_BOTH);
|
|
|
|
stp->st_deny_bmap |= mask;
|
2012-05-11 20:45:14 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* clear share deny for a given stateid */
|
|
|
|
static inline void
|
2014-07-11 01:07:30 +07:00
|
|
|
clear_deny(u32 deny, struct nfs4_ol_stateid *stp)
|
2012-05-11 20:45:14 +07:00
|
|
|
{
|
2014-07-11 01:07:30 +07:00
|
|
|
unsigned char mask = 1 << deny;
|
|
|
|
|
|
|
|
WARN_ON_ONCE(deny > NFS4_SHARE_DENY_BOTH);
|
|
|
|
stp->st_deny_bmap &= ~mask;
|
2012-05-11 20:45:14 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* test whether a given stateid is denying specific access */
|
|
|
|
static inline bool
|
2014-07-11 01:07:30 +07:00
|
|
|
test_deny(u32 deny, struct nfs4_ol_stateid *stp)
|
2012-05-11 20:45:14 +07:00
|
|
|
{
|
2014-07-11 01:07:30 +07:00
|
|
|
unsigned char mask = 1 << deny;
|
|
|
|
|
|
|
|
return (bool)(stp->st_deny_bmap & mask);
|
2010-07-08 22:02:09 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int nfs4_access_to_omode(u32 access)
|
|
|
|
{
|
2010-09-03 02:23:16 +07:00
|
|
|
switch (access & NFS4_SHARE_ACCESS_BOTH) {
|
2010-07-08 22:02:09 +07:00
|
|
|
case NFS4_SHARE_ACCESS_READ:
|
|
|
|
return O_RDONLY;
|
|
|
|
case NFS4_SHARE_ACCESS_WRITE:
|
|
|
|
return O_WRONLY;
|
|
|
|
case NFS4_SHARE_ACCESS_BOTH:
|
|
|
|
return O_RDWR;
|
|
|
|
}
|
2012-11-26 02:48:10 +07:00
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
return O_RDONLY;
|
2010-07-08 22:02:09 +07:00
|
|
|
}
|
|
|
|
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
/*
|
|
|
|
* A stateid that had a deny mode associated with it is being released
|
|
|
|
* or downgraded. Recalculate the deny mode on the file.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
recalculate_deny_mode(struct nfs4_file *fp)
|
|
|
|
{
|
|
|
|
struct nfs4_ol_stateid *stp;
|
|
|
|
|
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
fp->fi_share_deny = 0;
|
|
|
|
list_for_each_entry(stp, &fp->fi_stateids, st_perfile)
|
|
|
|
fp->fi_share_deny |= bmap_to_share_mode(stp->st_deny_bmap);
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
reset_union_bmap_deny(u32 deny, struct nfs4_ol_stateid *stp)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
bool change = false;
|
|
|
|
|
|
|
|
for (i = 1; i < 4; i++) {
|
|
|
|
if ((i & deny) != i) {
|
|
|
|
change = true;
|
|
|
|
clear_deny(i, stp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Recalculate per-file deny mode if there was a change */
|
|
|
|
if (change)
|
2014-07-30 08:34:08 +07:00
|
|
|
recalculate_deny_mode(stp->st_stid.sc_file);
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
}
|
|
|
|
|
2012-05-11 20:45:13 +07:00
|
|
|
/* release all access and file references for a given stateid */
|
|
|
|
static void
|
|
|
|
release_all_access(struct nfs4_ol_stateid *stp)
|
|
|
|
{
|
|
|
|
int i;
|
2014-07-30 08:34:08 +07:00
|
|
|
struct nfs4_file *fp = stp->st_stid.sc_file;
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
|
|
|
|
if (fp && stp->st_deny_bmap != 0)
|
|
|
|
recalculate_deny_mode(fp);
|
2012-05-11 20:45:13 +07:00
|
|
|
|
|
|
|
for (i = 1; i < 4; i++) {
|
|
|
|
if (test_access(i, stp))
|
2014-07-30 08:34:08 +07:00
|
|
|
nfs4_file_put_access(stp->st_stid.sc_file, i);
|
2012-05-11 20:45:13 +07:00
|
|
|
clear_access(i, stp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-16 11:05:07 +07:00
|
|
|
static inline void nfs4_free_stateowner(struct nfs4_stateowner *sop)
|
|
|
|
{
|
|
|
|
kfree(sop->so_owner.data);
|
|
|
|
sop->so_ops->so_free(sop);
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:26 +07:00
|
|
|
static void nfs4_put_stateowner(struct nfs4_stateowner *sop)
|
|
|
|
{
|
2014-07-30 08:34:38 +07:00
|
|
|
struct nfs4_client *clp = sop->so_client;
|
|
|
|
|
|
|
|
might_lock(&clp->cl_lock);
|
|
|
|
|
|
|
|
if (!atomic_dec_and_lock(&sop->so_count, &clp->cl_lock))
|
2014-07-30 08:34:26 +07:00
|
|
|
return;
|
2014-07-30 08:34:29 +07:00
|
|
|
sop->so_ops->so_unhash(sop);
|
2014-07-30 08:34:38 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2015-07-16 11:05:07 +07:00
|
|
|
nfs4_free_stateowner(sop);
|
2014-07-30 08:34:26 +07:00
|
|
|
}
|
|
|
|
|
2015-08-24 23:41:47 +07:00
|
|
|
static bool unhash_ol_stateid(struct nfs4_ol_stateid *stp)
|
2011-03-03 11:48:33 +07:00
|
|
|
{
|
2014-07-30 08:34:08 +07:00
|
|
|
struct nfs4_file *fp = stp->st_stid.sc_file;
|
2014-07-11 01:07:25 +07:00
|
|
|
|
2014-07-30 08:34:12 +07:00
|
|
|
lockdep_assert_held(&stp->st_stateowner->so_client->cl_lock);
|
|
|
|
|
2015-08-24 23:41:47 +07:00
|
|
|
if (list_empty(&stp->st_perfile))
|
|
|
|
return false;
|
|
|
|
|
2014-07-11 01:07:25 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
2015-08-24 23:41:47 +07:00
|
|
|
list_del_init(&stp->st_perfile);
|
2014-07-11 01:07:25 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
2011-03-03 11:48:33 +07:00
|
|
|
list_del(&stp->st_perstateowner);
|
2015-08-24 23:41:47 +07:00
|
|
|
return true;
|
2011-03-03 11:48:33 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:06 +07:00
|
|
|
static void nfs4_free_ol_stateid(struct nfs4_stid *stid)
|
2011-03-03 11:48:33 +07:00
|
|
|
{
|
2014-07-30 08:34:06 +07:00
|
|
|
struct nfs4_ol_stateid *stp = openlockstateid(stid);
|
2011-09-07 01:50:49 +07:00
|
|
|
|
2015-04-27 19:50:14 +07:00
|
|
|
put_clnt_odstate(stp->st_clnt_odstate);
|
2014-07-30 08:34:06 +07:00
|
|
|
release_all_access(stp);
|
2014-07-30 08:34:32 +07:00
|
|
|
if (stp->st_stateowner)
|
|
|
|
nfs4_put_stateowner(stp->st_stateowner);
|
2014-07-30 08:34:06 +07:00
|
|
|
kmem_cache_free(stateid_slab, stid);
|
2011-03-03 11:48:33 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:11 +07:00
|
|
|
static void nfs4_free_lock_stateid(struct nfs4_stid *stid)
|
2011-03-03 11:48:33 +07:00
|
|
|
{
|
2014-07-30 08:34:11 +07:00
|
|
|
struct nfs4_ol_stateid *stp = openlockstateid(stid);
|
|
|
|
struct nfs4_lockowner *lo = lockowner(stp->st_stateowner);
|
2011-03-03 11:48:33 +07:00
|
|
|
struct file *file;
|
|
|
|
|
2014-07-30 08:34:11 +07:00
|
|
|
file = find_any_file(stp->st_stid.sc_file);
|
|
|
|
if (file)
|
|
|
|
filp_close(file, (fl_owner_t)lo);
|
|
|
|
nfs4_free_ol_stateid(stid);
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:41 +07:00
|
|
|
/*
|
|
|
|
* Put the persistent reference to an already unhashed generic stateid, while
|
|
|
|
* holding the cl_lock. If it's the last reference, then put it onto the
|
|
|
|
* reaplist for later destruction.
|
|
|
|
*/
|
|
|
|
static void put_ol_stateid_locked(struct nfs4_ol_stateid *stp,
|
|
|
|
struct list_head *reaplist)
|
|
|
|
{
|
|
|
|
struct nfs4_stid *s = &stp->st_stid;
|
|
|
|
struct nfs4_client *clp = s->sc_client;
|
|
|
|
|
|
|
|
lockdep_assert_held(&clp->cl_lock);
|
|
|
|
|
|
|
|
WARN_ON_ONCE(!list_empty(&stp->st_locks));
|
|
|
|
|
|
|
|
if (!atomic_dec_and_test(&s->sc_count)) {
|
|
|
|
wake_up_all(&close_wq);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
|
|
|
|
list_add(&stp->st_locks, reaplist);
|
|
|
|
}
|
|
|
|
|
2015-08-24 23:41:47 +07:00
|
|
|
static bool unhash_lock_stateid(struct nfs4_ol_stateid *stp)
|
2014-07-30 08:34:11 +07:00
|
|
|
{
|
2014-07-30 08:34:12 +07:00
|
|
|
struct nfs4_openowner *oo = openowner(stp->st_openstp->st_stateowner);
|
|
|
|
|
2014-07-30 08:34:39 +07:00
|
|
|
lockdep_assert_held(&oo->oo_owner.so_client->cl_lock);
|
|
|
|
|
|
|
|
list_del_init(&stp->st_locks);
|
2014-08-14 13:44:57 +07:00
|
|
|
nfs4_unhash_stid(&stp->st_stid);
|
2015-08-24 23:41:47 +07:00
|
|
|
return unhash_ol_stateid(stp);
|
2014-07-30 08:34:39 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void release_lock_stateid(struct nfs4_ol_stateid *stp)
|
|
|
|
{
|
|
|
|
struct nfs4_openowner *oo = openowner(stp->st_openstp->st_stateowner);
|
2015-08-24 23:41:47 +07:00
|
|
|
bool unhashed;
|
2014-07-30 08:34:39 +07:00
|
|
|
|
|
|
|
spin_lock(&oo->oo_owner.so_client->cl_lock);
|
2015-08-24 23:41:47 +07:00
|
|
|
unhashed = unhash_lock_stateid(stp);
|
2014-07-30 08:34:12 +07:00
|
|
|
spin_unlock(&oo->oo_owner.so_client->cl_lock);
|
2015-08-24 23:41:47 +07:00
|
|
|
if (unhashed)
|
|
|
|
nfs4_put_stid(&stp->st_stid);
|
2011-03-03 11:48:33 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:35 +07:00
|
|
|
static void unhash_lockowner_locked(struct nfs4_lockowner *lo)
|
2014-07-30 08:34:29 +07:00
|
|
|
{
|
2014-07-30 08:34:36 +07:00
|
|
|
struct nfs4_client *clp = lo->lo_owner.so_client;
|
2014-07-30 08:34:35 +07:00
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
lockdep_assert_held(&clp->cl_lock);
|
2014-07-30 08:34:35 +07:00
|
|
|
|
2014-07-30 08:34:29 +07:00
|
|
|
list_del_init(&lo->lo_owner.so_strhash);
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:41 +07:00
|
|
|
/*
|
|
|
|
* Free a list of generic stateids that were collected earlier after being
|
|
|
|
* fully unhashed.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
free_ol_stateid_reaplist(struct list_head *reaplist)
|
|
|
|
{
|
|
|
|
struct nfs4_ol_stateid *stp;
|
2014-08-05 20:20:27 +07:00
|
|
|
struct nfs4_file *fp;
|
2014-07-30 08:34:41 +07:00
|
|
|
|
|
|
|
might_sleep();
|
|
|
|
|
|
|
|
while (!list_empty(reaplist)) {
|
|
|
|
stp = list_first_entry(reaplist, struct nfs4_ol_stateid,
|
|
|
|
st_locks);
|
|
|
|
list_del(&stp->st_locks);
|
2014-08-05 20:20:27 +07:00
|
|
|
fp = stp->st_stid.sc_file;
|
2014-07-30 08:34:41 +07:00
|
|
|
stp->st_stid.sc_free(&stp->st_stid);
|
2014-08-05 20:20:27 +07:00
|
|
|
if (fp)
|
|
|
|
put_nfs4_file(fp);
|
2014-07-30 08:34:41 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:42 +07:00
|
|
|
static void release_open_stateid_locks(struct nfs4_ol_stateid *open_stp,
|
|
|
|
struct list_head *reaplist)
|
2014-06-30 22:48:38 +07:00
|
|
|
{
|
|
|
|
struct nfs4_ol_stateid *stp;
|
|
|
|
|
2015-08-24 23:41:47 +07:00
|
|
|
lockdep_assert_held(&open_stp->st_stid.sc_client->cl_lock);
|
|
|
|
|
2014-06-30 22:48:38 +07:00
|
|
|
while (!list_empty(&open_stp->st_locks)) {
|
|
|
|
stp = list_entry(open_stp->st_locks.next,
|
|
|
|
struct nfs4_ol_stateid, st_locks);
|
2015-08-24 23:41:47 +07:00
|
|
|
WARN_ON(!unhash_lock_stateid(stp));
|
2014-07-30 08:34:42 +07:00
|
|
|
put_ol_stateid_locked(stp, reaplist);
|
2011-03-03 11:48:33 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-24 23:41:47 +07:00
|
|
|
static bool unhash_open_stateid(struct nfs4_ol_stateid *stp,
|
2014-07-30 08:34:42 +07:00
|
|
|
struct list_head *reaplist)
|
2009-01-12 02:27:17 +07:00
|
|
|
{
|
2015-08-24 23:41:47 +07:00
|
|
|
bool unhashed;
|
|
|
|
|
2014-07-30 08:34:41 +07:00
|
|
|
lockdep_assert_held(&stp->st_stid.sc_client->cl_lock);
|
|
|
|
|
2015-08-24 23:41:47 +07:00
|
|
|
unhashed = unhash_ol_stateid(stp);
|
2014-07-30 08:34:42 +07:00
|
|
|
release_open_stateid_locks(stp, reaplist);
|
2015-08-24 23:41:47 +07:00
|
|
|
return unhashed;
|
2011-09-17 04:42:48 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void release_open_stateid(struct nfs4_ol_stateid *stp)
|
|
|
|
{
|
2014-07-30 08:34:41 +07:00
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
spin_lock(&stp->st_stid.sc_client->cl_lock);
|
2015-08-24 23:41:47 +07:00
|
|
|
if (unhash_open_stateid(stp, &reaplist))
|
|
|
|
put_ol_stateid_locked(stp, &reaplist);
|
2014-07-30 08:34:41 +07:00
|
|
|
spin_unlock(&stp->st_stid.sc_client->cl_lock);
|
|
|
|
free_ol_stateid_reaplist(&reaplist);
|
2009-01-12 02:27:17 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:34 +07:00
|
|
|
static void unhash_openowner_locked(struct nfs4_openowner *oo)
|
2009-01-12 02:37:31 +07:00
|
|
|
{
|
2014-07-30 08:34:36 +07:00
|
|
|
struct nfs4_client *clp = oo->oo_owner.so_client;
|
2014-07-30 08:34:34 +07:00
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
lockdep_assert_held(&clp->cl_lock);
|
2014-07-30 08:34:34 +07:00
|
|
|
|
2014-07-30 08:34:29 +07:00
|
|
|
list_del_init(&oo->oo_owner.so_strhash);
|
|
|
|
list_del_init(&oo->oo_perclient);
|
2009-01-12 02:37:31 +07:00
|
|
|
}
|
|
|
|
|
2011-09-17 07:12:38 +07:00
|
|
|
static void release_last_closed_stateid(struct nfs4_openowner *oo)
|
|
|
|
{
|
2014-07-30 19:27:11 +07:00
|
|
|
struct nfsd_net *nn = net_generic(oo->oo_owner.so_client->net,
|
|
|
|
nfsd_net_id);
|
|
|
|
struct nfs4_ol_stateid *s;
|
2011-09-17 07:12:38 +07:00
|
|
|
|
2014-07-30 19:27:11 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
s = oo->oo_last_closed_stid;
|
2011-09-17 07:12:38 +07:00
|
|
|
if (s) {
|
2014-07-30 08:34:32 +07:00
|
|
|
list_del_init(&oo->oo_close_lru);
|
2011-09-17 07:12:38 +07:00
|
|
|
oo->oo_last_closed_stid = NULL;
|
|
|
|
}
|
2014-07-30 19:27:11 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
if (s)
|
|
|
|
nfs4_put_stid(&s->st_stid);
|
2011-09-17 07:12:38 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:41 +07:00
|
|
|
static void release_openowner(struct nfs4_openowner *oo)
|
2014-07-30 08:34:29 +07:00
|
|
|
{
|
|
|
|
struct nfs4_ol_stateid *stp;
|
2014-07-30 08:34:36 +07:00
|
|
|
struct nfs4_client *clp = oo->oo_owner.so_client;
|
2014-07-30 08:34:41 +07:00
|
|
|
struct list_head reaplist;
|
2014-07-30 08:34:34 +07:00
|
|
|
|
2014-07-30 08:34:41 +07:00
|
|
|
INIT_LIST_HEAD(&reaplist);
|
2014-07-30 08:34:29 +07:00
|
|
|
|
2014-07-30 08:34:41 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
unhash_openowner_locked(oo);
|
2014-07-30 08:34:29 +07:00
|
|
|
while (!list_empty(&oo->oo_owner.so_stateids)) {
|
|
|
|
stp = list_first_entry(&oo->oo_owner.so_stateids,
|
|
|
|
struct nfs4_ol_stateid, st_perstateowner);
|
2015-08-24 23:41:47 +07:00
|
|
|
if (unhash_open_stateid(stp, &reaplist))
|
|
|
|
put_ol_stateid_locked(stp, &reaplist);
|
2014-07-30 08:34:29 +07:00
|
|
|
}
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2014-07-30 08:34:41 +07:00
|
|
|
free_ol_stateid_reaplist(&reaplist);
|
2011-09-17 07:12:38 +07:00
|
|
|
release_last_closed_stateid(oo);
|
2014-07-30 08:34:26 +07:00
|
|
|
nfs4_put_stateowner(&oo->oo_owner);
|
2009-01-12 02:37:31 +07:00
|
|
|
}
|
|
|
|
|
2009-04-03 12:27:52 +07:00
|
|
|
static inline int
|
|
|
|
hash_sessionid(struct nfs4_sessionid *sessionid)
|
|
|
|
{
|
|
|
|
struct nfsd4_sessionid *sid = (struct nfsd4_sessionid *)sessionid;
|
|
|
|
|
|
|
|
return sid->sequence % SESSION_HASH_SIZE;
|
|
|
|
}
|
|
|
|
|
2015-04-06 20:46:00 +07:00
|
|
|
#ifdef CONFIG_SUNRPC_DEBUG
|
2009-04-03 12:27:52 +07:00
|
|
|
static inline void
|
|
|
|
dump_sessionid(const char *fn, struct nfs4_sessionid *sessionid)
|
|
|
|
{
|
|
|
|
u32 *ptr = (u32 *)(&sessionid->data[0]);
|
|
|
|
dprintk("%s: %u:%u:%u:%u\n", fn, ptr[0], ptr[1], ptr[2], ptr[3]);
|
|
|
|
}
|
2012-03-21 02:11:17 +07:00
|
|
|
#else
|
|
|
|
static inline void
|
|
|
|
dump_sessionid(const char *fn, struct nfs4_sessionid *sessionid)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-04-02 03:37:12 +07:00
|
|
|
/*
|
|
|
|
* Bump the seqid on cstate->replay_owner, and clear replay_owner if it
|
|
|
|
* won't be used for replay.
|
|
|
|
*/
|
|
|
|
void nfsd4_bump_seqid(struct nfsd4_compound_state *cstate, __be32 nfserr)
|
|
|
|
{
|
|
|
|
struct nfs4_stateowner *so = cstate->replay_owner;
|
|
|
|
|
|
|
|
if (nfserr == nfserr_replay_me)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!seqid_mutating_err(ntohl(nfserr))) {
|
2014-07-30 08:34:27 +07:00
|
|
|
nfsd4_cstate_clear_replay(cstate);
|
2013-04-02 03:37:12 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!so)
|
|
|
|
return;
|
|
|
|
if (so->so_is_open_owner)
|
|
|
|
release_last_closed_stateid(openowner(so));
|
|
|
|
so->so_seqid++;
|
|
|
|
return;
|
|
|
|
}
|
2009-04-03 12:27:52 +07:00
|
|
|
|
2009-04-03 12:28:28 +07:00
|
|
|
static void
|
|
|
|
gen_sessionid(struct nfsd4_session *ses)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp = ses->se_client;
|
|
|
|
struct nfsd4_sessionid *sid;
|
|
|
|
|
|
|
|
sid = (struct nfsd4_sessionid *)ses->se_sessionid.data;
|
|
|
|
sid->clientid = clp->cl_clientid;
|
|
|
|
sid->sequence = current_sessionid++;
|
|
|
|
sid->reserved = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2009-08-28 19:45:01 +07:00
|
|
|
* The protocol defines ca_maxresponssize_cached to include the size of
|
|
|
|
* the rpc header, but all we need to cache is the data starting after
|
|
|
|
* the end of the initial SEQUENCE operation--the rest we regenerate
|
|
|
|
* each time. Therefore we can advertise a ca_maxresponssize_cached
|
|
|
|
* value that is the number of bytes in our cache plus a few additional
|
|
|
|
* bytes. In order to stay on the safe side, and not promise more than
|
|
|
|
* we can cache, those additional bytes must be the minimum possible: 24
|
|
|
|
* bytes of rpc header (xid through accept state, with AUTH_NULL
|
|
|
|
* verifier), 12 for the compound header (with zero-length tag), and 44
|
|
|
|
* for the SEQUENCE op response:
|
|
|
|
*/
|
|
|
|
#define NFSD_MIN_HDR_SEQ_SZ (24 + 12 + 44)
|
|
|
|
|
2009-08-28 19:45:04 +07:00
|
|
|
static void
|
|
|
|
free_session_slots(struct nfsd4_session *ses)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ses->se_fchannel.maxreqs; i++)
|
|
|
|
kfree(ses->se_slots[i]);
|
|
|
|
}
|
|
|
|
|
2009-08-28 19:45:01 +07:00
|
|
|
/*
|
2009-10-25 07:52:16 +07:00
|
|
|
* We don't actually need to cache the rpc and session headers, so we
|
|
|
|
* can allocate a little less for each slot:
|
|
|
|
*/
|
2013-04-09 03:44:14 +07:00
|
|
|
static inline u32 slot_bytes(struct nfsd4_channel_attrs *ca)
|
2009-10-25 07:52:16 +07:00
|
|
|
{
|
2013-04-09 03:44:14 +07:00
|
|
|
u32 size;
|
2009-10-25 07:52:16 +07:00
|
|
|
|
2013-04-09 03:44:14 +07:00
|
|
|
if (ca->maxresp_cached < NFSD_MIN_HDR_SEQ_SZ)
|
|
|
|
size = 0;
|
|
|
|
else
|
|
|
|
size = ca->maxresp_cached - NFSD_MIN_HDR_SEQ_SZ;
|
|
|
|
return size + sizeof(struct nfsd4_slot);
|
2010-09-28 04:12:05 +07:00
|
|
|
}
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-09-28 04:12:05 +07:00
|
|
|
/*
|
|
|
|
* XXX: If we run out of reserved DRC memory we could (up to a point)
|
2009-08-28 19:45:01 +07:00
|
|
|
* re-negotiate active sessions and reduce their slot usage to make
|
2011-11-29 11:31:00 +07:00
|
|
|
* room for new connections. For now we just fail the create session.
|
2009-04-03 12:28:28 +07:00
|
|
|
*/
|
2013-04-09 03:44:14 +07:00
|
|
|
static u32 nfsd4_get_drc_mem(struct nfsd4_channel_attrs *ca)
|
2009-04-03 12:28:28 +07:00
|
|
|
{
|
2013-04-09 03:44:14 +07:00
|
|
|
u32 slotsize = slot_bytes(ca);
|
|
|
|
u32 num = ca->maxreqs;
|
2010-09-28 04:12:05 +07:00
|
|
|
int avail;
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-09-28 04:12:05 +07:00
|
|
|
spin_lock(&nfsd_drc_lock);
|
2013-02-23 07:35:47 +07:00
|
|
|
avail = min((unsigned long)NFSD_MAX_MEM_PER_SESSION,
|
|
|
|
nfsd_drc_max_mem - nfsd_drc_mem_used);
|
2010-09-28 04:12:05 +07:00
|
|
|
num = min_t(int, num, avail / slotsize);
|
|
|
|
nfsd_drc_mem_used += num * slotsize;
|
|
|
|
spin_unlock(&nfsd_drc_lock);
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-09-28 04:12:05 +07:00
|
|
|
return num;
|
|
|
|
}
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2013-04-09 03:44:14 +07:00
|
|
|
static void nfsd4_put_drc_mem(struct nfsd4_channel_attrs *ca)
|
2010-09-28 04:12:05 +07:00
|
|
|
{
|
2013-04-09 03:44:14 +07:00
|
|
|
int slotsize = slot_bytes(ca);
|
|
|
|
|
2009-06-25 02:37:45 +07:00
|
|
|
spin_lock(&nfsd_drc_lock);
|
2013-04-09 03:44:14 +07:00
|
|
|
nfsd_drc_mem_used -= slotsize * ca->maxreqs;
|
2009-06-25 02:37:45 +07:00
|
|
|
spin_unlock(&nfsd_drc_lock);
|
2010-09-28 04:12:05 +07:00
|
|
|
}
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2013-12-31 23:35:47 +07:00
|
|
|
static struct nfsd4_session *alloc_session(struct nfsd4_channel_attrs *fattrs,
|
|
|
|
struct nfsd4_channel_attrs *battrs)
|
2010-09-28 04:12:05 +07:00
|
|
|
{
|
2013-12-31 23:35:47 +07:00
|
|
|
int numslots = fattrs->maxreqs;
|
|
|
|
int slotsize = slot_bytes(fattrs);
|
2010-09-28 04:12:05 +07:00
|
|
|
struct nfsd4_session *new;
|
|
|
|
int mem, i;
|
2009-08-28 19:45:01 +07:00
|
|
|
|
2010-09-28 04:12:05 +07:00
|
|
|
BUILD_BUG_ON(NFSD_MAX_SLOTS_PER_SESSION * sizeof(struct nfsd4_slot *)
|
|
|
|
+ sizeof(struct nfsd4_session) > PAGE_SIZE);
|
|
|
|
mem = numslots * sizeof(struct nfsd4_slot *);
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-09-28 04:12:05 +07:00
|
|
|
new = kzalloc(sizeof(*new) + mem, GFP_KERNEL);
|
|
|
|
if (!new)
|
|
|
|
return NULL;
|
2009-08-28 19:45:04 +07:00
|
|
|
/* allocate each struct nfsd4_slot and data cache in one piece */
|
2010-09-28 04:12:05 +07:00
|
|
|
for (i = 0; i < numslots; i++) {
|
2013-04-09 03:44:14 +07:00
|
|
|
new->se_slots[i] = kzalloc(slotsize, GFP_KERNEL);
|
2010-09-28 04:12:05 +07:00
|
|
|
if (!new->se_slots[i])
|
2009-08-28 19:45:04 +07:00
|
|
|
goto out_free;
|
|
|
|
}
|
2013-12-31 23:35:47 +07:00
|
|
|
|
|
|
|
memcpy(&new->se_fchannel, fattrs, sizeof(struct nfsd4_channel_attrs));
|
|
|
|
memcpy(&new->se_bchannel, battrs, sizeof(struct nfsd4_channel_attrs));
|
|
|
|
|
2010-09-28 04:12:05 +07:00
|
|
|
return new;
|
|
|
|
out_free:
|
|
|
|
while (i--)
|
|
|
|
kfree(new->se_slots[i]);
|
|
|
|
kfree(new);
|
|
|
|
return NULL;
|
2009-04-03 12:28:28 +07:00
|
|
|
}
|
|
|
|
|
2010-06-07 05:37:16 +07:00
|
|
|
static void free_conn(struct nfsd4_conn *c)
|
|
|
|
{
|
|
|
|
svc_xprt_put(c->cn_xprt);
|
|
|
|
kfree(c);
|
|
|
|
}
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-06-07 05:37:16 +07:00
|
|
|
static void nfsd4_conn_lost(struct svc_xpt_user *u)
|
|
|
|
{
|
|
|
|
struct nfsd4_conn *c = container_of(u, struct nfsd4_conn, cn_xpt_user);
|
|
|
|
struct nfs4_client *clp = c->cn_session->se_client;
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-06-07 05:37:16 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
if (!list_empty(&c->cn_persession)) {
|
|
|
|
list_del(&c->cn_persession);
|
|
|
|
free_conn(c);
|
|
|
|
}
|
2010-11-18 20:34:12 +07:00
|
|
|
nfsd4_probe_callback(clp);
|
2013-03-08 21:30:43 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2010-06-07 05:37:16 +07:00
|
|
|
}
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-06-16 04:34:11 +07:00
|
|
|
static struct nfsd4_conn *alloc_conn(struct svc_rqst *rqstp, u32 flags)
|
2010-06-07 05:12:14 +07:00
|
|
|
{
|
|
|
|
struct nfsd4_conn *conn;
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-06-07 05:12:14 +07:00
|
|
|
conn = kmalloc(sizeof(struct nfsd4_conn), GFP_KERNEL);
|
|
|
|
if (!conn)
|
2010-09-30 02:29:32 +07:00
|
|
|
return NULL;
|
2010-06-07 05:12:14 +07:00
|
|
|
svc_xprt_get(rqstp->rq_xprt);
|
|
|
|
conn->cn_xprt = rqstp->rq_xprt;
|
2010-06-16 04:34:11 +07:00
|
|
|
conn->cn_flags = flags;
|
2010-09-30 02:29:32 +07:00
|
|
|
INIT_LIST_HEAD(&conn->cn_xpt_user.list);
|
|
|
|
return conn;
|
|
|
|
}
|
2009-08-28 19:45:01 +07:00
|
|
|
|
2010-09-30 03:11:06 +07:00
|
|
|
static void __nfsd4_hash_conn(struct nfsd4_conn *conn, struct nfsd4_session *ses)
|
|
|
|
{
|
|
|
|
conn->cn_session = ses;
|
|
|
|
list_add(&conn->cn_persession, &ses->se_conns);
|
2009-04-03 12:28:28 +07:00
|
|
|
}
|
|
|
|
|
2010-09-30 02:29:32 +07:00
|
|
|
static void nfsd4_hash_conn(struct nfsd4_conn *conn, struct nfsd4_session *ses)
|
2009-08-28 19:45:04 +07:00
|
|
|
{
|
2010-09-30 02:29:32 +07:00
|
|
|
struct nfs4_client *clp = ses->se_client;
|
2009-08-28 19:45:04 +07:00
|
|
|
|
2010-06-07 05:12:14 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
2010-09-30 03:11:06 +07:00
|
|
|
__nfsd4_hash_conn(conn, ses);
|
2010-06-07 05:12:14 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2009-08-28 19:45:04 +07:00
|
|
|
}
|
|
|
|
|
2010-10-26 21:07:17 +07:00
|
|
|
static int nfsd4_register_conn(struct nfsd4_conn *conn)
|
2009-10-25 07:52:16 +07:00
|
|
|
{
|
2010-06-07 05:37:16 +07:00
|
|
|
conn->cn_xpt_user.callback = nfsd4_conn_lost;
|
2010-10-26 21:07:17 +07:00
|
|
|
return register_xpt_user(conn->cn_xprt, &conn->cn_xpt_user);
|
2009-10-25 07:52:16 +07:00
|
|
|
}
|
|
|
|
|
2012-09-12 04:10:25 +07:00
|
|
|
static void nfsd4_init_conn(struct svc_rqst *rqstp, struct nfsd4_conn *conn, struct nfsd4_session *ses)
|
2009-04-03 12:28:28 +07:00
|
|
|
{
|
2010-10-26 21:07:17 +07:00
|
|
|
int ret;
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-09-30 02:29:32 +07:00
|
|
|
nfsd4_hash_conn(conn, ses);
|
2010-10-26 21:07:17 +07:00
|
|
|
ret = nfsd4_register_conn(conn);
|
|
|
|
if (ret)
|
|
|
|
/* oops; xprt is already down: */
|
|
|
|
nfsd4_conn_lost(&conn->cn_xpt_user);
|
2014-07-19 02:06:47 +07:00
|
|
|
/* We may have gained or lost a callback channel: */
|
|
|
|
nfsd4_probe_callback_sync(ses->se_client);
|
2010-06-07 05:12:14 +07:00
|
|
|
}
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2012-09-12 04:10:25 +07:00
|
|
|
static struct nfsd4_conn *alloc_conn_from_crses(struct svc_rqst *rqstp, struct nfsd4_create_session *cses)
|
2010-10-05 10:12:59 +07:00
|
|
|
{
|
|
|
|
u32 dir = NFS4_CDFC4_FORE;
|
|
|
|
|
2012-09-12 04:10:25 +07:00
|
|
|
if (cses->flags & SESSION4_BACK_CHAN)
|
2010-10-05 10:12:59 +07:00
|
|
|
dir |= NFS4_CDFC4_BACK;
|
2012-09-12 04:10:25 +07:00
|
|
|
return alloc_conn(rqstp, dir);
|
2010-10-05 10:12:59 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* must be called under client_lock */
|
2010-06-07 05:37:16 +07:00
|
|
|
static void nfsd4_del_conns(struct nfsd4_session *s)
|
2010-06-07 05:12:14 +07:00
|
|
|
{
|
2010-06-07 05:37:16 +07:00
|
|
|
struct nfs4_client *clp = s->se_client;
|
|
|
|
struct nfsd4_conn *c;
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-06-07 05:37:16 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
while (!list_empty(&s->se_conns)) {
|
|
|
|
c = list_first_entry(&s->se_conns, struct nfsd4_conn, cn_persession);
|
|
|
|
list_del_init(&c->cn_persession);
|
|
|
|
spin_unlock(&clp->cl_lock);
|
2009-08-28 19:45:04 +07:00
|
|
|
|
2010-06-07 05:37:16 +07:00
|
|
|
unregister_xpt_user(c->cn_xprt, &c->cn_xpt_user);
|
|
|
|
free_conn(c);
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2010-06-07 05:37:16 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
}
|
|
|
|
spin_unlock(&clp->cl_lock);
|
2010-06-07 05:12:14 +07:00
|
|
|
}
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2012-09-12 08:42:40 +07:00
|
|
|
static void __free_session(struct nfsd4_session *ses)
|
|
|
|
{
|
|
|
|
free_session_slots(ses);
|
|
|
|
kfree(ses);
|
|
|
|
}
|
|
|
|
|
2013-03-19 23:05:39 +07:00
|
|
|
static void free_session(struct nfsd4_session *ses)
|
2010-06-07 05:12:14 +07:00
|
|
|
{
|
2010-06-07 05:37:16 +07:00
|
|
|
nfsd4_del_conns(ses);
|
2013-04-09 03:44:14 +07:00
|
|
|
nfsd4_put_drc_mem(&ses->se_fchannel);
|
2012-09-12 08:42:40 +07:00
|
|
|
__free_session(ses);
|
2010-06-07 05:12:14 +07:00
|
|
|
}
|
|
|
|
|
2012-11-10 19:20:25 +07:00
|
|
|
static void init_session(struct svc_rqst *rqstp, struct nfsd4_session *new, struct nfs4_client *clp, struct nfsd4_create_session *cses)
|
2012-09-12 20:51:34 +07:00
|
|
|
{
|
|
|
|
int idx;
|
2012-11-14 22:21:51 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2012-09-12 20:51:34 +07:00
|
|
|
|
2009-04-03 12:28:28 +07:00
|
|
|
new->se_client = clp;
|
|
|
|
gen_sessionid(new);
|
|
|
|
|
2010-06-07 05:12:14 +07:00
|
|
|
INIT_LIST_HEAD(&new->se_conns);
|
|
|
|
|
2010-06-15 06:01:57 +07:00
|
|
|
new->se_cb_seq_nr = 1;
|
2009-04-03 12:28:28 +07:00
|
|
|
new->se_flags = cses->flags;
|
2010-10-20 04:31:50 +07:00
|
|
|
new->se_cb_prog = cses->callback_prog;
|
2012-11-02 03:31:02 +07:00
|
|
|
new->se_cb_sec = cses->cb_sec;
|
2013-03-19 23:05:39 +07:00
|
|
|
atomic_set(&new->se_ref, 0);
|
2010-09-28 04:12:05 +07:00
|
|
|
idx = hash_sessionid(&new->se_sessionid);
|
2012-11-14 22:21:51 +07:00
|
|
|
list_add(&new->se_hash, &nn->sessionid_hashtbl[idx]);
|
2010-06-16 01:22:37 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
2009-04-03 12:28:28 +07:00
|
|
|
list_add(&new->se_perclnt, &clp->cl_sessions);
|
2010-06-16 01:22:37 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2013-12-31 23:35:47 +07:00
|
|
|
|
2014-08-23 02:10:59 +07:00
|
|
|
{
|
2010-06-15 09:26:31 +07:00
|
|
|
struct sockaddr *sa = svc_addr(rqstp);
|
2010-06-16 04:25:45 +07:00
|
|
|
/*
|
|
|
|
* This is a little silly; with sessions there's no real
|
|
|
|
* use for the callback address. Use the peer address
|
|
|
|
* as a reasonable default for now, but consider fixing
|
|
|
|
* the rpc client not to require an address in the
|
|
|
|
* future:
|
|
|
|
*/
|
2010-06-15 09:26:31 +07:00
|
|
|
rpc_copy_addr((struct sockaddr *)&clp->cl_cb_conn.cb_addr, sa);
|
|
|
|
clp->cl_cb_conn.cb_addrlen = svc_addr_len(sa);
|
|
|
|
}
|
2009-04-03 12:28:28 +07:00
|
|
|
}
|
|
|
|
|
2010-05-12 04:12:26 +07:00
|
|
|
/* caller must hold client_lock */
|
2009-04-03 12:27:52 +07:00
|
|
|
static struct nfsd4_session *
|
2014-06-30 22:48:42 +07:00
|
|
|
__find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net)
|
2009-04-03 12:27:52 +07:00
|
|
|
{
|
|
|
|
struct nfsd4_session *elem;
|
|
|
|
int idx;
|
2012-11-14 22:21:51 +07:00
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2009-04-03 12:27:52 +07:00
|
|
|
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
2009-04-03 12:27:52 +07:00
|
|
|
dump_sessionid(__func__, sessionid);
|
|
|
|
idx = hash_sessionid(sessionid);
|
|
|
|
/* Search in the appropriate list */
|
2012-11-14 22:21:51 +07:00
|
|
|
list_for_each_entry(elem, &nn->sessionid_hashtbl[idx], se_hash) {
|
2009-04-03 12:27:52 +07:00
|
|
|
if (!memcmp(elem->se_sessionid.data, sessionid->data,
|
|
|
|
NFS4_MAX_SESSIONID_LEN)) {
|
|
|
|
return elem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dprintk("%s: session not found\n", __func__);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-06-30 22:48:42 +07:00
|
|
|
static struct nfsd4_session *
|
|
|
|
find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net,
|
|
|
|
__be32 *ret)
|
|
|
|
{
|
|
|
|
struct nfsd4_session *session;
|
|
|
|
__be32 status = nfserr_badsession;
|
|
|
|
|
|
|
|
session = __find_in_sessionid_hashtbl(sessionid, net);
|
|
|
|
if (!session)
|
|
|
|
goto out;
|
|
|
|
status = nfsd4_get_session_locked(session);
|
|
|
|
if (status)
|
|
|
|
session = NULL;
|
|
|
|
out:
|
|
|
|
*ret = status;
|
|
|
|
return session;
|
|
|
|
}
|
|
|
|
|
2010-05-12 04:12:26 +07:00
|
|
|
/* caller must hold client_lock */
|
2009-04-03 12:27:43 +07:00
|
|
|
static void
|
2009-04-03 12:27:52 +07:00
|
|
|
unhash_session(struct nfsd4_session *ses)
|
2009-04-03 12:27:43 +07:00
|
|
|
{
|
2014-07-30 19:27:10 +07:00
|
|
|
struct nfs4_client *clp = ses->se_client;
|
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
|
|
|
|
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
2009-04-03 12:27:43 +07:00
|
|
|
list_del(&ses->se_hash);
|
2010-06-16 01:22:37 +07:00
|
|
|
spin_lock(&ses->se_client->cl_lock);
|
2009-04-03 12:27:43 +07:00
|
|
|
list_del(&ses->se_perclnt);
|
2010-06-16 01:22:37 +07:00
|
|
|
spin_unlock(&ses->se_client->cl_lock);
|
2009-04-03 12:27:52 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* SETCLIENTID and SETCLIENTID_CONFIRM Helper functions */
|
|
|
|
static int
|
2012-07-25 19:57:45 +07:00
|
|
|
STALE_CLIENTID(clientid_t *clid, struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2015-01-20 23:51:26 +07:00
|
|
|
/*
|
|
|
|
* We're assuming the clid was not given out from a boot
|
|
|
|
* precisely 2^32 (about 136 years) before this one. That seems
|
|
|
|
* a safe assumption:
|
|
|
|
*/
|
|
|
|
if (clid->cl_boot == (u32)nn->boot_time)
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
2009-04-03 12:28:50 +07:00
|
|
|
dprintk("NFSD stale clientid (%08x/%08x) boot_time %08lx\n",
|
2012-07-25 19:57:45 +07:00
|
|
|
clid->cl_boot, clid->cl_id, nn->boot_time);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX Should we use a slab cache ?
|
|
|
|
* This type of memory management is somewhat inefficient, but we use it
|
|
|
|
* anyway since SETCLIENTID is not a common operation.
|
|
|
|
*/
|
2007-11-22 10:07:08 +07:00
|
|
|
static struct nfs4_client *alloc_client(struct xdr_netobj name)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
2014-07-30 08:34:36 +07:00
|
|
|
int i;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-11-22 10:07:08 +07:00
|
|
|
clp = kzalloc(sizeof(struct nfs4_client), GFP_KERNEL);
|
|
|
|
if (clp == NULL)
|
|
|
|
return NULL;
|
2011-11-18 05:43:40 +07:00
|
|
|
clp->cl_name.data = kmemdup(name.data, name.len, GFP_KERNEL);
|
2014-07-30 08:34:36 +07:00
|
|
|
if (clp->cl_name.data == NULL)
|
|
|
|
goto err_no_name;
|
|
|
|
clp->cl_ownerstr_hashtbl = kmalloc(sizeof(struct list_head) *
|
|
|
|
OWNER_HASH_SIZE, GFP_KERNEL);
|
|
|
|
if (!clp->cl_ownerstr_hashtbl)
|
|
|
|
goto err_no_hashtbl;
|
|
|
|
for (i = 0; i < OWNER_HASH_SIZE; i++)
|
|
|
|
INIT_LIST_HEAD(&clp->cl_ownerstr_hashtbl[i]);
|
2007-11-22 10:07:08 +07:00
|
|
|
clp->cl_name.len = name.len;
|
2014-04-19 01:43:56 +07:00
|
|
|
INIT_LIST_HEAD(&clp->cl_sessions);
|
|
|
|
idr_init(&clp->cl_stateids);
|
|
|
|
atomic_set(&clp->cl_refcount, 0);
|
|
|
|
clp->cl_cb_state = NFSD4_CB_UNKNOWN;
|
|
|
|
INIT_LIST_HEAD(&clp->cl_idhash);
|
|
|
|
INIT_LIST_HEAD(&clp->cl_openowners);
|
|
|
|
INIT_LIST_HEAD(&clp->cl_delegations);
|
|
|
|
INIT_LIST_HEAD(&clp->cl_lru);
|
|
|
|
INIT_LIST_HEAD(&clp->cl_revoked);
|
nfsd: implement pNFS operations
Add support for the GETDEVICEINFO, LAYOUTGET, LAYOUTCOMMIT and
LAYOUTRETURN NFSv4.1 operations, as well as backing code to manage
outstanding layouts and devices.
Layout management is very straight forward, with a nfs4_layout_stateid
structure that extends nfs4_stid to manage layout stateids as the
top-level structure. It is linked into the nfs4_file and nfs4_client
structures like the other stateids, and contains a linked list of
layouts that hang of the stateid. The actual layout operations are
implemented in layout drivers that are not part of this commit, but
will be added later.
The worst part of this commit is the management of the pNFS device IDs,
which suffers from a specification that is not sanely implementable due
to the fact that the device-IDs are global and not bound to an export,
and have a small enough size so that we can't store the fsid portion of
a file handle, and must never be reused. As we still do need perform all
export authentication and validation checks on a device ID passed to
GETDEVICEINFO we are caught between a rock and a hard place. To work
around this issue we add a new hash that maps from a 64-bit integer to a
fsid so that we can look up the export to authenticate against it,
a 32-bit integer as a generation that we can bump when changing the device,
and a currently unused 32-bit integer that could be used in the future
to handle more than a single device per export. Entries in this hash
table are never deleted as we can't reuse the ids anyway, and would have
a severe lifetime problem anyway as Linux export structures are temporary
structures that can go away under load.
Parts of the XDR data, structures and marshaling/unmarshaling code, as
well as many concepts are derived from the old pNFS server implementation
from Andy Adamson, Benny Halevy, Dean Hildebrand, Marc Eshel, Fred Isaman,
Mike Sager, Ricardo Labiaga and many others.
Signed-off-by: Christoph Hellwig <hch@lst.de>
2014-05-05 18:11:59 +07:00
|
|
|
#ifdef CONFIG_NFSD_PNFS
|
|
|
|
INIT_LIST_HEAD(&clp->cl_lo_states);
|
|
|
|
#endif
|
2014-04-19 01:43:56 +07:00
|
|
|
spin_lock_init(&clp->cl_lock);
|
|
|
|
rpc_init_wait_queue(&clp->cl_cb_waitq, "Backchannel slot table");
|
2005-04-17 05:20:36 +07:00
|
|
|
return clp;
|
2014-07-30 08:34:36 +07:00
|
|
|
err_no_hashtbl:
|
|
|
|
kfree(clp->cl_name.data);
|
|
|
|
err_no_name:
|
|
|
|
kfree(clp);
|
|
|
|
return NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-04-19 01:43:58 +07:00
|
|
|
static void
|
2005-04-17 05:20:36 +07:00
|
|
|
free_client(struct nfs4_client *clp)
|
|
|
|
{
|
2010-10-13 06:55:25 +07:00
|
|
|
while (!list_empty(&clp->cl_sessions)) {
|
|
|
|
struct nfsd4_session *ses;
|
|
|
|
ses = list_entry(clp->cl_sessions.next, struct nfsd4_session,
|
|
|
|
se_perclnt);
|
|
|
|
list_del(&ses->se_perclnt);
|
2013-03-19 23:05:39 +07:00
|
|
|
WARN_ON_ONCE(atomic_read(&ses->se_ref));
|
|
|
|
free_session(ses);
|
2010-10-13 06:55:25 +07:00
|
|
|
}
|
2014-04-19 01:43:57 +07:00
|
|
|
rpc_destroy_wait_queue(&clp->cl_cb_waitq);
|
2012-05-15 06:55:22 +07:00
|
|
|
free_svc_cred(&clp->cl_cred);
|
2014-07-30 08:34:36 +07:00
|
|
|
kfree(clp->cl_ownerstr_hashtbl);
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(clp->cl_name.data);
|
2013-01-29 12:16:06 +07:00
|
|
|
idr_destroy(&clp->cl_stateids);
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(clp);
|
|
|
|
}
|
|
|
|
|
2010-05-12 04:13:16 +07:00
|
|
|
/* must be called under the client_lock */
|
2014-07-30 19:27:02 +07:00
|
|
|
static void
|
2010-05-12 04:13:16 +07:00
|
|
|
unhash_client_locked(struct nfs4_client *clp)
|
|
|
|
{
|
2014-07-30 19:27:02 +07:00
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
2010-10-13 06:55:25 +07:00
|
|
|
struct nfsd4_session *ses;
|
|
|
|
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
2014-07-30 19:27:02 +07:00
|
|
|
/* Mark the client as expired! */
|
|
|
|
clp->cl_time = 0;
|
|
|
|
/* Make it invisible */
|
|
|
|
if (!list_empty(&clp->cl_idhash)) {
|
|
|
|
list_del_init(&clp->cl_idhash);
|
|
|
|
if (test_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags))
|
|
|
|
rb_erase(&clp->cl_namenode, &nn->conf_name_tree);
|
|
|
|
else
|
|
|
|
rb_erase(&clp->cl_namenode, &nn->unconf_name_tree);
|
|
|
|
}
|
|
|
|
list_del_init(&clp->cl_lru);
|
2010-06-16 01:22:37 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
2010-10-13 06:55:25 +07:00
|
|
|
list_for_each_entry(ses, &clp->cl_sessions, se_perclnt)
|
|
|
|
list_del_init(&ses->se_hash);
|
2010-06-16 01:22:37 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2010-05-12 04:13:16 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static void
|
2014-07-30 19:27:02 +07:00
|
|
|
unhash_client(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
unhash_client_locked(clp);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:12 +07:00
|
|
|
static __be32 mark_client_expired_locked(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
if (atomic_read(&clp->cl_refcount))
|
|
|
|
return nfserr_jukebox;
|
|
|
|
unhash_client_locked(clp);
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:02 +07:00
|
|
|
static void
|
|
|
|
__destroy_client(struct nfs4_client *clp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2011-07-31 10:33:59 +07:00
|
|
|
struct nfs4_openowner *oo;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct nfs4_delegation *dp;
|
|
|
|
struct list_head reaplist;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&reaplist);
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_lock(&state_lock);
|
2005-06-24 12:04:17 +07:00
|
|
|
while (!list_empty(&clp->cl_delegations)) {
|
|
|
|
dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt);
|
2015-08-24 23:41:48 +07:00
|
|
|
WARN_ON(!unhash_delegation_locked(dp));
|
2014-07-25 18:34:20 +07:00
|
|
|
list_add(&dp->dl_recall_lru, &reaplist);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_unlock(&state_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
while (!list_empty(&reaplist)) {
|
|
|
|
dp = list_entry(reaplist.next, struct nfs4_delegation, dl_recall_lru);
|
2014-07-25 18:34:20 +07:00
|
|
|
list_del_init(&dp->dl_recall_lru);
|
2015-04-27 19:50:14 +07:00
|
|
|
put_clnt_odstate(dp->dl_clnt_odstate);
|
2014-08-09 21:22:41 +07:00
|
|
|
nfs4_put_deleg_lease(dp->dl_stid.sc_file);
|
2014-07-30 08:34:06 +07:00
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-07-25 18:34:21 +07:00
|
|
|
while (!list_empty(&clp->cl_revoked)) {
|
2015-02-26 05:46:27 +07:00
|
|
|
dp = list_entry(clp->cl_revoked.next, struct nfs4_delegation, dl_recall_lru);
|
2014-07-25 18:34:21 +07:00
|
|
|
list_del_init(&dp->dl_recall_lru);
|
2014-07-30 08:34:06 +07:00
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
2013-10-29 16:39:12 +07:00
|
|
|
}
|
2005-06-24 12:04:17 +07:00
|
|
|
while (!list_empty(&clp->cl_openowners)) {
|
2011-07-31 10:33:59 +07:00
|
|
|
oo = list_entry(clp->cl_openowners.next, struct nfs4_openowner, oo_perclient);
|
2014-08-22 21:18:43 +07:00
|
|
|
nfs4_get_stateowner(&oo->oo_owner);
|
2011-07-31 10:33:59 +07:00
|
|
|
release_openowner(oo);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
nfsd: implement pNFS operations
Add support for the GETDEVICEINFO, LAYOUTGET, LAYOUTCOMMIT and
LAYOUTRETURN NFSv4.1 operations, as well as backing code to manage
outstanding layouts and devices.
Layout management is very straight forward, with a nfs4_layout_stateid
structure that extends nfs4_stid to manage layout stateids as the
top-level structure. It is linked into the nfs4_file and nfs4_client
structures like the other stateids, and contains a linked list of
layouts that hang of the stateid. The actual layout operations are
implemented in layout drivers that are not part of this commit, but
will be added later.
The worst part of this commit is the management of the pNFS device IDs,
which suffers from a specification that is not sanely implementable due
to the fact that the device-IDs are global and not bound to an export,
and have a small enough size so that we can't store the fsid portion of
a file handle, and must never be reused. As we still do need perform all
export authentication and validation checks on a device ID passed to
GETDEVICEINFO we are caught between a rock and a hard place. To work
around this issue we add a new hash that maps from a 64-bit integer to a
fsid so that we can look up the export to authenticate against it,
a 32-bit integer as a generation that we can bump when changing the device,
and a currently unused 32-bit integer that could be used in the future
to handle more than a single device per export. Entries in this hash
table are never deleted as we can't reuse the ids anyway, and would have
a severe lifetime problem anyway as Linux export structures are temporary
structures that can go away under load.
Parts of the XDR data, structures and marshaling/unmarshaling code, as
well as many concepts are derived from the old pNFS server implementation
from Andy Adamson, Benny Halevy, Dean Hildebrand, Marc Eshel, Fred Isaman,
Mike Sager, Ricardo Labiaga and many others.
Signed-off-by: Christoph Hellwig <hch@lst.de>
2014-05-05 18:11:59 +07:00
|
|
|
nfsd4_return_all_client_layouts(clp);
|
2010-06-05 07:04:45 +07:00
|
|
|
nfsd4_shutdown_callback(clp);
|
2010-05-12 04:13:16 +07:00
|
|
|
if (clp->cl_cb_conn.cb_xprt)
|
|
|
|
svc_xprt_put(clp->cl_cb_conn.cb_xprt);
|
2013-04-02 09:23:49 +07:00
|
|
|
free_client(clp);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:02 +07:00
|
|
|
static void
|
|
|
|
destroy_client(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
unhash_client(clp);
|
|
|
|
__destroy_client(clp);
|
|
|
|
}
|
|
|
|
|
2012-09-26 22:36:16 +07:00
|
|
|
static void expire_client(struct nfs4_client *clp)
|
|
|
|
{
|
2014-07-30 19:27:02 +07:00
|
|
|
unhash_client(clp);
|
2012-09-26 22:36:16 +07:00
|
|
|
nfsd4_client_record_remove(clp);
|
2014-07-30 19:27:02 +07:00
|
|
|
__destroy_client(clp);
|
2012-09-26 22:36:16 +07:00
|
|
|
}
|
|
|
|
|
2007-11-22 10:07:08 +07:00
|
|
|
static void copy_verf(struct nfs4_client *target, nfs4_verifier *source)
|
|
|
|
{
|
|
|
|
memcpy(target->cl_verifier.data, source->data,
|
|
|
|
sizeof(target->cl_verifier.data));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2007-11-22 10:07:08 +07:00
|
|
|
static void copy_clid(struct nfs4_client *target, struct nfs4_client *source)
|
|
|
|
{
|
2005-04-17 05:20:36 +07:00
|
|
|
target->cl_clientid.cl_boot = source->cl_clientid.cl_boot;
|
|
|
|
target->cl_clientid.cl_id = source->cl_clientid.cl_id;
|
|
|
|
}
|
|
|
|
|
2015-11-21 03:58:37 +07:00
|
|
|
int strdup_if_nonnull(char **target, char *source)
|
2007-11-22 10:07:08 +07:00
|
|
|
{
|
2015-11-21 03:58:37 +07:00
|
|
|
if (source) {
|
|
|
|
*target = kstrdup(source, GFP_KERNEL);
|
|
|
|
if (!*target)
|
2012-05-15 06:55:22 +07:00
|
|
|
return -ENOMEM;
|
|
|
|
} else
|
2015-11-21 03:58:37 +07:00
|
|
|
*target = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int copy_cred(struct svc_cred *target, struct svc_cred *source)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = strdup_if_nonnull(&target->cr_principal, source->cr_principal);
|
2015-11-20 22:48:02 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
ret = strdup_if_nonnull(&target->cr_raw_principal,
|
|
|
|
source->cr_raw_principal);
|
2015-11-21 03:58:37 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2012-05-15 09:06:49 +07:00
|
|
|
target->cr_flavor = source->cr_flavor;
|
2005-04-17 05:20:36 +07:00
|
|
|
target->cr_uid = source->cr_uid;
|
|
|
|
target->cr_gid = source->cr_gid;
|
|
|
|
target->cr_group_info = source->cr_group_info;
|
|
|
|
get_group_info(target->cr_group_info);
|
2013-05-15 03:07:13 +07:00
|
|
|
target->cr_gss_mech = source->cr_gss_mech;
|
|
|
|
if (source->cr_gss_mech)
|
|
|
|
gss_mech_get(source->cr_gss_mech);
|
2012-05-15 06:55:22 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-12-05 22:40:07 +07:00
|
|
|
static int
|
2012-11-13 03:00:56 +07:00
|
|
|
compare_blob(const struct xdr_netobj *o1, const struct xdr_netobj *o2)
|
|
|
|
{
|
2014-12-05 22:40:07 +07:00
|
|
|
if (o1->len < o2->len)
|
|
|
|
return -1;
|
|
|
|
if (o1->len > o2->len)
|
|
|
|
return 1;
|
|
|
|
return memcmp(o1->data, o2->data, o1->len);
|
2012-11-13 03:00:56 +07:00
|
|
|
}
|
|
|
|
|
2007-11-22 10:07:08 +07:00
|
|
|
static int same_name(const char *n1, const char *n2)
|
2007-07-27 04:04:54 +07:00
|
|
|
{
|
2005-06-24 12:03:52 +07:00
|
|
|
return 0 == memcmp(n1, n2, HEXDIR_LEN);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2007-07-27 04:04:54 +07:00
|
|
|
same_verf(nfs4_verifier *v1, nfs4_verifier *v2)
|
|
|
|
{
|
|
|
|
return 0 == memcmp(v1->data, v2->data, sizeof(v1->data));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2007-07-27 04:04:54 +07:00
|
|
|
same_clid(clientid_t *cl1, clientid_t *cl2)
|
|
|
|
{
|
|
|
|
return (cl1->cl_boot == cl2->cl_boot) && (cl1->cl_id == cl2->cl_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2012-05-15 08:20:54 +07:00
|
|
|
static bool groups_equal(struct group_info *g1, struct group_info *g2)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (g1->ngroups != g2->ngroups)
|
|
|
|
return false;
|
|
|
|
for (i=0; i<g1->ngroups; i++)
|
cred: simpler, 1D supplementary groups
Current supplementary groups code can massively overallocate memory and
is implemented in a way so that access to individual gid is done via 2D
array.
If number of gids is <= 32, memory allocation is more or less tolerable
(140/148 bytes). But if it is not, code allocates full page (!)
regardless and, what's even more fun, doesn't reuse small 32-entry
array.
2D array means dependent shifts, loads and LEAs without possibility to
optimize them (gid is never known at compile time).
All of the above is unnecessary. Switch to the usual
trailing-zero-len-array scheme. Memory is allocated with
kmalloc/vmalloc() and only as much as needed. Accesses become simpler
(LEA 8(gi,idx,4) or even without displacement).
Maximum number of gids is 65536 which translates to 256KB+8 bytes. I
think kernel can handle such allocation.
On my usual desktop system with whole 9 (nine) aux groups, struct
group_info shrinks from 148 bytes to 44 bytes, yay!
Nice side effects:
- "gi->gid[i]" is shorter than "GROUP_AT(gi, i)", less typing,
- fix little mess in net/ipv4/ping.c
should have been using GROUP_AT macro but this point becomes moot,
- aux group allocation is persistent and should be accounted as such.
Link: http://lkml.kernel.org/r/20160817201927.GA2096@p183.telecom.by
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Vasily Kulikov <segoon@openwall.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-10-08 07:03:12 +07:00
|
|
|
if (!gid_eq(g1->gid[i], g2->gid[i]))
|
2012-05-15 08:20:54 +07:00
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-08-21 23:48:30 +07:00
|
|
|
/*
|
|
|
|
* RFC 3530 language requires clid_inuse be returned when the
|
|
|
|
* "principal" associated with a requests differs from that previously
|
|
|
|
* used. We use uid, gid's, and gss principal string as our best
|
|
|
|
* approximation. We also don't want to allow non-gss use of a client
|
|
|
|
* established using gss: in theory cr_principal should catch that
|
|
|
|
* change, but in practice cr_principal can be null even in the gss case
|
|
|
|
* since gssd doesn't always pass down a principal string.
|
|
|
|
*/
|
|
|
|
static bool is_gss_cred(struct svc_cred *cr)
|
|
|
|
{
|
|
|
|
/* Is cr_flavor one of the gss "pseudoflavors"?: */
|
|
|
|
return (cr->cr_flavor > RPC_AUTH_MAXFLAVOR);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-24 22:48:20 +07:00
|
|
|
static bool
|
2007-07-27 04:04:54 +07:00
|
|
|
same_creds(struct svc_cred *cr1, struct svc_cred *cr2)
|
|
|
|
{
|
2012-08-21 23:48:30 +07:00
|
|
|
if ((is_gss_cred(cr1) != is_gss_cred(cr2))
|
2013-02-02 21:53:11 +07:00
|
|
|
|| (!uid_eq(cr1->cr_uid, cr2->cr_uid))
|
|
|
|
|| (!gid_eq(cr1->cr_gid, cr2->cr_gid))
|
2012-05-15 08:20:54 +07:00
|
|
|
|| !groups_equal(cr1->cr_group_info, cr2->cr_group_info))
|
|
|
|
return false;
|
|
|
|
if (cr1->cr_principal == cr2->cr_principal)
|
|
|
|
return true;
|
|
|
|
if (!cr1->cr_principal || !cr2->cr_principal)
|
|
|
|
return false;
|
2012-07-24 22:48:20 +07:00
|
|
|
return 0 == strcmp(cr1->cr_principal, cr2->cr_principal);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2013-04-14 01:27:29 +07:00
|
|
|
static bool svc_rqst_integrity_protected(struct svc_rqst *rqstp)
|
|
|
|
{
|
|
|
|
struct svc_cred *cr = &rqstp->rq_cred;
|
|
|
|
u32 service;
|
|
|
|
|
2013-08-07 22:41:49 +07:00
|
|
|
if (!cr->cr_gss_mech)
|
|
|
|
return false;
|
2013-04-14 01:27:29 +07:00
|
|
|
service = gss_pseudoflavor_to_service(cr->cr_gss_mech, cr->cr_flavor);
|
|
|
|
return service == RPC_GSS_SVC_INTEGRITY ||
|
|
|
|
service == RPC_GSS_SVC_PRIVACY;
|
|
|
|
}
|
|
|
|
|
2016-06-15 23:52:08 +07:00
|
|
|
bool nfsd4_mach_creds_match(struct nfs4_client *cl, struct svc_rqst *rqstp)
|
2013-04-14 01:27:29 +07:00
|
|
|
{
|
|
|
|
struct svc_cred *cr = &rqstp->rq_cred;
|
|
|
|
|
|
|
|
if (!cl->cl_mach_cred)
|
|
|
|
return true;
|
|
|
|
if (cl->cl_cred.cr_gss_mech != cr->cr_gss_mech)
|
|
|
|
return false;
|
|
|
|
if (!svc_rqst_integrity_protected(rqstp))
|
|
|
|
return false;
|
2015-11-20 22:48:02 +07:00
|
|
|
if (cl->cl_cred.cr_raw_principal)
|
|
|
|
return 0 == strcmp(cl->cl_cred.cr_raw_principal,
|
|
|
|
cr->cr_raw_principal);
|
2013-04-14 01:27:29 +07:00
|
|
|
if (!cr->cr_principal)
|
|
|
|
return false;
|
|
|
|
return 0 == strcmp(cl->cl_cred.cr_principal, cr->cr_principal);
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:15 +07:00
|
|
|
static void gen_confirm(struct nfs4_client *clp, struct nfsd_net *nn)
|
2007-11-20 08:31:04 +07:00
|
|
|
{
|
2012-03-03 05:13:50 +07:00
|
|
|
__be32 verf[2];
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-06-17 18:44:11 +07:00
|
|
|
/*
|
|
|
|
* This is opaque to client, so no need to byte-swap. Use
|
|
|
|
* __force to keep sparse happy
|
|
|
|
*/
|
|
|
|
verf[0] = (__force __be32)get_seconds();
|
2015-07-18 06:33:31 +07:00
|
|
|
verf[1] = (__force __be32)nn->clverifier_counter++;
|
2012-03-03 05:13:50 +07:00
|
|
|
memcpy(clp->cl_confirm.data, verf, sizeof(clp->cl_confirm.data));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:15 +07:00
|
|
|
static void gen_clid(struct nfs4_client *clp, struct nfsd_net *nn)
|
|
|
|
{
|
|
|
|
clp->cl_clientid.cl_boot = nn->boot_time;
|
|
|
|
clp->cl_clientid.cl_id = nn->clientid_counter++;
|
|
|
|
gen_confirm(clp, nn);
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:10 +07:00
|
|
|
static struct nfs4_stid *
|
|
|
|
find_stateid_locked(struct nfs4_client *cl, stateid_t *t)
|
2011-09-07 01:56:09 +07:00
|
|
|
{
|
2013-02-04 00:23:01 +07:00
|
|
|
struct nfs4_stid *ret;
|
|
|
|
|
|
|
|
ret = idr_find(&cl->cl_stateids, t->si_opaque.so_id);
|
|
|
|
if (!ret || !ret->sc_type)
|
|
|
|
return NULL;
|
|
|
|
return ret;
|
2011-09-07 03:48:57 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:10 +07:00
|
|
|
static struct nfs4_stid *
|
|
|
|
find_stateid_by_type(struct nfs4_client *cl, stateid_t *t, char typemask)
|
2011-09-09 20:06:12 +07:00
|
|
|
{
|
|
|
|
struct nfs4_stid *s;
|
2011-09-07 03:48:57 +07:00
|
|
|
|
2014-07-30 08:34:10 +07:00
|
|
|
spin_lock(&cl->cl_lock);
|
|
|
|
s = find_stateid_locked(cl, t);
|
2014-07-30 08:34:25 +07:00
|
|
|
if (s != NULL) {
|
|
|
|
if (typemask & s->sc_type)
|
|
|
|
atomic_inc(&s->sc_count);
|
|
|
|
else
|
|
|
|
s = NULL;
|
|
|
|
}
|
2014-07-30 08:34:10 +07:00
|
|
|
spin_unlock(&cl->cl_lock);
|
|
|
|
return s;
|
2011-09-07 01:56:09 +07:00
|
|
|
}
|
|
|
|
|
2012-11-13 03:00:57 +07:00
|
|
|
static struct nfs4_client *create_client(struct xdr_netobj name,
|
2009-09-10 16:27:34 +07:00
|
|
|
struct svc_rqst *rqstp, nfs4_verifier *verf)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct sockaddr *sa = svc_addr(rqstp);
|
2012-05-15 06:55:22 +07:00
|
|
|
int ret;
|
2012-11-14 22:21:10 +07:00
|
|
|
struct net *net = SVC_NET(rqstp);
|
2009-09-10 16:27:34 +07:00
|
|
|
|
|
|
|
clp = alloc_client(name);
|
|
|
|
if (clp == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
2012-05-15 06:55:22 +07:00
|
|
|
ret = copy_cred(&clp->cl_cred, &rqstp->rq_cred);
|
|
|
|
if (ret) {
|
|
|
|
free_client(clp);
|
|
|
|
return NULL;
|
2009-09-10 16:27:34 +07:00
|
|
|
}
|
2014-09-24 17:19:19 +07:00
|
|
|
nfsd4_init_cb(&clp->cl_cb_null, clp, NULL, NFSPROC4_CLNT_CB_NULL);
|
2010-05-12 04:13:41 +07:00
|
|
|
clp->cl_time = get_seconds();
|
2009-09-10 16:27:34 +07:00
|
|
|
clear_bit(0, &clp->cl_cb_slot_busy);
|
|
|
|
copy_verf(clp, verf);
|
|
|
|
rpc_copy_addr((struct sockaddr *) &clp->cl_addr, sa);
|
2010-06-15 09:26:31 +07:00
|
|
|
clp->cl_cb_session = NULL;
|
2012-11-14 22:21:10 +07:00
|
|
|
clp->net = net;
|
2009-09-10 16:27:34 +07:00
|
|
|
return clp;
|
|
|
|
}
|
|
|
|
|
2005-06-24 12:04:03 +07:00
|
|
|
static void
|
2012-11-13 03:00:56 +07:00
|
|
|
add_clp_to_name_tree(struct nfs4_client *new_clp, struct rb_root *root)
|
|
|
|
{
|
|
|
|
struct rb_node **new = &(root->rb_node), *parent = NULL;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
|
|
|
|
while (*new) {
|
|
|
|
clp = rb_entry(*new, struct nfs4_client, cl_namenode);
|
|
|
|
parent = *new;
|
|
|
|
|
|
|
|
if (compare_blob(&clp->cl_name, &new_clp->cl_name) > 0)
|
|
|
|
new = &((*new)->rb_left);
|
|
|
|
else
|
|
|
|
new = &((*new)->rb_right);
|
|
|
|
}
|
|
|
|
|
|
|
|
rb_link_node(&new_clp->cl_namenode, parent, new);
|
|
|
|
rb_insert_color(&new_clp->cl_namenode, root);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfs4_client *
|
|
|
|
find_clp_in_name_tree(struct xdr_netobj *name, struct rb_root *root)
|
|
|
|
{
|
2014-12-05 22:40:07 +07:00
|
|
|
int cmp;
|
2012-11-13 03:00:56 +07:00
|
|
|
struct rb_node *node = root->rb_node;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
|
|
|
|
while (node) {
|
|
|
|
clp = rb_entry(node, struct nfs4_client, cl_namenode);
|
|
|
|
cmp = compare_blob(&clp->cl_name, name);
|
|
|
|
if (cmp > 0)
|
|
|
|
node = node->rb_left;
|
|
|
|
else if (cmp < 0)
|
|
|
|
node = node->rb_right;
|
|
|
|
else
|
|
|
|
return clp;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
add_to_unconfirmed(struct nfs4_client *clp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int idhashval;
|
2012-11-14 22:21:31 +07:00
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
2012-11-13 03:00:56 +07:00
|
|
|
clear_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags);
|
2012-11-14 22:21:36 +07:00
|
|
|
add_clp_to_name_tree(clp, &nn->unconf_name_tree);
|
2005-04-17 05:20:36 +07:00
|
|
|
idhashval = clientid_hashval(clp->cl_clientid.cl_id);
|
2012-11-14 22:21:31 +07:00
|
|
|
list_add(&clp->cl_idhash, &nn->unconf_id_hashtbl[idhashval]);
|
2014-07-30 19:27:06 +07:00
|
|
|
renew_client_locked(clp);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2005-06-24 12:04:03 +07:00
|
|
|
static void
|
2005-04-17 05:20:36 +07:00
|
|
|
move_to_confirmed(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
unsigned int idhashval = clientid_hashval(clp->cl_clientid.cl_id);
|
2012-11-14 22:21:21 +07:00
|
|
|
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
dprintk("NFSD: move_to_confirm nfs4_client %p\n", clp);
|
2012-11-14 22:21:21 +07:00
|
|
|
list_move(&clp->cl_idhash, &nn->conf_id_hashtbl[idhashval]);
|
2012-11-14 22:21:36 +07:00
|
|
|
rb_erase(&clp->cl_namenode, &nn->unconf_name_tree);
|
2012-11-14 22:21:26 +07:00
|
|
|
add_clp_to_name_tree(clp, &nn->conf_name_tree);
|
2012-11-13 03:00:56 +07:00
|
|
|
set_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags);
|
2014-07-30 19:27:06 +07:00
|
|
|
renew_client_locked(clp);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfs4_client *
|
2013-03-15 05:24:52 +07:00
|
|
|
find_client_in_id_table(struct list_head *tbl, clientid_t *clid, bool sessions)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
unsigned int idhashval = clientid_hashval(clid->cl_id);
|
|
|
|
|
2013-03-15 05:24:52 +07:00
|
|
|
list_for_each_entry(clp, &tbl[idhashval], cl_idhash) {
|
2011-10-13 03:24:27 +07:00
|
|
|
if (same_clid(&clp->cl_clientid, clid)) {
|
2012-09-14 03:19:31 +07:00
|
|
|
if ((bool)clp->cl_minorversion != sessions)
|
|
|
|
return NULL;
|
2014-07-30 19:27:06 +07:00
|
|
|
renew_client_locked(clp);
|
2005-04-17 05:20:36 +07:00
|
|
|
return clp;
|
2011-10-13 03:24:27 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-03-15 05:24:52 +07:00
|
|
|
static struct nfs4_client *
|
|
|
|
find_confirmed_client(clientid_t *clid, bool sessions, struct nfsd_net *nn)
|
|
|
|
{
|
|
|
|
struct list_head *tbl = nn->conf_id_hashtbl;
|
|
|
|
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
2013-03-15 05:24:52 +07:00
|
|
|
return find_client_in_id_table(tbl, clid, sessions);
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static struct nfs4_client *
|
2012-11-14 22:21:31 +07:00
|
|
|
find_unconfirmed_client(clientid_t *clid, bool sessions, struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2013-03-15 05:24:52 +07:00
|
|
|
struct list_head *tbl = nn->unconf_id_hashtbl;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
2013-03-15 05:24:52 +07:00
|
|
|
return find_client_in_id_table(tbl, clid, sessions);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2010-11-25 05:17:34 +07:00
|
|
|
static bool clp_used_exchangeid(struct nfs4_client *clp)
|
2009-04-03 12:28:05 +07:00
|
|
|
{
|
2010-11-25 05:17:34 +07:00
|
|
|
return clp->cl_exchange_flags != 0;
|
2010-11-25 05:30:54 +07:00
|
|
|
}
|
2009-04-03 12:28:05 +07:00
|
|
|
|
2005-06-24 12:03:56 +07:00
|
|
|
static struct nfs4_client *
|
2012-11-14 22:21:26 +07:00
|
|
|
find_confirmed_client_by_name(struct xdr_netobj *name, struct nfsd_net *nn)
|
2005-06-24 12:03:56 +07:00
|
|
|
{
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
2012-11-14 22:21:26 +07:00
|
|
|
return find_clp_in_name_tree(name, &nn->conf_name_tree);
|
2005-06-24 12:03:56 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfs4_client *
|
2012-11-14 22:21:36 +07:00
|
|
|
find_unconfirmed_client_by_name(struct xdr_netobj *name, struct nfsd_net *nn)
|
2005-06-24 12:03:56 +07:00
|
|
|
{
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
2012-11-14 22:21:36 +07:00
|
|
|
return find_clp_in_name_tree(name, &nn->unconf_name_tree);
|
2005-06-24 12:03:56 +07:00
|
|
|
}
|
|
|
|
|
2005-06-24 12:04:03 +07:00
|
|
|
static void
|
2010-12-15 12:09:01 +07:00
|
|
|
gen_callback(struct nfs4_client *clp, struct nfsd4_setclientid *se, struct svc_rqst *rqstp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2010-06-01 06:09:40 +07:00
|
|
|
struct nfs4_cb_conn *conn = &clp->cl_cb_conn;
|
2010-12-15 12:09:01 +07:00
|
|
|
struct sockaddr *sa = svc_addr(rqstp);
|
|
|
|
u32 scopeid = rpc_get_scope_id(sa);
|
2009-08-14 23:57:58 +07:00
|
|
|
unsigned short expected_family;
|
|
|
|
|
|
|
|
/* Currently, we only support tcp and tcp6 for the callback channel */
|
|
|
|
if (se->se_callback_netid_len == 3 &&
|
|
|
|
!memcmp(se->se_callback_netid_val, "tcp", 3))
|
|
|
|
expected_family = AF_INET;
|
|
|
|
else if (se->se_callback_netid_len == 4 &&
|
|
|
|
!memcmp(se->se_callback_netid_val, "tcp6", 4))
|
|
|
|
expected_family = AF_INET6;
|
|
|
|
else
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out_err;
|
|
|
|
|
2012-11-14 22:21:10 +07:00
|
|
|
conn->cb_addrlen = rpc_uaddr2sockaddr(clp->net, se->se_callback_addr_val,
|
2009-08-14 23:57:57 +07:00
|
|
|
se->se_callback_addr_len,
|
2010-06-01 06:09:40 +07:00
|
|
|
(struct sockaddr *)&conn->cb_addr,
|
|
|
|
sizeof(conn->cb_addr));
|
2009-08-14 23:57:57 +07:00
|
|
|
|
2010-06-01 06:09:40 +07:00
|
|
|
if (!conn->cb_addrlen || conn->cb_addr.ss_family != expected_family)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out_err;
|
2009-08-14 23:57:57 +07:00
|
|
|
|
2010-06-01 06:09:40 +07:00
|
|
|
if (conn->cb_addr.ss_family == AF_INET6)
|
|
|
|
((struct sockaddr_in6 *)&conn->cb_addr)->sin6_scope_id = scopeid;
|
2009-08-14 23:57:59 +07:00
|
|
|
|
2010-06-01 06:09:40 +07:00
|
|
|
conn->cb_prog = se->se_callback_prog;
|
|
|
|
conn->cb_ident = se->se_callback_ident;
|
2011-08-30 16:18:41 +07:00
|
|
|
memcpy(&conn->cb_saddr, &rqstp->rq_daddr, rqstp->rq_daddrlen);
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
out_err:
|
2010-06-01 06:09:40 +07:00
|
|
|
conn->cb_addr.ss_family = AF_UNSPEC;
|
|
|
|
conn->cb_addrlen = 0;
|
2005-09-13 15:25:36 +07:00
|
|
|
dprintk(KERN_INFO "NFSD: this client (clientid %08x/%08x) "
|
2005-04-17 05:20:36 +07:00
|
|
|
"will not receive delegations\n",
|
|
|
|
clp->cl_clientid.cl_boot, clp->cl_clientid.cl_id);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-04-03 12:28:15 +07:00
|
|
|
/*
|
2014-03-22 04:26:44 +07:00
|
|
|
* Cache a reply. nfsd4_check_resp_size() has bounded the cache size.
|
2009-04-03 12:28:15 +07:00
|
|
|
*/
|
2014-06-30 22:48:35 +07:00
|
|
|
static void
|
2009-04-03 12:28:15 +07:00
|
|
|
nfsd4_store_cache_entry(struct nfsd4_compoundres *resp)
|
|
|
|
{
|
2014-03-22 04:57:57 +07:00
|
|
|
struct xdr_buf *buf = resp->xdr.buf;
|
2009-08-28 19:45:04 +07:00
|
|
|
struct nfsd4_slot *slot = resp->cstate.slot;
|
|
|
|
unsigned int base;
|
2009-04-03 12:28:15 +07:00
|
|
|
|
2009-08-28 19:45:04 +07:00
|
|
|
dprintk("--> %s slot %p\n", __func__, slot);
|
2009-04-03 12:28:15 +07:00
|
|
|
|
2009-08-28 19:45:04 +07:00
|
|
|
slot->sl_opcnt = resp->opcnt;
|
|
|
|
slot->sl_status = resp->cstate.status;
|
2009-04-03 12:28:15 +07:00
|
|
|
|
2012-02-14 04:56:19 +07:00
|
|
|
slot->sl_flags |= NFSD4_SLOT_INITIALIZED;
|
2009-04-03 12:28:35 +07:00
|
|
|
if (nfsd4_not_cached(resp)) {
|
2009-08-28 19:45:04 +07:00
|
|
|
slot->sl_datalen = 0;
|
2009-04-03 12:28:35 +07:00
|
|
|
return;
|
2009-04-03 12:28:15 +07:00
|
|
|
}
|
2014-03-22 04:57:57 +07:00
|
|
|
base = resp->cstate.data_offset;
|
|
|
|
slot->sl_datalen = buf->len - base;
|
|
|
|
if (read_bytes_from_xdr_buf(buf, base, slot->sl_data, slot->sl_datalen))
|
2015-11-21 17:28:50 +07:00
|
|
|
WARN(1, "%s: sessions DRC could not cache compound\n",
|
|
|
|
__func__);
|
2009-08-28 19:45:04 +07:00
|
|
|
return;
|
2009-04-03 12:28:15 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2009-07-24 06:02:18 +07:00
|
|
|
* Encode the replay sequence operation from the slot values.
|
|
|
|
* If cachethis is FALSE encode the uncached rep error on the next
|
|
|
|
* operation which sets resp->p and increments resp->opcnt for
|
|
|
|
* nfs4svc_encode_compoundres.
|
2009-04-03 12:28:15 +07:00
|
|
|
*
|
|
|
|
*/
|
2009-07-24 06:02:18 +07:00
|
|
|
static __be32
|
|
|
|
nfsd4_enc_sequence_replay(struct nfsd4_compoundargs *args,
|
|
|
|
struct nfsd4_compoundres *resp)
|
2009-04-03 12:28:15 +07:00
|
|
|
{
|
2009-07-24 06:02:18 +07:00
|
|
|
struct nfsd4_op *op;
|
|
|
|
struct nfsd4_slot *slot = resp->cstate.slot;
|
2009-04-03 12:28:35 +07:00
|
|
|
|
2009-07-24 06:02:18 +07:00
|
|
|
/* Encode the replayed sequence operation */
|
|
|
|
op = &args->ops[resp->opcnt - 1];
|
|
|
|
nfsd4_encode_operation(resp, op);
|
2009-04-03 12:28:35 +07:00
|
|
|
|
2009-07-24 06:02:18 +07:00
|
|
|
/* Return nfserr_retry_uncached_rep in next operation. */
|
2012-02-14 04:39:00 +07:00
|
|
|
if (args->opcnt > 1 && !(slot->sl_flags & NFSD4_SLOT_CACHETHIS)) {
|
2009-07-24 06:02:18 +07:00
|
|
|
op = &args->ops[resp->opcnt++];
|
|
|
|
op->status = nfserr_retry_uncached_rep;
|
|
|
|
nfsd4_encode_operation(resp, op);
|
2009-04-03 12:28:15 +07:00
|
|
|
}
|
2009-07-24 06:02:18 +07:00
|
|
|
return op->status;
|
2009-04-03 12:28:15 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2009-08-28 19:45:04 +07:00
|
|
|
* The sequence operation is not cached because we can use the slot and
|
|
|
|
* session values.
|
2009-04-03 12:28:15 +07:00
|
|
|
*/
|
2014-03-21 23:04:21 +07:00
|
|
|
static __be32
|
2009-04-03 12:28:35 +07:00
|
|
|
nfsd4_replay_cache_entry(struct nfsd4_compoundres *resp,
|
|
|
|
struct nfsd4_sequence *seq)
|
2009-04-03 12:28:15 +07:00
|
|
|
{
|
2009-08-28 19:45:04 +07:00
|
|
|
struct nfsd4_slot *slot = resp->cstate.slot;
|
2014-03-22 04:57:57 +07:00
|
|
|
struct xdr_stream *xdr = &resp->xdr;
|
|
|
|
__be32 *p;
|
2009-04-03 12:28:15 +07:00
|
|
|
__be32 status;
|
|
|
|
|
2009-08-28 19:45:04 +07:00
|
|
|
dprintk("--> %s slot %p\n", __func__, slot);
|
2009-04-03 12:28:15 +07:00
|
|
|
|
2009-07-24 06:02:18 +07:00
|
|
|
status = nfsd4_enc_sequence_replay(resp->rqstp->rq_argp, resp);
|
2014-03-21 22:43:34 +07:00
|
|
|
if (status)
|
2009-07-24 06:02:18 +07:00
|
|
|
return status;
|
2009-04-03 12:28:15 +07:00
|
|
|
|
2014-03-22 04:57:57 +07:00
|
|
|
p = xdr_reserve_space(xdr, slot->sl_datalen);
|
|
|
|
if (!p) {
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
return nfserr_serverfault;
|
|
|
|
}
|
|
|
|
xdr_encode_opaque_fixed(p, slot->sl_data, slot->sl_datalen);
|
|
|
|
xdr_commit_encode(xdr);
|
2009-04-03 12:28:15 +07:00
|
|
|
|
2009-08-28 19:45:04 +07:00
|
|
|
resp->opcnt = slot->sl_opcnt;
|
2014-03-22 04:57:57 +07:00
|
|
|
return slot->sl_status;
|
2009-04-03 12:28:15 +07:00
|
|
|
}
|
|
|
|
|
2009-04-03 12:28:01 +07:00
|
|
|
/*
|
|
|
|
* Set the exchange_id flags returned by the server.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
nfsd4_set_ex_flags(struct nfs4_client *new, struct nfsd4_exchange_id *clid)
|
|
|
|
{
|
nfsd: implement pNFS operations
Add support for the GETDEVICEINFO, LAYOUTGET, LAYOUTCOMMIT and
LAYOUTRETURN NFSv4.1 operations, as well as backing code to manage
outstanding layouts and devices.
Layout management is very straight forward, with a nfs4_layout_stateid
structure that extends nfs4_stid to manage layout stateids as the
top-level structure. It is linked into the nfs4_file and nfs4_client
structures like the other stateids, and contains a linked list of
layouts that hang of the stateid. The actual layout operations are
implemented in layout drivers that are not part of this commit, but
will be added later.
The worst part of this commit is the management of the pNFS device IDs,
which suffers from a specification that is not sanely implementable due
to the fact that the device-IDs are global and not bound to an export,
and have a small enough size so that we can't store the fsid portion of
a file handle, and must never be reused. As we still do need perform all
export authentication and validation checks on a device ID passed to
GETDEVICEINFO we are caught between a rock and a hard place. To work
around this issue we add a new hash that maps from a 64-bit integer to a
fsid so that we can look up the export to authenticate against it,
a 32-bit integer as a generation that we can bump when changing the device,
and a currently unused 32-bit integer that could be used in the future
to handle more than a single device per export. Entries in this hash
table are never deleted as we can't reuse the ids anyway, and would have
a severe lifetime problem anyway as Linux export structures are temporary
structures that can go away under load.
Parts of the XDR data, structures and marshaling/unmarshaling code, as
well as many concepts are derived from the old pNFS server implementation
from Andy Adamson, Benny Halevy, Dean Hildebrand, Marc Eshel, Fred Isaman,
Mike Sager, Ricardo Labiaga and many others.
Signed-off-by: Christoph Hellwig <hch@lst.de>
2014-05-05 18:11:59 +07:00
|
|
|
#ifdef CONFIG_NFSD_PNFS
|
|
|
|
new->cl_exchange_flags |= EXCHGID4_FLAG_USE_PNFS_MDS;
|
|
|
|
#else
|
2009-04-03 12:28:01 +07:00
|
|
|
new->cl_exchange_flags |= EXCHGID4_FLAG_USE_NON_PNFS;
|
nfsd: implement pNFS operations
Add support for the GETDEVICEINFO, LAYOUTGET, LAYOUTCOMMIT and
LAYOUTRETURN NFSv4.1 operations, as well as backing code to manage
outstanding layouts and devices.
Layout management is very straight forward, with a nfs4_layout_stateid
structure that extends nfs4_stid to manage layout stateids as the
top-level structure. It is linked into the nfs4_file and nfs4_client
structures like the other stateids, and contains a linked list of
layouts that hang of the stateid. The actual layout operations are
implemented in layout drivers that are not part of this commit, but
will be added later.
The worst part of this commit is the management of the pNFS device IDs,
which suffers from a specification that is not sanely implementable due
to the fact that the device-IDs are global and not bound to an export,
and have a small enough size so that we can't store the fsid portion of
a file handle, and must never be reused. As we still do need perform all
export authentication and validation checks on a device ID passed to
GETDEVICEINFO we are caught between a rock and a hard place. To work
around this issue we add a new hash that maps from a 64-bit integer to a
fsid so that we can look up the export to authenticate against it,
a 32-bit integer as a generation that we can bump when changing the device,
and a currently unused 32-bit integer that could be used in the future
to handle more than a single device per export. Entries in this hash
table are never deleted as we can't reuse the ids anyway, and would have
a severe lifetime problem anyway as Linux export structures are temporary
structures that can go away under load.
Parts of the XDR data, structures and marshaling/unmarshaling code, as
well as many concepts are derived from the old pNFS server implementation
from Andy Adamson, Benny Halevy, Dean Hildebrand, Marc Eshel, Fred Isaman,
Mike Sager, Ricardo Labiaga and many others.
Signed-off-by: Christoph Hellwig <hch@lst.de>
2014-05-05 18:11:59 +07:00
|
|
|
#endif
|
2009-04-03 12:28:01 +07:00
|
|
|
|
|
|
|
/* Referrals are supported, Migration is not. */
|
|
|
|
new->cl_exchange_flags |= EXCHGID4_FLAG_SUPP_MOVED_REFER;
|
|
|
|
|
|
|
|
/* set the wire flags to return to client. */
|
|
|
|
clid->flags = new->cl_exchange_flags;
|
|
|
|
}
|
|
|
|
|
2015-10-16 03:11:01 +07:00
|
|
|
static bool client_has_openowners(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
struct nfs4_openowner *oo;
|
|
|
|
|
|
|
|
list_for_each_entry(oo, &clp->cl_openowners, oo_perclient) {
|
|
|
|
if (!list_empty(&oo->oo_owner.so_stateids))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-05-15 02:57:23 +07:00
|
|
|
static bool client_has_state(struct nfs4_client *clp)
|
|
|
|
{
|
2015-10-16 03:11:01 +07:00
|
|
|
return client_has_openowners(clp)
|
2015-07-21 12:09:17 +07:00
|
|
|
#ifdef CONFIG_NFSD_PNFS
|
|
|
|
|| !list_empty(&clp->cl_lo_states)
|
|
|
|
#endif
|
2012-05-30 03:37:44 +07:00
|
|
|
|| !list_empty(&clp->cl_delegations)
|
|
|
|
|| !list_empty(&clp->cl_sessions);
|
2012-05-15 02:57:23 +07:00
|
|
|
}
|
|
|
|
|
2009-04-03 12:27:58 +07:00
|
|
|
__be32
|
|
|
|
nfsd4_exchange_id(struct svc_rqst *rqstp,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_exchange_id *exid)
|
|
|
|
{
|
2014-07-30 19:27:06 +07:00
|
|
|
struct nfs4_client *conf, *new;
|
|
|
|
struct nfs4_client *unconf = NULL;
|
2012-04-26 04:58:50 +07:00
|
|
|
__be32 status;
|
2009-08-14 23:57:56 +07:00
|
|
|
char addr_str[INET6_ADDRSTRLEN];
|
2009-04-03 12:28:01 +07:00
|
|
|
nfs4_verifier verf = exid->verifier;
|
2009-08-14 23:57:56 +07:00
|
|
|
struct sockaddr *sa = svc_addr(rqstp);
|
2012-05-14 20:08:10 +07:00
|
|
|
bool update = exid->flags & EXCHGID4_FLAG_UPD_CONFIRMED_REC_A;
|
2012-11-14 22:21:10 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2009-04-03 12:28:01 +07:00
|
|
|
|
2009-08-14 23:57:56 +07:00
|
|
|
rpc_ntop(sa, addr_str, sizeof(addr_str));
|
2009-04-03 12:28:01 +07:00
|
|
|
dprintk("%s rqstp=%p exid=%p clname.len=%u clname.data=%p "
|
2009-08-14 23:57:56 +07:00
|
|
|
"ip_addr=%s flags %x, spa_how %d\n",
|
2009-04-03 12:28:01 +07:00
|
|
|
__func__, rqstp, exid, exid->clname.len, exid->clname.data,
|
2009-08-14 23:57:56 +07:00
|
|
|
addr_str, exid->flags, exid->spa_how);
|
2009-04-03 12:28:01 +07:00
|
|
|
|
2011-10-11 02:07:40 +07:00
|
|
|
if (exid->flags & ~EXCHGID4_FLAG_MASK_A)
|
2009-04-03 12:28:01 +07:00
|
|
|
return nfserr_inval;
|
|
|
|
|
2015-11-24 05:39:12 +07:00
|
|
|
new = create_client(exid->clname, rqstp, &verf);
|
|
|
|
if (new == NULL)
|
|
|
|
return nfserr_jukebox;
|
|
|
|
|
2009-04-03 12:28:01 +07:00
|
|
|
switch (exid->spa_how) {
|
2013-04-14 01:27:29 +07:00
|
|
|
case SP4_MACH_CRED:
|
2016-06-15 23:52:09 +07:00
|
|
|
exid->spo_must_enforce[0] = 0;
|
|
|
|
exid->spo_must_enforce[1] = (
|
|
|
|
1 << (OP_BIND_CONN_TO_SESSION - 32) |
|
|
|
|
1 << (OP_EXCHANGE_ID - 32) |
|
|
|
|
1 << (OP_CREATE_SESSION - 32) |
|
|
|
|
1 << (OP_DESTROY_SESSION - 32) |
|
|
|
|
1 << (OP_DESTROY_CLIENTID - 32));
|
|
|
|
|
|
|
|
exid->spo_must_allow[0] &= (1 << (OP_CLOSE) |
|
|
|
|
1 << (OP_OPEN_DOWNGRADE) |
|
|
|
|
1 << (OP_LOCKU) |
|
|
|
|
1 << (OP_DELEGRETURN));
|
|
|
|
|
|
|
|
exid->spo_must_allow[1] &= (
|
|
|
|
1 << (OP_TEST_STATEID - 32) |
|
|
|
|
1 << (OP_FREE_STATEID - 32));
|
2015-11-24 05:39:12 +07:00
|
|
|
if (!svc_rqst_integrity_protected(rqstp)) {
|
|
|
|
status = nfserr_inval;
|
|
|
|
goto out_nolock;
|
2015-11-21 04:42:40 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Sometimes userspace doesn't give us a principal.
|
|
|
|
* Which is a bug, really. Anyway, we can't enforce
|
|
|
|
* MACH_CRED in that case, better to give up now:
|
|
|
|
*/
|
2015-11-20 22:48:02 +07:00
|
|
|
if (!new->cl_cred.cr_principal &&
|
|
|
|
!new->cl_cred.cr_raw_principal) {
|
2015-11-21 04:42:40 +07:00
|
|
|
status = nfserr_serverfault;
|
|
|
|
goto out_nolock;
|
2015-11-24 05:39:12 +07:00
|
|
|
}
|
|
|
|
new->cl_mach_cred = true;
|
2009-04-03 12:28:01 +07:00
|
|
|
case SP4_NONE:
|
|
|
|
break;
|
2012-11-26 02:48:10 +07:00
|
|
|
default: /* checked by xdr code */
|
|
|
|
WARN_ON_ONCE(1);
|
2009-04-03 12:28:01 +07:00
|
|
|
case SP4_SSV:
|
2016-02-26 21:36:42 +07:00
|
|
|
status = nfserr_encr_alg_unsupp;
|
|
|
|
goto out_nolock;
|
2009-04-03 12:28:01 +07:00
|
|
|
}
|
|
|
|
|
2012-05-14 20:47:11 +07:00
|
|
|
/* Cases below refer to rfc 5661 section 18.35.4: */
|
2014-07-30 19:27:06 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2012-11-14 22:21:26 +07:00
|
|
|
conf = find_confirmed_client_by_name(&exid->clname, nn);
|
2009-04-03 12:28:01 +07:00
|
|
|
if (conf) {
|
2012-05-14 20:08:10 +07:00
|
|
|
bool creds_match = same_creds(&conf->cl_cred, &rqstp->rq_cred);
|
|
|
|
bool verfs_match = same_verf(&verf, &conf->cl_verifier);
|
|
|
|
|
2012-05-13 07:37:23 +07:00
|
|
|
if (update) {
|
|
|
|
if (!clp_used_exchangeid(conf)) { /* buggy client */
|
2012-05-14 20:47:11 +07:00
|
|
|
status = nfserr_inval;
|
2012-05-13 08:32:30 +07:00
|
|
|
goto out;
|
|
|
|
}
|
2016-06-15 23:52:08 +07:00
|
|
|
if (!nfsd4_mach_creds_match(conf, rqstp)) {
|
2013-04-14 01:27:29 +07:00
|
|
|
status = nfserr_wrong_cred;
|
|
|
|
goto out;
|
|
|
|
}
|
2012-05-13 07:37:23 +07:00
|
|
|
if (!creds_match) { /* case 9 */
|
2012-05-13 08:08:41 +07:00
|
|
|
status = nfserr_perm;
|
2012-05-13 07:37:23 +07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (!verfs_match) { /* case 8 */
|
2009-04-03 12:28:01 +07:00
|
|
|
status = nfserr_not_same;
|
|
|
|
goto out;
|
|
|
|
}
|
2012-05-13 07:37:23 +07:00
|
|
|
/* case 6 */
|
|
|
|
exid->flags |= EXCHGID4_FLAG_CONFIRMED_R;
|
|
|
|
goto out_copy;
|
2009-04-03 12:28:01 +07:00
|
|
|
}
|
2012-05-13 07:37:23 +07:00
|
|
|
if (!creds_match) { /* case 3 */
|
2012-05-15 02:57:23 +07:00
|
|
|
if (client_has_state(conf)) {
|
|
|
|
status = nfserr_clid_inuse;
|
2009-04-03 12:28:01 +07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
goto out_new;
|
|
|
|
}
|
2012-05-13 07:37:23 +07:00
|
|
|
if (verfs_match) { /* case 2 */
|
2012-05-26 08:24:40 +07:00
|
|
|
conf->cl_exchange_flags |= EXCHGID4_FLAG_CONFIRMED_R;
|
2012-05-13 07:37:23 +07:00
|
|
|
goto out_copy;
|
|
|
|
}
|
|
|
|
/* case 5, client reboot */
|
2014-07-30 19:27:06 +07:00
|
|
|
conf = NULL;
|
2012-05-13 07:37:23 +07:00
|
|
|
goto out_new;
|
2009-06-16 08:20:47 +07:00
|
|
|
}
|
|
|
|
|
2012-05-14 20:47:11 +07:00
|
|
|
if (update) { /* case 7 */
|
2009-06-16 08:20:47 +07:00
|
|
|
status = nfserr_noent;
|
|
|
|
goto out;
|
2009-04-03 12:28:01 +07:00
|
|
|
}
|
|
|
|
|
2012-11-14 22:21:36 +07:00
|
|
|
unconf = find_unconfirmed_client_by_name(&exid->clname, nn);
|
2012-05-14 20:47:11 +07:00
|
|
|
if (unconf) /* case 4, possible retry or client restart */
|
2014-07-30 19:27:06 +07:00
|
|
|
unhash_client_locked(unconf);
|
2009-04-03 12:28:01 +07:00
|
|
|
|
2012-05-14 20:47:11 +07:00
|
|
|
/* case 1 (normal case) */
|
2009-04-03 12:28:01 +07:00
|
|
|
out_new:
|
2014-07-30 19:27:14 +07:00
|
|
|
if (conf) {
|
|
|
|
status = mark_client_expired_locked(conf);
|
|
|
|
if (status)
|
|
|
|
goto out;
|
|
|
|
}
|
2013-05-07 22:57:52 +07:00
|
|
|
new->cl_minorversion = cstate->minorversion;
|
2016-06-15 23:52:09 +07:00
|
|
|
new->cl_spo_must_allow.u.words[0] = exid->spo_must_allow[0];
|
|
|
|
new->cl_spo_must_allow.u.words[1] = exid->spo_must_allow[1];
|
2009-04-03 12:28:01 +07:00
|
|
|
|
2012-11-14 22:21:10 +07:00
|
|
|
gen_clid(new, nn);
|
2012-11-13 03:00:56 +07:00
|
|
|
add_to_unconfirmed(new);
|
2014-07-30 19:27:06 +07:00
|
|
|
swap(new, conf);
|
2009-04-03 12:28:01 +07:00
|
|
|
out_copy:
|
2014-07-30 19:27:05 +07:00
|
|
|
exid->clientid.cl_boot = conf->cl_clientid.cl_boot;
|
|
|
|
exid->clientid.cl_id = conf->cl_clientid.cl_id;
|
2009-04-03 12:28:01 +07:00
|
|
|
|
2014-07-30 19:27:05 +07:00
|
|
|
exid->seqid = conf->cl_cs_slot.sl_seqid + 1;
|
|
|
|
nfsd4_set_ex_flags(conf, exid);
|
2009-04-03 12:28:01 +07:00
|
|
|
|
|
|
|
dprintk("nfsd4_exchange_id seqid %d flags %x\n",
|
2014-07-30 19:27:05 +07:00
|
|
|
conf->cl_cs_slot.sl_seqid, conf->cl_exchange_flags);
|
2009-04-03 12:28:01 +07:00
|
|
|
status = nfs_ok;
|
|
|
|
|
|
|
|
out:
|
2014-07-30 19:27:06 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
2015-11-24 05:39:12 +07:00
|
|
|
out_nolock:
|
2014-07-30 19:27:05 +07:00
|
|
|
if (new)
|
2014-07-30 19:27:06 +07:00
|
|
|
expire_client(new);
|
|
|
|
if (unconf)
|
|
|
|
expire_client(unconf);
|
2009-04-03 12:28:01 +07:00
|
|
|
return status;
|
2009-04-03 12:27:58 +07:00
|
|
|
}
|
|
|
|
|
2012-04-26 04:58:50 +07:00
|
|
|
static __be32
|
2009-07-24 06:02:15 +07:00
|
|
|
check_slot_seqid(u32 seqid, u32 slot_seqid, int slot_inuse)
|
2009-04-03 12:28:08 +07:00
|
|
|
{
|
2009-07-24 06:02:15 +07:00
|
|
|
dprintk("%s enter. seqid %d slot_seqid %d\n", __func__, seqid,
|
|
|
|
slot_seqid);
|
2009-04-03 12:28:08 +07:00
|
|
|
|
|
|
|
/* The slot is in use, and no response has been sent. */
|
2009-07-24 06:02:15 +07:00
|
|
|
if (slot_inuse) {
|
|
|
|
if (seqid == slot_seqid)
|
2009-04-03 12:28:08 +07:00
|
|
|
return nfserr_jukebox;
|
|
|
|
else
|
|
|
|
return nfserr_seq_misordered;
|
|
|
|
}
|
2012-02-14 04:13:41 +07:00
|
|
|
/* Note unsigned 32-bit arithmetic handles wraparound: */
|
2009-07-24 06:02:15 +07:00
|
|
|
if (likely(seqid == slot_seqid + 1))
|
2009-04-03 12:28:08 +07:00
|
|
|
return nfs_ok;
|
2009-07-24 06:02:15 +07:00
|
|
|
if (seqid == slot_seqid)
|
2009-04-03 12:28:08 +07:00
|
|
|
return nfserr_replay_cache;
|
|
|
|
return nfserr_seq_misordered;
|
|
|
|
}
|
|
|
|
|
2009-07-24 06:02:16 +07:00
|
|
|
/*
|
|
|
|
* Cache the create session result into the create session single DRC
|
|
|
|
* slot cache by saving the xdr structure. sl_seqid has been set.
|
|
|
|
* Do this for solo or embedded create session operations.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
nfsd4_cache_create_session(struct nfsd4_create_session *cr_ses,
|
2012-04-26 04:58:50 +07:00
|
|
|
struct nfsd4_clid_slot *slot, __be32 nfserr)
|
2009-07-24 06:02:16 +07:00
|
|
|
{
|
|
|
|
slot->sl_status = nfserr;
|
|
|
|
memcpy(&slot->sl_cr_ses, cr_ses, sizeof(*cr_ses));
|
|
|
|
}
|
|
|
|
|
|
|
|
static __be32
|
|
|
|
nfsd4_replay_create_session(struct nfsd4_create_session *cr_ses,
|
|
|
|
struct nfsd4_clid_slot *slot)
|
|
|
|
{
|
|
|
|
memcpy(cr_ses, &slot->sl_cr_ses, sizeof(*cr_ses));
|
|
|
|
return slot->sl_status;
|
|
|
|
}
|
|
|
|
|
2011-07-14 13:50:17 +07:00
|
|
|
#define NFSD_MIN_REQ_HDR_SEQ_SZ ((\
|
|
|
|
2 * 2 + /* credential,verifier: AUTH_NULL, length 0 */ \
|
|
|
|
1 + /* MIN tag is length with zero, only length */ \
|
|
|
|
3 + /* version, opcount, opcode */ \
|
|
|
|
XDR_QUADLEN(NFS4_MAX_SESSIONID_LEN) + \
|
|
|
|
/* seqid, slotID, slotID, cache */ \
|
|
|
|
4 ) * sizeof(__be32))
|
|
|
|
|
|
|
|
#define NFSD_MIN_RESP_HDR_SEQ_SZ ((\
|
|
|
|
2 + /* verifier: AUTH_NULL, length 0 */\
|
|
|
|
1 + /* status */ \
|
|
|
|
1 + /* MIN tag is length with zero, only length */ \
|
|
|
|
3 + /* opcount, opcode, opstatus*/ \
|
|
|
|
XDR_QUADLEN(NFS4_MAX_SESSIONID_LEN) + \
|
|
|
|
/* seqid, slotID, slotID, slotID, status */ \
|
|
|
|
5 ) * sizeof(__be32))
|
|
|
|
|
2013-04-09 03:44:14 +07:00
|
|
|
static __be32 check_forechannel_attrs(struct nfsd4_channel_attrs *ca, struct nfsd_net *nn)
|
2011-07-14 13:50:17 +07:00
|
|
|
{
|
2013-04-09 03:44:14 +07:00
|
|
|
u32 maxrpc = nn->nfsd_serv->sv_max_mesg;
|
|
|
|
|
2013-04-09 02:42:12 +07:00
|
|
|
if (ca->maxreq_sz < NFSD_MIN_REQ_HDR_SEQ_SZ)
|
|
|
|
return nfserr_toosmall;
|
|
|
|
if (ca->maxresp_sz < NFSD_MIN_RESP_HDR_SEQ_SZ)
|
|
|
|
return nfserr_toosmall;
|
2013-04-09 03:44:14 +07:00
|
|
|
ca->headerpadsz = 0;
|
|
|
|
ca->maxreq_sz = min_t(u32, ca->maxreq_sz, maxrpc);
|
|
|
|
ca->maxresp_sz = min_t(u32, ca->maxresp_sz, maxrpc);
|
|
|
|
ca->maxops = min_t(u32, ca->maxops, NFSD_MAX_OPS_PER_COMPOUND);
|
|
|
|
ca->maxresp_cached = min_t(u32, ca->maxresp_cached,
|
|
|
|
NFSD_SLOT_CACHE_SIZE + NFSD_MIN_HDR_SEQ_SZ);
|
|
|
|
ca->maxreqs = min_t(u32, ca->maxreqs, NFSD_MAX_SLOTS_PER_SESSION);
|
|
|
|
/*
|
|
|
|
* Note decreasing slot size below client's request may make it
|
|
|
|
* difficult for client to function correctly, whereas
|
|
|
|
* decreasing the number of slots will (just?) affect
|
|
|
|
* performance. When short on memory we therefore prefer to
|
|
|
|
* decrease number of slots instead of their size. Clients that
|
|
|
|
* request larger slots than they need will get poor results:
|
|
|
|
*/
|
|
|
|
ca->maxreqs = nfsd4_get_drc_mem(ca);
|
|
|
|
if (!ca->maxreqs)
|
|
|
|
return nfserr_jukebox;
|
|
|
|
|
2013-04-09 02:42:12 +07:00
|
|
|
return nfs_ok;
|
2011-07-14 13:50:17 +07:00
|
|
|
}
|
|
|
|
|
2016-03-02 01:06:02 +07:00
|
|
|
/*
|
|
|
|
* Server's NFSv4.1 backchannel support is AUTH_SYS-only for now.
|
|
|
|
* These are based on similar macros in linux/sunrpc/msg_prot.h .
|
|
|
|
*/
|
|
|
|
#define RPC_MAX_HEADER_WITH_AUTH_SYS \
|
|
|
|
(RPC_CALLHDRSIZE + 2 * (2 + UNX_CALLSLACK))
|
|
|
|
|
|
|
|
#define RPC_MAX_REPHEADER_WITH_AUTH_SYS \
|
|
|
|
(RPC_REPHDRSIZE + (2 + NUL_REPLYSLACK))
|
|
|
|
|
2013-12-23 17:11:02 +07:00
|
|
|
#define NFSD_CB_MAX_REQ_SZ ((NFS4_enc_cb_recall_sz + \
|
2016-03-02 01:06:02 +07:00
|
|
|
RPC_MAX_HEADER_WITH_AUTH_SYS) * sizeof(__be32))
|
2013-12-23 17:11:02 +07:00
|
|
|
#define NFSD_CB_MAX_RESP_SZ ((NFS4_dec_cb_recall_sz + \
|
2016-03-02 01:06:02 +07:00
|
|
|
RPC_MAX_REPHEADER_WITH_AUTH_SYS) * \
|
|
|
|
sizeof(__be32))
|
2013-12-23 17:11:02 +07:00
|
|
|
|
2013-04-09 22:34:36 +07:00
|
|
|
static __be32 check_backchannel_attrs(struct nfsd4_channel_attrs *ca)
|
2011-07-14 13:50:17 +07:00
|
|
|
{
|
2013-04-09 22:34:36 +07:00
|
|
|
ca->headerpadsz = 0;
|
|
|
|
|
2013-12-23 17:11:02 +07:00
|
|
|
if (ca->maxreq_sz < NFSD_CB_MAX_REQ_SZ)
|
2013-04-09 22:34:36 +07:00
|
|
|
return nfserr_toosmall;
|
2013-12-23 17:11:02 +07:00
|
|
|
if (ca->maxresp_sz < NFSD_CB_MAX_RESP_SZ)
|
2013-04-09 22:34:36 +07:00
|
|
|
return nfserr_toosmall;
|
|
|
|
ca->maxresp_cached = 0;
|
|
|
|
if (ca->maxops < 2)
|
|
|
|
return nfserr_toosmall;
|
|
|
|
|
|
|
|
return nfs_ok;
|
2011-07-14 13:50:17 +07:00
|
|
|
}
|
|
|
|
|
2013-05-16 04:34:39 +07:00
|
|
|
static __be32 nfsd4_check_cb_sec(struct nfsd4_cb_sec *cbs)
|
|
|
|
{
|
|
|
|
switch (cbs->flavor) {
|
|
|
|
case RPC_AUTH_NULL:
|
|
|
|
case RPC_AUTH_UNIX:
|
|
|
|
return nfs_ok;
|
|
|
|
default:
|
|
|
|
/*
|
|
|
|
* GSS case: the spec doesn't allow us to return this
|
|
|
|
* error. But it also doesn't allow us not to support
|
|
|
|
* GSS.
|
|
|
|
* I'd rather this fail hard than return some error the
|
|
|
|
* client might think it can already handle:
|
|
|
|
*/
|
|
|
|
return nfserr_encr_alg_unsupp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-03 12:27:58 +07:00
|
|
|
__be32
|
|
|
|
nfsd4_create_session(struct svc_rqst *rqstp,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_create_session *cr_ses)
|
|
|
|
{
|
2009-08-14 23:57:56 +07:00
|
|
|
struct sockaddr *sa = svc_addr(rqstp);
|
2009-04-03 12:28:28 +07:00
|
|
|
struct nfs4_client *conf, *unconf;
|
2014-07-30 19:27:07 +07:00
|
|
|
struct nfs4_client *old = NULL;
|
2010-06-15 06:01:57 +07:00
|
|
|
struct nfsd4_session *new;
|
2012-09-12 22:04:33 +07:00
|
|
|
struct nfsd4_conn *conn;
|
2009-07-24 06:02:16 +07:00
|
|
|
struct nfsd4_clid_slot *cs_slot = NULL;
|
2012-04-26 04:58:50 +07:00
|
|
|
__be32 status = 0;
|
2012-11-14 22:21:21 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2011-03-23 16:57:07 +07:00
|
|
|
if (cr_ses->flags & ~SESSION4_FLAG_MASK_A)
|
|
|
|
return nfserr_inval;
|
2013-05-16 04:34:39 +07:00
|
|
|
status = nfsd4_check_cb_sec(&cr_ses->cb_sec);
|
|
|
|
if (status)
|
|
|
|
return status;
|
2013-04-09 03:44:14 +07:00
|
|
|
status = check_forechannel_attrs(&cr_ses->fore_channel, nn);
|
2013-04-09 22:34:36 +07:00
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
status = check_backchannel_attrs(&cr_ses->back_channel);
|
2013-04-09 02:42:12 +07:00
|
|
|
if (status)
|
2013-12-23 16:31:21 +07:00
|
|
|
goto out_release_drc_mem;
|
2012-09-12 22:04:33 +07:00
|
|
|
status = nfserr_jukebox;
|
2013-12-31 23:35:47 +07:00
|
|
|
new = alloc_session(&cr_ses->fore_channel, &cr_ses->back_channel);
|
2013-04-09 03:44:14 +07:00
|
|
|
if (!new)
|
|
|
|
goto out_release_drc_mem;
|
2012-09-12 22:04:33 +07:00
|
|
|
conn = alloc_conn_from_crses(rqstp, cr_ses);
|
|
|
|
if (!conn)
|
|
|
|
goto out_free_session;
|
2011-03-23 16:57:07 +07:00
|
|
|
|
2014-07-30 19:27:07 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2012-11-14 22:21:31 +07:00
|
|
|
unconf = find_unconfirmed_client(&cr_ses->clientid, true, nn);
|
2012-11-14 22:21:21 +07:00
|
|
|
conf = find_confirmed_client(&cr_ses->clientid, true, nn);
|
2013-03-12 21:12:37 +07:00
|
|
|
WARN_ON_ONCE(conf && unconf);
|
2009-04-03 12:28:28 +07:00
|
|
|
|
|
|
|
if (conf) {
|
2013-04-14 01:27:29 +07:00
|
|
|
status = nfserr_wrong_cred;
|
2016-06-15 23:52:08 +07:00
|
|
|
if (!nfsd4_mach_creds_match(conf, rqstp))
|
2013-04-14 01:27:29 +07:00
|
|
|
goto out_free_conn;
|
2009-07-24 06:02:16 +07:00
|
|
|
cs_slot = &conf->cl_cs_slot;
|
|
|
|
status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
|
2015-07-13 16:32:34 +07:00
|
|
|
if (status) {
|
|
|
|
if (status == nfserr_replay_cache)
|
|
|
|
status = nfsd4_replay_create_session(cr_ses, cs_slot);
|
2012-09-12 22:04:33 +07:00
|
|
|
goto out_free_conn;
|
2009-04-03 12:28:28 +07:00
|
|
|
}
|
|
|
|
} else if (unconf) {
|
|
|
|
if (!same_creds(&unconf->cl_cred, &rqstp->rq_cred) ||
|
2009-08-14 23:57:56 +07:00
|
|
|
!rpc_cmp_addr(sa, (struct sockaddr *) &unconf->cl_addr)) {
|
2009-04-03 12:28:28 +07:00
|
|
|
status = nfserr_clid_inuse;
|
2012-09-12 22:04:33 +07:00
|
|
|
goto out_free_conn;
|
2009-04-03 12:28:28 +07:00
|
|
|
}
|
2013-04-14 01:27:29 +07:00
|
|
|
status = nfserr_wrong_cred;
|
2016-06-15 23:52:08 +07:00
|
|
|
if (!nfsd4_mach_creds_match(unconf, rqstp))
|
2013-04-14 01:27:29 +07:00
|
|
|
goto out_free_conn;
|
2009-07-24 06:02:16 +07:00
|
|
|
cs_slot = &unconf->cl_cs_slot;
|
|
|
|
status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
|
2009-04-03 12:28:32 +07:00
|
|
|
if (status) {
|
|
|
|
/* an unconfirmed replay returns misordered */
|
2009-04-03 12:28:28 +07:00
|
|
|
status = nfserr_seq_misordered;
|
2012-09-12 22:04:33 +07:00
|
|
|
goto out_free_conn;
|
2009-04-03 12:28:28 +07:00
|
|
|
}
|
2012-11-14 22:21:26 +07:00
|
|
|
old = find_confirmed_client_by_name(&unconf->cl_name, nn);
|
2013-04-02 09:23:49 +07:00
|
|
|
if (old) {
|
2014-07-30 19:27:07 +07:00
|
|
|
status = mark_client_expired_locked(old);
|
2014-07-30 19:27:13 +07:00
|
|
|
if (status) {
|
|
|
|
old = NULL;
|
2013-04-02 09:23:49 +07:00
|
|
|
goto out_free_conn;
|
2014-07-30 19:27:13 +07:00
|
|
|
}
|
2013-04-02 09:23:49 +07:00
|
|
|
}
|
2012-09-13 01:41:31 +07:00
|
|
|
move_to_confirmed(unconf);
|
2009-04-03 12:28:28 +07:00
|
|
|
conf = unconf;
|
|
|
|
} else {
|
|
|
|
status = nfserr_stale_clientid;
|
2012-09-12 22:04:33 +07:00
|
|
|
goto out_free_conn;
|
2009-04-03 12:28:28 +07:00
|
|
|
}
|
2012-09-12 22:04:33 +07:00
|
|
|
status = nfs_ok;
|
2016-03-02 01:05:27 +07:00
|
|
|
/* Persistent sessions are not supported */
|
2010-04-16 02:11:09 +07:00
|
|
|
cr_ses->flags &= ~SESSION4_PERSIST;
|
2016-03-02 01:05:27 +07:00
|
|
|
/* Upshifting from TCP to RDMA is not supported */
|
2010-04-16 02:11:09 +07:00
|
|
|
cr_ses->flags &= ~SESSION4_RDMA;
|
|
|
|
|
2012-09-12 22:04:33 +07:00
|
|
|
init_session(rqstp, new, conf, cr_ses);
|
2014-07-30 19:27:07 +07:00
|
|
|
nfsd4_get_session_locked(new);
|
2012-09-12 22:04:33 +07:00
|
|
|
|
2010-06-15 06:01:57 +07:00
|
|
|
memcpy(cr_ses->sessionid.data, new->se_sessionid.data,
|
2009-04-03 12:28:28 +07:00
|
|
|
NFS4_MAX_SESSIONID_LEN);
|
2010-10-03 04:04:00 +07:00
|
|
|
cs_slot->sl_seqid++;
|
2009-07-24 06:02:16 +07:00
|
|
|
cr_ses->seqid = cs_slot->sl_seqid;
|
2009-04-03 12:28:28 +07:00
|
|
|
|
2014-07-30 19:27:07 +07:00
|
|
|
/* cache solo and embedded create sessions under the client_lock */
|
2009-07-24 06:02:16 +07:00
|
|
|
nfsd4_cache_create_session(cr_ses, cs_slot, status);
|
2014-07-30 19:27:07 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
/* init connection and backchannel */
|
|
|
|
nfsd4_init_conn(rqstp, conn, new);
|
|
|
|
nfsd4_put_session(new);
|
|
|
|
if (old)
|
|
|
|
expire_client(old);
|
2009-04-03 12:28:28 +07:00
|
|
|
return status;
|
2012-09-12 22:04:33 +07:00
|
|
|
out_free_conn:
|
2014-07-30 19:27:07 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
2012-09-12 22:04:33 +07:00
|
|
|
free_conn(conn);
|
2014-07-30 19:27:07 +07:00
|
|
|
if (old)
|
|
|
|
expire_client(old);
|
2012-09-12 22:04:33 +07:00
|
|
|
out_free_session:
|
|
|
|
__free_session(new);
|
2013-04-09 03:44:14 +07:00
|
|
|
out_release_drc_mem:
|
|
|
|
nfsd4_put_drc_mem(&cr_ses->fore_channel);
|
2013-03-15 05:12:03 +07:00
|
|
|
return status;
|
2009-04-03 12:27:58 +07:00
|
|
|
}
|
|
|
|
|
2010-10-05 10:12:59 +07:00
|
|
|
static __be32 nfsd4_map_bcts_dir(u32 *dir)
|
|
|
|
{
|
|
|
|
switch (*dir) {
|
|
|
|
case NFS4_CDFC4_FORE:
|
|
|
|
case NFS4_CDFC4_BACK:
|
|
|
|
return nfs_ok;
|
|
|
|
case NFS4_CDFC4_FORE_OR_BOTH:
|
|
|
|
case NFS4_CDFC4_BACK_OR_BOTH:
|
|
|
|
*dir = NFS4_CDFC4_BOTH;
|
|
|
|
return nfs_ok;
|
|
|
|
};
|
|
|
|
return nfserr_inval;
|
|
|
|
}
|
|
|
|
|
2012-11-02 05:09:48 +07:00
|
|
|
__be32 nfsd4_backchannel_ctl(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd4_backchannel_ctl *bc)
|
|
|
|
{
|
|
|
|
struct nfsd4_session *session = cstate->session;
|
2012-11-26 19:21:58 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2013-05-16 04:34:39 +07:00
|
|
|
__be32 status;
|
2012-11-02 05:09:48 +07:00
|
|
|
|
2013-05-16 04:34:39 +07:00
|
|
|
status = nfsd4_check_cb_sec(&bc->bc_cb_sec);
|
|
|
|
if (status)
|
|
|
|
return status;
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2012-11-02 05:09:48 +07:00
|
|
|
session->se_cb_prog = bc->bc_cb_program;
|
|
|
|
session->se_cb_sec = bc->bc_cb_sec;
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
2012-11-02 05:09:48 +07:00
|
|
|
|
|
|
|
nfsd4_probe_callback(session->se_client);
|
|
|
|
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
2010-10-05 10:12:59 +07:00
|
|
|
__be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_bind_conn_to_session *bcts)
|
|
|
|
{
|
|
|
|
__be32 status;
|
2012-09-12 02:37:40 +07:00
|
|
|
struct nfsd4_conn *conn;
|
2013-03-19 04:31:30 +07:00
|
|
|
struct nfsd4_session *session;
|
2014-06-30 22:48:42 +07:00
|
|
|
struct net *net = SVC_NET(rqstp);
|
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2010-10-05 10:12:59 +07:00
|
|
|
|
|
|
|
if (!nfsd4_last_compound_op(rqstp))
|
|
|
|
return nfserr_not_only_op;
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2014-06-30 22:48:42 +07:00
|
|
|
session = find_in_sessionid_hashtbl(&bcts->sessionid, net, &status);
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
2013-03-19 04:31:30 +07:00
|
|
|
if (!session)
|
2014-06-30 22:48:42 +07:00
|
|
|
goto out_no_session;
|
2013-04-14 01:27:29 +07:00
|
|
|
status = nfserr_wrong_cred;
|
2016-06-15 23:52:08 +07:00
|
|
|
if (!nfsd4_mach_creds_match(session->se_client, rqstp))
|
2013-04-14 01:27:29 +07:00
|
|
|
goto out;
|
2010-10-05 10:12:59 +07:00
|
|
|
status = nfsd4_map_bcts_dir(&bcts->dir);
|
2012-09-12 02:37:40 +07:00
|
|
|
if (status)
|
2013-03-19 04:31:30 +07:00
|
|
|
goto out;
|
2012-09-12 02:37:40 +07:00
|
|
|
conn = alloc_conn(rqstp, bcts->dir);
|
2013-03-19 04:31:30 +07:00
|
|
|
status = nfserr_jukebox;
|
2012-09-12 02:37:40 +07:00
|
|
|
if (!conn)
|
2013-03-19 04:31:30 +07:00
|
|
|
goto out;
|
|
|
|
nfsd4_init_conn(rqstp, conn, session);
|
|
|
|
status = nfs_ok;
|
|
|
|
out:
|
2014-06-30 22:48:42 +07:00
|
|
|
nfsd4_put_session(session);
|
|
|
|
out_no_session:
|
2013-03-19 04:31:30 +07:00
|
|
|
return status;
|
2010-10-05 10:12:59 +07:00
|
|
|
}
|
|
|
|
|
2010-05-01 23:56:06 +07:00
|
|
|
static bool nfsd4_compound_in_session(struct nfsd4_session *session, struct nfs4_sessionid *sid)
|
|
|
|
{
|
|
|
|
if (!session)
|
|
|
|
return 0;
|
|
|
|
return !memcmp(sid, &session->se_sessionid, sizeof(*sid));
|
|
|
|
}
|
|
|
|
|
2009-04-03 12:27:58 +07:00
|
|
|
__be32
|
|
|
|
nfsd4_destroy_session(struct svc_rqst *r,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_destroy_session *sessionid)
|
|
|
|
{
|
2009-04-03 12:28:38 +07:00
|
|
|
struct nfsd4_session *ses;
|
2013-03-15 06:55:33 +07:00
|
|
|
__be32 status;
|
2013-06-19 01:26:02 +07:00
|
|
|
int ref_held_by_me = 0;
|
2014-06-30 22:48:42 +07:00
|
|
|
struct net *net = SVC_NET(r);
|
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2009-04-03 12:28:38 +07:00
|
|
|
|
2013-03-15 06:55:33 +07:00
|
|
|
status = nfserr_not_only_op;
|
2010-05-01 23:56:06 +07:00
|
|
|
if (nfsd4_compound_in_session(cstate->session, &sessionid->sessionid)) {
|
2010-04-21 23:27:19 +07:00
|
|
|
if (!nfsd4_last_compound_op(r))
|
2013-03-15 06:55:33 +07:00
|
|
|
goto out;
|
2013-06-19 01:26:02 +07:00
|
|
|
ref_held_by_me++;
|
2010-04-21 23:27:19 +07:00
|
|
|
}
|
2009-04-03 12:28:38 +07:00
|
|
|
dump_sessionid(__func__, &sessionid->sessionid);
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2014-06-30 22:48:42 +07:00
|
|
|
ses = find_in_sessionid_hashtbl(&sessionid->sessionid, net, &status);
|
2013-03-15 06:55:33 +07:00
|
|
|
if (!ses)
|
|
|
|
goto out_client_lock;
|
2013-04-14 01:27:29 +07:00
|
|
|
status = nfserr_wrong_cred;
|
2016-06-15 23:52:08 +07:00
|
|
|
if (!nfsd4_mach_creds_match(ses->se_client, r))
|
2014-06-30 22:48:42 +07:00
|
|
|
goto out_put_session;
|
2013-06-19 01:26:02 +07:00
|
|
|
status = mark_session_dead_locked(ses, 1 + ref_held_by_me);
|
2013-03-19 23:05:39 +07:00
|
|
|
if (status)
|
2013-06-19 01:26:02 +07:00
|
|
|
goto out_put_session;
|
2009-04-03 12:28:38 +07:00
|
|
|
unhash_session(ses);
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
2009-04-03 12:28:38 +07:00
|
|
|
|
2010-12-10 03:52:19 +07:00
|
|
|
nfsd4_probe_callback_sync(ses->se_client);
|
2010-06-07 05:37:16 +07:00
|
|
|
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2009-04-03 12:28:38 +07:00
|
|
|
status = nfs_ok;
|
2013-06-19 01:26:02 +07:00
|
|
|
out_put_session:
|
2014-06-30 22:48:42 +07:00
|
|
|
nfsd4_put_session_locked(ses);
|
2013-03-15 06:55:33 +07:00
|
|
|
out_client_lock:
|
|
|
|
spin_unlock(&nn->client_lock);
|
2009-04-03 12:28:38 +07:00
|
|
|
out:
|
|
|
|
return status;
|
2009-04-03 12:27:58 +07:00
|
|
|
}
|
|
|
|
|
2010-10-22 04:17:31 +07:00
|
|
|
static struct nfsd4_conn *__nfsd4_find_conn(struct svc_xprt *xpt, struct nfsd4_session *s)
|
2010-09-30 03:11:06 +07:00
|
|
|
{
|
|
|
|
struct nfsd4_conn *c;
|
|
|
|
|
|
|
|
list_for_each_entry(c, &s->se_conns, cn_persession) {
|
2010-10-22 04:17:31 +07:00
|
|
|
if (c->cn_xprt == xpt) {
|
2010-09-30 03:11:06 +07:00
|
|
|
return c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-04-14 01:27:29 +07:00
|
|
|
static __be32 nfsd4_sequence_check_conn(struct nfsd4_conn *new, struct nfsd4_session *ses)
|
2010-09-30 03:11:06 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp = ses->se_client;
|
2010-10-22 04:17:31 +07:00
|
|
|
struct nfsd4_conn *c;
|
2013-04-14 01:27:29 +07:00
|
|
|
__be32 status = nfs_ok;
|
2010-10-26 21:07:17 +07:00
|
|
|
int ret;
|
2010-09-30 03:11:06 +07:00
|
|
|
|
|
|
|
spin_lock(&clp->cl_lock);
|
2010-10-22 04:17:31 +07:00
|
|
|
c = __nfsd4_find_conn(new->cn_xprt, ses);
|
2013-04-14 01:27:29 +07:00
|
|
|
if (c)
|
|
|
|
goto out_free;
|
|
|
|
status = nfserr_conn_not_bound_to_session;
|
|
|
|
if (clp->cl_mach_cred)
|
|
|
|
goto out_free;
|
2010-09-30 03:11:06 +07:00
|
|
|
__nfsd4_hash_conn(new, ses);
|
|
|
|
spin_unlock(&clp->cl_lock);
|
2010-10-26 21:07:17 +07:00
|
|
|
ret = nfsd4_register_conn(new);
|
|
|
|
if (ret)
|
|
|
|
/* oops; xprt is already down: */
|
|
|
|
nfsd4_conn_lost(&new->cn_xpt_user);
|
2013-04-14 01:27:29 +07:00
|
|
|
return nfs_ok;
|
|
|
|
out_free:
|
|
|
|
spin_unlock(&clp->cl_lock);
|
|
|
|
free_conn(new);
|
|
|
|
return status;
|
2010-09-30 03:11:06 +07:00
|
|
|
}
|
|
|
|
|
2011-04-27 08:09:58 +07:00
|
|
|
static bool nfsd4_session_too_many_ops(struct svc_rqst *rqstp, struct nfsd4_session *session)
|
|
|
|
{
|
|
|
|
struct nfsd4_compoundargs *args = rqstp->rq_argp;
|
|
|
|
|
|
|
|
return args->opcnt > session->se_fchannel.maxops;
|
|
|
|
}
|
|
|
|
|
2011-07-14 13:56:02 +07:00
|
|
|
static bool nfsd4_request_too_big(struct svc_rqst *rqstp,
|
|
|
|
struct nfsd4_session *session)
|
|
|
|
{
|
|
|
|
struct xdr_buf *xb = &rqstp->rq_arg;
|
|
|
|
|
|
|
|
return xb->len > session->se_fchannel.maxreq_sz;
|
|
|
|
}
|
|
|
|
|
2009-04-03 12:27:58 +07:00
|
|
|
__be32
|
2009-04-03 12:28:08 +07:00
|
|
|
nfsd4_sequence(struct svc_rqst *rqstp,
|
2009-04-03 12:27:58 +07:00
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_sequence *seq)
|
|
|
|
{
|
2009-04-03 12:28:12 +07:00
|
|
|
struct nfsd4_compoundres *resp = rqstp->rq_resp;
|
2014-03-13 08:39:35 +07:00
|
|
|
struct xdr_stream *xdr = &resp->xdr;
|
2009-04-03 12:28:08 +07:00
|
|
|
struct nfsd4_session *session;
|
2013-04-02 09:23:49 +07:00
|
|
|
struct nfs4_client *clp;
|
2009-04-03 12:28:08 +07:00
|
|
|
struct nfsd4_slot *slot;
|
2010-10-22 04:17:31 +07:00
|
|
|
struct nfsd4_conn *conn;
|
2012-04-26 04:58:50 +07:00
|
|
|
__be32 status;
|
2014-03-13 08:39:35 +07:00
|
|
|
int buflen;
|
2014-06-30 22:48:42 +07:00
|
|
|
struct net *net = SVC_NET(rqstp);
|
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2009-04-03 12:28:08 +07:00
|
|
|
|
2009-04-03 12:28:12 +07:00
|
|
|
if (resp->opcnt != 1)
|
|
|
|
return nfserr_sequence_pos;
|
|
|
|
|
2010-10-22 04:17:31 +07:00
|
|
|
/*
|
|
|
|
* Will be either used or freed by nfsd4_sequence_check_conn
|
|
|
|
* below.
|
|
|
|
*/
|
|
|
|
conn = alloc_conn(rqstp, NFS4_CDFC4_FORE);
|
|
|
|
if (!conn)
|
|
|
|
return nfserr_jukebox;
|
|
|
|
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2014-06-30 22:48:42 +07:00
|
|
|
session = find_in_sessionid_hashtbl(&seq->sessionid, net, &status);
|
2009-04-03 12:28:08 +07:00
|
|
|
if (!session)
|
2013-04-02 09:23:49 +07:00
|
|
|
goto out_no_session;
|
|
|
|
clp = session->se_client;
|
2009-04-03 12:28:08 +07:00
|
|
|
|
2011-04-27 08:09:58 +07:00
|
|
|
status = nfserr_too_many_ops;
|
|
|
|
if (nfsd4_session_too_many_ops(rqstp, session))
|
2013-03-19 23:05:39 +07:00
|
|
|
goto out_put_session;
|
2011-04-27 08:09:58 +07:00
|
|
|
|
2011-07-14 13:56:02 +07:00
|
|
|
status = nfserr_req_too_big;
|
|
|
|
if (nfsd4_request_too_big(rqstp, session))
|
2013-03-19 23:05:39 +07:00
|
|
|
goto out_put_session;
|
2011-07-14 13:56:02 +07:00
|
|
|
|
2009-04-03 12:28:08 +07:00
|
|
|
status = nfserr_badslot;
|
2009-06-16 08:19:13 +07:00
|
|
|
if (seq->slotid >= session->se_fchannel.maxreqs)
|
2013-03-19 23:05:39 +07:00
|
|
|
goto out_put_session;
|
2009-04-03 12:28:08 +07:00
|
|
|
|
2009-08-28 19:45:04 +07:00
|
|
|
slot = session->se_slots[seq->slotid];
|
2009-04-03 12:28:08 +07:00
|
|
|
dprintk("%s: slotid %d\n", __func__, seq->slotid);
|
|
|
|
|
2009-08-28 19:45:02 +07:00
|
|
|
/* We do not negotiate the number of slots yet, so set the
|
|
|
|
* maxslots to the session maxreqs which is used to encode
|
|
|
|
* sr_highest_slotid and the sr_target_slot id to maxslots */
|
|
|
|
seq->maxslots = session->se_fchannel.maxreqs;
|
|
|
|
|
2012-02-14 04:39:00 +07:00
|
|
|
status = check_slot_seqid(seq->seqid, slot->sl_seqid,
|
|
|
|
slot->sl_flags & NFSD4_SLOT_INUSE);
|
2009-04-03 12:28:08 +07:00
|
|
|
if (status == nfserr_replay_cache) {
|
2012-02-14 04:56:19 +07:00
|
|
|
status = nfserr_seq_misordered;
|
|
|
|
if (!(slot->sl_flags & NFSD4_SLOT_INITIALIZED))
|
2013-03-19 23:05:39 +07:00
|
|
|
goto out_put_session;
|
2009-04-03 12:28:08 +07:00
|
|
|
cstate->slot = slot;
|
|
|
|
cstate->session = session;
|
2014-06-30 22:48:44 +07:00
|
|
|
cstate->clp = clp;
|
2009-04-03 12:28:22 +07:00
|
|
|
/* Return the cached reply status and set cstate->status
|
2009-08-28 19:45:04 +07:00
|
|
|
* for nfsd4_proc_compound processing */
|
2009-04-03 12:28:35 +07:00
|
|
|
status = nfsd4_replay_cache_entry(resp, seq);
|
2009-04-03 12:28:22 +07:00
|
|
|
cstate->status = nfserr_replay_cache;
|
2009-08-20 07:21:56 +07:00
|
|
|
goto out;
|
2009-04-03 12:28:08 +07:00
|
|
|
}
|
|
|
|
if (status)
|
2013-03-19 23:05:39 +07:00
|
|
|
goto out_put_session;
|
2009-04-03 12:28:08 +07:00
|
|
|
|
2013-04-14 01:27:29 +07:00
|
|
|
status = nfsd4_sequence_check_conn(conn, session);
|
2010-10-22 04:17:31 +07:00
|
|
|
conn = NULL;
|
2013-04-14 01:27:29 +07:00
|
|
|
if (status)
|
|
|
|
goto out_put_session;
|
2010-09-30 03:11:06 +07:00
|
|
|
|
2014-03-13 08:39:35 +07:00
|
|
|
buflen = (seq->cachethis) ?
|
|
|
|
session->se_fchannel.maxresp_cached :
|
|
|
|
session->se_fchannel.maxresp_sz;
|
|
|
|
status = (seq->cachethis) ? nfserr_rep_too_big_to_cache :
|
|
|
|
nfserr_rep_too_big;
|
2014-05-13 05:10:58 +07:00
|
|
|
if (xdr_restrict_buflen(xdr, buflen - rqstp->rq_auth_slack))
|
2014-03-13 08:39:35 +07:00
|
|
|
goto out_put_session;
|
2014-03-21 07:47:41 +07:00
|
|
|
svc_reserve(rqstp, buflen);
|
2014-03-13 08:39:35 +07:00
|
|
|
|
|
|
|
status = nfs_ok;
|
2009-04-03 12:28:08 +07:00
|
|
|
/* Success! bump slot seqid */
|
|
|
|
slot->sl_seqid = seq->seqid;
|
2012-02-14 04:56:19 +07:00
|
|
|
slot->sl_flags |= NFSD4_SLOT_INUSE;
|
2012-02-14 04:39:00 +07:00
|
|
|
if (seq->cachethis)
|
|
|
|
slot->sl_flags |= NFSD4_SLOT_CACHETHIS;
|
2012-02-14 04:56:19 +07:00
|
|
|
else
|
|
|
|
slot->sl_flags &= ~NFSD4_SLOT_CACHETHIS;
|
2009-04-03 12:28:08 +07:00
|
|
|
|
|
|
|
cstate->slot = slot;
|
|
|
|
cstate->session = session;
|
2014-06-30 22:48:44 +07:00
|
|
|
cstate->clp = clp;
|
2009-04-03 12:28:08 +07:00
|
|
|
|
|
|
|
out:
|
2013-04-02 09:23:49 +07:00
|
|
|
switch (clp->cl_cb_state) {
|
|
|
|
case NFSD4_CB_DOWN:
|
|
|
|
seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
|
|
|
|
break;
|
|
|
|
case NFSD4_CB_FAULT:
|
|
|
|
seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
seq->status_flags = 0;
|
2009-08-20 07:21:56 +07:00
|
|
|
}
|
2013-04-10 04:02:51 +07:00
|
|
|
if (!list_empty(&clp->cl_revoked))
|
|
|
|
seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
|
2013-04-02 09:23:49 +07:00
|
|
|
out_no_session:
|
2014-03-24 10:56:59 +07:00
|
|
|
if (conn)
|
|
|
|
free_conn(conn);
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
2009-04-03 12:28:08 +07:00
|
|
|
return status;
|
2013-03-19 23:05:39 +07:00
|
|
|
out_put_session:
|
2014-06-30 22:48:42 +07:00
|
|
|
nfsd4_put_session_locked(session);
|
2013-04-02 09:23:49 +07:00
|
|
|
goto out_no_session;
|
2009-04-03 12:27:58 +07:00
|
|
|
}
|
|
|
|
|
2014-06-30 22:48:35 +07:00
|
|
|
void
|
|
|
|
nfsd4_sequence_done(struct nfsd4_compoundres *resp)
|
|
|
|
{
|
|
|
|
struct nfsd4_compound_state *cs = &resp->cstate;
|
|
|
|
|
|
|
|
if (nfsd4_has_session(cs)) {
|
|
|
|
if (cs->status != nfserr_replay_cache) {
|
|
|
|
nfsd4_store_cache_entry(resp);
|
|
|
|
cs->slot->sl_flags &= ~NFSD4_SLOT_INUSE;
|
|
|
|
}
|
2014-06-30 22:48:42 +07:00
|
|
|
/* Drop session reference that was taken in nfsd4_sequence() */
|
2014-06-30 22:48:35 +07:00
|
|
|
nfsd4_put_session(cs->session);
|
2014-06-30 22:48:44 +07:00
|
|
|
} else if (cs->clp)
|
|
|
|
put_client_renew(cs->clp);
|
2014-06-30 22:48:35 +07:00
|
|
|
}
|
|
|
|
|
2011-10-20 16:51:39 +07:00
|
|
|
__be32
|
|
|
|
nfsd4_destroy_clientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd4_destroy_clientid *dc)
|
|
|
|
{
|
2014-07-30 19:27:08 +07:00
|
|
|
struct nfs4_client *conf, *unconf;
|
|
|
|
struct nfs4_client *clp = NULL;
|
2012-04-26 04:58:50 +07:00
|
|
|
__be32 status = 0;
|
2012-11-14 22:21:21 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2011-10-20 16:51:39 +07:00
|
|
|
|
2014-07-30 19:27:08 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2012-11-14 22:21:31 +07:00
|
|
|
unconf = find_unconfirmed_client(&dc->clientid, true, nn);
|
2012-11-14 22:21:21 +07:00
|
|
|
conf = find_confirmed_client(&dc->clientid, true, nn);
|
2013-03-12 21:12:37 +07:00
|
|
|
WARN_ON_ONCE(conf && unconf);
|
2011-10-20 16:51:39 +07:00
|
|
|
|
|
|
|
if (conf) {
|
2013-03-15 05:20:01 +07:00
|
|
|
if (client_has_state(conf)) {
|
2011-10-20 16:51:39 +07:00
|
|
|
status = nfserr_clientid_busy;
|
|
|
|
goto out;
|
|
|
|
}
|
2014-07-30 19:27:14 +07:00
|
|
|
status = mark_client_expired_locked(conf);
|
|
|
|
if (status)
|
|
|
|
goto out;
|
2014-07-30 19:27:08 +07:00
|
|
|
clp = conf;
|
2011-10-20 16:51:39 +07:00
|
|
|
} else if (unconf)
|
|
|
|
clp = unconf;
|
|
|
|
else {
|
|
|
|
status = nfserr_stale_clientid;
|
|
|
|
goto out;
|
|
|
|
}
|
2016-06-15 23:52:08 +07:00
|
|
|
if (!nfsd4_mach_creds_match(clp, rqstp)) {
|
2014-07-30 19:27:08 +07:00
|
|
|
clp = NULL;
|
2013-04-14 01:27:29 +07:00
|
|
|
status = nfserr_wrong_cred;
|
|
|
|
goto out;
|
|
|
|
}
|
2014-07-30 19:27:08 +07:00
|
|
|
unhash_client_locked(clp);
|
2011-10-20 16:51:39 +07:00
|
|
|
out:
|
2014-07-30 19:27:08 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
if (clp)
|
|
|
|
expire_client(clp);
|
2011-10-20 16:51:39 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2010-04-20 02:11:28 +07:00
|
|
|
__be32
|
|
|
|
nfsd4_reclaim_complete(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd4_reclaim_complete *rc)
|
|
|
|
{
|
2012-04-26 04:58:50 +07:00
|
|
|
__be32 status = 0;
|
2011-04-27 08:14:30 +07:00
|
|
|
|
2010-04-20 02:11:28 +07:00
|
|
|
if (rc->rca_one_fs) {
|
|
|
|
if (!cstate->current_fh.fh_dentry)
|
|
|
|
return nfserr_nofilehandle;
|
|
|
|
/*
|
|
|
|
* We don't take advantage of the rca_one_fs case.
|
|
|
|
* That's OK, it's optional, we can safely ignore it.
|
|
|
|
*/
|
2016-07-02 13:24:32 +07:00
|
|
|
return nfs_ok;
|
2010-04-20 02:11:28 +07:00
|
|
|
}
|
2011-04-27 08:14:30 +07:00
|
|
|
|
|
|
|
status = nfserr_complete_already;
|
2012-03-21 20:52:02 +07:00
|
|
|
if (test_and_set_bit(NFSD4_CLIENT_RECLAIM_COMPLETE,
|
|
|
|
&cstate->session->se_client->cl_flags))
|
2011-04-27 08:14:30 +07:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
status = nfserr_stale_clientid;
|
|
|
|
if (is_client_expired(cstate->session->se_client))
|
2010-04-20 02:11:28 +07:00
|
|
|
/*
|
|
|
|
* The following error isn't really legal.
|
|
|
|
* But we only get here if the client just explicitly
|
|
|
|
* destroyed the client. Surely it no longer cares what
|
|
|
|
* error it gets back on an operation for the dead
|
|
|
|
* client.
|
|
|
|
*/
|
2011-04-27 08:14:30 +07:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
status = nfs_ok;
|
2012-03-22 03:42:43 +07:00
|
|
|
nfsd4_client_record_create(cstate->session->se_client);
|
2011-04-27 08:14:30 +07:00
|
|
|
out:
|
|
|
|
return status;
|
2010-04-20 02:11:28 +07:00
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:38 +07:00
|
|
|
nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_setclientid *setclid)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2011-10-11 02:07:40 +07:00
|
|
|
struct xdr_netobj clname = setclid->se_name;
|
2005-04-17 05:20:36 +07:00
|
|
|
nfs4_verifier clverifier = setclid->se_verf;
|
2014-07-30 19:27:06 +07:00
|
|
|
struct nfs4_client *conf, *new;
|
|
|
|
struct nfs4_client *unconf = NULL;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2012-11-14 22:21:10 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
|
|
|
|
2014-07-30 19:27:05 +07:00
|
|
|
new = create_client(clname, rqstp, &clverifier);
|
|
|
|
if (new == NULL)
|
|
|
|
return nfserr_jukebox;
|
2012-05-19 08:54:19 +07:00
|
|
|
/* Cases below refer to rfc 3530 section 14.2.33: */
|
2014-07-30 19:27:06 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2012-11-14 22:21:26 +07:00
|
|
|
conf = find_confirmed_client_by_name(&clname, nn);
|
2015-10-16 02:33:23 +07:00
|
|
|
if (conf && client_has_state(conf)) {
|
2012-05-19 08:54:19 +07:00
|
|
|
/* case 0: */
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfserr_clid_inuse;
|
2010-11-25 05:30:54 +07:00
|
|
|
if (clp_used_exchangeid(conf))
|
|
|
|
goto out;
|
2009-03-19 02:06:26 +07:00
|
|
|
if (!same_creds(&conf->cl_cred, &rqstp->rq_cred)) {
|
2009-08-14 23:57:56 +07:00
|
|
|
char addr_str[INET6_ADDRSTRLEN];
|
|
|
|
rpc_ntop((struct sockaddr *) &conf->cl_addr, addr_str,
|
|
|
|
sizeof(addr_str));
|
|
|
|
dprintk("NFSD: setclientid: string in use by client "
|
|
|
|
"at %s\n", addr_str);
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
2012-11-14 22:21:36 +07:00
|
|
|
unconf = find_unconfirmed_client_by_name(&clname, nn);
|
2012-05-19 09:06:41 +07:00
|
|
|
if (unconf)
|
2014-07-30 19:27:06 +07:00
|
|
|
unhash_client_locked(unconf);
|
2015-07-13 16:29:41 +07:00
|
|
|
if (conf && same_verf(&conf->cl_verifier, &clverifier)) {
|
2012-05-19 08:54:19 +07:00
|
|
|
/* case 1: probable callback update */
|
2005-04-17 05:20:36 +07:00
|
|
|
copy_clid(new, conf);
|
2015-07-13 16:29:41 +07:00
|
|
|
gen_confirm(new, nn);
|
|
|
|
} else /* case 4 (new client) or cases 2, 3 (client reboot): */
|
2012-11-14 22:21:10 +07:00
|
|
|
gen_clid(new, nn);
|
2010-10-20 06:36:51 +07:00
|
|
|
new->cl_minorversion = 0;
|
2010-12-15 12:09:01 +07:00
|
|
|
gen_callback(new, setclid, rqstp);
|
2012-11-13 03:00:56 +07:00
|
|
|
add_to_unconfirmed(new);
|
2005-04-17 05:20:36 +07:00
|
|
|
setclid->se_clientid.cl_boot = new->cl_clientid.cl_boot;
|
|
|
|
setclid->se_clientid.cl_id = new->cl_clientid.cl_id;
|
|
|
|
memcpy(setclid->se_confirm.data, new->cl_confirm.data, sizeof(setclid->se_confirm.data));
|
2014-07-30 19:27:05 +07:00
|
|
|
new = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfs_ok;
|
|
|
|
out:
|
2014-07-30 19:27:06 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
2014-07-30 19:27:05 +07:00
|
|
|
if (new)
|
|
|
|
free_client(new);
|
2014-07-30 19:27:06 +07:00
|
|
|
if (unconf)
|
|
|
|
expire_client(unconf);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:38 +07:00
|
|
|
nfsd4_setclientid_confirm(struct svc_rqst *rqstp,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_setclientid_confirm *setclientid_confirm)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-06-24 12:04:14 +07:00
|
|
|
struct nfs4_client *conf, *unconf;
|
2014-07-30 19:27:07 +07:00
|
|
|
struct nfs4_client *old = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
nfs4_verifier confirm = setclientid_confirm->sc_confirm;
|
|
|
|
clientid_t * clid = &setclientid_confirm->sc_clientid;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2012-11-14 22:21:05 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-07-25 19:57:45 +07:00
|
|
|
if (STALE_CLIENTID(clid, nn))
|
2005-04-17 05:20:36 +07:00
|
|
|
return nfserr_stale_clientid;
|
2005-06-24 12:04:14 +07:00
|
|
|
|
2014-07-30 19:27:07 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2012-11-14 22:21:21 +07:00
|
|
|
conf = find_confirmed_client(clid, false, nn);
|
2012-11-14 22:21:31 +07:00
|
|
|
unconf = find_unconfirmed_client(clid, false, nn);
|
2007-11-21 04:11:27 +07:00
|
|
|
/*
|
2012-05-19 21:05:58 +07:00
|
|
|
* We try hard to give out unique clientid's, so if we get an
|
|
|
|
* attempt to confirm the same clientid with a different cred,
|
2015-09-02 00:40:53 +07:00
|
|
|
* the client may be buggy; this should never happen.
|
|
|
|
*
|
|
|
|
* Nevertheless, RFC 7530 recommends INUSE for this case:
|
2007-11-21 04:11:27 +07:00
|
|
|
*/
|
2015-09-02 00:40:53 +07:00
|
|
|
status = nfserr_clid_inuse;
|
2012-05-19 21:05:58 +07:00
|
|
|
if (unconf && !same_creds(&unconf->cl_cred, &rqstp->rq_cred))
|
|
|
|
goto out;
|
|
|
|
if (conf && !same_creds(&conf->cl_cred, &rqstp->rq_cred))
|
|
|
|
goto out;
|
2012-05-19 08:54:19 +07:00
|
|
|
/* cases below refer to rfc 3530 section 14.2.34: */
|
2012-05-20 00:55:22 +07:00
|
|
|
if (!unconf || !same_verf(&confirm, &unconf->cl_confirm)) {
|
2016-09-21 07:55:36 +07:00
|
|
|
if (conf && same_verf(&confirm, &conf->cl_confirm)) {
|
|
|
|
/* case 2: probable retransmit */
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfs_ok;
|
2016-09-21 07:55:36 +07:00
|
|
|
} else /* case 4: client hasn't noticed we rebooted yet? */
|
2012-05-20 00:55:22 +07:00
|
|
|
status = nfserr_stale_clientid;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
status = nfs_ok;
|
|
|
|
if (conf) { /* case 1: callback update */
|
2014-07-30 19:27:07 +07:00
|
|
|
old = unconf;
|
|
|
|
unhash_client_locked(old);
|
2012-05-19 21:05:58 +07:00
|
|
|
nfsd4_change_callback(conf, &unconf->cl_cb_conn);
|
2012-05-20 00:55:22 +07:00
|
|
|
} else { /* case 3: normal case; new or rebooted client */
|
2014-07-30 19:27:07 +07:00
|
|
|
old = find_confirmed_client_by_name(&unconf->cl_name, nn);
|
|
|
|
if (old) {
|
2015-10-16 02:33:23 +07:00
|
|
|
status = nfserr_clid_inuse;
|
|
|
|
if (client_has_state(old)
|
|
|
|
&& !same_creds(&unconf->cl_cred,
|
|
|
|
&old->cl_cred))
|
|
|
|
goto out;
|
2014-07-30 19:27:07 +07:00
|
|
|
status = mark_client_expired_locked(old);
|
2014-07-30 19:27:13 +07:00
|
|
|
if (status) {
|
|
|
|
old = NULL;
|
2013-04-02 09:23:49 +07:00
|
|
|
goto out;
|
2014-07-30 19:27:13 +07:00
|
|
|
}
|
2013-04-02 09:23:49 +07:00
|
|
|
}
|
2012-05-19 21:05:58 +07:00
|
|
|
move_to_confirmed(unconf);
|
2014-07-30 19:27:07 +07:00
|
|
|
conf = unconf;
|
2005-06-24 12:04:11 +07:00
|
|
|
}
|
2014-07-30 19:27:07 +07:00
|
|
|
get_client_locked(conf);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
nfsd4_probe_callback(conf);
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
put_client_renew_locked(conf);
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
2014-07-30 19:27:07 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
if (old)
|
|
|
|
expire_client(old);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2011-10-14 03:00:16 +07:00
|
|
|
static struct nfs4_file *nfsd4_alloc_file(void)
|
|
|
|
{
|
|
|
|
return kmem_cache_alloc(file_slab, GFP_KERNEL);
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* OPEN Share state helper functions */
|
2014-10-23 19:01:02 +07:00
|
|
|
static void nfsd4_init_file(struct knfsd_fh *fh, unsigned int hashval,
|
|
|
|
struct nfs4_file *fp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-06-30 22:48:31 +07:00
|
|
|
lockdep_assert_held(&state_lock);
|
|
|
|
|
2011-10-14 03:00:16 +07:00
|
|
|
atomic_set(&fp->fi_ref, 1);
|
2014-07-11 01:07:25 +07:00
|
|
|
spin_lock_init(&fp->fi_lock);
|
2011-10-14 03:00:16 +07:00
|
|
|
INIT_LIST_HEAD(&fp->fi_stateids);
|
|
|
|
INIT_LIST_HEAD(&fp->fi_delegations);
|
2015-04-27 19:50:14 +07:00
|
|
|
INIT_LIST_HEAD(&fp->fi_clnt_odstate);
|
2014-07-24 03:17:38 +07:00
|
|
|
fh_copy_shallow(&fp->fi_fhandle, fh);
|
2014-08-22 23:05:43 +07:00
|
|
|
fp->fi_deleg_file = NULL;
|
2011-10-14 03:00:16 +07:00
|
|
|
fp->fi_had_conflict = false;
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
fp->fi_share_deny = 0;
|
2011-10-14 03:00:16 +07:00
|
|
|
memset(fp->fi_fds, 0, sizeof(fp->fi_fds));
|
|
|
|
memset(fp->fi_access, 0, sizeof(fp->fi_access));
|
nfsd: implement pNFS operations
Add support for the GETDEVICEINFO, LAYOUTGET, LAYOUTCOMMIT and
LAYOUTRETURN NFSv4.1 operations, as well as backing code to manage
outstanding layouts and devices.
Layout management is very straight forward, with a nfs4_layout_stateid
structure that extends nfs4_stid to manage layout stateids as the
top-level structure. It is linked into the nfs4_file and nfs4_client
structures like the other stateids, and contains a linked list of
layouts that hang of the stateid. The actual layout operations are
implemented in layout drivers that are not part of this commit, but
will be added later.
The worst part of this commit is the management of the pNFS device IDs,
which suffers from a specification that is not sanely implementable due
to the fact that the device-IDs are global and not bound to an export,
and have a small enough size so that we can't store the fsid portion of
a file handle, and must never be reused. As we still do need perform all
export authentication and validation checks on a device ID passed to
GETDEVICEINFO we are caught between a rock and a hard place. To work
around this issue we add a new hash that maps from a 64-bit integer to a
fsid so that we can look up the export to authenticate against it,
a 32-bit integer as a generation that we can bump when changing the device,
and a currently unused 32-bit integer that could be used in the future
to handle more than a single device per export. Entries in this hash
table are never deleted as we can't reuse the ids anyway, and would have
a severe lifetime problem anyway as Linux export structures are temporary
structures that can go away under load.
Parts of the XDR data, structures and marshaling/unmarshaling code, as
well as many concepts are derived from the old pNFS server implementation
from Andy Adamson, Benny Halevy, Dean Hildebrand, Marc Eshel, Fred Isaman,
Mike Sager, Ricardo Labiaga and many others.
Signed-off-by: Christoph Hellwig <hch@lst.de>
2014-05-05 18:11:59 +07:00
|
|
|
#ifdef CONFIG_NFSD_PNFS
|
|
|
|
INIT_LIST_HEAD(&fp->fi_lo_states);
|
2014-09-23 17:38:48 +07:00
|
|
|
atomic_set(&fp->fi_lo_recalls, 0);
|
nfsd: implement pNFS operations
Add support for the GETDEVICEINFO, LAYOUTGET, LAYOUTCOMMIT and
LAYOUTRETURN NFSv4.1 operations, as well as backing code to manage
outstanding layouts and devices.
Layout management is very straight forward, with a nfs4_layout_stateid
structure that extends nfs4_stid to manage layout stateids as the
top-level structure. It is linked into the nfs4_file and nfs4_client
structures like the other stateids, and contains a linked list of
layouts that hang of the stateid. The actual layout operations are
implemented in layout drivers that are not part of this commit, but
will be added later.
The worst part of this commit is the management of the pNFS device IDs,
which suffers from a specification that is not sanely implementable due
to the fact that the device-IDs are global and not bound to an export,
and have a small enough size so that we can't store the fsid portion of
a file handle, and must never be reused. As we still do need perform all
export authentication and validation checks on a device ID passed to
GETDEVICEINFO we are caught between a rock and a hard place. To work
around this issue we add a new hash that maps from a 64-bit integer to a
fsid so that we can look up the export to authenticate against it,
a 32-bit integer as a generation that we can bump when changing the device,
and a currently unused 32-bit integer that could be used in the future
to handle more than a single device per export. Entries in this hash
table are never deleted as we can't reuse the ids anyway, and would have
a severe lifetime problem anyway as Linux export structures are temporary
structures that can go away under load.
Parts of the XDR data, structures and marshaling/unmarshaling code, as
well as many concepts are derived from the old pNFS server implementation
from Andy Adamson, Benny Halevy, Dean Hildebrand, Marc Eshel, Fred Isaman,
Mike Sager, Ricardo Labiaga and many others.
Signed-off-by: Christoph Hellwig <hch@lst.de>
2014-05-05 18:11:59 +07:00
|
|
|
#endif
|
2014-10-23 19:01:02 +07:00
|
|
|
hlist_add_head_rcu(&fp->fi_hash, &file_hashtbl[hashval]);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2007-08-02 02:30:59 +07:00
|
|
|
void
|
2005-04-17 05:20:36 +07:00
|
|
|
nfsd4_free_slabs(void)
|
|
|
|
{
|
2015-04-27 19:50:14 +07:00
|
|
|
kmem_cache_destroy(odstate_slab);
|
2014-05-21 21:43:03 +07:00
|
|
|
kmem_cache_destroy(openowner_slab);
|
|
|
|
kmem_cache_destroy(lockowner_slab);
|
|
|
|
kmem_cache_destroy(file_slab);
|
|
|
|
kmem_cache_destroy(stateid_slab);
|
|
|
|
kmem_cache_destroy(deleg_slab);
|
2005-06-24 12:03:01 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-11-02 02:24:59 +07:00
|
|
|
int
|
2005-06-24 12:03:01 +07:00
|
|
|
nfsd4_init_slabs(void)
|
|
|
|
{
|
2011-07-31 10:33:59 +07:00
|
|
|
openowner_slab = kmem_cache_create("nfsd4_openowners",
|
|
|
|
sizeof(struct nfs4_openowner), 0, 0, NULL);
|
|
|
|
if (openowner_slab == NULL)
|
2014-05-21 21:43:03 +07:00
|
|
|
goto out;
|
2011-07-31 10:33:59 +07:00
|
|
|
lockowner_slab = kmem_cache_create("nfsd4_lockowners",
|
2012-10-24 13:44:19 +07:00
|
|
|
sizeof(struct nfs4_lockowner), 0, 0, NULL);
|
2011-07-31 10:33:59 +07:00
|
|
|
if (lockowner_slab == NULL)
|
2014-05-21 21:43:03 +07:00
|
|
|
goto out_free_openowner_slab;
|
2005-06-24 12:03:01 +07:00
|
|
|
file_slab = kmem_cache_create("nfsd4_files",
|
2007-07-20 08:11:58 +07:00
|
|
|
sizeof(struct nfs4_file), 0, 0, NULL);
|
2005-06-24 12:03:01 +07:00
|
|
|
if (file_slab == NULL)
|
2014-05-21 21:43:03 +07:00
|
|
|
goto out_free_lockowner_slab;
|
2005-06-24 12:03:03 +07:00
|
|
|
stateid_slab = kmem_cache_create("nfsd4_stateids",
|
2011-09-08 03:06:42 +07:00
|
|
|
sizeof(struct nfs4_ol_stateid), 0, 0, NULL);
|
2005-06-24 12:03:03 +07:00
|
|
|
if (stateid_slab == NULL)
|
2014-05-21 21:43:03 +07:00
|
|
|
goto out_free_file_slab;
|
2005-06-24 12:03:04 +07:00
|
|
|
deleg_slab = kmem_cache_create("nfsd4_delegations",
|
2007-07-20 08:11:58 +07:00
|
|
|
sizeof(struct nfs4_delegation), 0, 0, NULL);
|
2005-06-24 12:03:04 +07:00
|
|
|
if (deleg_slab == NULL)
|
2014-05-21 21:43:03 +07:00
|
|
|
goto out_free_stateid_slab;
|
2015-04-27 19:50:14 +07:00
|
|
|
odstate_slab = kmem_cache_create("nfsd4_odstate",
|
|
|
|
sizeof(struct nfs4_clnt_odstate), 0, 0, NULL);
|
|
|
|
if (odstate_slab == NULL)
|
|
|
|
goto out_free_deleg_slab;
|
2005-06-24 12:03:01 +07:00
|
|
|
return 0;
|
2014-05-21 21:43:03 +07:00
|
|
|
|
2015-04-27 19:50:14 +07:00
|
|
|
out_free_deleg_slab:
|
|
|
|
kmem_cache_destroy(deleg_slab);
|
2014-05-21 21:43:03 +07:00
|
|
|
out_free_stateid_slab:
|
|
|
|
kmem_cache_destroy(stateid_slab);
|
|
|
|
out_free_file_slab:
|
|
|
|
kmem_cache_destroy(file_slab);
|
|
|
|
out_free_lockowner_slab:
|
|
|
|
kmem_cache_destroy(lockowner_slab);
|
|
|
|
out_free_openowner_slab:
|
|
|
|
kmem_cache_destroy(openowner_slab);
|
|
|
|
out:
|
2005-06-24 12:03:01 +07:00
|
|
|
dprintk("nfsd4: out of memory while initializing nfsv4\n");
|
|
|
|
return -ENOMEM;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2011-08-12 20:42:57 +07:00
|
|
|
static void init_nfs4_replay(struct nfs4_replay *rp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2011-08-12 20:42:57 +07:00
|
|
|
rp->rp_status = nfserr_serverfault;
|
|
|
|
rp->rp_buflen = 0;
|
|
|
|
rp->rp_buf = rp->rp_ibuf;
|
2014-07-30 08:34:27 +07:00
|
|
|
mutex_init(&rp->rp_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nfsd4_cstate_assign_replay(struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfs4_stateowner *so)
|
|
|
|
{
|
|
|
|
if (!nfsd4_has_session(cstate)) {
|
|
|
|
mutex_lock(&so->so_replay.rp_mutex);
|
2014-08-22 21:18:43 +07:00
|
|
|
cstate->replay_owner = nfs4_get_stateowner(so);
|
2014-07-30 08:34:27 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nfsd4_cstate_clear_replay(struct nfsd4_compound_state *cstate)
|
|
|
|
{
|
|
|
|
struct nfs4_stateowner *so = cstate->replay_owner;
|
|
|
|
|
|
|
|
if (so != NULL) {
|
|
|
|
cstate->replay_owner = NULL;
|
|
|
|
mutex_unlock(&so->so_replay.rp_mutex);
|
|
|
|
nfs4_put_stateowner(so);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
static inline void *alloc_stateowner(struct kmem_cache *slab, struct xdr_netobj *owner, struct nfs4_client *clp)
|
2011-08-12 20:42:57 +07:00
|
|
|
{
|
2005-04-17 05:20:36 +07:00
|
|
|
struct nfs4_stateowner *sop;
|
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
sop = kmem_cache_alloc(slab, GFP_KERNEL);
|
2011-08-12 20:42:57 +07:00
|
|
|
if (!sop)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
sop->so_owner.data = kmemdup(owner->data, owner->len, GFP_KERNEL);
|
|
|
|
if (!sop->so_owner.data) {
|
2011-07-31 10:33:59 +07:00
|
|
|
kmem_cache_free(slab, sop);
|
2005-04-17 05:20:36 +07:00
|
|
|
return NULL;
|
2011-08-12 20:42:57 +07:00
|
|
|
}
|
|
|
|
sop->so_owner.len = owner->len;
|
|
|
|
|
2005-06-24 12:04:17 +07:00
|
|
|
INIT_LIST_HEAD(&sop->so_stateids);
|
2011-08-12 20:42:57 +07:00
|
|
|
sop->so_client = clp;
|
|
|
|
init_nfs4_replay(&sop->so_replay);
|
2014-07-30 08:34:26 +07:00
|
|
|
atomic_set(&sop->so_count, 1);
|
2011-08-12 20:42:57 +07:00
|
|
|
return sop;
|
|
|
|
}
|
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
static void hash_openowner(struct nfs4_openowner *oo, struct nfs4_client *clp, unsigned int strhashval)
|
2011-08-12 20:42:57 +07:00
|
|
|
{
|
2014-07-30 08:34:36 +07:00
|
|
|
lockdep_assert_held(&clp->cl_lock);
|
2012-11-14 22:21:41 +07:00
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
list_add(&oo->oo_owner.so_strhash,
|
|
|
|
&clp->cl_ownerstr_hashtbl[strhashval]);
|
2011-07-31 10:33:59 +07:00
|
|
|
list_add(&oo->oo_perclient, &clp->cl_openowners);
|
2011-08-12 20:42:57 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:29 +07:00
|
|
|
static void nfs4_unhash_openowner(struct nfs4_stateowner *so)
|
|
|
|
{
|
2014-07-30 08:34:36 +07:00
|
|
|
unhash_openowner_locked(openowner(so));
|
2014-07-30 08:34:29 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:26 +07:00
|
|
|
static void nfs4_free_openowner(struct nfs4_stateowner *so)
|
|
|
|
{
|
|
|
|
struct nfs4_openowner *oo = openowner(so);
|
|
|
|
|
|
|
|
kmem_cache_free(openowner_slab, oo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct nfs4_stateowner_operations openowner_ops = {
|
2014-07-30 08:34:29 +07:00
|
|
|
.so_unhash = nfs4_unhash_openowner,
|
|
|
|
.so_free = nfs4_free_openowner,
|
2014-07-30 08:34:26 +07:00
|
|
|
};
|
|
|
|
|
2015-11-06 08:42:43 +07:00
|
|
|
static struct nfs4_ol_stateid *
|
|
|
|
nfsd4_find_existing_open(struct nfs4_file *fp, struct nfsd4_open *open)
|
|
|
|
{
|
|
|
|
struct nfs4_ol_stateid *local, *ret = NULL;
|
|
|
|
struct nfs4_openowner *oo = open->op_openowner;
|
|
|
|
|
|
|
|
lockdep_assert_held(&fp->fi_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(local, &fp->fi_stateids, st_perfile) {
|
|
|
|
/* ignore lock owners */
|
|
|
|
if (local->st_stateowner->so_is_open_owner == 0)
|
|
|
|
continue;
|
|
|
|
if (local->st_stateowner == &oo->oo_owner) {
|
|
|
|
ret = local;
|
|
|
|
atomic_inc(&ret->st_stid.sc_count);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
static struct nfs4_openowner *
|
2014-06-30 22:48:45 +07:00
|
|
|
alloc_init_open_stateowner(unsigned int strhashval, struct nfsd4_open *open,
|
2014-06-30 22:48:36 +07:00
|
|
|
struct nfsd4_compound_state *cstate)
|
|
|
|
{
|
2014-06-30 22:48:45 +07:00
|
|
|
struct nfs4_client *clp = cstate->clp;
|
2014-07-30 08:34:34 +07:00
|
|
|
struct nfs4_openowner *oo, *ret;
|
2011-08-12 20:42:57 +07:00
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
oo = alloc_stateowner(openowner_slab, &open->op_owner, clp);
|
|
|
|
if (!oo)
|
2011-08-12 20:42:57 +07:00
|
|
|
return NULL;
|
2014-07-30 08:34:26 +07:00
|
|
|
oo->oo_owner.so_ops = &openowner_ops;
|
2011-07-31 10:33:59 +07:00
|
|
|
oo->oo_owner.so_is_open_owner = 1;
|
|
|
|
oo->oo_owner.so_seqid = open->op_seqid;
|
2014-07-30 08:34:32 +07:00
|
|
|
oo->oo_flags = 0;
|
2014-06-30 22:48:36 +07:00
|
|
|
if (nfsd4_has_session(cstate))
|
|
|
|
oo->oo_flags |= NFS4_OO_CONFIRMED;
|
2011-07-31 10:33:59 +07:00
|
|
|
oo->oo_time = 0;
|
2011-09-17 04:42:48 +07:00
|
|
|
oo->oo_last_closed_stid = NULL;
|
2011-07-31 10:33:59 +07:00
|
|
|
INIT_LIST_HEAD(&oo->oo_close_lru);
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
ret = find_openstateowner_str_locked(strhashval, open, clp);
|
2014-07-30 08:34:34 +07:00
|
|
|
if (ret == NULL) {
|
|
|
|
hash_openowner(oo, clp, strhashval);
|
|
|
|
ret = oo;
|
|
|
|
} else
|
2015-07-16 11:05:07 +07:00
|
|
|
nfs4_free_stateowner(&oo->oo_owner);
|
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2015-03-23 21:53:42 +07:00
|
|
|
return ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2015-11-06 08:42:43 +07:00
|
|
|
static struct nfs4_ol_stateid *
|
2016-06-15 10:28:06 +07:00
|
|
|
init_open_stateid(struct nfs4_file *fp, struct nfsd4_open *open)
|
2015-11-06 08:42:43 +07:00
|
|
|
{
|
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
struct nfs4_openowner *oo = open->op_openowner;
|
2015-11-06 08:42:43 +07:00
|
|
|
struct nfs4_ol_stateid *retstp = NULL;
|
2016-06-15 10:28:06 +07:00
|
|
|
struct nfs4_ol_stateid *stp;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2016-06-15 10:28:06 +07:00
|
|
|
stp = open->op_stp;
|
2016-06-15 10:28:05 +07:00
|
|
|
/* We are moving these outside of the spinlocks to avoid the warnings */
|
|
|
|
mutex_init(&stp->st_mutex);
|
|
|
|
mutex_lock(&stp->st_mutex);
|
|
|
|
|
2015-11-06 08:42:43 +07:00
|
|
|
spin_lock(&oo->oo_owner.so_client->cl_lock);
|
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
|
|
|
|
retstp = nfsd4_find_existing_open(fp, open);
|
|
|
|
if (retstp)
|
|
|
|
goto out_unlock;
|
2016-06-15 10:28:06 +07:00
|
|
|
|
|
|
|
open->op_stp = NULL;
|
2014-07-30 08:34:19 +07:00
|
|
|
atomic_inc(&stp->st_stid.sc_count);
|
2013-02-04 00:23:01 +07:00
|
|
|
stp->st_stid.sc_type = NFS4_OPEN_STID;
|
2014-06-30 22:48:38 +07:00
|
|
|
INIT_LIST_HEAD(&stp->st_locks);
|
2014-08-22 21:18:43 +07:00
|
|
|
stp->st_stateowner = nfs4_get_stateowner(&oo->oo_owner);
|
2005-06-24 12:03:10 +07:00
|
|
|
get_nfs4_file(fp);
|
2014-07-30 08:34:08 +07:00
|
|
|
stp->st_stid.sc_file = fp;
|
2005-04-17 05:20:36 +07:00
|
|
|
stp->st_access_bmap = 0;
|
|
|
|
stp->st_deny_bmap = 0;
|
2005-07-08 07:59:27 +07:00
|
|
|
stp->st_openstp = NULL;
|
2014-07-30 08:34:12 +07:00
|
|
|
list_add(&stp->st_perstateowner, &oo->oo_owner.so_stateids);
|
2014-07-11 01:07:25 +07:00
|
|
|
list_add(&stp->st_perfile, &fp->fi_stateids);
|
2015-11-06 08:42:43 +07:00
|
|
|
|
|
|
|
out_unlock:
|
2014-07-11 01:07:25 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
2014-07-30 08:34:12 +07:00
|
|
|
spin_unlock(&oo->oo_owner.so_client->cl_lock);
|
2016-06-15 10:28:05 +07:00
|
|
|
if (retstp) {
|
|
|
|
mutex_lock(&retstp->st_mutex);
|
2016-06-15 10:28:06 +07:00
|
|
|
/* To keep mutex tracking happy */
|
2016-06-15 10:28:05 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2016-06-15 10:28:06 +07:00
|
|
|
stp = retstp;
|
2016-06-15 10:28:05 +07:00
|
|
|
}
|
2016-06-15 10:28:06 +07:00
|
|
|
return stp;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:32 +07:00
|
|
|
/*
|
|
|
|
* In the 4.0 case we need to keep the owners around a little while to handle
|
|
|
|
* CLOSE replay. We still do need to release any file access that is held by
|
|
|
|
* them before returning however.
|
|
|
|
*/
|
2005-06-24 12:04:03 +07:00
|
|
|
static void
|
2014-07-30 08:34:32 +07:00
|
|
|
move_to_close_lru(struct nfs4_ol_stateid *s, struct net *net)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-07-30 19:27:11 +07:00
|
|
|
struct nfs4_ol_stateid *last;
|
2014-07-30 08:34:32 +07:00
|
|
|
struct nfs4_openowner *oo = openowner(s->st_stateowner);
|
|
|
|
struct nfsd_net *nn = net_generic(s->st_stid.sc_client->net,
|
|
|
|
nfsd_net_id);
|
2012-11-14 22:22:01 +07:00
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
dprintk("NFSD: move_to_close_lru nfs4_openowner %p\n", oo);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-30 08:34:33 +07:00
|
|
|
/*
|
|
|
|
* We know that we hold one reference via nfsd4_close, and another
|
|
|
|
* "persistent" reference for the client. If the refcount is higher
|
|
|
|
* than 2, then there are still calls in progress that are using this
|
|
|
|
* stateid. We can't put the sc_file reference until they are finished.
|
|
|
|
* Wait for the refcount to drop to 2. Since it has been unhashed,
|
|
|
|
* there should be no danger of the refcount going back up again at
|
|
|
|
* this point.
|
|
|
|
*/
|
|
|
|
wait_event(close_wq, atomic_read(&s->st_stid.sc_count) == 2);
|
|
|
|
|
2014-07-30 08:34:32 +07:00
|
|
|
release_all_access(s);
|
|
|
|
if (s->st_stid.sc_file) {
|
|
|
|
put_nfs4_file(s->st_stid.sc_file);
|
|
|
|
s->st_stid.sc_file = NULL;
|
|
|
|
}
|
2014-07-30 19:27:11 +07:00
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
last = oo->oo_last_closed_stid;
|
2014-07-30 08:34:32 +07:00
|
|
|
oo->oo_last_closed_stid = s;
|
2012-11-14 22:22:01 +07:00
|
|
|
list_move_tail(&oo->oo_close_lru, &nn->close_lru);
|
2011-07-31 10:33:59 +07:00
|
|
|
oo->oo_time = get_seconds();
|
2014-07-30 19:27:11 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
if (last)
|
|
|
|
nfs4_put_stid(&last->st_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* search file_hashtbl[] for file */
|
|
|
|
static struct nfs4_file *
|
2014-10-23 19:01:02 +07:00
|
|
|
find_file_locked(struct knfsd_fh *fh, unsigned int hashval)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_file *fp;
|
|
|
|
|
2014-10-23 19:01:02 +07:00
|
|
|
hlist_for_each_entry_rcu(fp, &file_hashtbl[hashval], fi_hash) {
|
2014-08-14 13:41:48 +07:00
|
|
|
if (fh_match(&fp->fi_fhandle, fh)) {
|
2014-10-23 19:01:02 +07:00
|
|
|
if (atomic_inc_not_zero(&fp->fi_ref))
|
|
|
|
return fp;
|
2005-06-24 12:03:10 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-08-14 13:50:16 +07:00
|
|
|
struct nfs4_file *
|
2014-07-24 03:17:39 +07:00
|
|
|
find_file(struct knfsd_fh *fh)
|
2014-06-30 22:48:31 +07:00
|
|
|
{
|
|
|
|
struct nfs4_file *fp;
|
2014-10-23 19:01:02 +07:00
|
|
|
unsigned int hashval = file_hashval(fh);
|
2014-06-30 22:48:31 +07:00
|
|
|
|
2014-10-23 19:01:02 +07:00
|
|
|
rcu_read_lock();
|
|
|
|
fp = find_file_locked(fh, hashval);
|
|
|
|
rcu_read_unlock();
|
2014-06-30 22:48:31 +07:00
|
|
|
return fp;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfs4_file *
|
2014-07-24 03:17:41 +07:00
|
|
|
find_or_add_file(struct nfs4_file *new, struct knfsd_fh *fh)
|
2014-06-30 22:48:31 +07:00
|
|
|
{
|
|
|
|
struct nfs4_file *fp;
|
2014-10-23 19:01:02 +07:00
|
|
|
unsigned int hashval = file_hashval(fh);
|
|
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
fp = find_file_locked(fh, hashval);
|
|
|
|
rcu_read_unlock();
|
|
|
|
if (fp)
|
|
|
|
return fp;
|
2014-06-30 22:48:31 +07:00
|
|
|
|
|
|
|
spin_lock(&state_lock);
|
2014-10-23 19:01:02 +07:00
|
|
|
fp = find_file_locked(fh, hashval);
|
|
|
|
if (likely(fp == NULL)) {
|
|
|
|
nfsd4_init_file(fh, hashval, new);
|
2014-06-30 22:48:31 +07:00
|
|
|
fp = new;
|
|
|
|
}
|
|
|
|
spin_unlock(&state_lock);
|
|
|
|
|
|
|
|
return fp;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Called to check deny when READ with all zero stateid or
|
|
|
|
* WRITE with all zero or all one stateid
|
|
|
|
*/
|
2006-10-20 13:28:59 +07:00
|
|
|
static __be32
|
2005-04-17 05:20:36 +07:00
|
|
|
nfs4_share_conflict(struct svc_fh *current_fh, unsigned int deny_type)
|
|
|
|
{
|
|
|
|
struct nfs4_file *fp;
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
__be32 ret = nfs_ok;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-24 03:17:39 +07:00
|
|
|
fp = find_file(¤t_fh->fh_handle);
|
2005-06-24 12:03:10 +07:00
|
|
|
if (!fp)
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
return ret;
|
|
|
|
/* Check for conflicting share reservations */
|
2014-07-11 01:07:25 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
if (fp->fi_share_deny & deny_type)
|
|
|
|
ret = nfserr_locked;
|
2014-07-11 01:07:25 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
2005-06-24 12:03:10 +07:00
|
|
|
put_nfs4_file(fp);
|
|
|
|
return ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-09-24 17:19:19 +07:00
|
|
|
static void nfsd4_cb_recall_prepare(struct nfsd4_callback *cb)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-09-24 17:19:19 +07:00
|
|
|
struct nfs4_delegation *dp = cb_to_delegation(cb);
|
2014-07-30 08:34:08 +07:00
|
|
|
struct nfsd_net *nn = net_generic(dp->dl_stid.sc_client->net,
|
|
|
|
nfsd_net_id);
|
2013-03-22 02:19:33 +07:00
|
|
|
|
2014-07-30 08:34:08 +07:00
|
|
|
block_delegations(&dp->dl_stid.sc_file->fi_fhandle);
|
2014-07-25 18:34:26 +07:00
|
|
|
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
/*
|
2014-07-25 18:34:26 +07:00
|
|
|
* We can't do this in nfsd_break_deleg_cb because it is
|
|
|
|
* already holding inode->i_lock.
|
|
|
|
*
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
* If the dl_time != 0, then we know that it has already been
|
|
|
|
* queued for a lease break. Don't queue it again.
|
|
|
|
*/
|
2014-07-25 18:34:26 +07:00
|
|
|
spin_lock(&state_lock);
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
if (dp->dl_time == 0) {
|
|
|
|
dp->dl_time = get_seconds();
|
2014-07-16 21:31:57 +07:00
|
|
|
list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru);
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
}
|
2014-07-16 21:31:57 +07:00
|
|
|
spin_unlock(&state_lock);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-09-24 17:19:19 +07:00
|
|
|
static int nfsd4_cb_recall_done(struct nfsd4_callback *cb,
|
|
|
|
struct rpc_task *task)
|
|
|
|
{
|
|
|
|
struct nfs4_delegation *dp = cb_to_delegation(cb);
|
|
|
|
|
2015-08-31 23:06:41 +07:00
|
|
|
if (dp->dl_stid.sc_type == NFS4_CLOSED_DELEG_STID)
|
|
|
|
return 1;
|
|
|
|
|
2014-09-24 17:19:19 +07:00
|
|
|
switch (task->tk_status) {
|
|
|
|
case 0:
|
|
|
|
return 1;
|
|
|
|
case -EBADHANDLE:
|
|
|
|
case -NFS4ERR_BAD_STATEID:
|
|
|
|
/*
|
|
|
|
* Race: client probably got cb_recall before open reply
|
|
|
|
* granting delegation.
|
|
|
|
*/
|
|
|
|
if (dp->dl_retries--) {
|
|
|
|
rpc_delay(task, 2 * HZ);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*FALLTHRU*/
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nfsd4_cb_recall_release(struct nfsd4_callback *cb)
|
|
|
|
{
|
|
|
|
struct nfs4_delegation *dp = cb_to_delegation(cb);
|
|
|
|
|
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
|
|
|
}
|
|
|
|
|
2015-11-22 04:57:39 +07:00
|
|
|
static const struct nfsd4_callback_ops nfsd4_cb_recall_ops = {
|
2014-09-24 17:19:19 +07:00
|
|
|
.prepare = nfsd4_cb_recall_prepare,
|
|
|
|
.done = nfsd4_cb_recall_done,
|
|
|
|
.release = nfsd4_cb_recall_release,
|
|
|
|
};
|
|
|
|
|
2014-07-16 21:31:57 +07:00
|
|
|
static void nfsd_break_one_deleg(struct nfs4_delegation *dp)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We're assuming the state code never drops its reference
|
|
|
|
* without first removing the lease. Since we're in this lease
|
|
|
|
* callback (and since the lease code is serialized by the kernel
|
|
|
|
* lock) we know the server hasn't removed the lease yet, we know
|
|
|
|
* it's safe to take a reference.
|
|
|
|
*/
|
2014-07-21 20:34:58 +07:00
|
|
|
atomic_inc(&dp->dl_stid.sc_count);
|
2014-09-24 17:19:18 +07:00
|
|
|
nfsd4_run_cb(&dp->dl_recall);
|
2011-01-31 23:54:04 +07:00
|
|
|
}
|
|
|
|
|
2013-06-21 19:58:15 +07:00
|
|
|
/* Called from break_lease() with i_lock held. */
|
2014-09-02 02:06:54 +07:00
|
|
|
static bool
|
|
|
|
nfsd_break_deleg_cb(struct file_lock *fl)
|
2011-01-31 23:54:04 +07:00
|
|
|
{
|
2014-09-02 02:06:54 +07:00
|
|
|
bool ret = false;
|
2011-02-01 07:20:39 +07:00
|
|
|
struct nfs4_file *fp = (struct nfs4_file *)fl->fl_owner;
|
|
|
|
struct nfs4_delegation *dp;
|
2011-01-31 23:54:04 +07:00
|
|
|
|
2012-10-16 23:39:33 +07:00
|
|
|
if (!fp) {
|
|
|
|
WARN(1, "(%p)->fl_owner NULL\n", fl);
|
2014-09-02 02:06:54 +07:00
|
|
|
return ret;
|
2012-10-16 23:39:33 +07:00
|
|
|
}
|
|
|
|
if (fp->fi_had_conflict) {
|
|
|
|
WARN(1, "duplicate break on %p\n", fp);
|
2014-09-02 02:06:54 +07:00
|
|
|
return ret;
|
2012-10-16 23:39:33 +07:00
|
|
|
}
|
2007-09-13 05:56:12 +07:00
|
|
|
/*
|
|
|
|
* We don't want the locks code to timeout the lease for us;
|
2011-02-01 07:20:39 +07:00
|
|
|
* we'll remove it ourself if a delegation isn't returned
|
2011-01-31 23:54:04 +07:00
|
|
|
* in time:
|
2007-09-13 05:56:12 +07:00
|
|
|
*/
|
|
|
|
fl->fl_break_time = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-16 21:31:57 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
2014-07-21 20:34:57 +07:00
|
|
|
fp->fi_had_conflict = true;
|
|
|
|
/*
|
2014-09-02 02:06:54 +07:00
|
|
|
* If there are no delegations on the list, then return true
|
|
|
|
* so that the lease code will go ahead and delete it.
|
2014-07-21 20:34:57 +07:00
|
|
|
*/
|
|
|
|
if (list_empty(&fp->fi_delegations))
|
2014-09-02 02:06:54 +07:00
|
|
|
ret = true;
|
2014-07-21 20:34:57 +07:00
|
|
|
else
|
|
|
|
list_for_each_entry(dp, &fp->fi_delegations, dl_perfile)
|
|
|
|
nfsd_break_one_deleg(dp);
|
2014-07-16 21:31:57 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
2014-09-02 02:06:54 +07:00
|
|
|
return ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-09-01 18:12:07 +07:00
|
|
|
static int
|
2015-01-17 03:05:57 +07:00
|
|
|
nfsd_change_deleg_cb(struct file_lock *onlist, int arg,
|
|
|
|
struct list_head *dispose)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
if (arg & F_UNLCK)
|
2014-09-01 18:12:07 +07:00
|
|
|
return lease_modify(onlist, arg, dispose);
|
2005-04-17 05:20:36 +07:00
|
|
|
else
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
|
2009-09-22 07:01:12 +07:00
|
|
|
static const struct lock_manager_operations nfsd_lease_mng_ops = {
|
2011-07-21 07:21:59 +07:00
|
|
|
.lm_break = nfsd_break_deleg_cb,
|
|
|
|
.lm_change = nfsd_change_deleg_cb,
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
2011-09-02 20:03:37 +07:00
|
|
|
static __be32 nfsd4_check_seqid(struct nfsd4_compound_state *cstate, struct nfs4_stateowner *so, u32 seqid)
|
|
|
|
{
|
|
|
|
if (nfsd4_has_session(cstate))
|
|
|
|
return nfs_ok;
|
|
|
|
if (seqid == so->so_seqid - 1)
|
|
|
|
return nfserr_replay_me;
|
|
|
|
if (seqid == so->so_seqid)
|
|
|
|
return nfs_ok;
|
|
|
|
return nfserr_bad_seqid;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-06-30 22:48:44 +07:00
|
|
|
static __be32 lookup_clientid(clientid_t *clid,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd_net *nn)
|
|
|
|
{
|
|
|
|
struct nfs4_client *found;
|
|
|
|
|
|
|
|
if (cstate->clp) {
|
|
|
|
found = cstate->clp;
|
|
|
|
if (!same_clid(&found->cl_clientid, clid))
|
|
|
|
return nfserr_stale_clientid;
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (STALE_CLIENTID(clid, nn))
|
|
|
|
return nfserr_stale_clientid;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For v4.1+ we get the client in the SEQUENCE op. If we don't have one
|
|
|
|
* cached already then we know this is for is for v4.0 and "sessions"
|
|
|
|
* will be false.
|
|
|
|
*/
|
|
|
|
WARN_ON_ONCE(cstate->session);
|
2014-07-30 19:27:09 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2014-06-30 22:48:44 +07:00
|
|
|
found = find_confirmed_client(clid, false, nn);
|
2014-07-30 19:27:09 +07:00
|
|
|
if (!found) {
|
|
|
|
spin_unlock(&nn->client_lock);
|
2014-06-30 22:48:44 +07:00
|
|
|
return nfserr_expired;
|
2014-07-30 19:27:09 +07:00
|
|
|
}
|
|
|
|
atomic_inc(&found->cl_refcount);
|
|
|
|
spin_unlock(&nn->client_lock);
|
2014-06-30 22:48:44 +07:00
|
|
|
|
|
|
|
/* Cache the nfs4_client in cstate! */
|
|
|
|
cstate->clp = found;
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2009-04-03 12:28:45 +07:00
|
|
|
nfsd4_process_open1(struct nfsd4_compound_state *cstate,
|
2012-11-14 22:22:07 +07:00
|
|
|
struct nfsd4_open *open, struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
clientid_t *clientid = &open->op_clientid;
|
|
|
|
struct nfs4_client *clp = NULL;
|
|
|
|
unsigned int strhashval;
|
2011-07-31 10:33:59 +07:00
|
|
|
struct nfs4_openowner *oo = NULL;
|
2011-10-18 02:57:47 +07:00
|
|
|
__be32 status;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-07-25 19:57:45 +07:00
|
|
|
if (STALE_CLIENTID(&open->op_clientid, nn))
|
2005-04-17 05:20:36 +07:00
|
|
|
return nfserr_stale_clientid;
|
2011-10-14 03:00:16 +07:00
|
|
|
/*
|
|
|
|
* In case we need it later, after we've already created the
|
|
|
|
* file and don't want to risk a further failure:
|
|
|
|
*/
|
|
|
|
open->op_file = nfsd4_alloc_file();
|
|
|
|
if (open->op_file == NULL)
|
|
|
|
return nfserr_jukebox;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-06-30 22:48:46 +07:00
|
|
|
status = lookup_clientid(clientid, cstate, nn);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
clp = cstate->clp;
|
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
strhashval = ownerstr_hashval(&open->op_owner);
|
|
|
|
oo = find_openstateowner_str(strhashval, open, clp);
|
2011-07-31 10:33:59 +07:00
|
|
|
open->op_openowner = oo;
|
|
|
|
if (!oo) {
|
2011-10-13 07:44:20 +07:00
|
|
|
goto new_owner;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2011-09-12 23:24:13 +07:00
|
|
|
if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) {
|
2006-01-19 08:43:34 +07:00
|
|
|
/* Replace unconfirmed owners without checking for replay. */
|
2011-07-31 10:33:59 +07:00
|
|
|
release_openowner(oo);
|
|
|
|
open->op_openowner = NULL;
|
2011-10-13 07:44:20 +07:00
|
|
|
goto new_owner;
|
2006-01-19 08:43:34 +07:00
|
|
|
}
|
2011-10-18 02:57:47 +07:00
|
|
|
status = nfsd4_check_seqid(cstate, &oo->oo_owner, open->op_seqid);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
goto alloc_stateid;
|
2011-10-13 07:44:20 +07:00
|
|
|
new_owner:
|
2014-06-30 22:48:45 +07:00
|
|
|
oo = alloc_init_open_stateowner(strhashval, open, cstate);
|
2011-10-13 07:44:20 +07:00
|
|
|
if (oo == NULL)
|
|
|
|
return nfserr_jukebox;
|
|
|
|
open->op_openowner = oo;
|
2011-10-18 02:57:47 +07:00
|
|
|
alloc_stateid:
|
2014-07-30 08:34:11 +07:00
|
|
|
open->op_stp = nfs4_alloc_open_stateid(clp);
|
2011-10-18 02:57:47 +07:00
|
|
|
if (!open->op_stp)
|
|
|
|
return nfserr_jukebox;
|
2015-04-27 19:50:14 +07:00
|
|
|
|
|
|
|
if (nfsd4_has_session(cstate) &&
|
|
|
|
(cstate->current_fh.fh_export->ex_flags & NFSEXP_PNFS)) {
|
|
|
|
open->op_odstate = alloc_clnt_odstate(clp);
|
|
|
|
if (!open->op_odstate)
|
|
|
|
return nfserr_jukebox;
|
|
|
|
}
|
|
|
|
|
2006-01-19 08:43:34 +07:00
|
|
|
return nfs_ok;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
static inline __be32
|
2005-06-24 12:02:50 +07:00
|
|
|
nfs4_check_delegmode(struct nfs4_delegation *dp, int flags)
|
|
|
|
{
|
|
|
|
if ((flags & WR_STATE) && (dp->dl_type == NFS4_OPEN_DELEGATE_READ))
|
|
|
|
return nfserr_openmode;
|
|
|
|
else
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
2011-09-09 20:06:12 +07:00
|
|
|
static int share_access_to_flags(u32 share_access)
|
2005-06-24 12:02:49 +07:00
|
|
|
{
|
2011-09-09 20:06:12 +07:00
|
|
|
return share_access == NFS4_SHARE_ACCESS_READ ? RD_STATE : WR_STATE;
|
2005-06-24 12:02:49 +07:00
|
|
|
}
|
|
|
|
|
2011-09-24 04:01:19 +07:00
|
|
|
static struct nfs4_delegation *find_deleg_stateid(struct nfs4_client *cl, stateid_t *s)
|
2010-05-19 07:01:35 +07:00
|
|
|
{
|
2011-09-09 20:06:12 +07:00
|
|
|
struct nfs4_stid *ret;
|
2010-05-19 07:01:35 +07:00
|
|
|
|
2011-09-24 04:01:19 +07:00
|
|
|
ret = find_stateid_by_type(cl, s, NFS4_DELEG_STID);
|
2011-09-09 20:06:12 +07:00
|
|
|
if (!ret)
|
|
|
|
return NULL;
|
|
|
|
return delegstateid(ret);
|
2010-05-19 07:01:35 +07:00
|
|
|
}
|
|
|
|
|
2011-10-19 22:52:12 +07:00
|
|
|
static bool nfsd4_is_deleg_cur(struct nfsd4_open *open)
|
|
|
|
{
|
|
|
|
return open->op_claim_type == NFS4_OPEN_CLAIM_DELEGATE_CUR ||
|
|
|
|
open->op_claim_type == NFS4_OPEN_CLAIM_DELEG_CUR_FH;
|
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
static __be32
|
2013-03-22 02:49:47 +07:00
|
|
|
nfs4_check_deleg(struct nfs4_client *cl, struct nfsd4_open *open,
|
2005-06-24 12:02:53 +07:00
|
|
|
struct nfs4_delegation **dp)
|
|
|
|
{
|
|
|
|
int flags;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status = nfserr_bad_stateid;
|
2014-07-30 08:34:18 +07:00
|
|
|
struct nfs4_delegation *deleg;
|
2005-06-24 12:02:53 +07:00
|
|
|
|
2014-07-30 08:34:18 +07:00
|
|
|
deleg = find_deleg_stateid(cl, &open->op_delegate_stateid);
|
|
|
|
if (deleg == NULL)
|
2005-06-24 12:02:54 +07:00
|
|
|
goto out;
|
2010-05-19 07:01:35 +07:00
|
|
|
flags = share_access_to_flags(open->op_share_access);
|
2014-07-30 08:34:18 +07:00
|
|
|
status = nfs4_check_delegmode(deleg, flags);
|
|
|
|
if (status) {
|
|
|
|
nfs4_put_stid(&deleg->dl_stid);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
*dp = deleg;
|
2005-06-24 12:02:54 +07:00
|
|
|
out:
|
2011-10-19 22:52:12 +07:00
|
|
|
if (!nfsd4_is_deleg_cur(open))
|
2005-06-24 12:02:54 +07:00
|
|
|
return nfs_ok;
|
|
|
|
if (status)
|
|
|
|
return status;
|
2011-09-12 23:24:13 +07:00
|
|
|
open->op_openowner->oo_flags |= NFS4_OO_CONFIRMED;
|
2005-06-24 12:02:54 +07:00
|
|
|
return nfs_ok;
|
2005-06-24 12:02:53 +07:00
|
|
|
}
|
|
|
|
|
2010-07-28 23:21:23 +07:00
|
|
|
static inline int nfs4_access_to_access(u32 nfs4_access)
|
|
|
|
{
|
|
|
|
int flags = 0;
|
|
|
|
|
|
|
|
if (nfs4_access & NFS4_SHARE_ACCESS_READ)
|
|
|
|
flags |= NFSD_MAY_READ;
|
|
|
|
if (nfs4_access & NFS4_SHARE_ACCESS_WRITE)
|
|
|
|
flags |= NFSD_MAY_WRITE;
|
|
|
|
return flags;
|
|
|
|
}
|
|
|
|
|
2014-06-30 22:48:30 +07:00
|
|
|
static inline __be32
|
|
|
|
nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,
|
|
|
|
struct nfsd4_open *open)
|
|
|
|
{
|
|
|
|
struct iattr iattr = {
|
|
|
|
.ia_valid = ATTR_SIZE,
|
|
|
|
.ia_size = 0,
|
|
|
|
};
|
|
|
|
if (!open->op_truncate)
|
|
|
|
return 0;
|
|
|
|
if (!(open->op_share_access & NFS4_SHARE_ACCESS_WRITE))
|
|
|
|
return nfserr_inval;
|
|
|
|
return nfsd_setattr(rqstp, fh, &iattr, 0, (time_t)0);
|
|
|
|
}
|
|
|
|
|
2011-07-24 01:58:10 +07:00
|
|
|
static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
|
2014-07-11 01:07:31 +07:00
|
|
|
struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp,
|
|
|
|
struct nfsd4_open *open)
|
2010-07-08 22:02:09 +07:00
|
|
|
{
|
2014-07-11 01:07:26 +07:00
|
|
|
struct file *filp = NULL;
|
2010-07-08 22:02:09 +07:00
|
|
|
__be32 status;
|
2011-07-24 01:58:10 +07:00
|
|
|
int oflag = nfs4_access_to_omode(open->op_share_access);
|
|
|
|
int access = nfs4_access_to_access(open->op_share_access);
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
unsigned char old_access_bmap, old_deny_bmap;
|
2011-07-24 01:58:10 +07:00
|
|
|
|
2014-07-11 01:07:26 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Are we trying to set a deny mode that would conflict with
|
|
|
|
* current access?
|
|
|
|
*/
|
|
|
|
status = nfs4_file_check_deny(fp, open->op_share_deny);
|
|
|
|
if (status != nfs_ok) {
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set access to the file */
|
|
|
|
status = nfs4_file_get_access(fp, open->op_share_access);
|
|
|
|
if (status != nfs_ok) {
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set access bits in stateid */
|
|
|
|
old_access_bmap = stp->st_access_bmap;
|
|
|
|
set_access(open->op_share_access, stp);
|
|
|
|
|
|
|
|
/* Set new deny mask */
|
|
|
|
old_deny_bmap = stp->st_deny_bmap;
|
|
|
|
set_deny(open->op_share_deny, stp);
|
|
|
|
fp->fi_share_deny |= (open->op_share_deny & NFS4_SHARE_DENY_BOTH);
|
|
|
|
|
2010-07-08 22:02:09 +07:00
|
|
|
if (!fp->fi_fds[oflag]) {
|
2014-07-11 01:07:26 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
status = nfsd_open(rqstp, cur_fh, S_IFREG, access, &filp);
|
2010-07-08 22:02:09 +07:00
|
|
|
if (status)
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
goto out_put_access;
|
2014-07-11 01:07:26 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
if (!fp->fi_fds[oflag]) {
|
|
|
|
fp->fi_fds[oflag] = filp;
|
|
|
|
filp = NULL;
|
|
|
|
}
|
2010-07-08 22:02:09 +07:00
|
|
|
}
|
2014-07-11 01:07:26 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
if (filp)
|
|
|
|
fput(filp);
|
2010-07-08 22:02:09 +07:00
|
|
|
|
2014-06-30 22:48:30 +07:00
|
|
|
status = nfsd4_truncate(rqstp, cur_fh, open);
|
|
|
|
if (status)
|
|
|
|
goto out_put_access;
|
|
|
|
out:
|
|
|
|
return status;
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
out_put_access:
|
|
|
|
stp->st_access_bmap = old_access_bmap;
|
|
|
|
nfs4_file_put_access(fp, open->op_share_access);
|
|
|
|
reset_union_bmap_deny(bmap_to_share_mode(old_deny_bmap), stp);
|
|
|
|
goto out;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
static __be32
|
2011-09-08 03:06:42 +07:00
|
|
|
nfs4_upgrade_open(struct svc_rqst *rqstp, struct nfs4_file *fp, struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp, struct nfsd4_open *open)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2015-05-13 04:31:29 +07:00
|
|
|
unsigned char old_deny_bmap = stp->st_deny_bmap;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-11 01:07:31 +07:00
|
|
|
if (!test_access(open->op_share_access, stp))
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
return nfs4_get_vfs_file(rqstp, fp, cur_fh, stp, open);
|
2014-06-30 22:48:30 +07:00
|
|
|
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
/* test and set deny mode */
|
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
status = nfs4_file_check_deny(fp, open->op_share_deny);
|
|
|
|
if (status == nfs_ok) {
|
|
|
|
set_deny(open->op_share_deny, stp);
|
|
|
|
fp->fi_share_deny |=
|
|
|
|
(open->op_share_deny & NFS4_SHARE_DENY_BOTH);
|
|
|
|
}
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
|
|
|
|
if (status != nfs_ok)
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
|
nfsd: make deny mode enforcement more efficient and close races in it
The current enforcement of deny modes is both inefficient and scattered
across several places, which makes it hard to guarantee atomicity. The
inefficiency is a problem now, and the lack of atomicity will mean races
once the client_mutex is removed.
First, we address the inefficiency. We have to track deny modes on a
per-stateid basis to ensure that open downgrades are sane, but when the
server goes to enforce them it has to walk the entire list of stateids
and check against each one.
Instead of doing that, maintain a per-nfs4_file deny mode. When a file
is opened, we simply set any deny bits in that mode that were specified
in the OPEN call. We can then use that unified deny mode to do a simple
check to see whether there are any conflicts without needing to walk the
entire stateid list.
The only time we'll need to walk the entire list of stateids is when a
stateid that has a deny mode on it is being released, or one is having
its deny mode downgraded. In that case, we must walk the entire list and
recalculate the fi_share_deny field. Since deny modes are pretty rare
today, this should be very rare under normal workloads.
To address the potential for races once the client_mutex is removed,
protect fi_share_deny with the fi_lock. In nfs4_get_vfs_file, check to
make sure that any deny mode we want to apply won't conflict with
existing access. If that's ok, then have nfs4_file_get_access check that
new access to the file won't conflict with existing deny modes.
If that also passes, then get file access references, set the correct
access and deny bits in the stateid, and update the fi_share_deny field.
If opening the file or truncating it fails, then unwind the whole mess
and return the appropriate error.
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-11 01:07:34 +07:00
|
|
|
status = nfsd4_truncate(rqstp, cur_fh, open);
|
|
|
|
if (status != nfs_ok)
|
|
|
|
reset_union_bmap_deny(old_deny_bmap, stp);
|
|
|
|
return status;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2010-12-11 07:02:49 +07:00
|
|
|
/* Should we give out recallable state?: */
|
|
|
|
static bool nfsd4_cb_channel_good(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
if (clp->cl_cb_state == NFSD4_CB_UP)
|
|
|
|
return true;
|
|
|
|
/*
|
|
|
|
* In the sessions case, since we don't have to establish a
|
|
|
|
* separate connection for callbacks, we assume it's OK
|
|
|
|
* until we hear otherwise:
|
|
|
|
*/
|
|
|
|
return clp->cl_minorversion && clp->cl_cb_state == NFSD4_CB_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
2014-07-16 21:31:58 +07:00
|
|
|
static struct file_lock *nfs4_alloc_init_lease(struct nfs4_file *fp, int flag)
|
2011-01-31 23:55:12 +07:00
|
|
|
{
|
|
|
|
struct file_lock *fl;
|
|
|
|
|
|
|
|
fl = locks_alloc_lock();
|
|
|
|
if (!fl)
|
|
|
|
return NULL;
|
|
|
|
fl->fl_lmops = &nfsd_lease_mng_ops;
|
2011-07-02 02:18:34 +07:00
|
|
|
fl->fl_flags = FL_DELEG;
|
2011-01-31 23:55:12 +07:00
|
|
|
fl->fl_type = flag == NFS4_OPEN_DELEGATE_READ? F_RDLCK: F_WRLCK;
|
|
|
|
fl->fl_end = OFFSET_MAX;
|
2014-07-16 21:31:58 +07:00
|
|
|
fl->fl_owner = (fl_owner_t)fp;
|
2011-01-31 23:55:12 +07:00
|
|
|
fl->fl_pid = current->tgid;
|
|
|
|
return fl;
|
|
|
|
}
|
|
|
|
|
2015-10-15 23:07:28 +07:00
|
|
|
/**
|
|
|
|
* nfs4_setlease - Obtain a delegation by requesting lease from vfs layer
|
|
|
|
* @dp: a pointer to the nfs4_delegation we're adding.
|
|
|
|
*
|
|
|
|
* Return:
|
|
|
|
* On success: Return code will be 0 on success.
|
|
|
|
*
|
|
|
|
* On error: -EAGAIN if there was an existing delegation.
|
|
|
|
* nonzero if there is an error in other cases.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2013-05-22 03:21:25 +07:00
|
|
|
static int nfs4_setlease(struct nfs4_delegation *dp)
|
2011-02-01 05:58:10 +07:00
|
|
|
{
|
2014-07-30 08:34:08 +07:00
|
|
|
struct nfs4_file *fp = dp->dl_stid.sc_file;
|
2015-07-13 16:33:24 +07:00
|
|
|
struct file_lock *fl;
|
2014-07-21 20:34:57 +07:00
|
|
|
struct file *filp;
|
|
|
|
int status = 0;
|
2011-02-01 05:58:10 +07:00
|
|
|
|
2014-07-16 21:31:58 +07:00
|
|
|
fl = nfs4_alloc_init_lease(fp, NFS4_OPEN_DELEGATE_READ);
|
2011-02-01 05:58:10 +07:00
|
|
|
if (!fl)
|
|
|
|
return -ENOMEM;
|
2014-07-21 20:34:57 +07:00
|
|
|
filp = find_readable_file(fp);
|
|
|
|
if (!filp) {
|
|
|
|
/* We should always have a readable file here */
|
|
|
|
WARN_ON_ONCE(1);
|
2015-07-13 16:30:51 +07:00
|
|
|
locks_free_lock(fl);
|
2014-07-21 20:34:57 +07:00
|
|
|
return -EBADF;
|
|
|
|
}
|
|
|
|
fl->fl_file = filp;
|
2014-08-22 21:40:25 +07:00
|
|
|
status = vfs_setlease(filp, fl->fl_type, &fl, NULL);
|
2014-08-22 21:55:47 +07:00
|
|
|
if (fl)
|
2014-07-21 20:34:57 +07:00
|
|
|
locks_free_lock(fl);
|
2014-08-22 21:55:47 +07:00
|
|
|
if (status)
|
2014-07-21 20:34:57 +07:00
|
|
|
goto out_fput;
|
|
|
|
spin_lock(&state_lock);
|
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
/* Did the lease get broken before we took the lock? */
|
|
|
|
status = -EAGAIN;
|
|
|
|
if (fp->fi_had_conflict)
|
|
|
|
goto out_unlock;
|
|
|
|
/* Race breaker */
|
2014-08-22 23:05:43 +07:00
|
|
|
if (fp->fi_deleg_file) {
|
2015-10-15 23:07:28 +07:00
|
|
|
status = hash_delegation_locked(dp, fp);
|
2014-07-21 20:34:57 +07:00
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
fp->fi_deleg_file = filp;
|
2015-10-15 23:07:28 +07:00
|
|
|
fp->fi_delegees = 0;
|
|
|
|
status = hash_delegation_locked(dp, fp);
|
2014-07-21 20:34:57 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_unlock(&state_lock);
|
2015-10-15 23:07:28 +07:00
|
|
|
if (status) {
|
|
|
|
/* Should never happen, this is a new fi_deleg_file */
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
goto out_fput;
|
|
|
|
}
|
2011-02-01 07:20:39 +07:00
|
|
|
return 0;
|
2014-07-21 20:34:57 +07:00
|
|
|
out_unlock:
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
spin_unlock(&state_lock);
|
|
|
|
out_fput:
|
|
|
|
fput(filp);
|
2012-01-24 01:52:01 +07:00
|
|
|
return status;
|
2011-02-01 07:20:39 +07:00
|
|
|
}
|
|
|
|
|
2014-07-25 18:34:25 +07:00
|
|
|
static struct nfs4_delegation *
|
|
|
|
nfs4_set_delegation(struct nfs4_client *clp, struct svc_fh *fh,
|
2015-04-27 19:50:14 +07:00
|
|
|
struct nfs4_file *fp, struct nfs4_clnt_odstate *odstate)
|
2011-02-01 07:20:39 +07:00
|
|
|
{
|
2014-07-25 18:34:25 +07:00
|
|
|
int status;
|
|
|
|
struct nfs4_delegation *dp;
|
2014-07-21 20:34:57 +07:00
|
|
|
|
2013-08-16 03:55:26 +07:00
|
|
|
if (fp->fi_had_conflict)
|
2014-07-25 18:34:25 +07:00
|
|
|
return ERR_PTR(-EAGAIN);
|
|
|
|
|
2015-10-15 23:07:28 +07:00
|
|
|
spin_lock(&state_lock);
|
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
status = nfs4_get_existing_delegation(clp, fp);
|
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
spin_unlock(&state_lock);
|
|
|
|
|
|
|
|
if (status)
|
|
|
|
return ERR_PTR(status);
|
|
|
|
|
2015-04-27 19:50:14 +07:00
|
|
|
dp = alloc_init_deleg(clp, fh, odstate);
|
2014-07-25 18:34:25 +07:00
|
|
|
if (!dp)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
2013-08-16 03:55:26 +07:00
|
|
|
get_nfs4_file(fp);
|
2014-07-21 20:34:57 +07:00
|
|
|
spin_lock(&state_lock);
|
|
|
|
spin_lock(&fp->fi_lock);
|
2014-07-30 08:34:08 +07:00
|
|
|
dp->dl_stid.sc_file = fp;
|
2014-08-22 23:05:43 +07:00
|
|
|
if (!fp->fi_deleg_file) {
|
2014-07-21 20:34:57 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
|
|
|
spin_unlock(&state_lock);
|
2014-07-25 18:34:25 +07:00
|
|
|
status = nfs4_setlease(dp);
|
|
|
|
goto out;
|
2014-07-21 20:34:57 +07:00
|
|
|
}
|
2011-02-01 07:20:39 +07:00
|
|
|
if (fp->fi_had_conflict) {
|
2014-07-21 20:34:57 +07:00
|
|
|
status = -EAGAIN;
|
|
|
|
goto out_unlock;
|
2011-02-01 07:20:39 +07:00
|
|
|
}
|
2015-10-15 23:07:28 +07:00
|
|
|
status = hash_delegation_locked(dp, fp);
|
2014-07-21 20:34:57 +07:00
|
|
|
out_unlock:
|
|
|
|
spin_unlock(&fp->fi_lock);
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_unlock(&state_lock);
|
2014-07-25 18:34:25 +07:00
|
|
|
out:
|
|
|
|
if (status) {
|
2015-04-27 19:50:14 +07:00
|
|
|
put_clnt_odstate(dp->dl_clnt_odstate);
|
2014-07-30 08:34:06 +07:00
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
2014-07-25 18:34:25 +07:00
|
|
|
return ERR_PTR(status);
|
|
|
|
}
|
|
|
|
return dp;
|
2011-02-01 05:58:10 +07:00
|
|
|
}
|
|
|
|
|
2012-02-22 05:16:44 +07:00
|
|
|
static void nfsd4_open_deleg_none_ext(struct nfsd4_open *open, int status)
|
|
|
|
{
|
|
|
|
open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE_EXT;
|
|
|
|
if (status == -EAGAIN)
|
|
|
|
open->op_why_no_deleg = WND4_CONTENTION;
|
|
|
|
else {
|
|
|
|
open->op_why_no_deleg = WND4_RESOURCE;
|
|
|
|
switch (open->op_deleg_want) {
|
|
|
|
case NFS4_SHARE_WANT_READ_DELEG:
|
|
|
|
case NFS4_SHARE_WANT_WRITE_DELEG:
|
|
|
|
case NFS4_SHARE_WANT_ANY_DELEG:
|
|
|
|
break;
|
|
|
|
case NFS4_SHARE_WANT_CANCEL:
|
|
|
|
open->op_why_no_deleg = WND4_CANCELLED;
|
|
|
|
break;
|
|
|
|
case NFS4_SHARE_WANT_NO_DELEG:
|
2012-11-26 02:48:10 +07:00
|
|
|
WARN_ON_ONCE(1);
|
2012-02-22 05:16:44 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Attempt to hand out a delegation.
|
2013-05-22 03:21:25 +07:00
|
|
|
*
|
|
|
|
* Note we don't support write delegations, and won't until the vfs has
|
|
|
|
* proper support for them.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
static void
|
2014-07-25 18:34:24 +07:00
|
|
|
nfs4_open_delegation(struct svc_fh *fh, struct nfsd4_open *open,
|
|
|
|
struct nfs4_ol_stateid *stp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_delegation *dp;
|
2014-07-25 18:34:24 +07:00
|
|
|
struct nfs4_openowner *oo = openowner(stp->st_stateowner);
|
|
|
|
struct nfs4_client *clp = stp->st_stid.sc_client;
|
2010-12-11 07:02:49 +07:00
|
|
|
int cb_up;
|
2013-05-22 03:21:25 +07:00
|
|
|
int status = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
cb_up = nfsd4_cb_channel_good(oo->oo_owner.so_client);
|
2005-06-24 12:03:23 +07:00
|
|
|
open->op_recall = 0;
|
|
|
|
switch (open->op_claim_type) {
|
|
|
|
case NFS4_OPEN_CLAIM_PREVIOUS:
|
2010-03-09 00:37:27 +07:00
|
|
|
if (!cb_up)
|
2005-06-24 12:03:23 +07:00
|
|
|
open->op_recall = 1;
|
2013-05-22 03:21:25 +07:00
|
|
|
if (open->op_delegate_type != NFS4_OPEN_DELEGATE_READ)
|
|
|
|
goto out_no_deleg;
|
2005-06-24 12:03:23 +07:00
|
|
|
break;
|
|
|
|
case NFS4_OPEN_CLAIM_NULL:
|
2014-01-10 04:26:10 +07:00
|
|
|
case NFS4_OPEN_CLAIM_FH:
|
2013-05-22 03:21:25 +07:00
|
|
|
/*
|
|
|
|
* Let's not give out any delegations till everyone's
|
2015-08-06 23:47:02 +07:00
|
|
|
* had the chance to reclaim theirs, *and* until
|
|
|
|
* NLM locks have all been reclaimed:
|
2013-05-22 03:21:25 +07:00
|
|
|
*/
|
2014-07-25 18:34:24 +07:00
|
|
|
if (locks_in_grace(clp->net))
|
2013-05-22 03:21:25 +07:00
|
|
|
goto out_no_deleg;
|
2011-09-12 23:24:13 +07:00
|
|
|
if (!cb_up || !(oo->oo_flags & NFS4_OO_CONFIRMED))
|
2013-05-22 03:21:25 +07:00
|
|
|
goto out_no_deleg;
|
2013-05-16 01:51:49 +07:00
|
|
|
/*
|
|
|
|
* Also, if the file was opened for write or
|
|
|
|
* create, there's a good chance the client's
|
|
|
|
* about to write to it, resulting in an
|
|
|
|
* immediate recall (since we don't support
|
|
|
|
* write delegations):
|
|
|
|
*/
|
2005-06-24 12:03:23 +07:00
|
|
|
if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE)
|
2013-05-22 03:21:25 +07:00
|
|
|
goto out_no_deleg;
|
|
|
|
if (open->op_create == NFS4_OPEN_CREATE)
|
|
|
|
goto out_no_deleg;
|
2005-06-24 12:03:23 +07:00
|
|
|
break;
|
|
|
|
default:
|
2013-05-22 03:21:25 +07:00
|
|
|
goto out_no_deleg;
|
2005-06-24 12:03:23 +07:00
|
|
|
}
|
2015-04-27 19:50:14 +07:00
|
|
|
dp = nfs4_set_delegation(clp, fh, stp->st_stid.sc_file, stp->st_clnt_odstate);
|
2014-07-25 18:34:25 +07:00
|
|
|
if (IS_ERR(dp))
|
2011-02-01 05:14:55 +07:00
|
|
|
goto out_no_deleg;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-09-08 23:07:44 +07:00
|
|
|
memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid));
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-10-19 17:04:53 +07:00
|
|
|
dprintk("NFSD: delegation stateid=" STATEID_FMT "\n",
|
2011-09-08 23:07:44 +07:00
|
|
|
STATEID_VAL(&dp->dl_stid.sc_stateid));
|
2013-05-22 03:21:25 +07:00
|
|
|
open->op_delegate_type = NFS4_OPEN_DELEGATE_READ;
|
2014-07-30 08:34:17 +07:00
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
2011-02-01 05:14:55 +07:00
|
|
|
return;
|
|
|
|
out_no_deleg:
|
2013-05-22 03:21:25 +07:00
|
|
|
open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE;
|
|
|
|
if (open->op_claim_type == NFS4_OPEN_CLAIM_PREVIOUS &&
|
2013-06-21 22:05:32 +07:00
|
|
|
open->op_delegate_type != NFS4_OPEN_DELEGATE_NONE) {
|
2013-05-22 03:21:25 +07:00
|
|
|
dprintk("NFSD: WARNING: refusing delegation reclaim\n");
|
2013-06-21 22:05:32 +07:00
|
|
|
open->op_recall = 1;
|
|
|
|
}
|
2013-05-22 03:21:25 +07:00
|
|
|
|
|
|
|
/* 4.1 client asking for a delegation? */
|
|
|
|
if (open->op_deleg_want)
|
|
|
|
nfsd4_open_deleg_none_ext(open, status);
|
|
|
|
return;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2012-02-22 05:16:54 +07:00
|
|
|
static void nfsd4_deleg_xgrade_none_ext(struct nfsd4_open *open,
|
|
|
|
struct nfs4_delegation *dp)
|
|
|
|
{
|
|
|
|
if (open->op_deleg_want == NFS4_SHARE_WANT_READ_DELEG &&
|
|
|
|
dp->dl_type == NFS4_OPEN_DELEGATE_WRITE) {
|
|
|
|
open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE_EXT;
|
|
|
|
open->op_why_no_deleg = WND4_NOT_SUPP_DOWNGRADE;
|
|
|
|
} else if (open->op_deleg_want == NFS4_SHARE_WANT_WRITE_DELEG &&
|
|
|
|
dp->dl_type == NFS4_OPEN_DELEGATE_WRITE) {
|
|
|
|
open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE_EXT;
|
|
|
|
open->op_why_no_deleg = WND4_NOT_SUPP_UPGRADE;
|
|
|
|
}
|
|
|
|
/* Otherwise the client must be confused wanting a delegation
|
|
|
|
* it already has, therefore we don't return
|
|
|
|
* NFS4_OPEN_DELEGATE_NONE_EXT and reason.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2005-04-17 05:20:36 +07:00
|
|
|
nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_open *open)
|
|
|
|
{
|
2009-04-03 12:28:45 +07:00
|
|
|
struct nfsd4_compoundres *resp = rqstp->rq_resp;
|
2011-09-24 04:01:19 +07:00
|
|
|
struct nfs4_client *cl = open->op_openowner->oo_owner.so_client;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct nfs4_file *fp = NULL;
|
2011-09-08 03:06:42 +07:00
|
|
|
struct nfs4_ol_stateid *stp = NULL;
|
2005-06-24 12:02:53 +07:00
|
|
|
struct nfs4_delegation *dp = NULL;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Lookup file; if found, lookup stateid and check open request,
|
|
|
|
* and check for delegations in the process of being recalled.
|
|
|
|
* If not found, create the nfs4_file struct
|
|
|
|
*/
|
2014-07-24 03:17:41 +07:00
|
|
|
fp = find_or_add_file(open->op_file, ¤t_fh->fh_handle);
|
2014-06-30 22:48:31 +07:00
|
|
|
if (fp != open->op_file) {
|
2013-03-22 02:49:47 +07:00
|
|
|
status = nfs4_check_deleg(cl, open, &dp);
|
2005-06-24 12:02:54 +07:00
|
|
|
if (status)
|
|
|
|
goto out;
|
2015-11-06 08:42:43 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
2014-07-11 01:07:35 +07:00
|
|
|
stp = nfsd4_find_existing_open(fp, open);
|
2015-11-06 08:42:43 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
2014-06-30 22:48:31 +07:00
|
|
|
open->op_file = NULL;
|
2005-06-24 12:02:54 +07:00
|
|
|
status = nfserr_bad_stateid;
|
2011-10-19 22:52:12 +07:00
|
|
|
if (nfsd4_is_deleg_cur(open))
|
2005-06-24 12:02:54 +07:00
|
|
|
goto out;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* OPEN the file, or upgrade an existing OPEN.
|
|
|
|
* If truncate fails, the OPEN fails.
|
|
|
|
*/
|
|
|
|
if (stp) {
|
|
|
|
/* Stateid was found, this is an OPEN upgrade */
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_lock(&stp->st_mutex);
|
2010-07-08 22:02:09 +07:00
|
|
|
status = nfs4_upgrade_open(rqstp, fp, current_fh, stp, open);
|
2015-09-17 18:47:08 +07:00
|
|
|
if (status) {
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
2015-09-17 18:47:08 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
2016-06-15 10:28:06 +07:00
|
|
|
/* stp is returned locked. */
|
|
|
|
stp = init_open_stateid(fp, open);
|
|
|
|
/* See if we lost the race to some other thread */
|
|
|
|
if (stp->st_access_bmap != 0) {
|
2015-11-06 08:42:43 +07:00
|
|
|
status = nfs4_upgrade_open(rqstp, fp, current_fh,
|
|
|
|
stp, open);
|
|
|
|
if (status) {
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2015-11-06 08:42:43 +07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
goto upgrade_out;
|
|
|
|
}
|
2014-07-11 01:07:31 +07:00
|
|
|
status = nfs4_get_vfs_file(rqstp, fp, current_fh, stp, open);
|
|
|
|
if (status) {
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2014-07-11 01:07:31 +07:00
|
|
|
release_open_stateid(stp);
|
|
|
|
goto out;
|
|
|
|
}
|
2015-04-27 19:50:14 +07:00
|
|
|
|
|
|
|
stp->st_clnt_odstate = find_or_hash_clnt_odstate(fp,
|
|
|
|
open->op_odstate);
|
|
|
|
if (stp->st_clnt_odstate == open->op_odstate)
|
|
|
|
open->op_odstate = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2015-11-06 08:42:43 +07:00
|
|
|
upgrade_out:
|
2015-10-01 20:05:50 +07:00
|
|
|
nfs4_inc_and_copy_stateid(&open->op_stateid, &stp->st_stid);
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-02-17 01:57:17 +07:00
|
|
|
if (nfsd4_has_session(&resp->cstate)) {
|
|
|
|
if (open->op_deleg_want & NFS4_SHARE_WANT_NO_DELEG) {
|
|
|
|
open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE_EXT;
|
|
|
|
open->op_why_no_deleg = WND4_NOT_WANTED;
|
|
|
|
goto nodeleg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Attempt to hand out a delegation. No error return, because the
|
|
|
|
* OPEN succeeds even if we fail.
|
|
|
|
*/
|
2014-07-25 18:34:24 +07:00
|
|
|
nfs4_open_delegation(current_fh, open, stp);
|
2012-02-17 01:57:17 +07:00
|
|
|
nodeleg:
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfs_ok;
|
|
|
|
|
2009-10-19 17:04:53 +07:00
|
|
|
dprintk("%s: stateid=" STATEID_FMT "\n", __func__,
|
2011-09-08 03:06:42 +07:00
|
|
|
STATEID_VAL(&stp->st_stid.sc_stateid));
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
2012-02-17 01:57:17 +07:00
|
|
|
/* 4.1 client trying to upgrade/downgrade delegation? */
|
|
|
|
if (open->op_delegate_type == NFS4_OPEN_DELEGATE_NONE && dp &&
|
2012-02-22 05:16:54 +07:00
|
|
|
open->op_deleg_want)
|
|
|
|
nfsd4_deleg_xgrade_none_ext(open, dp);
|
2012-02-17 01:57:17 +07:00
|
|
|
|
2005-06-24 12:03:10 +07:00
|
|
|
if (fp)
|
|
|
|
put_nfs4_file(fp);
|
2005-07-08 07:59:16 +07:00
|
|
|
if (status == 0 && open->op_claim_type == NFS4_OPEN_CLAIM_PREVIOUS)
|
2015-07-13 16:32:59 +07:00
|
|
|
open->op_openowner->oo_flags |= NFS4_OO_CONFIRMED;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* To finish the open response, we just need to set the rflags.
|
|
|
|
*/
|
|
|
|
open->op_rflags = NFS4_OPEN_RESULT_LOCKTYPE_POSIX;
|
2016-09-17 03:28:27 +07:00
|
|
|
if (nfsd4_has_session(&resp->cstate))
|
|
|
|
open->op_rflags |= NFS4_OPEN_RESULT_MAY_NOTIFY_LOCK;
|
|
|
|
else if (!(open->op_openowner->oo_flags & NFS4_OO_CONFIRMED))
|
2005-04-17 05:20:36 +07:00
|
|
|
open->op_rflags |= NFS4_OPEN_RESULT_CONFIRM;
|
2016-09-17 03:28:27 +07:00
|
|
|
|
2014-07-30 08:34:18 +07:00
|
|
|
if (dp)
|
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
2014-07-30 08:34:19 +07:00
|
|
|
if (stp)
|
|
|
|
nfs4_put_stid(&stp->st_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:27 +07:00
|
|
|
void nfsd4_cleanup_open_state(struct nfsd4_compound_state *cstate,
|
2015-03-23 21:53:44 +07:00
|
|
|
struct nfsd4_open *open)
|
2011-10-14 02:12:59 +07:00
|
|
|
{
|
|
|
|
if (open->op_openowner) {
|
2014-07-30 08:34:32 +07:00
|
|
|
struct nfs4_stateowner *so = &open->op_openowner->oo_owner;
|
|
|
|
|
|
|
|
nfsd4_cstate_assign_replay(cstate, so);
|
|
|
|
nfs4_put_stateowner(so);
|
2011-10-14 02:12:59 +07:00
|
|
|
}
|
2011-10-14 03:00:16 +07:00
|
|
|
if (open->op_file)
|
2014-10-23 19:01:02 +07:00
|
|
|
kmem_cache_free(file_slab, open->op_file);
|
2011-10-18 02:57:47 +07:00
|
|
|
if (open->op_stp)
|
2014-07-30 08:34:06 +07:00
|
|
|
nfs4_put_stid(&open->op_stp->st_stid);
|
2015-04-27 19:50:14 +07:00
|
|
|
if (open->op_odstate)
|
|
|
|
kmem_cache_free(odstate_slab, open->op_odstate);
|
2011-10-14 02:12:59 +07:00
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:38 +07:00
|
|
|
nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
|
|
|
clientid_t *clid)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2012-11-14 22:21:05 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
dprintk("process_renew(%08x/%08x): starting\n",
|
|
|
|
clid->cl_boot, clid->cl_id);
|
2014-06-30 22:48:44 +07:00
|
|
|
status = lookup_clientid(clid, cstate, nn);
|
2012-12-04 05:24:41 +07:00
|
|
|
if (status)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
2014-06-30 22:48:44 +07:00
|
|
|
clp = cstate->clp;
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfserr_cb_path_down;
|
2005-06-24 12:04:17 +07:00
|
|
|
if (!list_empty(&clp->cl_delegations)
|
2010-05-01 05:51:44 +07:00
|
|
|
&& clp->cl_cb_state != NFSD4_CB_UP)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
|
|
|
status = nfs_ok;
|
|
|
|
out:
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2014-09-13 03:40:21 +07:00
|
|
|
void
|
2012-11-14 22:22:12 +07:00
|
|
|
nfsd4_end_grace(struct nfsd_net *nn)
|
2005-06-24 12:04:01 +07:00
|
|
|
{
|
2012-04-10 22:08:48 +07:00
|
|
|
/* do nothing if grace period already ended */
|
2012-07-25 19:57:37 +07:00
|
|
|
if (nn->grace_ended)
|
2012-04-10 22:08:48 +07:00
|
|
|
return;
|
|
|
|
|
2005-06-24 12:04:01 +07:00
|
|
|
dprintk("NFSD: end of grace period\n");
|
2012-07-25 19:57:37 +07:00
|
|
|
nn->grace_ended = true;
|
2014-09-17 04:37:32 +07:00
|
|
|
/*
|
|
|
|
* If the server goes down again right now, an NFSv4
|
|
|
|
* client will still be allowed to reclaim after it comes back up,
|
|
|
|
* even if it hasn't yet had a chance to reclaim state this time.
|
|
|
|
*
|
|
|
|
*/
|
2014-09-13 03:40:20 +07:00
|
|
|
nfsd4_record_grace_done(nn);
|
2014-09-17 04:37:32 +07:00
|
|
|
/*
|
|
|
|
* At this point, NFSv4 clients can still reclaim. But if the
|
|
|
|
* server crashes, any that have not yet reclaimed will be out
|
|
|
|
* of luck on the next boot.
|
|
|
|
*
|
|
|
|
* (NFSv4.1+ clients are considered to have reclaimed once they
|
|
|
|
* call RECLAIM_COMPLETE. NFSv4.0 clients are considered to
|
|
|
|
* have reclaimed after their first OPEN.)
|
|
|
|
*/
|
2012-07-25 19:56:58 +07:00
|
|
|
locks_end_grace(&nn->nfsd4_manager);
|
2014-09-17 04:37:32 +07:00
|
|
|
/*
|
|
|
|
* At this point, and once lockd and/or any other containers
|
|
|
|
* exit their grace period, further reclaims will fail and
|
|
|
|
* regular locking can resume.
|
|
|
|
*/
|
2005-06-24 12:04:01 +07:00
|
|
|
}
|
|
|
|
|
2005-06-24 12:04:03 +07:00
|
|
|
static time_t
|
2012-11-14 22:22:17 +07:00
|
|
|
nfs4_laundromat(struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
2011-07-31 10:33:59 +07:00
|
|
|
struct nfs4_openowner *oo;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct nfs4_delegation *dp;
|
2014-07-30 19:27:11 +07:00
|
|
|
struct nfs4_ol_stateid *stp;
|
2016-09-17 03:28:25 +07:00
|
|
|
struct nfsd4_blocked_lock *nbl;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct list_head *pos, *next, reaplist;
|
2012-11-27 18:11:44 +07:00
|
|
|
time_t cutoff = get_seconds() - nn->nfsd4_lease;
|
2014-05-30 20:09:26 +07:00
|
|
|
time_t t, new_timeo = nn->nfsd4_lease;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
dprintk("NFSD: laundromat service - starting\n");
|
2012-11-14 22:22:12 +07:00
|
|
|
nfsd4_end_grace(nn);
|
2010-05-12 04:13:04 +07:00
|
|
|
INIT_LIST_HEAD(&reaplist);
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
2012-11-14 22:21:56 +07:00
|
|
|
list_for_each_safe(pos, next, &nn->client_lru) {
|
2005-04-17 05:20:36 +07:00
|
|
|
clp = list_entry(pos, struct nfs4_client, cl_lru);
|
|
|
|
if (time_after((unsigned long)clp->cl_time, (unsigned long)cutoff)) {
|
|
|
|
t = clp->cl_time - cutoff;
|
2014-05-30 20:09:26 +07:00
|
|
|
new_timeo = min(new_timeo, t);
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
}
|
2013-04-02 09:23:49 +07:00
|
|
|
if (mark_client_expired_locked(clp)) {
|
2010-05-12 04:13:54 +07:00
|
|
|
dprintk("NFSD: client in use (clientid %08x)\n",
|
|
|
|
clp->cl_clientid.cl_id);
|
|
|
|
continue;
|
|
|
|
}
|
2014-07-30 19:27:03 +07:00
|
|
|
list_add(&clp->cl_lru, &reaplist);
|
2010-05-12 04:13:04 +07:00
|
|
|
}
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
2010-05-12 04:13:04 +07:00
|
|
|
list_for_each_safe(pos, next, &reaplist) {
|
|
|
|
clp = list_entry(pos, struct nfs4_client, cl_lru);
|
2005-04-17 05:20:36 +07:00
|
|
|
dprintk("NFSD: purging unused client (clientid %08x)\n",
|
|
|
|
clp->cl_clientid.cl_id);
|
2014-07-30 19:27:03 +07:00
|
|
|
list_del_init(&clp->cl_lru);
|
2005-04-17 05:20:36 +07:00
|
|
|
expire_client(clp);
|
|
|
|
}
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_lock(&state_lock);
|
2013-03-22 02:19:33 +07:00
|
|
|
list_for_each_safe(pos, next, &nn->del_recall_lru) {
|
2005-04-17 05:20:36 +07:00
|
|
|
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
|
|
|
|
if (time_after((unsigned long)dp->dl_time, (unsigned long)cutoff)) {
|
2014-05-30 20:09:26 +07:00
|
|
|
t = dp->dl_time - cutoff;
|
|
|
|
new_timeo = min(new_timeo, t);
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
}
|
2015-08-24 23:41:48 +07:00
|
|
|
WARN_ON(!unhash_delegation_locked(dp));
|
2014-07-25 18:34:20 +07:00
|
|
|
list_add(&dp->dl_recall_lru, &reaplist);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_unlock(&state_lock);
|
2014-07-25 18:34:21 +07:00
|
|
|
while (!list_empty(&reaplist)) {
|
|
|
|
dp = list_first_entry(&reaplist, struct nfs4_delegation,
|
|
|
|
dl_recall_lru);
|
|
|
|
list_del_init(&dp->dl_recall_lru);
|
2013-04-10 04:02:51 +07:00
|
|
|
revoke_delegation(dp);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-07-30 19:27:11 +07:00
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
while (!list_empty(&nn->close_lru)) {
|
|
|
|
oo = list_first_entry(&nn->close_lru, struct nfs4_openowner,
|
|
|
|
oo_close_lru);
|
|
|
|
if (time_after((unsigned long)oo->oo_time,
|
|
|
|
(unsigned long)cutoff)) {
|
2014-05-30 20:09:26 +07:00
|
|
|
t = oo->oo_time - cutoff;
|
|
|
|
new_timeo = min(new_timeo, t);
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
}
|
2014-07-30 19:27:11 +07:00
|
|
|
list_del_init(&oo->oo_close_lru);
|
|
|
|
stp = oo->oo_last_closed_stid;
|
|
|
|
oo->oo_last_closed_stid = NULL;
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
nfs4_put_stid(&stp->st_stid);
|
|
|
|
spin_lock(&nn->client_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-07-30 19:27:11 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
2016-09-17 03:28:25 +07:00
|
|
|
/*
|
|
|
|
* It's possible for a client to try and acquire an already held lock
|
|
|
|
* that is being held for a long time, and then lose interest in it.
|
|
|
|
* So, we clean out any un-revisited request after a lease period
|
|
|
|
* under the assumption that the client is no longer interested.
|
|
|
|
*
|
|
|
|
* RFC5661, sec. 9.6 states that the client must not rely on getting
|
|
|
|
* notifications and must continue to poll for locks, even when the
|
|
|
|
* server supports them. Thus this shouldn't lead to clients blocking
|
|
|
|
* indefinitely once the lock does become free.
|
|
|
|
*/
|
|
|
|
BUG_ON(!list_empty(&reaplist));
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
while (!list_empty(&nn->blocked_locks_lru)) {
|
|
|
|
nbl = list_first_entry(&nn->blocked_locks_lru,
|
|
|
|
struct nfsd4_blocked_lock, nbl_lru);
|
|
|
|
if (time_after((unsigned long)nbl->nbl_time,
|
|
|
|
(unsigned long)cutoff)) {
|
|
|
|
t = nbl->nbl_time - cutoff;
|
|
|
|
new_timeo = min(new_timeo, t);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
list_move(&nbl->nbl_lru, &reaplist);
|
|
|
|
list_del_init(&nbl->nbl_list);
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
while (!list_empty(&reaplist)) {
|
|
|
|
nbl = list_first_entry(&nn->blocked_locks_lru,
|
|
|
|
struct nfsd4_blocked_lock, nbl_lru);
|
|
|
|
list_del_init(&nbl->nbl_lru);
|
|
|
|
posix_unblock_lock(&nbl->nbl_lock);
|
|
|
|
free_blocked_lock(nbl);
|
|
|
|
}
|
|
|
|
|
2014-05-30 20:09:26 +07:00
|
|
|
new_timeo = max_t(time_t, new_timeo, NFSD_LAUNDROMAT_MINTIMEOUT);
|
|
|
|
return new_timeo;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2008-02-21 03:49:00 +07:00
|
|
|
static struct workqueue_struct *laundry_wq;
|
|
|
|
static void laundromat_main(struct work_struct *);
|
|
|
|
|
|
|
|
static void
|
2012-11-14 22:22:17 +07:00
|
|
|
laundromat_main(struct work_struct *laundry)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
time_t t;
|
2016-01-01 21:06:28 +07:00
|
|
|
struct delayed_work *dwork = to_delayed_work(laundry);
|
2012-11-14 22:22:17 +07:00
|
|
|
struct nfsd_net *nn = container_of(dwork, struct nfsd_net,
|
|
|
|
laundromat_work);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-11-14 22:22:17 +07:00
|
|
|
t = nfs4_laundromat(nn);
|
2005-04-17 05:20:36 +07:00
|
|
|
dprintk("NFSD: laundromat_main - sleeping for %ld seconds\n", t);
|
2012-11-14 22:22:17 +07:00
|
|
|
queue_delayed_work(laundry_wq, &nn->laundromat_work, t*HZ);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2015-07-30 17:57:46 +07:00
|
|
|
static inline __be32 nfs4_check_fh(struct svc_fh *fhp, struct nfs4_stid *stp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2015-07-30 17:57:46 +07:00
|
|
|
if (!fh_match(&fhp->fh_handle, &stp->sc_file->fi_fhandle))
|
2011-09-17 07:12:38 +07:00
|
|
|
return nfserr_bad_stateid;
|
|
|
|
return nfs_ok;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
2012-05-11 20:45:13 +07:00
|
|
|
access_permit_read(struct nfs4_ol_stateid *stp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-05-11 20:45:13 +07:00
|
|
|
return test_access(NFS4_SHARE_ACCESS_READ, stp) ||
|
|
|
|
test_access(NFS4_SHARE_ACCESS_BOTH, stp) ||
|
|
|
|
test_access(NFS4_SHARE_ACCESS_WRITE, stp);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
2012-05-11 20:45:13 +07:00
|
|
|
access_permit_write(struct nfs4_ol_stateid *stp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-05-11 20:45:13 +07:00
|
|
|
return test_access(NFS4_SHARE_ACCESS_WRITE, stp) ||
|
|
|
|
test_access(NFS4_SHARE_ACCESS_BOTH, stp);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static
|
2011-09-08 03:06:42 +07:00
|
|
|
__be32 nfs4_check_openmode(struct nfs4_ol_stateid *stp, int flags)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status = nfserr_openmode;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2010-07-30 02:16:59 +07:00
|
|
|
/* For lock stateid's, we test the parent open, not the lock: */
|
|
|
|
if (stp->st_openstp)
|
|
|
|
stp = stp->st_openstp;
|
2012-05-11 20:45:13 +07:00
|
|
|
if ((flags & WR_STATE) && !access_permit_write(stp))
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
2012-05-11 20:45:13 +07:00
|
|
|
if ((flags & RD_STATE) && !access_permit_read(stp))
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
|
|
|
status = nfs_ok;
|
|
|
|
out:
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
static inline __be32
|
2012-07-25 19:57:22 +07:00
|
|
|
check_special_stateids(struct net *net, svc_fh *current_fh, stateid_t *stateid, int flags)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2009-02-22 04:29:14 +07:00
|
|
|
if (ONE_STATEID(stateid) && (flags & RD_STATE))
|
2005-04-17 05:20:36 +07:00
|
|
|
return nfs_ok;
|
2015-08-06 23:47:02 +07:00
|
|
|
else if (opens_in_grace(net)) {
|
2011-03-31 08:57:33 +07:00
|
|
|
/* Answer in remaining cases depends on existence of
|
2005-04-17 05:20:36 +07:00
|
|
|
* conflicting state; so we must wait out the grace period. */
|
|
|
|
return nfserr_grace;
|
|
|
|
} else if (flags & WR_STATE)
|
|
|
|
return nfs4_share_conflict(current_fh,
|
|
|
|
NFS4_SHARE_DENY_WRITE);
|
|
|
|
else /* (flags & RD_STATE) && ZERO_STATEID(stateid) */
|
|
|
|
return nfs4_share_conflict(current_fh,
|
|
|
|
NFS4_SHARE_DENY_READ);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allow READ/WRITE during grace period on recovered state only for files
|
|
|
|
* that are not able to provide mandatory locking.
|
|
|
|
*/
|
|
|
|
static inline int
|
2012-07-25 19:57:22 +07:00
|
|
|
grace_disallows_io(struct net *net, struct inode *inode)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2015-08-06 23:47:02 +07:00
|
|
|
return opens_in_grace(net) && mandatory_lock(inode);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2012-04-26 04:58:50 +07:00
|
|
|
static __be32 check_stateid_generation(stateid_t *in, stateid_t *ref, bool has_session)
|
2008-01-27 07:08:12 +07:00
|
|
|
{
|
2009-04-03 12:28:45 +07:00
|
|
|
/*
|
|
|
|
* When sessions are used the stateid generation number is ignored
|
|
|
|
* when it is zero.
|
|
|
|
*/
|
2011-08-22 21:07:12 +07:00
|
|
|
if (has_session && in->si_generation == 0)
|
2011-08-23 22:03:29 +07:00
|
|
|
return nfs_ok;
|
|
|
|
|
|
|
|
if (in->si_generation == ref->si_generation)
|
|
|
|
return nfs_ok;
|
2009-04-03 12:28:45 +07:00
|
|
|
|
2008-01-27 07:08:12 +07:00
|
|
|
/* If the client sends us a stateid from the future, it's buggy: */
|
2016-05-05 17:53:47 +07:00
|
|
|
if (nfsd4_stateid_generation_after(in, ref))
|
2008-01-27 07:08:12 +07:00
|
|
|
return nfserr_bad_stateid;
|
|
|
|
/*
|
2011-08-23 22:03:29 +07:00
|
|
|
* However, we could see a stateid from the past, even from a
|
|
|
|
* non-buggy client. For example, if the client sends a lock
|
|
|
|
* while some IO is outstanding, the lock may bump si_generation
|
|
|
|
* while the IO is still in flight. The client could avoid that
|
|
|
|
* situation by waiting for responses on all the IO requests,
|
|
|
|
* but better performance may result in retrying IO that
|
|
|
|
* receives an old_stateid error if requests are rarely
|
|
|
|
* reordered in flight:
|
2008-01-27 07:08:12 +07:00
|
|
|
*/
|
2011-08-23 22:03:29 +07:00
|
|
|
return nfserr_old_stateid;
|
2008-01-27 07:08:12 +07:00
|
|
|
}
|
|
|
|
|
2015-04-28 20:41:15 +07:00
|
|
|
static __be32 nfsd4_check_openowner_confirmed(struct nfs4_ol_stateid *ols)
|
|
|
|
{
|
|
|
|
if (ols->st_stateowner->so_is_open_owner &&
|
|
|
|
!(openowner(ols->st_stateowner)->oo_flags & NFS4_OO_CONFIRMED))
|
|
|
|
return nfserr_bad_stateid;
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
2012-05-30 00:56:37 +07:00
|
|
|
static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)
|
2011-07-13 21:50:48 +07:00
|
|
|
{
|
2011-09-09 22:26:58 +07:00
|
|
|
struct nfs4_stid *s;
|
2014-07-30 08:34:14 +07:00
|
|
|
__be32 status = nfserr_bad_stateid;
|
2011-07-13 21:50:48 +07:00
|
|
|
|
2012-05-30 00:56:37 +07:00
|
|
|
if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
|
2014-07-30 08:34:14 +07:00
|
|
|
return status;
|
2012-05-30 00:56:37 +07:00
|
|
|
/* Client debugging aid. */
|
|
|
|
if (!same_clid(&stateid->si_opaque.so_clid, &cl->cl_clientid)) {
|
|
|
|
char addr_str[INET6_ADDRSTRLEN];
|
|
|
|
rpc_ntop((struct sockaddr *)&cl->cl_addr, addr_str,
|
|
|
|
sizeof(addr_str));
|
|
|
|
pr_warn_ratelimited("NFSD: client %s testing state ID "
|
|
|
|
"with incorrect client ID\n", addr_str);
|
2014-07-30 08:34:14 +07:00
|
|
|
return status;
|
2012-05-30 00:56:37 +07:00
|
|
|
}
|
2014-07-30 08:34:14 +07:00
|
|
|
spin_lock(&cl->cl_lock);
|
|
|
|
s = find_stateid_locked(cl, stateid);
|
2011-09-09 22:26:58 +07:00
|
|
|
if (!s)
|
2014-07-30 08:34:14 +07:00
|
|
|
goto out_unlock;
|
2011-09-26 23:53:00 +07:00
|
|
|
status = check_stateid_generation(stateid, &s->sc_stateid, 1);
|
2011-07-13 21:50:48 +07:00
|
|
|
if (status)
|
2014-07-30 08:34:14 +07:00
|
|
|
goto out_unlock;
|
2013-04-10 04:42:28 +07:00
|
|
|
switch (s->sc_type) {
|
|
|
|
case NFS4_DELEG_STID:
|
2014-07-30 08:34:14 +07:00
|
|
|
status = nfs_ok;
|
|
|
|
break;
|
2013-04-10 04:02:51 +07:00
|
|
|
case NFS4_REVOKED_DELEG_STID:
|
2014-07-30 08:34:14 +07:00
|
|
|
status = nfserr_deleg_revoked;
|
|
|
|
break;
|
2013-04-10 04:42:28 +07:00
|
|
|
case NFS4_OPEN_STID:
|
|
|
|
case NFS4_LOCK_STID:
|
2015-04-28 20:41:15 +07:00
|
|
|
status = nfsd4_check_openowner_confirmed(openlockstateid(s));
|
2014-07-30 08:34:14 +07:00
|
|
|
break;
|
2013-04-10 04:42:28 +07:00
|
|
|
default:
|
|
|
|
printk("unknown stateid type %x\n", s->sc_type);
|
2014-07-16 21:31:59 +07:00
|
|
|
/* Fallthrough */
|
2013-04-10 04:42:28 +07:00
|
|
|
case NFS4_CLOSED_STID:
|
2014-07-16 21:31:59 +07:00
|
|
|
case NFS4_CLOSED_DELEG_STID:
|
2014-07-30 08:34:14 +07:00
|
|
|
status = nfserr_bad_stateid;
|
2013-04-10 04:42:28 +07:00
|
|
|
}
|
2014-07-30 08:34:14 +07:00
|
|
|
out_unlock:
|
|
|
|
spin_unlock(&cl->cl_lock);
|
|
|
|
return status;
|
2011-07-13 21:50:48 +07:00
|
|
|
}
|
|
|
|
|
2014-08-14 13:44:57 +07:00
|
|
|
__be32
|
2014-06-30 22:48:43 +07:00
|
|
|
nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
|
|
|
|
stateid_t *stateid, unsigned char typemask,
|
|
|
|
struct nfs4_stid **s, struct nfsd_net *nn)
|
2011-09-24 04:01:19 +07:00
|
|
|
{
|
2013-03-13 04:36:17 +07:00
|
|
|
__be32 status;
|
2011-09-24 04:01:19 +07:00
|
|
|
|
|
|
|
if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
|
|
|
|
return nfserr_bad_stateid;
|
2014-06-30 22:48:44 +07:00
|
|
|
status = lookup_clientid(&stateid->si_opaque.so_clid, cstate, nn);
|
2014-03-30 01:43:38 +07:00
|
|
|
if (status == nfserr_stale_clientid) {
|
2014-06-30 22:48:44 +07:00
|
|
|
if (cstate->session)
|
2014-03-30 01:43:38 +07:00
|
|
|
return nfserr_bad_stateid;
|
2011-09-24 04:01:19 +07:00
|
|
|
return nfserr_stale_stateid;
|
2014-03-30 01:43:38 +07:00
|
|
|
}
|
2013-03-13 04:36:17 +07:00
|
|
|
if (status)
|
|
|
|
return status;
|
2014-06-30 22:48:44 +07:00
|
|
|
*s = find_stateid_by_type(cstate->clp, stateid, typemask);
|
2011-09-24 04:01:19 +07:00
|
|
|
if (!*s)
|
|
|
|
return nfserr_bad_stateid;
|
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
2015-06-18 21:44:59 +07:00
|
|
|
static struct file *
|
|
|
|
nfs4_find_file(struct nfs4_stid *s, int flags)
|
|
|
|
{
|
2015-06-18 21:45:00 +07:00
|
|
|
if (!s)
|
|
|
|
return NULL;
|
|
|
|
|
2015-06-18 21:44:59 +07:00
|
|
|
switch (s->sc_type) {
|
|
|
|
case NFS4_DELEG_STID:
|
|
|
|
if (WARN_ON_ONCE(!s->sc_file->fi_deleg_file))
|
|
|
|
return NULL;
|
|
|
|
return get_file(s->sc_file->fi_deleg_file);
|
|
|
|
case NFS4_OPEN_STID:
|
|
|
|
case NFS4_LOCK_STID:
|
|
|
|
if (flags & RD_STATE)
|
|
|
|
return find_readable_file(s->sc_file);
|
|
|
|
else
|
|
|
|
return find_writeable_file(s->sc_file);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static __be32
|
|
|
|
nfs4_check_olstateid(struct svc_fh *fhp, struct nfs4_ol_stateid *ols, int flags)
|
|
|
|
{
|
|
|
|
__be32 status;
|
|
|
|
|
|
|
|
status = nfsd4_check_openowner_confirmed(ols);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
return nfs4_check_openmode(ols, flags);
|
|
|
|
}
|
|
|
|
|
2015-06-18 21:45:00 +07:00
|
|
|
static __be32
|
|
|
|
nfs4_check_file(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfs4_stid *s,
|
|
|
|
struct file **filpp, bool *tmp_file, int flags)
|
|
|
|
{
|
|
|
|
int acc = (flags & RD_STATE) ? NFSD_MAY_READ : NFSD_MAY_WRITE;
|
|
|
|
struct file *file;
|
|
|
|
__be32 status;
|
|
|
|
|
|
|
|
file = nfs4_find_file(s, flags);
|
|
|
|
if (file) {
|
|
|
|
status = nfsd_permission(rqstp, fhp->fh_export, fhp->fh_dentry,
|
|
|
|
acc | NFSD_MAY_OWNER_OVERRIDE);
|
|
|
|
if (status) {
|
|
|
|
fput(file);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
*filpp = file;
|
|
|
|
} else {
|
|
|
|
status = nfsd_open(rqstp, fhp, S_IFREG, acc, filpp);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
if (tmp_file)
|
|
|
|
*tmp_file = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
2015-06-18 21:44:59 +07:00
|
|
|
* Checks for stateid operations
|
|
|
|
*/
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2015-06-18 21:45:00 +07:00
|
|
|
nfs4_preprocess_stateid_op(struct svc_rqst *rqstp,
|
2015-12-03 18:59:51 +07:00
|
|
|
struct nfsd4_compound_state *cstate, struct svc_fh *fhp,
|
|
|
|
stateid_t *stateid, int flags, struct file **filpp, bool *tmp_file)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2015-06-18 21:44:59 +07:00
|
|
|
struct inode *ino = d_inode(fhp->fh_dentry);
|
2015-06-18 21:45:00 +07:00
|
|
|
struct net *net = SVC_NET(rqstp);
|
2012-11-14 22:22:07 +07:00
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2015-06-18 21:45:00 +07:00
|
|
|
struct nfs4_stid *s = NULL;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (filpp)
|
|
|
|
*filpp = NULL;
|
2015-06-18 21:45:00 +07:00
|
|
|
if (tmp_file)
|
|
|
|
*tmp_file = false;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-07-25 19:57:22 +07:00
|
|
|
if (grace_disallows_io(net, ino))
|
2005-04-17 05:20:36 +07:00
|
|
|
return nfserr_grace;
|
|
|
|
|
2015-06-18 21:45:00 +07:00
|
|
|
if (ZERO_STATEID(stateid) || ONE_STATEID(stateid)) {
|
|
|
|
status = check_special_stateids(net, fhp, stateid, flags);
|
|
|
|
goto done;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-06-30 22:48:43 +07:00
|
|
|
status = nfsd4_lookup_stateid(cstate, stateid,
|
2014-06-30 22:48:36 +07:00
|
|
|
NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID,
|
2014-06-30 22:48:43 +07:00
|
|
|
&s, nn);
|
2011-09-24 04:01:19 +07:00
|
|
|
if (status)
|
2014-07-30 19:27:25 +07:00
|
|
|
return status;
|
2015-06-18 21:44:59 +07:00
|
|
|
status = check_stateid_generation(stateid, &s->sc_stateid,
|
|
|
|
nfsd4_has_session(cstate));
|
2011-09-09 22:54:57 +07:00
|
|
|
if (status)
|
|
|
|
goto out;
|
2015-06-18 21:44:59 +07:00
|
|
|
|
2011-09-17 07:12:38 +07:00
|
|
|
switch (s->sc_type) {
|
|
|
|
case NFS4_DELEG_STID:
|
2015-06-18 21:44:59 +07:00
|
|
|
status = nfs4_check_delegmode(delegstateid(s), flags);
|
2011-09-17 07:12:38 +07:00
|
|
|
break;
|
|
|
|
case NFS4_OPEN_STID:
|
|
|
|
case NFS4_LOCK_STID:
|
2015-06-18 21:44:59 +07:00
|
|
|
status = nfs4_check_olstateid(fhp, openlockstateid(s), flags);
|
2011-09-17 07:12:38 +07:00
|
|
|
break;
|
|
|
|
default:
|
2014-04-19 01:44:07 +07:00
|
|
|
status = nfserr_bad_stateid;
|
2015-06-18 21:44:59 +07:00
|
|
|
break;
|
|
|
|
}
|
2015-07-30 17:57:46 +07:00
|
|
|
if (status)
|
|
|
|
goto out;
|
|
|
|
status = nfs4_check_fh(fhp, s);
|
2015-06-18 21:44:59 +07:00
|
|
|
|
2015-06-18 21:45:00 +07:00
|
|
|
done:
|
|
|
|
if (!status && filpp)
|
|
|
|
status = nfs4_check_file(rqstp, fhp, s, filpp, tmp_file, flags);
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
2015-06-18 21:45:00 +07:00
|
|
|
if (s)
|
|
|
|
nfs4_put_stid(s);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2011-07-13 21:50:48 +07:00
|
|
|
/*
|
|
|
|
* Test if the stateid is valid
|
|
|
|
*/
|
|
|
|
__be32
|
|
|
|
nfsd4_test_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_test_stateid *test_stateid)
|
|
|
|
{
|
2012-01-27 22:22:49 +07:00
|
|
|
struct nfsd4_test_stateid_id *stateid;
|
|
|
|
struct nfs4_client *cl = cstate->session->se_client;
|
|
|
|
|
|
|
|
list_for_each_entry(stateid, &test_stateid->ts_stateid_list, ts_id_list)
|
2012-05-30 00:56:37 +07:00
|
|
|
stateid->ts_id_status =
|
|
|
|
nfsd4_validate_stateid(cl, &stateid->ts_id_stateid);
|
2012-01-27 22:22:49 +07:00
|
|
|
|
2011-07-13 21:50:48 +07:00
|
|
|
return nfs_ok;
|
|
|
|
}
|
|
|
|
|
nfsd: Fix race between FREE_STATEID and LOCK
When running LTP's nfslock01 test, the Linux client can send a LOCK
and a FREE_STATEID request at the same time. The outcome is:
Frame 324 R OPEN stateid [2,O]
Frame 115004 C LOCK lockowner_is_new stateid [2,O] offset 672000 len 64
Frame 115008 R LOCK stateid [1,L]
Frame 115012 C WRITE stateid [0,L] offset 672000 len 64
Frame 115016 R WRITE NFS4_OK
Frame 115019 C LOCKU stateid [1,L] offset 672000 len 64
Frame 115022 R LOCKU NFS4_OK
Frame 115025 C FREE_STATEID stateid [2,L]
Frame 115026 C LOCK lockowner_is_new stateid [2,O] offset 672128 len 64
Frame 115029 R FREE_STATEID NFS4_OK
Frame 115030 R LOCK stateid [3,L]
Frame 115034 C WRITE stateid [0,L] offset 672128 len 64
Frame 115038 R WRITE NFS4ERR_BAD_STATEID
In other words, the server returns stateid L in a successful LOCK
reply, but it has already released it. Subsequent uses of stateid L
fail.
To address this, protect the generation check in nfsd4_free_stateid
with the st_mutex. This should guarantee that only one of two
outcomes occurs: either LOCK returns a fresh valid stateid, or
FREE_STATEID returns NFS4ERR_LOCKS_HELD.
Reported-by: Alexey Kodanev <alexey.kodanev@oracle.com>
Fix-suggested-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Tested-by: Alexey Kodanev <alexey.kodanev@oracle.com>
Cc: stable@vger.kernel.org
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2016-08-11 21:37:30 +07:00
|
|
|
static __be32
|
|
|
|
nfsd4_free_lock_stateid(stateid_t *stateid, struct nfs4_stid *s)
|
|
|
|
{
|
|
|
|
struct nfs4_ol_stateid *stp = openlockstateid(s);
|
|
|
|
__be32 ret;
|
|
|
|
|
|
|
|
mutex_lock(&stp->st_mutex);
|
|
|
|
|
|
|
|
ret = check_stateid_generation(stateid, &s->sc_stateid, 1);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = nfserr_locks_held;
|
|
|
|
if (check_for_locks(stp->st_stid.sc_file,
|
|
|
|
lockowner(stp->st_stateowner)))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
release_lock_stateid(stp);
|
|
|
|
ret = nfs_ok;
|
|
|
|
|
|
|
|
out:
|
|
|
|
mutex_unlock(&stp->st_mutex);
|
|
|
|
nfs4_put_stid(s);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-07-13 22:04:21 +07:00
|
|
|
__be32
|
|
|
|
nfsd4_free_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_free_stateid *free_stateid)
|
|
|
|
{
|
|
|
|
stateid_t *stateid = &free_stateid->fr_stateid;
|
2011-09-17 05:56:20 +07:00
|
|
|
struct nfs4_stid *s;
|
2013-04-10 04:02:51 +07:00
|
|
|
struct nfs4_delegation *dp;
|
2011-09-24 04:01:19 +07:00
|
|
|
struct nfs4_client *cl = cstate->session->se_client;
|
2011-09-17 05:56:20 +07:00
|
|
|
__be32 ret = nfserr_bad_stateid;
|
2011-07-13 22:04:21 +07:00
|
|
|
|
2014-07-30 08:34:14 +07:00
|
|
|
spin_lock(&cl->cl_lock);
|
|
|
|
s = find_stateid_locked(cl, stateid);
|
2011-09-17 05:56:20 +07:00
|
|
|
if (!s)
|
2014-07-30 08:34:14 +07:00
|
|
|
goto out_unlock;
|
2011-09-17 05:56:20 +07:00
|
|
|
switch (s->sc_type) {
|
|
|
|
case NFS4_DELEG_STID:
|
2011-07-13 22:04:21 +07:00
|
|
|
ret = nfserr_locks_held;
|
2014-07-30 08:34:14 +07:00
|
|
|
break;
|
2011-09-17 05:56:20 +07:00
|
|
|
case NFS4_OPEN_STID:
|
|
|
|
ret = check_stateid_generation(stateid, &s->sc_stateid, 1);
|
|
|
|
if (ret)
|
2014-07-30 08:34:14 +07:00
|
|
|
break;
|
|
|
|
ret = nfserr_locks_held;
|
2011-09-17 07:12:38 +07:00
|
|
|
break;
|
2014-07-30 08:34:14 +07:00
|
|
|
case NFS4_LOCK_STID:
|
nfsd: Fix race between FREE_STATEID and LOCK
When running LTP's nfslock01 test, the Linux client can send a LOCK
and a FREE_STATEID request at the same time. The outcome is:
Frame 324 R OPEN stateid [2,O]
Frame 115004 C LOCK lockowner_is_new stateid [2,O] offset 672000 len 64
Frame 115008 R LOCK stateid [1,L]
Frame 115012 C WRITE stateid [0,L] offset 672000 len 64
Frame 115016 R WRITE NFS4_OK
Frame 115019 C LOCKU stateid [1,L] offset 672000 len 64
Frame 115022 R LOCKU NFS4_OK
Frame 115025 C FREE_STATEID stateid [2,L]
Frame 115026 C LOCK lockowner_is_new stateid [2,O] offset 672128 len 64
Frame 115029 R FREE_STATEID NFS4_OK
Frame 115030 R LOCK stateid [3,L]
Frame 115034 C WRITE stateid [0,L] offset 672128 len 64
Frame 115038 R WRITE NFS4ERR_BAD_STATEID
In other words, the server returns stateid L in a successful LOCK
reply, but it has already released it. Subsequent uses of stateid L
fail.
To address this, protect the generation check in nfsd4_free_stateid
with the st_mutex. This should guarantee that only one of two
outcomes occurs: either LOCK returns a fresh valid stateid, or
FREE_STATEID returns NFS4ERR_LOCKS_HELD.
Reported-by: Alexey Kodanev <alexey.kodanev@oracle.com>
Fix-suggested-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Tested-by: Alexey Kodanev <alexey.kodanev@oracle.com>
Cc: stable@vger.kernel.org
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2016-08-11 21:37:30 +07:00
|
|
|
atomic_inc(&s->sc_count);
|
2014-07-30 08:34:14 +07:00
|
|
|
spin_unlock(&cl->cl_lock);
|
nfsd: Fix race between FREE_STATEID and LOCK
When running LTP's nfslock01 test, the Linux client can send a LOCK
and a FREE_STATEID request at the same time. The outcome is:
Frame 324 R OPEN stateid [2,O]
Frame 115004 C LOCK lockowner_is_new stateid [2,O] offset 672000 len 64
Frame 115008 R LOCK stateid [1,L]
Frame 115012 C WRITE stateid [0,L] offset 672000 len 64
Frame 115016 R WRITE NFS4_OK
Frame 115019 C LOCKU stateid [1,L] offset 672000 len 64
Frame 115022 R LOCKU NFS4_OK
Frame 115025 C FREE_STATEID stateid [2,L]
Frame 115026 C LOCK lockowner_is_new stateid [2,O] offset 672128 len 64
Frame 115029 R FREE_STATEID NFS4_OK
Frame 115030 R LOCK stateid [3,L]
Frame 115034 C WRITE stateid [0,L] offset 672128 len 64
Frame 115038 R WRITE NFS4ERR_BAD_STATEID
In other words, the server returns stateid L in a successful LOCK
reply, but it has already released it. Subsequent uses of stateid L
fail.
To address this, protect the generation check in nfsd4_free_stateid
with the st_mutex. This should guarantee that only one of two
outcomes occurs: either LOCK returns a fresh valid stateid, or
FREE_STATEID returns NFS4ERR_LOCKS_HELD.
Reported-by: Alexey Kodanev <alexey.kodanev@oracle.com>
Fix-suggested-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Tested-by: Alexey Kodanev <alexey.kodanev@oracle.com>
Cc: stable@vger.kernel.org
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2016-08-11 21:37:30 +07:00
|
|
|
ret = nfsd4_free_lock_stateid(stateid, s);
|
2014-07-30 08:34:14 +07:00
|
|
|
goto out;
|
2013-04-10 04:02:51 +07:00
|
|
|
case NFS4_REVOKED_DELEG_STID:
|
|
|
|
dp = delegstateid(s);
|
2014-07-25 18:34:21 +07:00
|
|
|
list_del_init(&dp->dl_recall_lru);
|
|
|
|
spin_unlock(&cl->cl_lock);
|
2014-07-30 08:34:06 +07:00
|
|
|
nfs4_put_stid(s);
|
2013-04-10 04:02:51 +07:00
|
|
|
ret = nfs_ok;
|
2014-07-30 08:34:14 +07:00
|
|
|
goto out;
|
|
|
|
/* Default falls through and returns nfserr_bad_stateid */
|
2011-07-13 22:04:21 +07:00
|
|
|
}
|
2014-07-30 08:34:14 +07:00
|
|
|
out_unlock:
|
|
|
|
spin_unlock(&cl->cl_lock);
|
2011-07-13 22:04:21 +07:00
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-07-08 07:59:27 +07:00
|
|
|
static inline int
|
|
|
|
setlkflg (int type)
|
|
|
|
{
|
|
|
|
return (type == NFS4_READW_LT || type == NFS4_READ_LT) ?
|
|
|
|
RD_STATE : WR_STATE;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-09-08 03:06:42 +07:00
|
|
|
static __be32 nfs4_seqid_op_checks(struct nfsd4_compound_state *cstate, stateid_t *stateid, u32 seqid, struct nfs4_ol_stateid *stp)
|
2011-09-07 02:19:46 +07:00
|
|
|
{
|
|
|
|
struct svc_fh *current_fh = &cstate->current_fh;
|
|
|
|
struct nfs4_stateowner *sop = stp->st_stateowner;
|
|
|
|
__be32 status;
|
|
|
|
|
|
|
|
status = nfsd4_check_seqid(cstate, sop, seqid);
|
|
|
|
if (status)
|
|
|
|
return status;
|
2013-04-10 04:02:51 +07:00
|
|
|
if (stp->st_stid.sc_type == NFS4_CLOSED_STID
|
|
|
|
|| stp->st_stid.sc_type == NFS4_REVOKED_DELEG_STID)
|
2011-09-17 07:12:38 +07:00
|
|
|
/*
|
|
|
|
* "Closed" stateid's exist *only* to return
|
2013-04-10 04:02:51 +07:00
|
|
|
* nfserr_replay_me from the previous step, and
|
|
|
|
* revoked delegations are kept only for free_stateid.
|
2011-09-17 07:12:38 +07:00
|
|
|
*/
|
|
|
|
return nfserr_bad_stateid;
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_lock(&stp->st_mutex);
|
2011-09-17 07:12:38 +07:00
|
|
|
status = check_stateid_generation(stateid, &stp->st_stid.sc_stateid, nfsd4_has_session(cstate));
|
2015-09-17 18:47:08 +07:00
|
|
|
if (status == nfs_ok)
|
|
|
|
status = nfs4_check_fh(current_fh, &stp->st_stid);
|
|
|
|
if (status != nfs_ok)
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2015-09-17 18:47:08 +07:00
|
|
|
return status;
|
2011-09-07 02:19:46 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Checks for sequence id mutating operations.
|
|
|
|
*/
|
2006-10-20 13:28:59 +07:00
|
|
|
static __be32
|
2009-04-03 12:28:41 +07:00
|
|
|
nfs4_preprocess_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,
|
2011-09-07 02:50:21 +07:00
|
|
|
stateid_t *stateid, char typemask,
|
2012-11-14 22:22:07 +07:00
|
|
|
struct nfs4_ol_stateid **stpp,
|
|
|
|
struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2008-01-27 07:08:12 +07:00
|
|
|
__be32 status;
|
2011-09-24 04:01:19 +07:00
|
|
|
struct nfs4_stid *s;
|
2014-06-30 22:48:34 +07:00
|
|
|
struct nfs4_ol_stateid *stp = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-10-19 17:04:53 +07:00
|
|
|
dprintk("NFSD: %s: seqid=%d stateid = " STATEID_FMT "\n", __func__,
|
|
|
|
seqid, STATEID_VAL(stateid));
|
2005-07-08 07:59:26 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
*stpp = NULL;
|
2014-06-30 22:48:43 +07:00
|
|
|
status = nfsd4_lookup_stateid(cstate, stateid, typemask, &s, nn);
|
2011-09-07 02:19:46 +07:00
|
|
|
if (status)
|
|
|
|
return status;
|
2014-06-30 22:48:34 +07:00
|
|
|
stp = openlockstateid(s);
|
2014-07-30 08:34:27 +07:00
|
|
|
nfsd4_cstate_assign_replay(cstate, stp->st_stateowner);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-06-30 22:48:34 +07:00
|
|
|
status = nfs4_seqid_op_checks(cstate, stateid, seqid, stp);
|
2014-07-30 08:34:24 +07:00
|
|
|
if (!status)
|
2014-06-30 22:48:34 +07:00
|
|
|
*stpp = stp;
|
2014-07-30 08:34:24 +07:00
|
|
|
else
|
|
|
|
nfs4_put_stid(&stp->st_stid);
|
2014-06-30 22:48:34 +07:00
|
|
|
return status;
|
2011-09-07 02:19:46 +07:00
|
|
|
}
|
2007-11-27 05:06:39 +07:00
|
|
|
|
2012-11-14 22:22:07 +07:00
|
|
|
static __be32 nfs4_preprocess_confirmed_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,
|
|
|
|
stateid_t *stateid, struct nfs4_ol_stateid **stpp, struct nfsd_net *nn)
|
2011-09-07 02:19:46 +07:00
|
|
|
{
|
|
|
|
__be32 status;
|
|
|
|
struct nfs4_openowner *oo;
|
2014-07-30 08:34:23 +07:00
|
|
|
struct nfs4_ol_stateid *stp;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-09-07 02:19:46 +07:00
|
|
|
status = nfs4_preprocess_seqid_op(cstate, seqid, stateid,
|
2014-07-30 08:34:23 +07:00
|
|
|
NFS4_OPEN_STID, &stp, nn);
|
2011-09-02 20:03:37 +07:00
|
|
|
if (status)
|
|
|
|
return status;
|
2014-07-30 08:34:23 +07:00
|
|
|
oo = openowner(stp->st_stateowner);
|
|
|
|
if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) {
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2014-07-30 08:34:23 +07:00
|
|
|
nfs4_put_stid(&stp->st_stid);
|
2005-07-08 07:59:26 +07:00
|
|
|
return nfserr_bad_stateid;
|
2014-07-30 08:34:23 +07:00
|
|
|
}
|
|
|
|
*stpp = stp;
|
2005-07-08 07:59:26 +07:00
|
|
|
return nfs_ok;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:27 +07:00
|
|
|
nfsd4_open_confirm(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
2006-12-13 15:35:28 +07:00
|
|
|
struct nfsd4_open_confirm *oc)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2011-07-31 10:33:59 +07:00
|
|
|
struct nfs4_openowner *oo;
|
2011-09-08 03:06:42 +07:00
|
|
|
struct nfs4_ol_stateid *stp;
|
2012-11-14 22:22:07 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-09-16 21:57:01 +07:00
|
|
|
dprintk("NFSD: nfsd4_open_confirm on file %pd\n",
|
|
|
|
cstate->current_fh.fh_dentry);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-12-13 15:35:27 +07:00
|
|
|
status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0);
|
2006-06-30 15:56:13 +07:00
|
|
|
if (status)
|
|
|
|
return status;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-08-24 23:45:03 +07:00
|
|
|
status = nfs4_preprocess_seqid_op(cstate,
|
2006-12-13 15:35:27 +07:00
|
|
|
oc->oc_seqid, &oc->oc_req_stateid,
|
2012-11-14 22:22:07 +07:00
|
|
|
NFS4_OPEN_STID, &stp, nn);
|
2011-08-24 23:45:03 +07:00
|
|
|
if (status)
|
2011-09-02 23:19:43 +07:00
|
|
|
goto out;
|
2011-07-31 10:33:59 +07:00
|
|
|
oo = openowner(stp->st_stateowner);
|
2011-09-02 23:19:43 +07:00
|
|
|
status = nfserr_bad_stateid;
|
2015-09-17 18:47:08 +07:00
|
|
|
if (oo->oo_flags & NFS4_OO_CONFIRMED) {
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2014-07-30 08:34:21 +07:00
|
|
|
goto put_stateid;
|
2015-09-17 18:47:08 +07:00
|
|
|
}
|
2011-09-12 23:24:13 +07:00
|
|
|
oo->oo_flags |= NFS4_OO_CONFIRMED;
|
2015-10-01 20:05:50 +07:00
|
|
|
nfs4_inc_and_copy_stateid(&oc->oc_resp_stateid, &stp->st_stid);
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2009-10-19 17:04:53 +07:00
|
|
|
dprintk("NFSD: %s: success, seqid=%d stateid=" STATEID_FMT "\n",
|
2011-09-08 03:06:42 +07:00
|
|
|
__func__, oc->oc_seqid, STATEID_VAL(&stp->st_stid.sc_stateid));
|
2005-06-24 12:04:30 +07:00
|
|
|
|
2012-03-22 03:42:43 +07:00
|
|
|
nfsd4_client_record_create(oo->oo_owner.so_client);
|
2011-09-02 23:19:43 +07:00
|
|
|
status = nfs_ok;
|
2014-07-30 08:34:21 +07:00
|
|
|
put_stateid:
|
|
|
|
nfs4_put_stid(&stp->st_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
2013-04-02 03:37:12 +07:00
|
|
|
nfsd4_bump_seqid(cstate, status);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2011-09-28 22:37:56 +07:00
|
|
|
static inline void nfs4_stateid_downgrade_bit(struct nfs4_ol_stateid *stp, u32 access)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-05-11 20:45:13 +07:00
|
|
|
if (!test_access(access, stp))
|
2011-09-28 22:37:56 +07:00
|
|
|
return;
|
2014-07-30 08:34:08 +07:00
|
|
|
nfs4_file_put_access(stp->st_stid.sc_file, access);
|
2012-05-11 20:45:13 +07:00
|
|
|
clear_access(access, stp);
|
2011-09-28 22:37:56 +07:00
|
|
|
}
|
2011-06-29 19:23:50 +07:00
|
|
|
|
2011-09-28 22:37:56 +07:00
|
|
|
static inline void nfs4_stateid_downgrade(struct nfs4_ol_stateid *stp, u32 to_access)
|
|
|
|
{
|
|
|
|
switch (to_access) {
|
|
|
|
case NFS4_SHARE_ACCESS_READ:
|
|
|
|
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_WRITE);
|
|
|
|
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_BOTH);
|
|
|
|
break;
|
|
|
|
case NFS4_SHARE_ACCESS_WRITE:
|
|
|
|
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_READ);
|
|
|
|
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_BOTH);
|
|
|
|
break;
|
|
|
|
case NFS4_SHARE_ACCESS_BOTH:
|
|
|
|
break;
|
|
|
|
default:
|
2012-11-26 02:48:10 +07:00
|
|
|
WARN_ON_ONCE(1);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:27 +07:00
|
|
|
nfsd4_open_downgrade(struct svc_rqst *rqstp,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
2006-12-13 15:35:28 +07:00
|
|
|
struct nfsd4_open_downgrade *od)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2011-09-08 03:06:42 +07:00
|
|
|
struct nfs4_ol_stateid *stp;
|
2012-11-14 22:22:07 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-09-16 21:57:01 +07:00
|
|
|
dprintk("NFSD: nfsd4_open_downgrade on file %pd\n",
|
|
|
|
cstate->current_fh.fh_dentry);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-10-11 04:34:31 +07:00
|
|
|
/* We don't yet support WANT bits: */
|
2012-02-17 01:57:09 +07:00
|
|
|
if (od->od_deleg_want)
|
|
|
|
dprintk("NFSD: %s: od_deleg_want=0x%x ignored\n", __func__,
|
|
|
|
od->od_deleg_want);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-09-07 02:19:46 +07:00
|
|
|
status = nfs4_preprocess_confirmed_seqid_op(cstate, od->od_seqid,
|
2012-11-14 22:22:07 +07:00
|
|
|
&od->od_stateid, &stp, nn);
|
2011-08-24 23:45:03 +07:00
|
|
|
if (status)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
|
|
|
status = nfserr_inval;
|
2012-05-11 20:45:13 +07:00
|
|
|
if (!test_access(od->od_share_access, stp)) {
|
2014-07-11 01:07:30 +07:00
|
|
|
dprintk("NFSD: access not a subset of current bitmap: 0x%hhx, input access=%08x\n",
|
2005-04-17 05:20:36 +07:00
|
|
|
stp->st_access_bmap, od->od_share_access);
|
2014-07-30 08:34:22 +07:00
|
|
|
goto put_stateid;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2012-05-11 20:45:14 +07:00
|
|
|
if (!test_deny(od->od_share_deny, stp)) {
|
2014-07-11 01:07:30 +07:00
|
|
|
dprintk("NFSD: deny not a subset of current bitmap: 0x%hhx, input deny=%08x\n",
|
2005-04-17 05:20:36 +07:00
|
|
|
stp->st_deny_bmap, od->od_share_deny);
|
2014-07-30 08:34:22 +07:00
|
|
|
goto put_stateid;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2011-09-28 22:37:56 +07:00
|
|
|
nfs4_stateid_downgrade(stp, od->od_share_access);
|
2012-05-11 20:45:14 +07:00
|
|
|
reset_union_bmap_deny(od->od_share_deny, stp);
|
2015-10-01 20:05:50 +07:00
|
|
|
nfs4_inc_and_copy_stateid(&od->od_stateid, &stp->st_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfs_ok;
|
2014-07-30 08:34:22 +07:00
|
|
|
put_stateid:
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2014-07-30 08:34:22 +07:00
|
|
|
nfs4_put_stid(&stp->st_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
2013-04-02 03:37:12 +07:00
|
|
|
nfsd4_bump_seqid(cstate, status);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2011-09-17 07:12:38 +07:00
|
|
|
static void nfsd4_close_open_stateid(struct nfs4_ol_stateid *s)
|
|
|
|
{
|
2014-06-30 22:48:37 +07:00
|
|
|
struct nfs4_client *clp = s->st_stid.sc_client;
|
2015-08-24 23:41:47 +07:00
|
|
|
bool unhashed;
|
2014-07-30 08:34:42 +07:00
|
|
|
LIST_HEAD(reaplist);
|
2014-06-30 22:48:37 +07:00
|
|
|
|
2011-09-17 07:12:38 +07:00
|
|
|
s->st_stid.sc_type = NFS4_CLOSED_STID;
|
2014-07-30 08:34:41 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
2015-08-24 23:41:47 +07:00
|
|
|
unhashed = unhash_open_stateid(s, &reaplist);
|
2014-06-30 22:48:37 +07:00
|
|
|
|
2014-07-30 08:34:42 +07:00
|
|
|
if (clp->cl_minorversion) {
|
2015-08-24 23:41:47 +07:00
|
|
|
if (unhashed)
|
|
|
|
put_ol_stateid_locked(s, &reaplist);
|
2014-07-30 08:34:42 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
|
|
|
free_ol_stateid_reaplist(&reaplist);
|
|
|
|
} else {
|
|
|
|
spin_unlock(&clp->cl_lock);
|
|
|
|
free_ol_stateid_reaplist(&reaplist);
|
2015-08-24 23:41:47 +07:00
|
|
|
if (unhashed)
|
|
|
|
move_to_close_lru(s, clp->net);
|
2014-07-30 08:34:42 +07:00
|
|
|
}
|
2011-09-17 04:42:48 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* nfs4_unlock_state() called after encode
|
|
|
|
*/
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:27 +07:00
|
|
|
nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
2006-12-13 15:35:28 +07:00
|
|
|
struct nfsd4_close *close)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2011-09-08 03:06:42 +07:00
|
|
|
struct nfs4_ol_stateid *stp;
|
2012-11-14 22:22:07 +07:00
|
|
|
struct net *net = SVC_NET(rqstp);
|
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-09-16 21:57:01 +07:00
|
|
|
dprintk("NFSD: nfsd4_close on file %pd\n",
|
|
|
|
cstate->current_fh.fh_dentry);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-09-17 07:12:38 +07:00
|
|
|
status = nfs4_preprocess_seqid_op(cstate, close->cl_seqid,
|
|
|
|
&close->cl_stateid,
|
|
|
|
NFS4_OPEN_STID|NFS4_CLOSED_STID,
|
2012-11-14 22:22:07 +07:00
|
|
|
&stp, nn);
|
2013-04-02 03:37:12 +07:00
|
|
|
nfsd4_bump_seqid(cstate, status);
|
2011-08-24 23:45:03 +07:00
|
|
|
if (status)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
2015-10-01 20:05:50 +07:00
|
|
|
nfs4_inc_and_copy_stateid(&close->cl_stateid, &stp->st_stid);
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-09-17 07:12:38 +07:00
|
|
|
nfsd4_close_open_stateid(stp);
|
2014-07-30 08:34:20 +07:00
|
|
|
|
|
|
|
/* put reference from nfs4_preprocess_seqid_op */
|
|
|
|
nfs4_put_stid(&stp->st_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:27 +07:00
|
|
|
nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_delegreturn *dr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2009-02-22 04:29:14 +07:00
|
|
|
struct nfs4_delegation *dp;
|
|
|
|
stateid_t *stateid = &dr->dr_stateid;
|
2011-09-24 04:01:19 +07:00
|
|
|
struct nfs4_stid *s;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2012-11-14 22:22:07 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-12-13 15:35:27 +07:00
|
|
|
if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))
|
2009-02-22 04:29:14 +07:00
|
|
|
return status;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-06-30 22:48:43 +07:00
|
|
|
status = nfsd4_lookup_stateid(cstate, stateid, NFS4_DELEG_STID, &s, nn);
|
2011-09-24 04:01:19 +07:00
|
|
|
if (status)
|
2009-02-22 04:29:14 +07:00
|
|
|
goto out;
|
2011-09-24 04:01:19 +07:00
|
|
|
dp = delegstateid(s);
|
2011-09-08 23:07:44 +07:00
|
|
|
status = check_stateid_generation(stateid, &dp->dl_stid.sc_stateid, nfsd4_has_session(cstate));
|
2009-02-22 04:29:14 +07:00
|
|
|
if (status)
|
2014-07-30 08:34:24 +07:00
|
|
|
goto put_stateid;
|
2009-02-22 04:29:14 +07:00
|
|
|
|
2013-04-10 04:02:51 +07:00
|
|
|
destroy_delegation(dp);
|
2014-07-30 08:34:24 +07:00
|
|
|
put_stateid:
|
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2008-12-16 00:42:03 +07:00
|
|
|
static inline u64
|
|
|
|
end_offset(u64 start, u64 len)
|
|
|
|
{
|
|
|
|
u64 end;
|
|
|
|
|
|
|
|
end = start + len;
|
|
|
|
return end >= start ? end: NFS4_MAX_UINT64;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* last octet in a range */
|
|
|
|
static inline u64
|
|
|
|
last_byte_offset(u64 start, u64 len)
|
|
|
|
{
|
|
|
|
u64 end;
|
|
|
|
|
2012-11-26 02:48:10 +07:00
|
|
|
WARN_ON_ONCE(!len);
|
2008-12-16 00:42:03 +07:00
|
|
|
end = start + len;
|
|
|
|
return end > start ? end - 1: NFS4_MAX_UINT64;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* TODO: Linux file offsets are _signed_ 64-bit quantities, which means that
|
|
|
|
* we can't properly handle lock requests that go beyond the (2^63 - 1)-th
|
|
|
|
* byte, because of sign extension problems. Since NFSv4 calls for 64-bit
|
|
|
|
* locking, this prevents us from being completely protocol-compliant. The
|
|
|
|
* real solution to this problem is to start using unsigned file offsets in
|
|
|
|
* the VFS, but this is a very deep change!
|
|
|
|
*/
|
|
|
|
static inline void
|
|
|
|
nfs4_transform_lock_offset(struct file_lock *lock)
|
|
|
|
{
|
|
|
|
if (lock->fl_start < 0)
|
|
|
|
lock->fl_start = OFFSET_MAX;
|
|
|
|
if (lock->fl_end < 0)
|
|
|
|
lock->fl_end = OFFSET_MAX;
|
|
|
|
}
|
|
|
|
|
2015-04-03 20:04:04 +07:00
|
|
|
static fl_owner_t
|
|
|
|
nfsd4_fl_get_owner(fl_owner_t owner)
|
2014-08-22 21:18:44 +07:00
|
|
|
{
|
2015-04-03 20:04:04 +07:00
|
|
|
struct nfs4_lockowner *lo = (struct nfs4_lockowner *)owner;
|
|
|
|
|
|
|
|
nfs4_get_stateowner(&lo->lo_owner);
|
|
|
|
return owner;
|
2014-08-22 21:18:44 +07:00
|
|
|
}
|
|
|
|
|
2015-04-03 20:04:04 +07:00
|
|
|
static void
|
|
|
|
nfsd4_fl_put_owner(fl_owner_t owner)
|
2014-08-22 21:18:44 +07:00
|
|
|
{
|
2015-04-03 20:04:04 +07:00
|
|
|
struct nfs4_lockowner *lo = (struct nfs4_lockowner *)owner;
|
2014-08-22 21:18:44 +07:00
|
|
|
|
2015-04-03 20:04:04 +07:00
|
|
|
if (lo)
|
2014-08-22 21:18:44 +07:00
|
|
|
nfs4_put_stateowner(&lo->lo_owner);
|
|
|
|
}
|
|
|
|
|
2016-09-17 03:28:24 +07:00
|
|
|
static void
|
|
|
|
nfsd4_lm_notify(struct file_lock *fl)
|
|
|
|
{
|
|
|
|
struct nfs4_lockowner *lo = (struct nfs4_lockowner *)fl->fl_owner;
|
|
|
|
struct net *net = lo->lo_owner.so_client->net;
|
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
|
|
struct nfsd4_blocked_lock *nbl = container_of(fl,
|
|
|
|
struct nfsd4_blocked_lock, nbl_lock);
|
|
|
|
bool queue = false;
|
|
|
|
|
2016-09-17 03:28:25 +07:00
|
|
|
/* An empty list means that something else is going to be using it */
|
2016-09-17 03:28:24 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
if (!list_empty(&nbl->nbl_list)) {
|
|
|
|
list_del_init(&nbl->nbl_list);
|
2016-09-17 03:28:25 +07:00
|
|
|
list_del_init(&nbl->nbl_lru);
|
2016-09-17 03:28:24 +07:00
|
|
|
queue = true;
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
if (queue)
|
|
|
|
nfsd4_run_cb(&nbl->nbl_cb);
|
|
|
|
}
|
|
|
|
|
2009-09-22 07:01:12 +07:00
|
|
|
static const struct lock_manager_operations nfsd_posix_mng_ops = {
|
2016-09-17 03:28:24 +07:00
|
|
|
.lm_notify = nfsd4_lm_notify,
|
2014-08-22 21:18:44 +07:00
|
|
|
.lm_get_owner = nfsd4_fl_get_owner,
|
|
|
|
.lm_put_owner = nfsd4_fl_put_owner,
|
2006-04-11 12:55:22 +07:00
|
|
|
};
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static inline void
|
|
|
|
nfs4_set_lock_denied(struct file_lock *fl, struct nfsd4_lock_denied *deny)
|
|
|
|
{
|
2011-07-31 10:33:59 +07:00
|
|
|
struct nfs4_lockowner *lo;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-04-11 12:55:22 +07:00
|
|
|
if (fl->fl_lmops == &nfsd_posix_mng_ops) {
|
2011-07-31 10:33:59 +07:00
|
|
|
lo = (struct nfs4_lockowner *) fl->fl_owner;
|
|
|
|
deny->ld_owner.data = kmemdup(lo->lo_owner.so_owner.data,
|
|
|
|
lo->lo_owner.so_owner.len, GFP_KERNEL);
|
2011-08-31 09:15:47 +07:00
|
|
|
if (!deny->ld_owner.data)
|
|
|
|
/* We just don't care that much */
|
|
|
|
goto nevermind;
|
2011-07-31 10:33:59 +07:00
|
|
|
deny->ld_owner.len = lo->lo_owner.so_owner.len;
|
|
|
|
deny->ld_clientid = lo->lo_owner.so_client->cl_clientid;
|
2006-04-11 12:55:22 +07:00
|
|
|
} else {
|
2011-08-31 09:15:47 +07:00
|
|
|
nevermind:
|
|
|
|
deny->ld_owner.len = 0;
|
|
|
|
deny->ld_owner.data = NULL;
|
2006-04-11 12:55:22 +07:00
|
|
|
deny->ld_clientid.cl_boot = 0;
|
|
|
|
deny->ld_clientid.cl_id = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
deny->ld_start = fl->fl_start;
|
2008-12-16 00:42:03 +07:00
|
|
|
deny->ld_length = NFS4_MAX_UINT64;
|
|
|
|
if (fl->fl_end != NFS4_MAX_UINT64)
|
2005-04-17 05:20:36 +07:00
|
|
|
deny->ld_length = fl->fl_end - fl->fl_start + 1;
|
|
|
|
deny->ld_type = NFS4_READ_LT;
|
|
|
|
if (fl->fl_type != F_RDLCK)
|
|
|
|
deny->ld_type = NFS4_WRITE_LT;
|
|
|
|
}
|
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
static struct nfs4_lockowner *
|
2015-07-13 16:35:37 +07:00
|
|
|
find_lockowner_str_locked(struct nfs4_client *clp, struct xdr_netobj *owner)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-07-30 08:34:36 +07:00
|
|
|
unsigned int strhashval = ownerstr_hashval(owner);
|
2014-06-30 22:48:40 +07:00
|
|
|
struct nfs4_stateowner *so;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-30 19:27:10 +07:00
|
|
|
lockdep_assert_held(&clp->cl_lock);
|
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
list_for_each_entry(so, &clp->cl_ownerstr_hashtbl[strhashval],
|
|
|
|
so_strhash) {
|
2014-06-30 22:48:40 +07:00
|
|
|
if (so->so_is_open_owner)
|
|
|
|
continue;
|
2014-08-22 21:18:43 +07:00
|
|
|
if (same_owner_str(so, owner))
|
|
|
|
return lockowner(nfs4_get_stateowner(so));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:35 +07:00
|
|
|
static struct nfs4_lockowner *
|
2015-07-13 16:35:37 +07:00
|
|
|
find_lockowner_str(struct nfs4_client *clp, struct xdr_netobj *owner)
|
2014-07-30 08:34:35 +07:00
|
|
|
{
|
|
|
|
struct nfs4_lockowner *lo;
|
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
2015-07-13 16:35:37 +07:00
|
|
|
lo = find_lockowner_str_locked(clp, owner);
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2014-07-30 08:34:35 +07:00
|
|
|
return lo;
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:29 +07:00
|
|
|
static void nfs4_unhash_lockowner(struct nfs4_stateowner *sop)
|
|
|
|
{
|
2014-07-30 08:34:35 +07:00
|
|
|
unhash_lockowner_locked(lockowner(sop));
|
2014-07-30 08:34:29 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:26 +07:00
|
|
|
static void nfs4_free_lockowner(struct nfs4_stateowner *sop)
|
|
|
|
{
|
|
|
|
struct nfs4_lockowner *lo = lockowner(sop);
|
|
|
|
|
|
|
|
kmem_cache_free(lockowner_slab, lo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct nfs4_stateowner_operations lockowner_ops = {
|
2014-07-30 08:34:29 +07:00
|
|
|
.so_unhash = nfs4_unhash_lockowner,
|
|
|
|
.so_free = nfs4_free_lockowner,
|
2014-07-30 08:34:26 +07:00
|
|
|
};
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Alloc a lock owner structure.
|
|
|
|
* Called in nfsd4_lock - therefore, OPEN and OPEN_CONFIRM (if needed) has
|
2011-03-31 08:57:33 +07:00
|
|
|
* occurred.
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2011-11-08 05:23:30 +07:00
|
|
|
* strhashval = ownerstr_hashval
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2011-07-31 10:33:59 +07:00
|
|
|
static struct nfs4_lockowner *
|
2014-07-30 08:34:35 +07:00
|
|
|
alloc_init_lock_stateowner(unsigned int strhashval, struct nfs4_client *clp,
|
|
|
|
struct nfs4_ol_stateid *open_stp,
|
|
|
|
struct nfsd4_lock *lock)
|
|
|
|
{
|
|
|
|
struct nfs4_lockowner *lo, *ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-07-31 10:33:59 +07:00
|
|
|
lo = alloc_stateowner(lockowner_slab, &lock->lk_new_owner, clp);
|
|
|
|
if (!lo)
|
2005-04-17 05:20:36 +07:00
|
|
|
return NULL;
|
2016-09-17 03:28:24 +07:00
|
|
|
INIT_LIST_HEAD(&lo->lo_blocked);
|
2011-07-31 10:33:59 +07:00
|
|
|
INIT_LIST_HEAD(&lo->lo_owner.so_stateids);
|
|
|
|
lo->lo_owner.so_is_open_owner = 0;
|
2014-07-30 08:34:28 +07:00
|
|
|
lo->lo_owner.so_seqid = lock->lk_new_lock_seqid;
|
2014-07-30 08:34:26 +07:00
|
|
|
lo->lo_owner.so_ops = &lockowner_ops;
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
2015-07-13 16:35:37 +07:00
|
|
|
ret = find_lockowner_str_locked(clp, &lock->lk_new_owner);
|
2014-07-30 08:34:35 +07:00
|
|
|
if (ret == NULL) {
|
|
|
|
list_add(&lo->lo_owner.so_strhash,
|
2014-07-30 08:34:36 +07:00
|
|
|
&clp->cl_ownerstr_hashtbl[strhashval]);
|
2014-07-30 08:34:35 +07:00
|
|
|
ret = lo;
|
|
|
|
} else
|
2015-07-16 11:05:07 +07:00
|
|
|
nfs4_free_stateowner(&lo->lo_owner);
|
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2015-03-23 22:02:30 +07:00
|
|
|
return ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:13 +07:00
|
|
|
static void
|
|
|
|
init_lock_stateid(struct nfs4_ol_stateid *stp, struct nfs4_lockowner *lo,
|
|
|
|
struct nfs4_file *fp, struct inode *inode,
|
|
|
|
struct nfs4_ol_stateid *open_stp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2011-09-16 02:02:41 +07:00
|
|
|
struct nfs4_client *clp = lo->lo_owner.so_client;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-07-30 08:34:13 +07:00
|
|
|
lockdep_assert_held(&clp->cl_lock);
|
|
|
|
|
2014-07-30 08:34:15 +07:00
|
|
|
atomic_inc(&stp->st_stid.sc_count);
|
2013-02-04 00:23:01 +07:00
|
|
|
stp->st_stid.sc_type = NFS4_LOCK_STID;
|
2014-08-22 21:18:43 +07:00
|
|
|
stp->st_stateowner = nfs4_get_stateowner(&lo->lo_owner);
|
2005-06-24 12:03:10 +07:00
|
|
|
get_nfs4_file(fp);
|
2014-07-30 08:34:08 +07:00
|
|
|
stp->st_stid.sc_file = fp;
|
2014-07-30 08:34:11 +07:00
|
|
|
stp->st_stid.sc_free = nfs4_free_lock_stateid;
|
2011-03-03 06:01:35 +07:00
|
|
|
stp->st_access_bmap = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
stp->st_deny_bmap = open_stp->st_deny_bmap;
|
2005-07-08 07:59:27 +07:00
|
|
|
stp->st_openstp = open_stp;
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_init(&stp->st_mutex);
|
2014-06-30 22:48:38 +07:00
|
|
|
list_add(&stp->st_locks, &open_stp->st_locks);
|
2014-07-30 08:34:12 +07:00
|
|
|
list_add(&stp->st_perstateowner, &lo->lo_owner.so_stateids);
|
2014-07-11 01:07:25 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
list_add(&stp->st_perfile, &fp->fi_stateids);
|
|
|
|
spin_unlock(&fp->fi_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-06-30 22:48:39 +07:00
|
|
|
static struct nfs4_ol_stateid *
|
|
|
|
find_lock_stateid(struct nfs4_lockowner *lo, struct nfs4_file *fp)
|
|
|
|
{
|
|
|
|
struct nfs4_ol_stateid *lst;
|
2014-07-30 08:34:13 +07:00
|
|
|
struct nfs4_client *clp = lo->lo_owner.so_client;
|
|
|
|
|
|
|
|
lockdep_assert_held(&clp->cl_lock);
|
2014-06-30 22:48:39 +07:00
|
|
|
|
|
|
|
list_for_each_entry(lst, &lo->lo_owner.so_stateids, st_perstateowner) {
|
2014-07-30 08:34:15 +07:00
|
|
|
if (lst->st_stid.sc_file == fp) {
|
|
|
|
atomic_inc(&lst->st_stid.sc_count);
|
2014-06-30 22:48:39 +07:00
|
|
|
return lst;
|
2014-07-30 08:34:15 +07:00
|
|
|
}
|
2014-06-30 22:48:39 +07:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:13 +07:00
|
|
|
static struct nfs4_ol_stateid *
|
|
|
|
find_or_create_lock_stateid(struct nfs4_lockowner *lo, struct nfs4_file *fi,
|
|
|
|
struct inode *inode, struct nfs4_ol_stateid *ost,
|
|
|
|
bool *new)
|
|
|
|
{
|
|
|
|
struct nfs4_stid *ns = NULL;
|
|
|
|
struct nfs4_ol_stateid *lst;
|
|
|
|
struct nfs4_openowner *oo = openowner(ost->st_stateowner);
|
|
|
|
struct nfs4_client *clp = oo->oo_owner.so_client;
|
|
|
|
|
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
lst = find_lock_stateid(lo, fi);
|
|
|
|
if (lst == NULL) {
|
|
|
|
spin_unlock(&clp->cl_lock);
|
|
|
|
ns = nfs4_alloc_stid(clp, stateid_slab);
|
|
|
|
if (ns == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
spin_lock(&clp->cl_lock);
|
|
|
|
lst = find_lock_stateid(lo, fi);
|
|
|
|
if (likely(!lst)) {
|
|
|
|
lst = openlockstateid(ns);
|
|
|
|
init_lock_stateid(lst, lo, fi, inode, ost);
|
|
|
|
ns = NULL;
|
|
|
|
*new = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock(&clp->cl_lock);
|
|
|
|
if (ns)
|
|
|
|
nfs4_put_stid(ns);
|
|
|
|
return lst;
|
|
|
|
}
|
2014-06-30 22:48:39 +07:00
|
|
|
|
2005-06-24 12:04:03 +07:00
|
|
|
static int
|
2005-04-17 05:20:36 +07:00
|
|
|
check_lock_length(u64 offset, u64 length)
|
|
|
|
{
|
2015-07-13 16:34:19 +07:00
|
|
|
return ((length == 0) || ((length != NFS4_MAX_UINT64) &&
|
|
|
|
(length > ~offset)));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2011-09-08 03:06:42 +07:00
|
|
|
static void get_lock_access(struct nfs4_ol_stateid *lock_stp, u32 access)
|
2011-03-03 06:01:35 +07:00
|
|
|
{
|
2014-07-30 08:34:08 +07:00
|
|
|
struct nfs4_file *fp = lock_stp->st_stid.sc_file;
|
2011-03-03 06:01:35 +07:00
|
|
|
|
2014-07-11 01:07:33 +07:00
|
|
|
lockdep_assert_held(&fp->fi_lock);
|
|
|
|
|
2012-05-11 20:45:13 +07:00
|
|
|
if (test_access(access, lock_stp))
|
2011-03-03 06:01:35 +07:00
|
|
|
return;
|
2014-07-11 01:07:28 +07:00
|
|
|
__nfs4_file_get_access(fp, access);
|
2012-05-11 20:45:13 +07:00
|
|
|
set_access(access, lock_stp);
|
2011-03-03 06:01:35 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 08:34:13 +07:00
|
|
|
static __be32
|
|
|
|
lookup_or_create_lock_state(struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfs4_ol_stateid *ost,
|
|
|
|
struct nfsd4_lock *lock,
|
2016-08-11 21:37:39 +07:00
|
|
|
struct nfs4_ol_stateid **plst, bool *new)
|
2011-10-20 17:57:46 +07:00
|
|
|
{
|
2014-07-30 08:34:28 +07:00
|
|
|
__be32 status;
|
2014-07-30 08:34:08 +07:00
|
|
|
struct nfs4_file *fi = ost->st_stid.sc_file;
|
2011-10-20 17:57:46 +07:00
|
|
|
struct nfs4_openowner *oo = openowner(ost->st_stateowner);
|
|
|
|
struct nfs4_client *cl = oo->oo_owner.so_client;
|
2015-03-18 05:25:59 +07:00
|
|
|
struct inode *inode = d_inode(cstate->current_fh.fh_dentry);
|
2011-10-20 17:57:46 +07:00
|
|
|
struct nfs4_lockowner *lo;
|
2016-08-11 21:37:39 +07:00
|
|
|
struct nfs4_ol_stateid *lst;
|
2011-10-20 17:57:46 +07:00
|
|
|
unsigned int strhashval;
|
2016-08-11 21:37:39 +07:00
|
|
|
bool hashed;
|
2011-10-20 17:57:46 +07:00
|
|
|
|
2015-07-13 16:35:37 +07:00
|
|
|
lo = find_lockowner_str(cl, &lock->lk_new_owner);
|
2014-06-30 22:48:39 +07:00
|
|
|
if (!lo) {
|
2015-07-13 16:35:10 +07:00
|
|
|
strhashval = ownerstr_hashval(&lock->lk_new_owner);
|
2014-06-30 22:48:39 +07:00
|
|
|
lo = alloc_init_lock_stateowner(strhashval, cl, ost, lock);
|
|
|
|
if (lo == NULL)
|
|
|
|
return nfserr_jukebox;
|
|
|
|
} else {
|
|
|
|
/* with an existing lockowner, seqids must be the same */
|
2014-07-30 08:34:28 +07:00
|
|
|
status = nfserr_bad_seqid;
|
2014-06-30 22:48:39 +07:00
|
|
|
if (!cstate->minorversion &&
|
|
|
|
lock->lk_new_lock_seqid != lo->lo_owner.so_seqid)
|
2014-07-30 08:34:28 +07:00
|
|
|
goto out;
|
2011-10-20 17:57:46 +07:00
|
|
|
}
|
2014-06-30 22:48:39 +07:00
|
|
|
|
2016-08-11 21:37:39 +07:00
|
|
|
retry:
|
|
|
|
lst = find_or_create_lock_stateid(lo, fi, inode, ost, new);
|
|
|
|
if (lst == NULL) {
|
2014-07-30 08:34:28 +07:00
|
|
|
status = nfserr_jukebox;
|
|
|
|
goto out;
|
2011-10-20 17:57:46 +07:00
|
|
|
}
|
2016-08-11 21:37:39 +07:00
|
|
|
|
|
|
|
mutex_lock(&lst->st_mutex);
|
|
|
|
|
|
|
|
/* See if it's still hashed to avoid race with FREE_STATEID */
|
|
|
|
spin_lock(&cl->cl_lock);
|
|
|
|
hashed = !list_empty(&lst->st_perfile);
|
|
|
|
spin_unlock(&cl->cl_lock);
|
|
|
|
|
|
|
|
if (!hashed) {
|
|
|
|
mutex_unlock(&lst->st_mutex);
|
|
|
|
nfs4_put_stid(&lst->st_stid);
|
|
|
|
goto retry;
|
|
|
|
}
|
2014-07-30 08:34:28 +07:00
|
|
|
status = nfs_ok;
|
2016-08-11 21:37:39 +07:00
|
|
|
*plst = lst;
|
2014-07-30 08:34:28 +07:00
|
|
|
out:
|
|
|
|
nfs4_put_stateowner(&lo->lo_owner);
|
|
|
|
return status;
|
2011-10-20 17:57:46 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* LOCK operation
|
|
|
|
*/
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:27 +07:00
|
|
|
nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
2006-12-13 15:35:28 +07:00
|
|
|
struct nfsd4_lock *lock)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2011-07-31 10:33:59 +07:00
|
|
|
struct nfs4_openowner *open_sop = NULL;
|
|
|
|
struct nfs4_lockowner *lock_sop = NULL;
|
2014-07-30 08:34:15 +07:00
|
|
|
struct nfs4_ol_stateid *lock_stp = NULL;
|
2014-07-30 08:34:22 +07:00
|
|
|
struct nfs4_ol_stateid *open_stp = NULL;
|
2014-07-11 01:07:33 +07:00
|
|
|
struct nfs4_file *fp;
|
2010-08-21 05:09:31 +07:00
|
|
|
struct file *filp = NULL;
|
2016-09-17 03:28:24 +07:00
|
|
|
struct nfsd4_blocked_lock *nbl = NULL;
|
2012-08-21 19:03:32 +07:00
|
|
|
struct file_lock *file_lock = NULL;
|
|
|
|
struct file_lock *conflock = NULL;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status = 0;
|
2011-08-23 00:13:31 +07:00
|
|
|
int lkflg;
|
2006-10-20 13:29:01 +07:00
|
|
|
int err;
|
2014-07-30 08:34:28 +07:00
|
|
|
bool new = false;
|
2016-09-17 03:28:24 +07:00
|
|
|
unsigned char fl_type;
|
|
|
|
unsigned int fl_flags = FL_POSIX;
|
2012-11-14 22:22:07 +07:00
|
|
|
struct net *net = SVC_NET(rqstp);
|
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
dprintk("NFSD: nfsd4_lock: start=%Ld length=%Ld\n",
|
|
|
|
(long long) lock->lk_offset,
|
|
|
|
(long long) lock->lk_length);
|
|
|
|
|
|
|
|
if (check_lock_length(lock->lk_offset, lock->lk_length))
|
|
|
|
return nfserr_inval;
|
|
|
|
|
2006-12-13 15:35:27 +07:00
|
|
|
if ((status = fh_verify(rqstp, &cstate->current_fh,
|
2008-06-16 18:20:29 +07:00
|
|
|
S_IFREG, NFSD_MAY_LOCK))) {
|
2006-01-19 08:43:17 +07:00
|
|
|
dprintk("NFSD: nfsd4_lock: permission denied!\n");
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (lock->lk_is_new) {
|
2011-11-05 04:08:10 +07:00
|
|
|
if (nfsd4_has_session(cstate))
|
|
|
|
/* See rfc 5661 18.10.3: given clientid is ignored: */
|
2015-07-13 16:35:10 +07:00
|
|
|
memcpy(&lock->lk_new_clientid,
|
2011-11-05 04:08:10 +07:00
|
|
|
&cstate->session->se_client->cl_clientid,
|
|
|
|
sizeof(clientid_t));
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfserr_stale_clientid;
|
2012-07-25 19:57:45 +07:00
|
|
|
if (STALE_CLIENTID(&lock->lk_new_clientid, nn))
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* validate and update open stateid and open seqid */
|
2011-09-07 02:19:46 +07:00
|
|
|
status = nfs4_preprocess_confirmed_seqid_op(cstate,
|
2005-04-17 05:20:36 +07:00
|
|
|
lock->lk_new_open_seqid,
|
|
|
|
&lock->lk_new_open_stateid,
|
2012-11-14 22:22:07 +07:00
|
|
|
&open_stp, nn);
|
2005-07-08 07:59:16 +07:00
|
|
|
if (status)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&open_stp->st_mutex);
|
2011-07-31 10:33:59 +07:00
|
|
|
open_sop = openowner(open_stp->st_stateowner);
|
2011-08-23 00:13:31 +07:00
|
|
|
status = nfserr_bad_stateid;
|
2011-11-05 04:08:10 +07:00
|
|
|
if (!same_clid(&open_sop->oo_owner.so_client->cl_clientid,
|
2015-07-13 16:35:10 +07:00
|
|
|
&lock->lk_new_clientid))
|
2011-08-23 00:13:31 +07:00
|
|
|
goto out;
|
2011-10-20 17:57:46 +07:00
|
|
|
status = lookup_or_create_lock_state(cstate, open_stp, lock,
|
2014-07-30 08:34:28 +07:00
|
|
|
&lock_stp, &new);
|
2014-07-30 08:34:15 +07:00
|
|
|
} else {
|
2009-04-03 12:28:41 +07:00
|
|
|
status = nfs4_preprocess_seqid_op(cstate,
|
2011-07-31 10:33:59 +07:00
|
|
|
lock->lk_old_lock_seqid,
|
|
|
|
&lock->lk_old_lock_stateid,
|
2012-11-14 22:22:07 +07:00
|
|
|
NFS4_LOCK_STID, &lock_stp, nn);
|
2014-07-30 08:34:15 +07:00
|
|
|
}
|
2012-06-07 03:01:37 +07:00
|
|
|
if (status)
|
|
|
|
goto out;
|
2011-10-20 17:57:46 +07:00
|
|
|
lock_sop = lockowner(lock_stp->st_stateowner);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-08-23 00:13:31 +07:00
|
|
|
lkflg = setlkflg(lock->lk_type);
|
|
|
|
status = nfs4_check_openmode(lock_stp, lkflg);
|
|
|
|
if (status)
|
|
|
|
goto out;
|
|
|
|
|
2005-07-08 07:59:15 +07:00
|
|
|
status = nfserr_grace;
|
2012-11-14 22:22:07 +07:00
|
|
|
if (locks_in_grace(net) && !lock->lk_reclaim)
|
2005-07-08 07:59:15 +07:00
|
|
|
goto out;
|
|
|
|
status = nfserr_no_grace;
|
2012-11-14 22:22:07 +07:00
|
|
|
if (!locks_in_grace(net) && lock->lk_reclaim)
|
2005-07-08 07:59:15 +07:00
|
|
|
goto out;
|
|
|
|
|
2014-07-30 08:34:08 +07:00
|
|
|
fp = lock_stp->st_stid.sc_file;
|
2005-04-17 05:20:36 +07:00
|
|
|
switch (lock->lk_type) {
|
|
|
|
case NFS4_READW_LT:
|
2016-09-17 03:28:24 +07:00
|
|
|
if (nfsd4_has_session(cstate))
|
|
|
|
fl_flags |= FL_SLEEP;
|
|
|
|
/* Fallthrough */
|
|
|
|
case NFS4_READ_LT:
|
2014-07-11 01:07:33 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
filp = find_readable_file_locked(fp);
|
2011-03-03 06:01:35 +07:00
|
|
|
if (filp)
|
|
|
|
get_lock_access(lock_stp, NFS4_SHARE_ACCESS_READ);
|
2014-07-11 01:07:33 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
2016-09-17 03:28:24 +07:00
|
|
|
fl_type = F_RDLCK;
|
2011-03-03 11:48:33 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
case NFS4_WRITEW_LT:
|
2016-09-17 03:28:24 +07:00
|
|
|
if (nfsd4_has_session(cstate))
|
|
|
|
fl_flags |= FL_SLEEP;
|
|
|
|
/* Fallthrough */
|
|
|
|
case NFS4_WRITE_LT:
|
2014-07-11 01:07:33 +07:00
|
|
|
spin_lock(&fp->fi_lock);
|
|
|
|
filp = find_writeable_file_locked(fp);
|
2011-03-03 06:01:35 +07:00
|
|
|
if (filp)
|
|
|
|
get_lock_access(lock_stp, NFS4_SHARE_ACCESS_WRITE);
|
2014-07-11 01:07:33 +07:00
|
|
|
spin_unlock(&fp->fi_lock);
|
2016-09-17 03:28:24 +07:00
|
|
|
fl_type = F_WRLCK;
|
2011-03-03 11:48:33 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
default:
|
|
|
|
status = nfserr_inval;
|
|
|
|
goto out;
|
|
|
|
}
|
2016-09-17 03:28:24 +07:00
|
|
|
|
2010-07-08 22:02:09 +07:00
|
|
|
if (!filp) {
|
|
|
|
status = nfserr_openmode;
|
|
|
|
goto out;
|
|
|
|
}
|
2014-08-22 21:18:44 +07:00
|
|
|
|
2016-09-17 03:28:24 +07:00
|
|
|
nbl = find_or_allocate_block(lock_sop, &fp->fi_fhandle, nn);
|
|
|
|
if (!nbl) {
|
|
|
|
dprintk("NFSD: %s: unable to allocate block!\n", __func__);
|
|
|
|
status = nfserr_jukebox;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
file_lock = &nbl->nbl_lock;
|
|
|
|
file_lock->fl_type = fl_type;
|
2014-08-22 21:18:44 +07:00
|
|
|
file_lock->fl_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(&lock_sop->lo_owner));
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock->fl_pid = current->tgid;
|
|
|
|
file_lock->fl_file = filp;
|
2016-09-17 03:28:24 +07:00
|
|
|
file_lock->fl_flags = fl_flags;
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock->fl_lmops = &nfsd_posix_mng_ops;
|
|
|
|
file_lock->fl_start = lock->lk_offset;
|
|
|
|
file_lock->fl_end = last_byte_offset(lock->lk_offset, lock->lk_length);
|
|
|
|
nfs4_transform_lock_offset(file_lock);
|
|
|
|
|
|
|
|
conflock = locks_alloc_lock();
|
|
|
|
if (!conflock) {
|
|
|
|
dprintk("NFSD: %s: unable to allocate lock!\n", __func__);
|
|
|
|
status = nfserr_jukebox;
|
|
|
|
goto out;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2016-09-17 03:28:24 +07:00
|
|
|
if (fl_flags & FL_SLEEP) {
|
2016-09-17 03:28:25 +07:00
|
|
|
nbl->nbl_time = jiffies;
|
2016-09-17 03:28:24 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_add_tail(&nbl->nbl_list, &lock_sop->lo_blocked);
|
2016-09-17 03:28:25 +07:00
|
|
|
list_add_tail(&nbl->nbl_lru, &nn->blocked_locks_lru);
|
2016-09-17 03:28:24 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
}
|
|
|
|
|
2012-08-21 19:03:32 +07:00
|
|
|
err = vfs_lock_file(filp, F_SETLK, file_lock, conflock);
|
2016-09-17 03:28:24 +07:00
|
|
|
switch (err) {
|
2005-04-17 05:20:36 +07:00
|
|
|
case 0: /* success! */
|
2015-10-01 20:05:50 +07:00
|
|
|
nfs4_inc_and_copy_stateid(&lock->lk_resp_stateid, &lock_stp->st_stid);
|
2006-10-20 13:29:01 +07:00
|
|
|
status = 0;
|
2006-03-26 16:37:26 +07:00
|
|
|
break;
|
2016-09-17 03:28:24 +07:00
|
|
|
case FILE_LOCK_DEFERRED:
|
|
|
|
nbl = NULL;
|
|
|
|
/* Fallthrough */
|
|
|
|
case -EAGAIN: /* conflock holds conflicting lock */
|
2006-03-26 16:37:26 +07:00
|
|
|
status = nfserr_denied;
|
|
|
|
dprintk("NFSD: nfsd4_lock: conflicting lock found!\n");
|
2012-08-21 19:03:32 +07:00
|
|
|
nfs4_set_lock_denied(conflock, &lock->lk_denied);
|
2006-03-26 16:37:26 +07:00
|
|
|
break;
|
2016-09-17 03:28:24 +07:00
|
|
|
case -EDEADLK:
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfserr_deadlock;
|
2006-03-26 16:37:26 +07:00
|
|
|
break;
|
2011-08-11 06:07:33 +07:00
|
|
|
default:
|
2006-11-29 04:26:41 +07:00
|
|
|
dprintk("NFSD: nfsd4_lock: vfs_lock_file() failed! status %d\n",err);
|
2011-08-11 06:07:33 +07:00
|
|
|
status = nfserrno(err);
|
2006-03-26 16:37:26 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
out:
|
2016-09-17 03:28:24 +07:00
|
|
|
if (nbl) {
|
|
|
|
/* dequeue it if we queued it before */
|
|
|
|
if (fl_flags & FL_SLEEP) {
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_del_init(&nbl->nbl_list);
|
2016-09-17 03:28:25 +07:00
|
|
|
list_del_init(&nbl->nbl_lru);
|
2016-09-17 03:28:24 +07:00
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
}
|
|
|
|
free_blocked_lock(nbl);
|
|
|
|
}
|
2014-07-11 01:07:26 +07:00
|
|
|
if (filp)
|
|
|
|
fput(filp);
|
2014-07-30 08:34:28 +07:00
|
|
|
if (lock_stp) {
|
|
|
|
/* Bump seqid manually if the 4.0 replay owner is openowner */
|
|
|
|
if (cstate->replay_owner &&
|
|
|
|
cstate->replay_owner != &lock_sop->lo_owner &&
|
|
|
|
seqid_mutating_err(ntohl(status)))
|
|
|
|
lock_sop->lo_owner.so_seqid++;
|
|
|
|
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&lock_stp->st_mutex);
|
2015-09-17 18:47:08 +07:00
|
|
|
|
2014-07-30 08:34:28 +07:00
|
|
|
/*
|
|
|
|
* If this is a new, never-before-used stateid, and we are
|
|
|
|
* returning an error, then just go ahead and release it.
|
|
|
|
*/
|
|
|
|
if (status && new)
|
|
|
|
release_lock_stateid(lock_stp);
|
|
|
|
|
2014-07-30 08:34:15 +07:00
|
|
|
nfs4_put_stid(&lock_stp->st_stid);
|
2014-07-30 08:34:28 +07:00
|
|
|
}
|
2014-07-30 08:34:22 +07:00
|
|
|
if (open_stp)
|
|
|
|
nfs4_put_stid(&open_stp->st_stid);
|
2013-04-02 03:37:12 +07:00
|
|
|
nfsd4_bump_seqid(cstate, status);
|
2012-08-21 19:03:32 +07:00
|
|
|
if (conflock)
|
|
|
|
locks_free_lock(conflock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2008-12-21 02:58:38 +07:00
|
|
|
/*
|
|
|
|
* The NFSv4 spec allows a client to do a LOCKT without holding an OPEN,
|
|
|
|
* so we do a temporary open here just to get an open file to pass to
|
|
|
|
* vfs_test_lock. (Arguably perhaps test_lock should be done with an
|
|
|
|
* inode operation.)
|
|
|
|
*/
|
2012-04-13 11:00:04 +07:00
|
|
|
static __be32 nfsd_test_lock(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file_lock *lock)
|
2008-12-21 02:58:38 +07:00
|
|
|
{
|
|
|
|
struct file *file;
|
2012-04-13 11:00:04 +07:00
|
|
|
__be32 err = nfsd_open(rqstp, fhp, S_IFREG, NFSD_MAY_READ, &file);
|
|
|
|
if (!err) {
|
|
|
|
err = nfserrno(vfs_test_lock(file, lock));
|
2015-04-28 20:41:16 +07:00
|
|
|
fput(file);
|
2012-04-13 11:00:04 +07:00
|
|
|
}
|
2008-12-21 02:58:38 +07:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* LOCKT operation
|
|
|
|
*/
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:27 +07:00
|
|
|
nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_lockt *lockt)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-08-21 19:03:32 +07:00
|
|
|
struct file_lock *file_lock = NULL;
|
2014-07-30 08:34:28 +07:00
|
|
|
struct nfs4_lockowner *lo = NULL;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2012-11-14 22:21:05 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-07-25 19:57:22 +07:00
|
|
|
if (locks_in_grace(SVC_NET(rqstp)))
|
2005-04-17 05:20:36 +07:00
|
|
|
return nfserr_grace;
|
|
|
|
|
|
|
|
if (check_lock_length(lockt->lt_offset, lockt->lt_length))
|
|
|
|
return nfserr_inval;
|
|
|
|
|
2012-12-04 05:24:41 +07:00
|
|
|
if (!nfsd4_has_session(cstate)) {
|
2014-06-30 22:48:44 +07:00
|
|
|
status = lookup_clientid(&lockt->lt_clientid, cstate, nn);
|
2012-12-04 05:24:41 +07:00
|
|
|
if (status)
|
|
|
|
goto out;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-08-16 05:39:32 +07:00
|
|
|
if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
|
|
|
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock = locks_alloc_lock();
|
|
|
|
if (!file_lock) {
|
|
|
|
dprintk("NFSD: %s: unable to allocate lock!\n", __func__);
|
|
|
|
status = nfserr_jukebox;
|
|
|
|
goto out;
|
|
|
|
}
|
2014-08-15 07:02:55 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
switch (lockt->lt_type) {
|
|
|
|
case NFS4_READ_LT:
|
|
|
|
case NFS4_READW_LT:
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock->fl_type = F_RDLCK;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
case NFS4_WRITE_LT:
|
|
|
|
case NFS4_WRITEW_LT:
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock->fl_type = F_WRLCK;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
default:
|
2007-07-28 03:10:37 +07:00
|
|
|
dprintk("NFSD: nfs4_lockt: bad lock type!\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfserr_inval;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:35:37 +07:00
|
|
|
lo = find_lockowner_str(cstate->clp, &lockt->lt_owner);
|
2011-07-31 10:33:59 +07:00
|
|
|
if (lo)
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock->fl_owner = (fl_owner_t)lo;
|
|
|
|
file_lock->fl_pid = current->tgid;
|
|
|
|
file_lock->fl_flags = FL_POSIX;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock->fl_start = lockt->lt_offset;
|
|
|
|
file_lock->fl_end = last_byte_offset(lockt->lt_offset, lockt->lt_length);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-08-21 19:03:32 +07:00
|
|
|
nfs4_transform_lock_offset(file_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-08-21 19:03:32 +07:00
|
|
|
status = nfsd_test_lock(rqstp, &cstate->current_fh, file_lock);
|
2012-04-13 11:00:04 +07:00
|
|
|
if (status)
|
2006-11-29 04:26:41 +07:00
|
|
|
goto out;
|
2012-04-13 11:00:04 +07:00
|
|
|
|
2012-08-21 19:03:32 +07:00
|
|
|
if (file_lock->fl_type != F_UNLCK) {
|
2005-04-17 05:20:36 +07:00
|
|
|
status = nfserr_denied;
|
2012-08-21 19:03:32 +07:00
|
|
|
nfs4_set_lock_denied(file_lock, &lockt->lt_denied);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
out:
|
2014-07-30 08:34:28 +07:00
|
|
|
if (lo)
|
|
|
|
nfs4_put_stateowner(&lo->lo_owner);
|
2012-08-21 19:03:32 +07:00
|
|
|
if (file_lock)
|
|
|
|
locks_free_lock(file_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:27 +07:00
|
|
|
nfsd4_locku(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
2006-12-13 15:35:28 +07:00
|
|
|
struct nfsd4_locku *locku)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2011-09-08 03:06:42 +07:00
|
|
|
struct nfs4_ol_stateid *stp;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct file *filp = NULL;
|
2012-08-21 19:03:32 +07:00
|
|
|
struct file_lock *file_lock = NULL;
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2006-10-20 13:29:01 +07:00
|
|
|
int err;
|
2012-11-14 22:22:07 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
dprintk("NFSD: nfsd4_locku: start=%Ld length=%Ld\n",
|
|
|
|
(long long) locku->lu_offset,
|
|
|
|
(long long) locku->lu_length);
|
|
|
|
|
|
|
|
if (check_lock_length(locku->lu_offset, locku->lu_length))
|
|
|
|
return nfserr_inval;
|
|
|
|
|
2011-08-24 23:45:03 +07:00
|
|
|
status = nfs4_preprocess_seqid_op(cstate, locku->lu_seqid,
|
2012-11-14 22:22:07 +07:00
|
|
|
&locku->lu_stateid, NFS4_LOCK_STID,
|
|
|
|
&stp, nn);
|
2011-08-24 23:45:03 +07:00
|
|
|
if (status)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out;
|
2014-07-30 08:34:08 +07:00
|
|
|
filp = find_any_file(stp->st_stid.sc_file);
|
2010-07-08 22:02:09 +07:00
|
|
|
if (!filp) {
|
|
|
|
status = nfserr_lock_range;
|
2014-07-30 08:34:16 +07:00
|
|
|
goto put_stateid;
|
2010-07-08 22:02:09 +07:00
|
|
|
}
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock = locks_alloc_lock();
|
|
|
|
if (!file_lock) {
|
|
|
|
dprintk("NFSD: %s: unable to allocate lock!\n", __func__);
|
|
|
|
status = nfserr_jukebox;
|
2014-07-11 01:07:26 +07:00
|
|
|
goto fput;
|
2012-08-21 19:03:32 +07:00
|
|
|
}
|
2014-08-15 07:02:55 +07:00
|
|
|
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock->fl_type = F_UNLCK;
|
2014-08-22 21:18:44 +07:00
|
|
|
file_lock->fl_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(stp->st_stateowner));
|
2012-08-21 19:03:32 +07:00
|
|
|
file_lock->fl_pid = current->tgid;
|
|
|
|
file_lock->fl_file = filp;
|
|
|
|
file_lock->fl_flags = FL_POSIX;
|
|
|
|
file_lock->fl_lmops = &nfsd_posix_mng_ops;
|
|
|
|
file_lock->fl_start = locku->lu_offset;
|
|
|
|
|
|
|
|
file_lock->fl_end = last_byte_offset(locku->lu_offset,
|
|
|
|
locku->lu_length);
|
|
|
|
nfs4_transform_lock_offset(file_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-08-21 19:03:32 +07:00
|
|
|
err = vfs_lock_file(filp, F_SETLK, file_lock, NULL);
|
2006-10-20 13:29:01 +07:00
|
|
|
if (err) {
|
2006-11-29 04:26:41 +07:00
|
|
|
dprintk("NFSD: nfs4_locku: vfs_lock_file failed!\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
goto out_nfserr;
|
|
|
|
}
|
2015-10-01 20:05:50 +07:00
|
|
|
nfs4_inc_and_copy_stateid(&locku->lu_stateid, &stp->st_stid);
|
2014-07-11 01:07:26 +07:00
|
|
|
fput:
|
|
|
|
fput(filp);
|
2014-07-30 08:34:16 +07:00
|
|
|
put_stateid:
|
2016-06-15 10:28:04 +07:00
|
|
|
mutex_unlock(&stp->st_mutex);
|
2014-07-30 08:34:16 +07:00
|
|
|
nfs4_put_stid(&stp->st_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
2013-04-02 03:37:12 +07:00
|
|
|
nfsd4_bump_seqid(cstate, status);
|
2012-08-21 19:03:32 +07:00
|
|
|
if (file_lock)
|
|
|
|
locks_free_lock(file_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
|
|
|
|
out_nfserr:
|
2006-10-20 13:29:01 +07:00
|
|
|
status = nfserrno(err);
|
2014-07-11 01:07:26 +07:00
|
|
|
goto fput;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* returns
|
2014-07-24 03:17:41 +07:00
|
|
|
* true: locks held by lockowner
|
|
|
|
* false: no locks held by lockowner
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2014-07-24 03:17:41 +07:00
|
|
|
static bool
|
|
|
|
check_for_locks(struct nfs4_file *fp, struct nfs4_lockowner *lowner)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2015-01-17 03:05:55 +07:00
|
|
|
struct file_lock *fl;
|
2014-07-24 03:17:41 +07:00
|
|
|
int status = false;
|
|
|
|
struct file *filp = find_any_file(fp);
|
|
|
|
struct inode *inode;
|
2015-01-17 03:05:55 +07:00
|
|
|
struct file_lock_context *flctx;
|
2014-07-24 03:17:41 +07:00
|
|
|
|
|
|
|
if (!filp) {
|
|
|
|
/* Any valid lock stateid should have some sort of access */
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
inode = file_inode(filp);
|
2015-01-17 03:05:55 +07:00
|
|
|
flctx = inode->i_flctx;
|
|
|
|
|
|
|
|
if (flctx && !list_empty_careful(&flctx->flc_posix)) {
|
2015-01-17 03:05:57 +07:00
|
|
|
spin_lock(&flctx->flc_lock);
|
2015-01-17 03:05:55 +07:00
|
|
|
list_for_each_entry(fl, &flctx->flc_posix, fl_list) {
|
|
|
|
if (fl->fl_owner == (fl_owner_t)lowner) {
|
|
|
|
status = true;
|
|
|
|
break;
|
|
|
|
}
|
2006-01-19 08:43:22 +07:00
|
|
|
}
|
2015-01-17 03:05:57 +07:00
|
|
|
spin_unlock(&flctx->flc_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-07-24 03:17:41 +07:00
|
|
|
fput(filp);
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2006-12-13 15:35:38 +07:00
|
|
|
nfsd4_release_lockowner(struct svc_rqst *rqstp,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd4_release_lockowner *rlockowner)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
clientid_t *clid = &rlockowner->rl_clientid;
|
2014-07-30 08:34:37 +07:00
|
|
|
struct nfs4_stateowner *sop;
|
|
|
|
struct nfs4_lockowner *lo = NULL;
|
2011-09-08 03:06:42 +07:00
|
|
|
struct nfs4_ol_stateid *stp;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct xdr_netobj *owner = &rlockowner->rl_owner;
|
2014-07-30 08:34:36 +07:00
|
|
|
unsigned int hashval = ownerstr_hashval(owner);
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32 status;
|
2012-11-14 22:21:05 +07:00
|
|
|
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
|
2014-07-30 08:34:35 +07:00
|
|
|
struct nfs4_client *clp;
|
2016-07-14 03:40:14 +07:00
|
|
|
LIST_HEAD (reaplist);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
dprintk("nfsd4_release_lockowner clientid: (%08x/%08x):\n",
|
|
|
|
clid->cl_boot, clid->cl_id);
|
|
|
|
|
2014-06-30 22:48:44 +07:00
|
|
|
status = lookup_clientid(clid, cstate, nn);
|
2012-12-04 05:24:41 +07:00
|
|
|
if (status)
|
2014-07-30 19:27:27 +07:00
|
|
|
return status;
|
2012-12-04 05:24:41 +07:00
|
|
|
|
2014-07-30 08:34:36 +07:00
|
|
|
clp = cstate->clp;
|
2014-06-30 22:48:41 +07:00
|
|
|
/* Find the matching lock stateowner */
|
2014-07-30 08:34:36 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
2014-07-30 08:34:37 +07:00
|
|
|
list_for_each_entry(sop, &clp->cl_ownerstr_hashtbl[hashval],
|
2014-07-30 08:34:36 +07:00
|
|
|
so_strhash) {
|
2014-06-30 22:48:41 +07:00
|
|
|
|
2014-07-30 08:34:37 +07:00
|
|
|
if (sop->so_is_open_owner || !same_owner_str(sop, owner))
|
|
|
|
continue;
|
2014-06-30 22:48:41 +07:00
|
|
|
|
2014-07-30 08:34:37 +07:00
|
|
|
/* see if there are still any locks associated with it */
|
|
|
|
lo = lockowner(sop);
|
|
|
|
list_for_each_entry(stp, &sop->so_stateids, st_perstateowner) {
|
|
|
|
if (check_for_locks(stp->st_stid.sc_file, lo)) {
|
|
|
|
status = nfserr_locks_held;
|
|
|
|
spin_unlock(&clp->cl_lock);
|
2014-07-30 19:27:27 +07:00
|
|
|
return status;
|
2014-07-30 08:34:37 +07:00
|
|
|
}
|
2014-07-30 08:34:31 +07:00
|
|
|
}
|
2014-07-30 08:34:37 +07:00
|
|
|
|
2014-08-22 21:18:43 +07:00
|
|
|
nfs4_get_stateowner(sop);
|
2014-07-30 08:34:37 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2016-07-14 03:40:14 +07:00
|
|
|
if (!lo) {
|
|
|
|
spin_unlock(&clp->cl_lock);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
unhash_lockowner_locked(lo);
|
|
|
|
while (!list_empty(&lo->lo_owner.so_stateids)) {
|
|
|
|
stp = list_first_entry(&lo->lo_owner.so_stateids,
|
|
|
|
struct nfs4_ol_stateid,
|
|
|
|
st_perstateowner);
|
|
|
|
WARN_ON(!unhash_lock_stateid(stp));
|
|
|
|
put_ol_stateid_locked(stp, &reaplist);
|
|
|
|
}
|
2014-07-30 08:34:35 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
2016-07-14 03:40:14 +07:00
|
|
|
free_ol_stateid_reaplist(&reaplist);
|
|
|
|
nfs4_put_stateowner(&lo->lo_owner);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline struct nfs4_client_reclaim *
|
2005-06-24 12:03:52 +07:00
|
|
|
alloc_reclaim(void)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-06-24 12:03:52 +07:00
|
|
|
return kmalloc(sizeof(struct nfs4_client_reclaim), GFP_KERNEL);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2012-11-13 03:00:55 +07:00
|
|
|
bool
|
2012-11-14 22:21:16 +07:00
|
|
|
nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn)
|
2005-06-24 12:04:30 +07:00
|
|
|
{
|
2012-11-13 03:00:55 +07:00
|
|
|
struct nfs4_client_reclaim *crp;
|
2005-06-24 12:04:30 +07:00
|
|
|
|
2012-11-14 22:21:16 +07:00
|
|
|
crp = nfsd4_find_reclaim_client(name, nn);
|
2012-11-13 03:00:55 +07:00
|
|
|
return (crp && crp->cr_clp);
|
2005-06-24 12:04:30 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* failure => all reset bets are off, nfserr_no_grace...
|
|
|
|
*/
|
2012-11-13 03:00:54 +07:00
|
|
|
struct nfs4_client_reclaim *
|
2012-11-14 22:21:16 +07:00
|
|
|
nfs4_client_to_reclaim(const char *name, struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int strhashval;
|
2012-11-13 03:00:54 +07:00
|
|
|
struct nfs4_client_reclaim *crp;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-06-24 12:03:52 +07:00
|
|
|
dprintk("NFSD nfs4_client_to_reclaim NAME: %.*s\n", HEXDIR_LEN, name);
|
|
|
|
crp = alloc_reclaim();
|
2012-11-13 03:00:54 +07:00
|
|
|
if (crp) {
|
|
|
|
strhashval = clientstr_hashval(name);
|
|
|
|
INIT_LIST_HEAD(&crp->cr_strhash);
|
2012-11-14 22:21:16 +07:00
|
|
|
list_add(&crp->cr_strhash, &nn->reclaim_str_hashtbl[strhashval]);
|
2012-11-13 03:00:54 +07:00
|
|
|
memcpy(crp->cr_recdir, name, HEXDIR_LEN);
|
2012-11-13 03:00:55 +07:00
|
|
|
crp->cr_clp = NULL;
|
2012-11-14 22:21:16 +07:00
|
|
|
nn->reclaim_str_hashtbl_size++;
|
2012-11-13 03:00:54 +07:00
|
|
|
}
|
|
|
|
return crp;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2012-11-13 03:00:53 +07:00
|
|
|
void
|
2012-11-14 22:21:16 +07:00
|
|
|
nfs4_remove_reclaim_record(struct nfs4_client_reclaim *crp, struct nfsd_net *nn)
|
2012-11-13 03:00:53 +07:00
|
|
|
{
|
|
|
|
list_del(&crp->cr_strhash);
|
|
|
|
kfree(crp);
|
2012-11-14 22:21:16 +07:00
|
|
|
nn->reclaim_str_hashtbl_size--;
|
2012-11-13 03:00:53 +07:00
|
|
|
}
|
|
|
|
|
2012-03-22 03:42:43 +07:00
|
|
|
void
|
2012-11-14 22:21:16 +07:00
|
|
|
nfs4_release_reclaim(struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client_reclaim *crp = NULL;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < CLIENT_HASH_SIZE; i++) {
|
2012-11-14 22:21:16 +07:00
|
|
|
while (!list_empty(&nn->reclaim_str_hashtbl[i])) {
|
|
|
|
crp = list_entry(nn->reclaim_str_hashtbl[i].next,
|
2005-04-17 05:20:36 +07:00
|
|
|
struct nfs4_client_reclaim, cr_strhash);
|
2012-11-14 22:21:16 +07:00
|
|
|
nfs4_remove_reclaim_record(crp, nn);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2012-11-26 02:48:10 +07:00
|
|
|
WARN_ON_ONCE(nn->reclaim_str_hashtbl_size);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* called from OPEN, CLAIM_PREVIOUS with a new clientid. */
|
2012-03-22 03:42:43 +07:00
|
|
|
struct nfs4_client_reclaim *
|
2012-11-14 22:21:16 +07:00
|
|
|
nfsd4_find_reclaim_client(const char *recdir, struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int strhashval;
|
|
|
|
struct nfs4_client_reclaim *crp = NULL;
|
|
|
|
|
2012-11-13 03:00:52 +07:00
|
|
|
dprintk("NFSD: nfs4_find_reclaim_client for recdir %s\n", recdir);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-11-13 03:00:52 +07:00
|
|
|
strhashval = clientstr_hashval(recdir);
|
2012-11-14 22:21:16 +07:00
|
|
|
list_for_each_entry(crp, &nn->reclaim_str_hashtbl[strhashval], cr_strhash) {
|
2012-11-13 03:00:52 +07:00
|
|
|
if (same_name(crp->cr_recdir, recdir)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
return crp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called from OPEN. Look for clientid in reclaim list.
|
|
|
|
*/
|
2006-10-20 13:28:59 +07:00
|
|
|
__be32
|
2014-06-30 22:48:47 +07:00
|
|
|
nfs4_check_open_reclaim(clientid_t *clid,
|
|
|
|
struct nfsd4_compound_state *cstate,
|
|
|
|
struct nfsd_net *nn)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-06-30 22:48:47 +07:00
|
|
|
__be32 status;
|
2012-03-21 20:52:02 +07:00
|
|
|
|
|
|
|
/* find clientid in conf_id_hashtbl */
|
2014-06-30 22:48:47 +07:00
|
|
|
status = lookup_clientid(clid, cstate, nn);
|
|
|
|
if (status)
|
2012-03-21 20:52:02 +07:00
|
|
|
return nfserr_reclaim_bad;
|
|
|
|
|
2014-09-13 03:40:22 +07:00
|
|
|
if (test_bit(NFSD4_CLIENT_RECLAIM_COMPLETE, &cstate->clp->cl_flags))
|
|
|
|
return nfserr_no_grace;
|
|
|
|
|
2014-06-30 22:48:47 +07:00
|
|
|
if (nfsd4_client_record_check(cstate->clp))
|
|
|
|
return nfserr_reclaim_bad;
|
|
|
|
|
|
|
|
return nfs_ok;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2011-11-02 00:35:21 +07:00
|
|
|
#ifdef CONFIG_NFSD_FAULT_INJECTION
|
2014-07-30 19:27:21 +07:00
|
|
|
static inline void
|
|
|
|
put_client(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
atomic_dec(&clp->cl_refcount);
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:24 +07:00
|
|
|
static struct nfs4_client *
|
|
|
|
nfsd_find_client(struct sockaddr_storage *addr, size_t addr_size)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
list_for_each_entry(clp, &nn->client_lru, cl_lru) {
|
|
|
|
if (memcmp(&clp->cl_addr, addr, addr_size) == 0)
|
|
|
|
return clp;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:17 +07:00
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_print_clients(void)
|
2014-07-30 19:27:17 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry(clp, &nn->client_lru, cl_lru) {
|
|
|
|
rpc_ntop((struct sockaddr *)&clp->cl_addr, buf, sizeof(buf));
|
|
|
|
pr_info("NFS Client: %s\n", buf);
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
2011-11-02 00:35:21 +07:00
|
|
|
|
2014-07-30 19:27:18 +07:00
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_forget_client(struct sockaddr_storage *addr, size_t addr_size)
|
2014-07-30 19:27:18 +07:00
|
|
|
{
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
clp = nfsd_find_client(addr, addr_size);
|
|
|
|
if (clp) {
|
|
|
|
if (mark_client_expired_locked(clp) == nfs_ok)
|
|
|
|
++count;
|
|
|
|
else
|
|
|
|
clp = NULL;
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
if (clp)
|
|
|
|
expire_client(clp);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:19 +07:00
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_forget_clients(u64 max)
|
2014-07-30 19:27:19 +07:00
|
|
|
{
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfs4_client *clp, *next;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry_safe(clp, next, &nn->client_lru, cl_lru) {
|
|
|
|
if (mark_client_expired_locked(clp) == nfs_ok) {
|
|
|
|
list_add(&clp->cl_lru, &reaplist);
|
|
|
|
if (max != 0 && ++count >= max)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
list_for_each_entry_safe(clp, next, &reaplist, cl_lru)
|
|
|
|
expire_client(clp);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2012-11-29 23:40:44 +07:00
|
|
|
static void nfsd_print_count(struct nfs4_client *clp, unsigned int count,
|
|
|
|
const char *type)
|
|
|
|
{
|
|
|
|
char buf[INET6_ADDRSTRLEN];
|
2012-12-08 04:17:28 +07:00
|
|
|
rpc_ntop((struct sockaddr *)&clp->cl_addr, buf, sizeof(buf));
|
2012-11-29 23:40:44 +07:00
|
|
|
printk(KERN_INFO "NFS Client: %s has %u %s\n", buf, count, type);
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:21 +07:00
|
|
|
static void
|
|
|
|
nfsd_inject_add_lock_to_list(struct nfs4_ol_stateid *lst,
|
|
|
|
struct list_head *collect)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp = lst->st_stid.sc_client;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
|
|
|
|
if (!collect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
atomic_inc(&clp->cl_refcount);
|
|
|
|
list_add(&lst->st_locks, collect);
|
|
|
|
}
|
|
|
|
|
2014-06-30 22:48:38 +07:00
|
|
|
static u64 nfsd_foreach_client_lock(struct nfs4_client *clp, u64 max,
|
2014-07-30 19:27:20 +07:00
|
|
|
struct list_head *collect,
|
2015-08-24 23:41:47 +07:00
|
|
|
bool (*func)(struct nfs4_ol_stateid *))
|
2012-11-29 23:40:40 +07:00
|
|
|
{
|
|
|
|
struct nfs4_openowner *oop;
|
|
|
|
struct nfs4_ol_stateid *stp, *st_next;
|
2014-06-30 22:48:38 +07:00
|
|
|
struct nfs4_ol_stateid *lst, *lst_next;
|
2012-11-29 23:40:40 +07:00
|
|
|
u64 count = 0;
|
|
|
|
|
2014-07-30 19:27:21 +07:00
|
|
|
spin_lock(&clp->cl_lock);
|
2012-11-29 23:40:40 +07:00
|
|
|
list_for_each_entry(oop, &clp->cl_openowners, oo_perclient) {
|
2014-06-30 22:48:38 +07:00
|
|
|
list_for_each_entry_safe(stp, st_next,
|
|
|
|
&oop->oo_owner.so_stateids, st_perstateowner) {
|
|
|
|
list_for_each_entry_safe(lst, lst_next,
|
|
|
|
&stp->st_locks, st_locks) {
|
2014-07-30 19:27:20 +07:00
|
|
|
if (func) {
|
2015-08-24 23:41:47 +07:00
|
|
|
if (func(lst))
|
|
|
|
nfsd_inject_add_lock_to_list(lst,
|
|
|
|
collect);
|
2014-07-30 19:27:20 +07:00
|
|
|
}
|
2014-07-30 19:27:21 +07:00
|
|
|
++count;
|
|
|
|
/*
|
|
|
|
* Despite the fact that these functions deal
|
|
|
|
* with 64-bit integers for "count", we must
|
|
|
|
* ensure that it doesn't blow up the
|
|
|
|
* clp->cl_refcount. Throw a warning if we
|
|
|
|
* start to approach INT_MAX here.
|
|
|
|
*/
|
|
|
|
WARN_ON_ONCE(count == (INT_MAX / 2));
|
|
|
|
if (count == max)
|
|
|
|
goto out;
|
2012-11-29 23:40:40 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-30 19:27:21 +07:00
|
|
|
out:
|
|
|
|
spin_unlock(&clp->cl_lock);
|
2012-11-29 23:40:40 +07:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:21 +07:00
|
|
|
static u64
|
|
|
|
nfsd_collect_client_locks(struct nfs4_client *clp, struct list_head *collect,
|
|
|
|
u64 max)
|
2012-11-29 23:40:40 +07:00
|
|
|
{
|
2014-07-30 19:27:21 +07:00
|
|
|
return nfsd_foreach_client_lock(clp, max, collect, unhash_lock_stateid);
|
2012-11-29 23:40:40 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:21 +07:00
|
|
|
static u64
|
|
|
|
nfsd_print_client_locks(struct nfs4_client *clp)
|
2012-11-29 23:40:44 +07:00
|
|
|
{
|
2014-07-30 19:27:21 +07:00
|
|
|
u64 count = nfsd_foreach_client_lock(clp, 0, NULL, NULL);
|
2012-11-29 23:40:44 +07:00
|
|
|
nfsd_print_count(clp, count, "locked files");
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:21 +07:00
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_print_locks(void)
|
2014-07-30 19:27:21 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry(clp, &nn->client_lru, cl_lru)
|
|
|
|
count += nfsd_print_client_locks(clp);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_reap_locks(struct list_head *reaplist)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfs4_ol_stateid *stp, *next;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(stp, next, reaplist, st_locks) {
|
|
|
|
list_del_init(&stp->st_locks);
|
|
|
|
clp = stp->st_stid.sc_client;
|
|
|
|
nfs4_put_stid(&stp->st_stid);
|
|
|
|
put_client(clp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_forget_client_locks(struct sockaddr_storage *addr, size_t addr_size)
|
2014-07-30 19:27:21 +07:00
|
|
|
{
|
|
|
|
unsigned int count = 0;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
clp = nfsd_find_client(addr, addr_size);
|
|
|
|
if (clp)
|
|
|
|
count = nfsd_collect_client_locks(clp, &reaplist, 0);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
nfsd_reap_locks(&reaplist);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_forget_locks(u64 max)
|
2014-07-30 19:27:21 +07:00
|
|
|
{
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry(clp, &nn->client_lru, cl_lru) {
|
|
|
|
count += nfsd_collect_client_locks(clp, &reaplist, max - count);
|
|
|
|
if (max != 0 && count >= max)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
nfsd_reap_locks(&reaplist);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:22 +07:00
|
|
|
static u64
|
|
|
|
nfsd_foreach_client_openowner(struct nfs4_client *clp, u64 max,
|
|
|
|
struct list_head *collect,
|
|
|
|
void (*func)(struct nfs4_openowner *))
|
2012-11-29 23:40:41 +07:00
|
|
|
{
|
|
|
|
struct nfs4_openowner *oop, *next;
|
2014-07-30 19:27:22 +07:00
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
2012-11-29 23:40:41 +07:00
|
|
|
u64 count = 0;
|
|
|
|
|
2014-07-30 19:27:22 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
|
|
|
spin_lock(&clp->cl_lock);
|
2012-11-29 23:40:41 +07:00
|
|
|
list_for_each_entry_safe(oop, next, &clp->cl_openowners, oo_perclient) {
|
2014-07-30 19:27:22 +07:00
|
|
|
if (func) {
|
2012-11-29 23:40:41 +07:00
|
|
|
func(oop);
|
2014-07-30 19:27:22 +07:00
|
|
|
if (collect) {
|
|
|
|
atomic_inc(&clp->cl_refcount);
|
|
|
|
list_add(&oop->oo_perclient, collect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
++count;
|
|
|
|
/*
|
|
|
|
* Despite the fact that these functions deal with
|
|
|
|
* 64-bit integers for "count", we must ensure that
|
|
|
|
* it doesn't blow up the clp->cl_refcount. Throw a
|
|
|
|
* warning if we start to approach INT_MAX here.
|
|
|
|
*/
|
|
|
|
WARN_ON_ONCE(count == (INT_MAX / 2));
|
|
|
|
if (count == max)
|
2012-11-29 23:40:41 +07:00
|
|
|
break;
|
|
|
|
}
|
2014-07-30 19:27:22 +07:00
|
|
|
spin_unlock(&clp->cl_lock);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u64
|
|
|
|
nfsd_print_client_openowners(struct nfs4_client *clp)
|
|
|
|
{
|
|
|
|
u64 count = nfsd_foreach_client_openowner(clp, 0, NULL, NULL);
|
|
|
|
|
|
|
|
nfsd_print_count(clp, count, "openowners");
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u64
|
|
|
|
nfsd_collect_client_openowners(struct nfs4_client *clp,
|
|
|
|
struct list_head *collect, u64 max)
|
|
|
|
{
|
|
|
|
return nfsd_foreach_client_openowner(clp, max, collect,
|
|
|
|
unhash_openowner_locked);
|
|
|
|
}
|
|
|
|
|
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_print_openowners(void)
|
2014-07-30 19:27:22 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry(clp, &nn->client_lru, cl_lru)
|
|
|
|
count += nfsd_print_client_openowners(clp);
|
|
|
|
spin_unlock(&nn->client_lock);
|
2012-11-29 23:40:41 +07:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:22 +07:00
|
|
|
static void
|
|
|
|
nfsd_reap_openowners(struct list_head *reaplist)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfs4_openowner *oop, *next;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(oop, next, reaplist, oo_perclient) {
|
|
|
|
list_del_init(&oop->oo_perclient);
|
|
|
|
clp = oop->oo_owner.so_client;
|
|
|
|
release_openowner(oop);
|
|
|
|
put_client(clp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_forget_client_openowners(struct sockaddr_storage *addr,
|
|
|
|
size_t addr_size)
|
2012-11-29 23:40:41 +07:00
|
|
|
{
|
2014-07-30 19:27:22 +07:00
|
|
|
unsigned int count = 0;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
clp = nfsd_find_client(addr, addr_size);
|
|
|
|
if (clp)
|
|
|
|
count = nfsd_collect_client_openowners(clp, &reaplist, 0);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
nfsd_reap_openowners(&reaplist);
|
|
|
|
return count;
|
2012-11-29 23:40:41 +07:00
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:22 +07:00
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_forget_openowners(u64 max)
|
2012-11-29 23:40:44 +07:00
|
|
|
{
|
2014-07-30 19:27:22 +07:00
|
|
|
u64 count = 0;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry(clp, &nn->client_lru, cl_lru) {
|
|
|
|
count += nfsd_collect_client_openowners(clp, &reaplist,
|
|
|
|
max - count);
|
|
|
|
if (max != 0 && count >= max)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
nfsd_reap_openowners(&reaplist);
|
2012-11-29 23:40:44 +07:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2012-11-29 23:40:42 +07:00
|
|
|
static u64 nfsd_find_all_delegations(struct nfs4_client *clp, u64 max,
|
|
|
|
struct list_head *victims)
|
|
|
|
{
|
|
|
|
struct nfs4_delegation *dp, *next;
|
2014-07-30 19:27:23 +07:00
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
2012-11-29 23:40:42 +07:00
|
|
|
u64 count = 0;
|
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
lockdep_assert_held(&nn->client_lock);
|
|
|
|
|
|
|
|
spin_lock(&state_lock);
|
2012-11-29 23:40:42 +07:00
|
|
|
list_for_each_entry_safe(dp, next, &clp->cl_delegations, dl_perclnt) {
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
if (victims) {
|
|
|
|
/*
|
|
|
|
* It's not safe to mess with delegations that have a
|
|
|
|
* non-zero dl_time. They might have already been broken
|
|
|
|
* and could be processed by the laundromat outside of
|
|
|
|
* the state_lock. Just leave them be.
|
|
|
|
*/
|
|
|
|
if (dp->dl_time != 0)
|
|
|
|
continue;
|
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
atomic_inc(&clp->cl_refcount);
|
2015-08-24 23:41:48 +07:00
|
|
|
WARN_ON(!unhash_delegation_locked(dp));
|
2014-07-25 18:34:20 +07:00
|
|
|
list_add(&dp->dl_recall_lru, victims);
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
}
|
2014-07-30 19:27:23 +07:00
|
|
|
++count;
|
|
|
|
/*
|
|
|
|
* Despite the fact that these functions deal with
|
|
|
|
* 64-bit integers for "count", we must ensure that
|
|
|
|
* it doesn't blow up the clp->cl_refcount. Throw a
|
|
|
|
* warning if we start to approach INT_MAX here.
|
|
|
|
*/
|
|
|
|
WARN_ON_ONCE(count == (INT_MAX / 2));
|
|
|
|
if (count == max)
|
2012-11-29 23:40:42 +07:00
|
|
|
break;
|
|
|
|
}
|
2014-07-30 19:27:23 +07:00
|
|
|
spin_unlock(&state_lock);
|
2012-11-29 23:40:42 +07:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
static u64
|
|
|
|
nfsd_print_client_delegations(struct nfs4_client *clp)
|
2012-11-29 23:40:42 +07:00
|
|
|
{
|
2014-07-30 19:27:23 +07:00
|
|
|
u64 count = nfsd_find_all_delegations(clp, 0, NULL);
|
2012-11-29 23:40:42 +07:00
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
nfsd_print_count(clp, count, "delegations");
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_print_delegations(void)
|
2014-07-30 19:27:23 +07:00
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return 0;
|
2012-11-29 23:40:42 +07:00
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry(clp, &nn->client_lru, cl_lru)
|
|
|
|
count += nfsd_print_client_delegations(clp);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_forget_delegations(struct list_head *reaplist)
|
|
|
|
{
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfs4_delegation *dp, *next;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(dp, next, reaplist, dl_recall_lru) {
|
2014-07-25 18:34:21 +07:00
|
|
|
list_del_init(&dp->dl_recall_lru);
|
2014-07-30 19:27:23 +07:00
|
|
|
clp = dp->dl_stid.sc_client;
|
2013-04-10 04:02:51 +07:00
|
|
|
revoke_delegation(dp);
|
2014-07-30 19:27:23 +07:00
|
|
|
put_client(clp);
|
2014-07-25 18:34:21 +07:00
|
|
|
}
|
2014-07-30 19:27:23 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_forget_client_delegations(struct sockaddr_storage *addr,
|
|
|
|
size_t addr_size)
|
2014-07-30 19:27:23 +07:00
|
|
|
{
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
clp = nfsd_find_client(addr, addr_size);
|
|
|
|
if (clp)
|
|
|
|
count = nfsd_find_all_delegations(clp, 0, &reaplist);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
nfsd_forget_delegations(&reaplist);
|
|
|
|
return count;
|
|
|
|
}
|
2012-11-29 23:40:42 +07:00
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_forget_delegations(u64 max)
|
2014-07-30 19:27:23 +07:00
|
|
|
{
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry(clp, &nn->client_lru, cl_lru) {
|
|
|
|
count += nfsd_find_all_delegations(clp, max - count, &reaplist);
|
|
|
|
if (max != 0 && count >= max)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
nfsd_forget_delegations(&reaplist);
|
2012-11-29 23:40:42 +07:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
static void
|
|
|
|
nfsd_recall_delegations(struct list_head *reaplist)
|
2012-11-29 23:40:42 +07:00
|
|
|
{
|
2014-07-30 19:27:23 +07:00
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfs4_delegation *dp, *next;
|
2012-11-29 23:40:42 +07:00
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
list_for_each_entry_safe(dp, next, reaplist, dl_recall_lru) {
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
list_del_init(&dp->dl_recall_lru);
|
2014-07-30 19:27:23 +07:00
|
|
|
clp = dp->dl_stid.sc_client;
|
|
|
|
/*
|
|
|
|
* We skipped all entries that had a zero dl_time before,
|
|
|
|
* so we can now reset the dl_time back to 0. If a delegation
|
|
|
|
* break comes in now, then it won't make any difference since
|
|
|
|
* we're recalling it either way.
|
|
|
|
*/
|
|
|
|
spin_lock(&state_lock);
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
dp->dl_time = 0;
|
2014-07-30 19:27:23 +07:00
|
|
|
spin_unlock(&state_lock);
|
2012-11-29 23:40:42 +07:00
|
|
|
nfsd_break_one_deleg(dp);
|
2014-07-30 19:27:23 +07:00
|
|
|
put_client(clp);
|
nfsd: close potential race between delegation break and laundromat
Bruce says:
There's also a preexisting expire_client/laundromat vs break race:
- expire_client/laundromat adds a delegation to its local
reaplist using the same dl_recall_lru field that a delegation
uses to track its position on the recall lru and drops the
state lock.
- a concurrent break_lease adds the delegation to the lru.
- expire/client/laundromat then walks it reaplist and sees the
lru head as just another delegation on the list....
Fix this race by checking the dl_time under the state_lock. If we find
that it's not 0, then we know that it has already been queued to the LRU
list and that we shouldn't queue it again.
In the case of destroy_client, we must also ensure that we don't hit
similar races by ensuring that we don't move any delegations to the
reaplist with a dl_time of 0. Just bump the dl_time by one before we
drop the state_lock. We're destroying the delegations anyway, so a 1s
difference there won't matter.
The fault injection code also requires a bit of surgery here:
First, in the case of nfsd_forget_client_delegations, we must prevent
the same sort of race vs. the delegation break callback. For that, we
just increment the dl_time to ensure that a delegation callback can't
race in while we're working on it.
We can't do that for nfsd_recall_client_delegations, as we need to have
it actually queue the delegation, and that won't happen if we increment
the dl_time. The state lock is held over that function, so we don't need
to worry about these sorts of races there.
There is one other potential bug nfsd_recall_client_delegations though.
Entries on the victims list are not dequeued before calling
nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
we do that there.
Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
2014-07-09 01:02:49 +07:00
|
|
|
}
|
2014-07-30 19:27:23 +07:00
|
|
|
}
|
2012-11-29 23:40:42 +07:00
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_recall_client_delegations(struct sockaddr_storage *addr,
|
2014-07-30 19:27:23 +07:00
|
|
|
size_t addr_size)
|
|
|
|
{
|
|
|
|
u64 count = 0;
|
|
|
|
struct nfs4_client *clp;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
|
|
|
|
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
clp = nfsd_find_client(addr, addr_size);
|
|
|
|
if (clp)
|
|
|
|
count = nfsd_find_all_delegations(clp, 0, &reaplist);
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
nfsd_recall_delegations(&reaplist);
|
2012-11-29 23:40:42 +07:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
u64
|
2014-07-30 19:27:24 +07:00
|
|
|
nfsd_inject_recall_delegations(u64 max)
|
2012-11-29 23:40:44 +07:00
|
|
|
{
|
|
|
|
u64 count = 0;
|
2014-07-30 19:27:23 +07:00
|
|
|
struct nfs4_client *clp, *next;
|
|
|
|
struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
|
|
|
|
nfsd_net_id);
|
|
|
|
LIST_HEAD(reaplist);
|
2012-11-29 23:40:44 +07:00
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
if (!nfsd_netns_ready(nn))
|
|
|
|
return count;
|
2012-11-29 23:40:44 +07:00
|
|
|
|
2014-07-30 19:27:23 +07:00
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
list_for_each_entry_safe(clp, next, &nn->client_lru, cl_lru) {
|
|
|
|
count += nfsd_find_all_delegations(clp, max - count, &reaplist);
|
|
|
|
if (max != 0 && ++count >= max)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
nfsd_recall_delegations(&reaplist);
|
2012-11-29 23:40:44 +07:00
|
|
|
return count;
|
|
|
|
}
|
2011-11-02 00:35:21 +07:00
|
|
|
#endif /* CONFIG_NFSD_FAULT_INJECTION */
|
|
|
|
|
2007-07-17 18:04:39 +07:00
|
|
|
/*
|
|
|
|
* Since the lifetime of a delegation isn't limited to that of an open, a
|
|
|
|
* client may quite reasonably hang on to a delegation as long as it has
|
|
|
|
* the inode cached. This becomes an obvious problem the first time a
|
|
|
|
* client's inode cache approaches the size of the server's total memory.
|
|
|
|
*
|
|
|
|
* For now we avoid this problem by imposing a hard limit on the number
|
|
|
|
* of delegations, which varies according to the server's memory size.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
set_max_delegations(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Allow at most 4 delegations per megabyte of RAM. Quick
|
|
|
|
* estimates suggest that in the worst case (where every delegation
|
|
|
|
* is for a different inode), a delegation could take about 1.5K,
|
|
|
|
* giving a worst case usage of about 6% of memory.
|
|
|
|
*/
|
|
|
|
max_delegations = nr_free_buffer_pages() >> (20 - 2 - PAGE_SHIFT);
|
|
|
|
}
|
|
|
|
|
2012-11-26 19:22:13 +07:00
|
|
|
static int nfs4_state_create_net(struct net *net)
|
2012-11-14 22:21:21 +07:00
|
|
|
{
|
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
|
|
int i;
|
|
|
|
|
|
|
|
nn->conf_id_hashtbl = kmalloc(sizeof(struct list_head) *
|
|
|
|
CLIENT_HASH_SIZE, GFP_KERNEL);
|
|
|
|
if (!nn->conf_id_hashtbl)
|
2012-11-14 22:21:26 +07:00
|
|
|
goto err;
|
2012-11-14 22:21:31 +07:00
|
|
|
nn->unconf_id_hashtbl = kmalloc(sizeof(struct list_head) *
|
|
|
|
CLIENT_HASH_SIZE, GFP_KERNEL);
|
|
|
|
if (!nn->unconf_id_hashtbl)
|
|
|
|
goto err_unconf_id;
|
2012-11-14 22:21:51 +07:00
|
|
|
nn->sessionid_hashtbl = kmalloc(sizeof(struct list_head) *
|
|
|
|
SESSION_HASH_SIZE, GFP_KERNEL);
|
|
|
|
if (!nn->sessionid_hashtbl)
|
|
|
|
goto err_sessionid;
|
2012-11-14 22:21:21 +07:00
|
|
|
|
2012-11-14 22:21:26 +07:00
|
|
|
for (i = 0; i < CLIENT_HASH_SIZE; i++) {
|
2012-11-14 22:21:21 +07:00
|
|
|
INIT_LIST_HEAD(&nn->conf_id_hashtbl[i]);
|
2012-11-14 22:21:31 +07:00
|
|
|
INIT_LIST_HEAD(&nn->unconf_id_hashtbl[i]);
|
2012-11-14 22:21:26 +07:00
|
|
|
}
|
2012-11-14 22:21:51 +07:00
|
|
|
for (i = 0; i < SESSION_HASH_SIZE; i++)
|
|
|
|
INIT_LIST_HEAD(&nn->sessionid_hashtbl[i]);
|
2012-11-14 22:21:26 +07:00
|
|
|
nn->conf_name_tree = RB_ROOT;
|
2012-11-14 22:21:36 +07:00
|
|
|
nn->unconf_name_tree = RB_ROOT;
|
2012-11-14 22:21:56 +07:00
|
|
|
INIT_LIST_HEAD(&nn->client_lru);
|
2012-11-14 22:22:01 +07:00
|
|
|
INIT_LIST_HEAD(&nn->close_lru);
|
2013-03-22 02:19:33 +07:00
|
|
|
INIT_LIST_HEAD(&nn->del_recall_lru);
|
2016-09-17 03:28:25 +07:00
|
|
|
INIT_LIST_HEAD(&nn->blocked_locks_lru);
|
2012-11-26 19:21:58 +07:00
|
|
|
spin_lock_init(&nn->client_lock);
|
2012-11-14 22:21:21 +07:00
|
|
|
|
2012-11-14 22:22:17 +07:00
|
|
|
INIT_DELAYED_WORK(&nn->laundromat_work, laundromat_main);
|
2012-11-26 19:22:13 +07:00
|
|
|
get_net(net);
|
2012-11-14 22:22:17 +07:00
|
|
|
|
2012-11-14 22:21:21 +07:00
|
|
|
return 0;
|
2012-11-14 22:21:26 +07:00
|
|
|
|
2012-11-14 22:21:51 +07:00
|
|
|
err_sessionid:
|
2012-11-14 22:21:41 +07:00
|
|
|
kfree(nn->unconf_id_hashtbl);
|
2012-11-14 22:21:31 +07:00
|
|
|
err_unconf_id:
|
|
|
|
kfree(nn->conf_id_hashtbl);
|
2012-11-14 22:21:26 +07:00
|
|
|
err:
|
|
|
|
return -ENOMEM;
|
2012-11-14 22:21:21 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2012-11-26 19:22:08 +07:00
|
|
|
nfs4_state_destroy_net(struct net *net)
|
2012-11-14 22:21:21 +07:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct nfs4_client *clp = NULL;
|
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
|
|
|
|
|
|
for (i = 0; i < CLIENT_HASH_SIZE; i++) {
|
|
|
|
while (!list_empty(&nn->conf_id_hashtbl[i])) {
|
|
|
|
clp = list_entry(nn->conf_id_hashtbl[i].next, struct nfs4_client, cl_idhash);
|
|
|
|
destroy_client(clp);
|
|
|
|
}
|
|
|
|
}
|
2012-11-14 22:21:36 +07:00
|
|
|
|
2014-03-26 21:09:30 +07:00
|
|
|
for (i = 0; i < CLIENT_HASH_SIZE; i++) {
|
|
|
|
while (!list_empty(&nn->unconf_id_hashtbl[i])) {
|
|
|
|
clp = list_entry(nn->unconf_id_hashtbl[i].next, struct nfs4_client, cl_idhash);
|
|
|
|
destroy_client(clp);
|
|
|
|
}
|
2012-11-14 22:21:36 +07:00
|
|
|
}
|
|
|
|
|
2012-11-14 22:21:51 +07:00
|
|
|
kfree(nn->sessionid_hashtbl);
|
2012-11-14 22:21:31 +07:00
|
|
|
kfree(nn->unconf_id_hashtbl);
|
2012-11-14 22:21:21 +07:00
|
|
|
kfree(nn->conf_id_hashtbl);
|
2012-11-26 19:22:08 +07:00
|
|
|
put_net(net);
|
2012-11-14 22:21:21 +07:00
|
|
|
}
|
|
|
|
|
2012-11-26 19:22:18 +07:00
|
|
|
int
|
2012-11-26 19:22:13 +07:00
|
|
|
nfs4_state_start_net(struct net *net)
|
2005-06-24 12:03:30 +07:00
|
|
|
{
|
2012-07-25 19:56:58 +07:00
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2010-03-04 02:52:55 +07:00
|
|
|
int ret;
|
|
|
|
|
2012-11-26 19:22:13 +07:00
|
|
|
ret = nfs4_state_create_net(net);
|
2012-11-14 22:21:21 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2012-07-25 19:57:45 +07:00
|
|
|
nn->boot_time = get_seconds();
|
2012-07-25 19:57:37 +07:00
|
|
|
nn->grace_ended = false;
|
2015-08-06 23:47:02 +07:00
|
|
|
nn->nfsd4_manager.block_opens = true;
|
2014-09-13 03:40:21 +07:00
|
|
|
locks_start_grace(net, &nn->nfsd4_manager);
|
|
|
|
nfsd4_client_tracking_init(net);
|
2012-11-26 19:22:13 +07:00
|
|
|
printk(KERN_INFO "NFSD: starting %ld-second grace period (net %p)\n",
|
2012-11-27 18:11:49 +07:00
|
|
|
nn->nfsd4_grace, net);
|
|
|
|
queue_delayed_work(laundry_wq, &nn->laundromat_work, nn->nfsd4_grace * HZ);
|
2012-11-26 19:22:13 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialization to perform when the nfsd service is started: */
|
|
|
|
|
|
|
|
int
|
|
|
|
nfs4_state_start(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2010-03-04 02:52:55 +07:00
|
|
|
ret = set_callback_cred();
|
2012-11-26 19:22:13 +07:00
|
|
|
if (ret)
|
|
|
|
return -ENOMEM;
|
2015-08-20 18:17:01 +07:00
|
|
|
laundry_wq = alloc_workqueue("%s", WQ_UNBOUND, 0, "nfsd4");
|
2012-03-05 23:42:36 +07:00
|
|
|
if (laundry_wq == NULL) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_recovery;
|
|
|
|
}
|
2010-03-04 02:52:55 +07:00
|
|
|
ret = nfsd4_create_callback_queue();
|
|
|
|
if (ret)
|
|
|
|
goto out_free_laundry;
|
2012-11-14 22:22:17 +07:00
|
|
|
|
2007-07-17 18:04:39 +07:00
|
|
|
set_max_delegations();
|
2012-11-26 19:22:13 +07:00
|
|
|
|
2010-03-04 02:52:55 +07:00
|
|
|
return 0;
|
2012-11-26 19:22:13 +07:00
|
|
|
|
2010-03-04 02:52:55 +07:00
|
|
|
out_free_laundry:
|
|
|
|
destroy_workqueue(laundry_wq);
|
2012-03-05 23:42:36 +07:00
|
|
|
out_recovery:
|
2010-03-04 02:52:55 +07:00
|
|
|
return ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2012-11-26 19:22:18 +07:00
|
|
|
void
|
2012-11-26 19:22:08 +07:00
|
|
|
nfs4_state_shutdown_net(struct net *net)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nfs4_delegation *dp = NULL;
|
|
|
|
struct list_head *pos, *next, reaplist;
|
2012-11-26 19:22:08 +07:00
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
2016-09-17 03:28:25 +07:00
|
|
|
struct nfsd4_blocked_lock *nbl;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-11-26 19:22:08 +07:00
|
|
|
cancel_delayed_work_sync(&nn->laundromat_work);
|
|
|
|
locks_end_grace(&nn->nfsd4_manager);
|
2012-11-13 03:00:56 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
INIT_LIST_HEAD(&reaplist);
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_lock(&state_lock);
|
2013-03-22 02:19:33 +07:00
|
|
|
list_for_each_safe(pos, next, &nn->del_recall_lru) {
|
2005-04-17 05:20:36 +07:00
|
|
|
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
|
2015-08-24 23:41:48 +07:00
|
|
|
WARN_ON(!unhash_delegation_locked(dp));
|
2014-07-25 18:34:20 +07:00
|
|
|
list_add(&dp->dl_recall_lru, &reaplist);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-05-30 20:09:30 +07:00
|
|
|
spin_unlock(&state_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
list_for_each_safe(pos, next, &reaplist) {
|
|
|
|
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
|
2014-07-25 18:34:20 +07:00
|
|
|
list_del_init(&dp->dl_recall_lru);
|
2015-04-27 19:50:14 +07:00
|
|
|
put_clnt_odstate(dp->dl_clnt_odstate);
|
2014-08-09 21:22:41 +07:00
|
|
|
nfs4_put_deleg_lease(dp->dl_stid.sc_file);
|
2014-07-30 08:34:06 +07:00
|
|
|
nfs4_put_stid(&dp->dl_stid);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2016-09-17 03:28:25 +07:00
|
|
|
BUG_ON(!list_empty(&reaplist));
|
|
|
|
spin_lock(&nn->client_lock);
|
|
|
|
while (!list_empty(&nn->blocked_locks_lru)) {
|
|
|
|
nbl = list_first_entry(&nn->blocked_locks_lru,
|
|
|
|
struct nfsd4_blocked_lock, nbl_lru);
|
|
|
|
list_move(&nbl->nbl_lru, &reaplist);
|
|
|
|
list_del_init(&nbl->nbl_list);
|
|
|
|
}
|
|
|
|
spin_unlock(&nn->client_lock);
|
|
|
|
|
|
|
|
while (!list_empty(&reaplist)) {
|
|
|
|
nbl = list_first_entry(&nn->blocked_locks_lru,
|
|
|
|
struct nfsd4_blocked_lock, nbl_lru);
|
|
|
|
list_del_init(&nbl->nbl_lru);
|
|
|
|
posix_unblock_lock(&nbl->nbl_lock);
|
|
|
|
free_blocked_lock(nbl);
|
|
|
|
}
|
|
|
|
|
2012-11-14 22:22:07 +07:00
|
|
|
nfsd4_client_tracking_exit(net);
|
2012-11-26 19:22:08 +07:00
|
|
|
nfs4_state_destroy_net(net);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nfs4_state_shutdown(void)
|
|
|
|
{
|
2006-04-11 12:55:37 +07:00
|
|
|
destroy_workqueue(laundry_wq);
|
2010-06-05 03:42:08 +07:00
|
|
|
nfsd4_destroy_callback_queue();
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2012-02-14 04:55:24 +07:00
|
|
|
|
|
|
|
static void
|
|
|
|
get_stateid(struct nfsd4_compound_state *cstate, stateid_t *stateid)
|
|
|
|
{
|
2012-02-14 04:55:32 +07:00
|
|
|
if (HAS_STATE_ID(cstate, CURRENT_STATE_ID_FLAG) && CURRENT_STATEID(stateid))
|
|
|
|
memcpy(stateid, &cstate->current_stateid, sizeof(stateid_t));
|
2012-02-14 04:55:24 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
put_stateid(struct nfsd4_compound_state *cstate, stateid_t *stateid)
|
|
|
|
{
|
2012-02-14 04:55:32 +07:00
|
|
|
if (cstate->minorversion) {
|
|
|
|
memcpy(&cstate->current_stateid, stateid, sizeof(stateid_t));
|
|
|
|
SET_STATE_ID(cstate, CURRENT_STATE_ID_FLAG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
clear_current_stateid(struct nfsd4_compound_state *cstate)
|
|
|
|
{
|
|
|
|
CLEAR_STATE_ID(cstate, CURRENT_STATE_ID_FLAG);
|
2012-02-14 04:55:24 +07:00
|
|
|
}
|
|
|
|
|
2012-02-14 04:55:25 +07:00
|
|
|
/*
|
|
|
|
* functions to set current state id
|
|
|
|
*/
|
2012-02-14 04:55:31 +07:00
|
|
|
void
|
|
|
|
nfsd4_set_opendowngradestateid(struct nfsd4_compound_state *cstate, struct nfsd4_open_downgrade *odp)
|
|
|
|
{
|
|
|
|
put_stateid(cstate, &odp->od_stateid);
|
|
|
|
}
|
|
|
|
|
2012-02-14 04:55:24 +07:00
|
|
|
void
|
|
|
|
nfsd4_set_openstateid(struct nfsd4_compound_state *cstate, struct nfsd4_open *open)
|
|
|
|
{
|
|
|
|
put_stateid(cstate, &open->op_stateid);
|
|
|
|
}
|
|
|
|
|
2012-02-14 04:55:25 +07:00
|
|
|
void
|
|
|
|
nfsd4_set_closestateid(struct nfsd4_compound_state *cstate, struct nfsd4_close *close)
|
|
|
|
{
|
|
|
|
put_stateid(cstate, &close->cl_stateid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nfsd4_set_lockstateid(struct nfsd4_compound_state *cstate, struct nfsd4_lock *lock)
|
|
|
|
{
|
|
|
|
put_stateid(cstate, &lock->lk_resp_stateid);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* functions to consume current state id
|
|
|
|
*/
|
2012-02-14 04:55:30 +07:00
|
|
|
|
2012-02-14 04:55:31 +07:00
|
|
|
void
|
|
|
|
nfsd4_get_opendowngradestateid(struct nfsd4_compound_state *cstate, struct nfsd4_open_downgrade *odp)
|
|
|
|
{
|
|
|
|
get_stateid(cstate, &odp->od_stateid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nfsd4_get_delegreturnstateid(struct nfsd4_compound_state *cstate, struct nfsd4_delegreturn *drp)
|
|
|
|
{
|
|
|
|
get_stateid(cstate, &drp->dr_stateid);
|
|
|
|
}
|
|
|
|
|
2012-02-14 04:55:30 +07:00
|
|
|
void
|
|
|
|
nfsd4_get_freestateid(struct nfsd4_compound_state *cstate, struct nfsd4_free_stateid *fsp)
|
|
|
|
{
|
|
|
|
get_stateid(cstate, &fsp->fr_stateid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nfsd4_get_setattrstateid(struct nfsd4_compound_state *cstate, struct nfsd4_setattr *setattr)
|
|
|
|
{
|
|
|
|
get_stateid(cstate, &setattr->sa_stateid);
|
|
|
|
}
|
|
|
|
|
2012-02-14 04:55:24 +07:00
|
|
|
void
|
|
|
|
nfsd4_get_closestateid(struct nfsd4_compound_state *cstate, struct nfsd4_close *close)
|
|
|
|
{
|
|
|
|
get_stateid(cstate, &close->cl_stateid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-02-14 04:55:25 +07:00
|
|
|
nfsd4_get_lockustateid(struct nfsd4_compound_state *cstate, struct nfsd4_locku *locku)
|
2012-02-14 04:55:24 +07:00
|
|
|
{
|
2012-02-14 04:55:25 +07:00
|
|
|
get_stateid(cstate, &locku->lu_stateid);
|
2012-02-14 04:55:24 +07:00
|
|
|
}
|
2012-02-14 04:55:26 +07:00
|
|
|
|
|
|
|
void
|
|
|
|
nfsd4_get_readstateid(struct nfsd4_compound_state *cstate, struct nfsd4_read *read)
|
|
|
|
{
|
|
|
|
get_stateid(cstate, &read->rd_stateid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nfsd4_get_writestateid(struct nfsd4_compound_state *cstate, struct nfsd4_write *write)
|
|
|
|
{
|
|
|
|
get_stateid(cstate, &write->wr_stateid);
|
|
|
|
}
|