Commit aeba12b2 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'nfsd-6.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux

Pull more nfsd updates from Chuck Lever:
 "This contains a number of crasher fixes that were not ready for the
  initial pull request last week.

  In particular, Jeff's patch attempts to address reference count
  underflows in NFSD's filecache, which have been very difficult to
  track down because there is no reliable reproducer.

  Common failure modes:
      https://bugzilla.kernel.org/show_bug.cgi?id=216691#c11
      https://bugzilla.kernel.org/show_bug.cgi?id=216674#c6
      https://bugzilla.redhat.com/show_bug.cgi?id=2138605

  The race windows were found by inspection and the clean-ups appear
  sensible and pass regression testing, so we include them here in the
  hope that they address the problem. However we remain vigilant because
  we don't have 100% certainty yet that the problem is fully addressed.

  Summary:

   - Address numerous reports of refcount underflows in NFSD's filecache

   - Address a UAF in callback setup error handling

   - Address a UAF during server-to-server copy"

* tag 'nfsd-6.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux:
  NFSD: fix use-after-free in __nfs42_ssc_open()
  nfsd: under NFSv4.1, fix double svc_xprt_put on rpc_create failure
  nfsd: rework refcounting in filecache
parents acd04af6 75333d48
...@@ -324,8 +324,7 @@ nfsd_file_alloc(struct nfsd_file_lookup_key *key, unsigned int may) ...@@ -324,8 +324,7 @@ nfsd_file_alloc(struct nfsd_file_lookup_key *key, unsigned int may)
if (key->gc) if (key->gc)
__set_bit(NFSD_FILE_GC, &nf->nf_flags); __set_bit(NFSD_FILE_GC, &nf->nf_flags);
nf->nf_inode = key->inode; nf->nf_inode = key->inode;
/* nf_ref is pre-incremented for hash table */ refcount_set(&nf->nf_ref, 1);
refcount_set(&nf->nf_ref, 2);
nf->nf_may = key->need; nf->nf_may = key->need;
nf->nf_mark = NULL; nf->nf_mark = NULL;
} }
...@@ -377,24 +376,35 @@ nfsd_file_unhash(struct nfsd_file *nf) ...@@ -377,24 +376,35 @@ nfsd_file_unhash(struct nfsd_file *nf)
return false; return false;
} }
static bool static void
nfsd_file_free(struct nfsd_file *nf) nfsd_file_free(struct nfsd_file *nf)
{ {
s64 age = ktime_to_ms(ktime_sub(ktime_get(), nf->nf_birthtime)); s64 age = ktime_to_ms(ktime_sub(ktime_get(), nf->nf_birthtime));
bool flush = false;
trace_nfsd_file_free(nf); trace_nfsd_file_free(nf);
this_cpu_inc(nfsd_file_releases); this_cpu_inc(nfsd_file_releases);
this_cpu_add(nfsd_file_total_age, age); this_cpu_add(nfsd_file_total_age, age);
nfsd_file_unhash(nf);
/*
* We call fsync here in order to catch writeback errors. It's not
* strictly required by the protocol, but an nfsd_file could get
* evicted from the cache before a COMMIT comes in. If another
* task were to open that file in the interim and scrape the error,
* then the client may never see it. By calling fsync here, we ensure
* that writeback happens before the entry is freed, and that any
* errors reported result in the write verifier changing.
*/
nfsd_file_fsync(nf);
if (nf->nf_mark) if (nf->nf_mark)
nfsd_file_mark_put(nf->nf_mark); nfsd_file_mark_put(nf->nf_mark);
if (nf->nf_file) { if (nf->nf_file) {
get_file(nf->nf_file); get_file(nf->nf_file);
filp_close(nf->nf_file, NULL); filp_close(nf->nf_file, NULL);
fput(nf->nf_file); fput(nf->nf_file);
flush = true;
} }
/* /*
...@@ -402,10 +412,9 @@ nfsd_file_free(struct nfsd_file *nf) ...@@ -402,10 +412,9 @@ nfsd_file_free(struct nfsd_file *nf)
* WARN and leak it to preserve system stability. * WARN and leak it to preserve system stability.
*/ */
if (WARN_ON_ONCE(!list_empty(&nf->nf_lru))) if (WARN_ON_ONCE(!list_empty(&nf->nf_lru)))
return flush; return;
call_rcu(&nf->nf_rcu, nfsd_file_slab_free); call_rcu(&nf->nf_rcu, nfsd_file_slab_free);
return flush;
} }
static bool static bool
...@@ -421,17 +430,23 @@ nfsd_file_check_writeback(struct nfsd_file *nf) ...@@ -421,17 +430,23 @@ nfsd_file_check_writeback(struct nfsd_file *nf)
mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK); mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK);
} }
static void nfsd_file_lru_add(struct nfsd_file *nf) static bool nfsd_file_lru_add(struct nfsd_file *nf)
{ {
set_bit(NFSD_FILE_REFERENCED, &nf->nf_flags); set_bit(NFSD_FILE_REFERENCED, &nf->nf_flags);
if (list_lru_add(&nfsd_file_lru, &nf->nf_lru)) if (list_lru_add(&nfsd_file_lru, &nf->nf_lru)) {
trace_nfsd_file_lru_add(nf); trace_nfsd_file_lru_add(nf);
return true;
}
return false;
} }
static void nfsd_file_lru_remove(struct nfsd_file *nf) static bool nfsd_file_lru_remove(struct nfsd_file *nf)
{ {
if (list_lru_del(&nfsd_file_lru, &nf->nf_lru)) if (list_lru_del(&nfsd_file_lru, &nf->nf_lru)) {
trace_nfsd_file_lru_del(nf); trace_nfsd_file_lru_del(nf);
return true;
}
return false;
} }
struct nfsd_file * struct nfsd_file *
...@@ -442,86 +457,60 @@ nfsd_file_get(struct nfsd_file *nf) ...@@ -442,86 +457,60 @@ nfsd_file_get(struct nfsd_file *nf)
return NULL; return NULL;
} }
static void /**
nfsd_file_unhash_and_queue(struct nfsd_file *nf, struct list_head *dispose) * nfsd_file_put - put the reference to a nfsd_file
{ * @nf: nfsd_file of which to put the reference
trace_nfsd_file_unhash_and_queue(nf); *
if (nfsd_file_unhash(nf)) { * Put a reference to a nfsd_file. In the non-GC case, we just put the
/* caller must call nfsd_file_dispose_list() later */ * reference immediately. In the GC case, if the reference would be
nfsd_file_lru_remove(nf); * the last one, the put it on the LRU instead to be cleaned up later.
list_add(&nf->nf_lru, dispose); */
}
}
static void
nfsd_file_put_noref(struct nfsd_file *nf)
{
trace_nfsd_file_put(nf);
if (refcount_dec_and_test(&nf->nf_ref)) {
WARN_ON(test_bit(NFSD_FILE_HASHED, &nf->nf_flags));
nfsd_file_lru_remove(nf);
nfsd_file_free(nf);
}
}
static void
nfsd_file_unhash_and_put(struct nfsd_file *nf)
{
if (nfsd_file_unhash(nf))
nfsd_file_put_noref(nf);
}
void void
nfsd_file_put(struct nfsd_file *nf) nfsd_file_put(struct nfsd_file *nf)
{ {
might_sleep(); might_sleep();
trace_nfsd_file_put(nf);
if (test_bit(NFSD_FILE_GC, &nf->nf_flags)) if (test_bit(NFSD_FILE_GC, &nf->nf_flags) &&
nfsd_file_lru_add(nf); test_bit(NFSD_FILE_HASHED, &nf->nf_flags)) {
else if (refcount_read(&nf->nf_ref) == 2) /*
nfsd_file_unhash_and_put(nf); * If this is the last reference (nf_ref == 1), then try to
* transfer it to the LRU.
if (!test_bit(NFSD_FILE_HASHED, &nf->nf_flags)) { */
nfsd_file_fsync(nf); if (refcount_dec_not_one(&nf->nf_ref))
nfsd_file_put_noref(nf); return;
} else if (nf->nf_file && test_bit(NFSD_FILE_GC, &nf->nf_flags)) {
nfsd_file_put_noref(nf); /* Try to add it to the LRU. If that fails, decrement. */
nfsd_file_schedule_laundrette(); if (nfsd_file_lru_add(nf)) {
} else /* If it's still hashed, we're done */
nfsd_file_put_noref(nf); if (test_bit(NFSD_FILE_HASHED, &nf->nf_flags)) {
} nfsd_file_schedule_laundrette();
return;
static void }
nfsd_file_dispose_list(struct list_head *dispose)
{
struct nfsd_file *nf;
while(!list_empty(dispose)) { /*
nf = list_first_entry(dispose, struct nfsd_file, nf_lru); * We're racing with unhashing, so try to remove it from
list_del_init(&nf->nf_lru); * the LRU. If removal fails, then someone else already
nfsd_file_fsync(nf); * has our reference.
nfsd_file_put_noref(nf); */
if (!nfsd_file_lru_remove(nf))
return;
}
} }
if (refcount_dec_and_test(&nf->nf_ref))
nfsd_file_free(nf);
} }
static void static void
nfsd_file_dispose_list_sync(struct list_head *dispose) nfsd_file_dispose_list(struct list_head *dispose)
{ {
bool flush = false;
struct nfsd_file *nf; struct nfsd_file *nf;
while(!list_empty(dispose)) { while (!list_empty(dispose)) {
nf = list_first_entry(dispose, struct nfsd_file, nf_lru); nf = list_first_entry(dispose, struct nfsd_file, nf_lru);
list_del_init(&nf->nf_lru); list_del_init(&nf->nf_lru);
nfsd_file_fsync(nf); nfsd_file_free(nf);
if (!refcount_dec_and_test(&nf->nf_ref))
continue;
if (nfsd_file_free(nf))
flush = true;
} }
if (flush)
flush_delayed_fput();
} }
static void static void
...@@ -591,21 +580,8 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru, ...@@ -591,21 +580,8 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru,
struct list_head *head = arg; struct list_head *head = arg;
struct nfsd_file *nf = list_entry(item, struct nfsd_file, nf_lru); struct nfsd_file *nf = list_entry(item, struct nfsd_file, nf_lru);
/* /* We should only be dealing with GC entries here */
* Do a lockless refcount check. The hashtable holds one reference, so WARN_ON_ONCE(!test_bit(NFSD_FILE_GC, &nf->nf_flags));
* we look to see if anything else has a reference, or if any have
* been put since the shrinker last ran. Those don't get unhashed and
* released.
*
* Note that in the put path, we set the flag and then decrement the
* counter. Here we check the counter and then test and clear the flag.
* That order is deliberate to ensure that we can do this locklessly.
*/
if (refcount_read(&nf->nf_ref) > 1) {
list_lru_isolate(lru, &nf->nf_lru);
trace_nfsd_file_gc_in_use(nf);
return LRU_REMOVED;
}
/* /*
* Don't throw out files that are still undergoing I/O or * Don't throw out files that are still undergoing I/O or
...@@ -616,40 +592,30 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru, ...@@ -616,40 +592,30 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru,
return LRU_SKIP; return LRU_SKIP;
} }
/* If it was recently added to the list, skip it */
if (test_and_clear_bit(NFSD_FILE_REFERENCED, &nf->nf_flags)) { if (test_and_clear_bit(NFSD_FILE_REFERENCED, &nf->nf_flags)) {
trace_nfsd_file_gc_referenced(nf); trace_nfsd_file_gc_referenced(nf);
return LRU_ROTATE; return LRU_ROTATE;
} }
if (!test_and_clear_bit(NFSD_FILE_HASHED, &nf->nf_flags)) { /*
trace_nfsd_file_gc_hashed(nf); * Put the reference held on behalf of the LRU. If it wasn't the last
return LRU_SKIP; * one, then just remove it from the LRU and ignore it.
*/
if (!refcount_dec_and_test(&nf->nf_ref)) {
trace_nfsd_file_gc_in_use(nf);
list_lru_isolate(lru, &nf->nf_lru);
return LRU_REMOVED;
} }
/* Refcount went to zero. Unhash it and queue it to the dispose list */
nfsd_file_unhash(nf);
list_lru_isolate_move(lru, &nf->nf_lru, head); list_lru_isolate_move(lru, &nf->nf_lru, head);
this_cpu_inc(nfsd_file_evictions); this_cpu_inc(nfsd_file_evictions);
trace_nfsd_file_gc_disposed(nf); trace_nfsd_file_gc_disposed(nf);
return LRU_REMOVED; return LRU_REMOVED;
} }
/*
* Unhash items on @dispose immediately, then queue them on the
* disposal workqueue to finish releasing them in the background.
*
* cel: Note that between the time list_lru_shrink_walk runs and
* now, these items are in the hash table but marked unhashed.
* Why release these outside of lru_cb ? There's no lock ordering
* problem since lru_cb currently takes no lock.
*/
static void nfsd_file_gc_dispose_list(struct list_head *dispose)
{
struct nfsd_file *nf;
list_for_each_entry(nf, dispose, nf_lru)
nfsd_file_hash_remove(nf);
nfsd_file_dispose_list_delayed(dispose);
}
static void static void
nfsd_file_gc(void) nfsd_file_gc(void)
{ {
...@@ -659,7 +625,7 @@ nfsd_file_gc(void) ...@@ -659,7 +625,7 @@ nfsd_file_gc(void)
ret = list_lru_walk(&nfsd_file_lru, nfsd_file_lru_cb, ret = list_lru_walk(&nfsd_file_lru, nfsd_file_lru_cb,
&dispose, list_lru_count(&nfsd_file_lru)); &dispose, list_lru_count(&nfsd_file_lru));
trace_nfsd_file_gc_removed(ret, list_lru_count(&nfsd_file_lru)); trace_nfsd_file_gc_removed(ret, list_lru_count(&nfsd_file_lru));
nfsd_file_gc_dispose_list(&dispose); nfsd_file_dispose_list_delayed(&dispose);
} }
static void static void
...@@ -685,7 +651,7 @@ nfsd_file_lru_scan(struct shrinker *s, struct shrink_control *sc) ...@@ -685,7 +651,7 @@ nfsd_file_lru_scan(struct shrinker *s, struct shrink_control *sc)
ret = list_lru_shrink_walk(&nfsd_file_lru, sc, ret = list_lru_shrink_walk(&nfsd_file_lru, sc,
nfsd_file_lru_cb, &dispose); nfsd_file_lru_cb, &dispose);
trace_nfsd_file_shrinker_removed(ret, list_lru_count(&nfsd_file_lru)); trace_nfsd_file_shrinker_removed(ret, list_lru_count(&nfsd_file_lru));
nfsd_file_gc_dispose_list(&dispose); nfsd_file_dispose_list_delayed(&dispose);
return ret; return ret;
} }
...@@ -695,72 +661,111 @@ static struct shrinker nfsd_file_shrinker = { ...@@ -695,72 +661,111 @@ static struct shrinker nfsd_file_shrinker = {
.seeks = 1, .seeks = 1,
}; };
/* /**
* Find all cache items across all net namespaces that match @inode and * nfsd_file_queue_for_close: try to close out any open nfsd_files for an inode
* move them to @dispose. The lookup is atomic wrt nfsd_file_acquire(). * @inode: inode on which to close out nfsd_files
* @dispose: list on which to gather nfsd_files to close out
*
* An nfsd_file represents a struct file being held open on behalf of nfsd. An
* open file however can block other activity (such as leases), or cause
* undesirable behavior (e.g. spurious silly-renames when reexporting NFS).
*
* This function is intended to find open nfsd_files when this sort of
* conflicting access occurs and then attempt to close those files out.
*
* Populates the dispose list with entries that have already had their
* refcounts go to zero. The actual free of an nfsd_file can be expensive,
* so we leave it up to the caller whether it wants to wait or not.
*/ */
static unsigned int static void
__nfsd_file_close_inode(struct inode *inode, struct list_head *dispose) nfsd_file_queue_for_close(struct inode *inode, struct list_head *dispose)
{ {
struct nfsd_file_lookup_key key = { struct nfsd_file_lookup_key key = {
.type = NFSD_FILE_KEY_INODE, .type = NFSD_FILE_KEY_INODE,
.inode = inode, .inode = inode,
}; };
unsigned int count = 0;
struct nfsd_file *nf; struct nfsd_file *nf;
rcu_read_lock(); rcu_read_lock();
do { do {
int decrement = 1;
nf = rhashtable_lookup(&nfsd_file_rhash_tbl, &key, nf = rhashtable_lookup(&nfsd_file_rhash_tbl, &key,
nfsd_file_rhash_params); nfsd_file_rhash_params);
if (!nf) if (!nf)
break; break;
nfsd_file_unhash_and_queue(nf, dispose);
count++; /* If we raced with someone else unhashing, ignore it */
if (!nfsd_file_unhash(nf))
continue;
/* If we can't get a reference, ignore it */
if (!nfsd_file_get(nf))
continue;
/* Extra decrement if we remove from the LRU */
if (nfsd_file_lru_remove(nf))
++decrement;
/* If refcount goes to 0, then put on the dispose list */
if (refcount_sub_and_test(decrement, &nf->nf_ref)) {
list_add(&nf->nf_lru, dispose);
trace_nfsd_file_closing(nf);
}
} while (1); } while (1);
rcu_read_unlock(); rcu_read_unlock();
return count;
} }
/** /**
* nfsd_file_close_inode_sync - attempt to forcibly close a nfsd_file * nfsd_file_close_inode - attempt a delayed close of a nfsd_file
* @inode: inode of the file to attempt to remove * @inode: inode of the file to attempt to remove
* *
* Unhash and put, then flush and fput all cache items associated with @inode. * Close out any open nfsd_files that can be reaped for @inode. The
* actual freeing is deferred to the dispose_list_delayed infrastructure.
*
* This is used by the fsnotify callbacks and setlease notifier.
*/ */
void static void
nfsd_file_close_inode_sync(struct inode *inode) nfsd_file_close_inode(struct inode *inode)
{ {
LIST_HEAD(dispose); LIST_HEAD(dispose);
unsigned int count;
count = __nfsd_file_close_inode(inode, &dispose); nfsd_file_queue_for_close(inode, &dispose);
trace_nfsd_file_close_inode_sync(inode, count); nfsd_file_dispose_list_delayed(&dispose);
nfsd_file_dispose_list_sync(&dispose);
} }
/** /**
* nfsd_file_close_inode - attempt a delayed close of a nfsd_file * nfsd_file_close_inode_sync - attempt to forcibly close a nfsd_file
* @inode: inode of the file to attempt to remove * @inode: inode of the file to attempt to remove
* *
* Unhash and put all cache item associated with @inode. * Close out any open nfsd_files that can be reaped for @inode. The
* nfsd_files are closed out synchronously.
*
* This is called from nfsd_rename and nfsd_unlink to avoid silly-renames
* when reexporting NFS.
*/ */
static void void
nfsd_file_close_inode(struct inode *inode) nfsd_file_close_inode_sync(struct inode *inode)
{ {
struct nfsd_file *nf;
LIST_HEAD(dispose); LIST_HEAD(dispose);
unsigned int count;
count = __nfsd_file_close_inode(inode, &dispose); trace_nfsd_file_close(inode);
trace_nfsd_file_close_inode(inode, count);
nfsd_file_dispose_list_delayed(&dispose); nfsd_file_queue_for_close(inode, &dispose);
while (!list_empty(&dispose)) {
nf = list_first_entry(&dispose, struct nfsd_file, nf_lru);
list_del_init(&nf->nf_lru);
nfsd_file_free(nf);
}
flush_delayed_fput();
} }
/** /**
* nfsd_file_delayed_close - close unused nfsd_files * nfsd_file_delayed_close - close unused nfsd_files
* @work: dummy * @work: dummy
* *
* Walk the LRU list and close any entries that have not been used since * Walk the LRU list and destroy any entries that have not been used since
* the last scan. * the last scan.
*/ */
static void static void
...@@ -782,7 +787,7 @@ nfsd_file_lease_notifier_call(struct notifier_block *nb, unsigned long arg, ...@@ -782,7 +787,7 @@ nfsd_file_lease_notifier_call(struct notifier_block *nb, unsigned long arg,
/* Only close files for F_SETLEASE leases */ /* Only close files for F_SETLEASE leases */
if (fl->fl_flags & FL_LEASE) if (fl->fl_flags & FL_LEASE)
nfsd_file_close_inode_sync(file_inode(fl->fl_file)); nfsd_file_close_inode(file_inode(fl->fl_file));
return 0; return 0;
} }
...@@ -903,6 +908,13 @@ nfsd_file_cache_init(void) ...@@ -903,6 +908,13 @@ nfsd_file_cache_init(void)
goto out; goto out;
} }
/**
* __nfsd_file_cache_purge: clean out the cache for shutdown
* @net: net-namespace to shut down the cache (may be NULL)
*
* Walk the nfsd_file cache and close out any that match @net. If @net is NULL,
* then close out everything. Called when an nfsd instance is being shut down.
*/
static void static void
__nfsd_file_cache_purge(struct net *net) __nfsd_file_cache_purge(struct net *net)
{ {
...@@ -916,8 +928,11 @@ __nfsd_file_cache_purge(struct net *net) ...@@ -916,8 +928,11 @@ __nfsd_file_cache_purge(struct net *net)
nf = rhashtable_walk_next(&iter); nf = rhashtable_walk_next(&iter);
while (!IS_ERR_OR_NULL(nf)) { while (!IS_ERR_OR_NULL(nf)) {
if (!net || nf->nf_net == net) if (!net || nf->nf_net == net) {
nfsd_file_unhash_and_queue(nf, &dispose); nfsd_file_unhash(nf);
nfsd_file_lru_remove(nf);
list_add(&nf->nf_lru, &dispose);
}
nf = rhashtable_walk_next(&iter); nf = rhashtable_walk_next(&iter);
} }
...@@ -1084,8 +1099,12 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp, ...@@ -1084,8 +1099,12 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (nf) if (nf)
nf = nfsd_file_get(nf); nf = nfsd_file_get(nf);
rcu_read_unlock(); rcu_read_unlock();
if (nf)
if (nf) {
if (nfsd_file_lru_remove(nf))
WARN_ON_ONCE(refcount_dec_and_test(&nf->nf_ref));
goto wait_for_construction; goto wait_for_construction;
}
nf = nfsd_file_alloc(&key, may_flags); nf = nfsd_file_alloc(&key, may_flags);
if (!nf) { if (!nf) {
...@@ -1118,11 +1137,11 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp, ...@@ -1118,11 +1137,11 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp,
goto out; goto out;
} }
open_retry = false; open_retry = false;
nfsd_file_put_noref(nf); if (refcount_dec_and_test(&nf->nf_ref))
nfsd_file_free(nf);
goto retry; goto retry;
} }
nfsd_file_lru_remove(nf);
this_cpu_inc(nfsd_file_cache_hits); this_cpu_inc(nfsd_file_cache_hits);
status = nfserrno(nfsd_open_break_lease(file_inode(nf->nf_file), may_flags)); status = nfserrno(nfsd_open_break_lease(file_inode(nf->nf_file), may_flags));
...@@ -1132,7 +1151,8 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp, ...@@ -1132,7 +1151,8 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp,
this_cpu_inc(nfsd_file_acquisitions); this_cpu_inc(nfsd_file_acquisitions);
*pnf = nf; *pnf = nf;
} else { } else {
nfsd_file_put(nf); if (refcount_dec_and_test(&nf->nf_ref))
nfsd_file_free(nf);
nf = NULL; nf = NULL;
} }
...@@ -1158,8 +1178,10 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp, ...@@ -1158,8 +1178,10 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp,
* If construction failed, or we raced with a call to unlink() * If construction failed, or we raced with a call to unlink()
* then unhash. * then unhash.
*/ */
if (status != nfs_ok || key.inode->i_nlink == 0) if (status == nfs_ok && key.inode->i_nlink == 0)
nfsd_file_unhash_and_put(nf); status = nfserr_jukebox;
if (status != nfs_ok)
nfsd_file_unhash(nf);
clear_bit_unlock(NFSD_FILE_PENDING, &nf->nf_flags); clear_bit_unlock(NFSD_FILE_PENDING, &nf->nf_flags);
smp_mb__after_atomic(); smp_mb__after_atomic();
wake_up_bit(&nf->nf_flags, NFSD_FILE_PENDING); wake_up_bit(&nf->nf_flags, NFSD_FILE_PENDING);
......
...@@ -988,7 +988,6 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c ...@@ -988,7 +988,6 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
} else { } else {
if (!conn->cb_xprt) if (!conn->cb_xprt)
return -EINVAL; return -EINVAL;
clp->cl_cb_conn.cb_xprt = conn->cb_xprt;
clp->cl_cb_session = ses; clp->cl_cb_session = ses;
args.bc_xprt = conn->cb_xprt; args.bc_xprt = conn->cb_xprt;
args.prognumber = clp->cl_cb_session->se_cb_prog; args.prognumber = clp->cl_cb_session->se_cb_prog;
...@@ -1008,6 +1007,9 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c ...@@ -1008,6 +1007,9 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
rpc_shutdown_client(client); rpc_shutdown_client(client);
return -ENOMEM; return -ENOMEM;
} }
if (clp->cl_minorversion != 0)
clp->cl_cb_conn.cb_xprt = conn->cb_xprt;
clp->cl_cb_client = client; clp->cl_cb_client = client;
clp->cl_cb_cred = cred; clp->cl_cb_cred = cred;
rcu_read_lock(); rcu_read_lock();
......
...@@ -1461,13 +1461,6 @@ nfsd4_interssc_connect(struct nl4_server *nss, struct svc_rqst *rqstp, ...@@ -1461,13 +1461,6 @@ nfsd4_interssc_connect(struct nl4_server *nss, struct svc_rqst *rqstp,
return status; return status;
} }
static void
nfsd4_interssc_disconnect(struct vfsmount *ss_mnt)
{
nfs_do_sb_deactive(ss_mnt->mnt_sb);
mntput(ss_mnt);
}
/* /*
* Verify COPY destination stateid. * Verify COPY destination stateid.
* *
...@@ -1570,11 +1563,6 @@ nfsd4_cleanup_inter_ssc(struct vfsmount *ss_mnt, struct file *filp, ...@@ -1570,11 +1563,6 @@ nfsd4_cleanup_inter_ssc(struct vfsmount *ss_mnt, struct file *filp,
{ {
} }
static void
nfsd4_interssc_disconnect(struct vfsmount *ss_mnt)
{
}
static struct file *nfs42_ssc_open(struct vfsmount *ss_mnt, static struct file *nfs42_ssc_open(struct vfsmount *ss_mnt,
struct nfs_fh *src_fh, struct nfs_fh *src_fh,
nfs4_stateid *stateid) nfs4_stateid *stateid)
...@@ -1770,7 +1758,7 @@ static int nfsd4_do_async_copy(void *data) ...@@ -1770,7 +1758,7 @@ static int nfsd4_do_async_copy(void *data)
default: default:
nfserr = nfserr_offload_denied; nfserr = nfserr_offload_denied;
} }
nfsd4_interssc_disconnect(copy->ss_mnt); /* ss_mnt will be unmounted by the laundromat */
goto do_callback; goto do_callback;
} }
nfserr = nfsd4_do_copy(copy, filp, copy->nf_dst->nf_file, nfserr = nfsd4_do_copy(copy, filp, copy->nf_dst->nf_file,
...@@ -1851,8 +1839,10 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, ...@@ -1851,8 +1839,10 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
if (async_copy) if (async_copy)
cleanup_async_copy(async_copy); cleanup_async_copy(async_copy);
status = nfserrno(-ENOMEM); status = nfserrno(-ENOMEM);
if (nfsd4_ssc_is_inter(copy)) /*
nfsd4_interssc_disconnect(copy->ss_mnt); * source's vfsmount of inter-copy will be unmounted
* by the laundromat
*/
goto out; goto out;
} }
......
...@@ -876,8 +876,8 @@ DEFINE_CLID_EVENT(confirmed_r); ...@@ -876,8 +876,8 @@ DEFINE_CLID_EVENT(confirmed_r);
__print_flags(val, "|", \ __print_flags(val, "|", \
{ 1 << NFSD_FILE_HASHED, "HASHED" }, \ { 1 << NFSD_FILE_HASHED, "HASHED" }, \
{ 1 << NFSD_FILE_PENDING, "PENDING" }, \ { 1 << NFSD_FILE_PENDING, "PENDING" }, \
{ 1 << NFSD_FILE_REFERENCED, "REFERENCED"}, \ { 1 << NFSD_FILE_REFERENCED, "REFERENCED" }, \
{ 1 << NFSD_FILE_GC, "GC"}) { 1 << NFSD_FILE_GC, "GC" })
DECLARE_EVENT_CLASS(nfsd_file_class, DECLARE_EVENT_CLASS(nfsd_file_class,
TP_PROTO(struct nfsd_file *nf), TP_PROTO(struct nfsd_file *nf),
...@@ -912,6 +912,7 @@ DEFINE_EVENT(nfsd_file_class, name, \ ...@@ -912,6 +912,7 @@ DEFINE_EVENT(nfsd_file_class, name, \
DEFINE_NFSD_FILE_EVENT(nfsd_file_free); DEFINE_NFSD_FILE_EVENT(nfsd_file_free);
DEFINE_NFSD_FILE_EVENT(nfsd_file_unhash); DEFINE_NFSD_FILE_EVENT(nfsd_file_unhash);
DEFINE_NFSD_FILE_EVENT(nfsd_file_put); DEFINE_NFSD_FILE_EVENT(nfsd_file_put);
DEFINE_NFSD_FILE_EVENT(nfsd_file_closing);
DEFINE_NFSD_FILE_EVENT(nfsd_file_unhash_and_queue); DEFINE_NFSD_FILE_EVENT(nfsd_file_unhash_and_queue);
TRACE_EVENT(nfsd_file_alloc, TRACE_EVENT(nfsd_file_alloc,
...@@ -1103,35 +1104,6 @@ TRACE_EVENT(nfsd_file_open, ...@@ -1103,35 +1104,6 @@ TRACE_EVENT(nfsd_file_open,
__entry->nf_file) __entry->nf_file)
) )
DECLARE_EVENT_CLASS(nfsd_file_search_class,
TP_PROTO(
const struct inode *inode,
unsigned int count
),
TP_ARGS(inode, count),
TP_STRUCT__entry(
__field(const struct inode *, inode)
__field(unsigned int, count)
),
TP_fast_assign(
__entry->inode = inode;
__entry->count = count;
),
TP_printk("inode=%p count=%u",
__entry->inode, __entry->count)
);
#define DEFINE_NFSD_FILE_SEARCH_EVENT(name) \
DEFINE_EVENT(nfsd_file_search_class, name, \
TP_PROTO( \
const struct inode *inode, \
unsigned int count \
), \
TP_ARGS(inode, count))
DEFINE_NFSD_FILE_SEARCH_EVENT(nfsd_file_close_inode_sync);
DEFINE_NFSD_FILE_SEARCH_EVENT(nfsd_file_close_inode);
TRACE_EVENT(nfsd_file_is_cached, TRACE_EVENT(nfsd_file_is_cached,
TP_PROTO( TP_PROTO(
const struct inode *inode, const struct inode *inode,
...@@ -1209,7 +1181,6 @@ DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_lru_del_disposed); ...@@ -1209,7 +1181,6 @@ DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_lru_del_disposed);
DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_in_use); DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_in_use);
DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_writeback); DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_writeback);
DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_referenced); DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_referenced);
DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_hashed);
DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_disposed); DEFINE_NFSD_FILE_GC_EVENT(nfsd_file_gc_disposed);
DECLARE_EVENT_CLASS(nfsd_file_lruwalk_class, DECLARE_EVENT_CLASS(nfsd_file_lruwalk_class,
...@@ -1241,6 +1212,22 @@ DEFINE_EVENT(nfsd_file_lruwalk_class, name, \ ...@@ -1241,6 +1212,22 @@ DEFINE_EVENT(nfsd_file_lruwalk_class, name, \
DEFINE_NFSD_FILE_LRUWALK_EVENT(nfsd_file_gc_removed); DEFINE_NFSD_FILE_LRUWALK_EVENT(nfsd_file_gc_removed);
DEFINE_NFSD_FILE_LRUWALK_EVENT(nfsd_file_shrinker_removed); DEFINE_NFSD_FILE_LRUWALK_EVENT(nfsd_file_shrinker_removed);
TRACE_EVENT(nfsd_file_close,
TP_PROTO(
const struct inode *inode
),
TP_ARGS(inode),
TP_STRUCT__entry(
__field(const void *, inode)
),
TP_fast_assign(
__entry->inode = inode;
),
TP_printk("inode=%p",
__entry->inode
)
);
TRACE_EVENT(nfsd_file_fsync, TRACE_EVENT(nfsd_file_fsync,
TP_PROTO( TP_PROTO(
const struct nfsd_file *nf, const struct nfsd_file *nf,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment