diff options
Diffstat (limited to 'fs/nfsd/filecache.c')
| -rw-r--r-- | fs/nfsd/filecache.c | 507 |
1 files changed, 361 insertions, 146 deletions
diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index fb9b1656a287..b9548eb17c77 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -55,6 +55,17 @@ /* We only care about NFSD_MAY_READ/WRITE for this cache */ #define NFSD_FILE_MAY_MASK (NFSD_MAY_READ|NFSD_MAY_WRITE|NFSD_MAY_LOCALIO) +/* If the shrinker runs between calls to list_lru_walk_node() in + * nfsd_file_gc(), the "remaining" count will be wrong. This could + * result in premature freeing of some files. This may not matter much + * but is easy to fix with this spinlock which temporarily disables + * the shrinker. + * + * It also serializes callers of nfsd_file_dispose_list_delayed() + * against per-net shutdown. + */ +static DEFINE_SPINLOCK(nfsd_gc_lock); + static DEFINE_PER_CPU(unsigned long, nfsd_file_cache_hits); static DEFINE_PER_CPU(unsigned long, nfsd_file_acquisitions); static DEFINE_PER_CPU(unsigned long, nfsd_file_allocations); @@ -62,16 +73,12 @@ static DEFINE_PER_CPU(unsigned long, nfsd_file_releases); static DEFINE_PER_CPU(unsigned long, nfsd_file_total_age); static DEFINE_PER_CPU(unsigned long, nfsd_file_evictions); -struct nfsd_fcache_disposal { - spinlock_t lock; - struct list_head freeme; -}; - static struct kmem_cache *nfsd_file_slab; static struct kmem_cache *nfsd_file_mark_slab; static struct list_lru nfsd_file_lru; static unsigned long nfsd_file_flags; static struct fsnotify_group *nfsd_file_fsnotify_group; +static struct fsnotify_group *nfsd_dir_fsnotify_group; static struct delayed_work nfsd_filecache_laundrette; static struct rhltable nfsd_file_rhltable ____cacheline_aligned_in_smp; @@ -113,7 +120,7 @@ static void nfsd_file_schedule_laundrette(void) { if (test_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags)) - queue_delayed_work(system_unbound_wq, &nfsd_filecache_laundrette, + queue_delayed_work(system_dfl_wq, &nfsd_filecache_laundrette, NFSD_LAUNDRETTE_DELAY); } @@ -147,7 +154,7 @@ static void nfsd_file_mark_put(struct nfsd_file_mark *nfm) { if (refcount_dec_and_test(&nfm->nfm_ref)) { - fsnotify_destroy_mark(&nfm->nfm_mark, nfsd_file_fsnotify_group); + fsnotify_destroy_mark(&nfm->nfm_mark, nfm->nfm_mark.group); fsnotify_put_mark(&nfm->nfm_mark); } } @@ -155,37 +162,40 @@ nfsd_file_mark_put(struct nfsd_file_mark *nfm) static struct nfsd_file_mark * nfsd_file_mark_find_or_create(struct inode *inode) { - int err; - struct fsnotify_mark *mark; struct nfsd_file_mark *nfm = NULL, *new; + struct fsnotify_group *group; + struct fsnotify_mark *mark; + int err; + + group = S_ISDIR(inode->i_mode) ? nfsd_dir_fsnotify_group : nfsd_file_fsnotify_group; do { - fsnotify_group_lock(nfsd_file_fsnotify_group); - mark = fsnotify_find_inode_mark(inode, - nfsd_file_fsnotify_group); + fsnotify_group_lock(group); + mark = fsnotify_find_inode_mark(inode, group); if (mark) { nfm = nfsd_file_mark_get(container_of(mark, struct nfsd_file_mark, nfm_mark)); - fsnotify_group_unlock(nfsd_file_fsnotify_group); + fsnotify_group_unlock(group); if (nfm) { fsnotify_put_mark(mark); break; } /* Avoid soft lockup race with nfsd_file_mark_put() */ - fsnotify_destroy_mark(mark, nfsd_file_fsnotify_group); + fsnotify_destroy_mark(mark, group); fsnotify_put_mark(mark); } else { - fsnotify_group_unlock(nfsd_file_fsnotify_group); + fsnotify_group_unlock(group); } /* allocate a new nfm */ new = kmem_cache_alloc(nfsd_file_mark_slab, GFP_KERNEL); if (!new) return NULL; - fsnotify_init_mark(&new->nfm_mark, nfsd_file_fsnotify_group); + fsnotify_init_mark(&new->nfm_mark, group); new->nfm_mark.mask = FS_ATTRIB|FS_DELETE_SELF; refcount_set(&new->nfm_ref, 1); + mutex_init(&new->nfm_recalc_mutex); err = fsnotify_add_inode_mark(&new->nfm_mark, inode, 0); @@ -231,6 +241,9 @@ nfsd_file_alloc(struct net *net, struct inode *inode, unsigned char need, refcount_set(&nf->nf_ref, 1); nf->nf_may = need; nf->nf_mark = NULL; + nf->nf_dio_mem_align = 0; + nf->nf_dio_offset_align = 0; + nf->nf_dio_read_offset_align = 0; return nf; } @@ -319,15 +332,17 @@ nfsd_file_check_writeback(struct nfsd_file *nf) mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK); } - -static bool nfsd_file_lru_add(struct nfsd_file *nf) +static void nfsd_file_lru_add(struct nfsd_file *nf) { - set_bit(NFSD_FILE_REFERENCED, &nf->nf_flags); - if (list_lru_add_obj(&nfsd_file_lru, &nf->nf_lru)) { + refcount_inc(&nf->nf_ref); + if (list_lru_add_obj(&nfsd_file_lru, &nf->nf_lru)) trace_nfsd_file_lru_add(nf); - return true; + else { + refcount_dec(&nf->nf_ref); + WARN_ON_ONCE(1); + return; } - return false; + nfsd_file_schedule_laundrette(); } static bool nfsd_file_lru_remove(struct nfsd_file *nf) @@ -363,47 +378,32 @@ nfsd_file_put(struct nfsd_file *nf) if (test_bit(NFSD_FILE_GC, &nf->nf_flags) && test_bit(NFSD_FILE_HASHED, &nf->nf_flags)) { - /* - * If this is the last reference (nf_ref == 1), then try to - * transfer it to the LRU. - */ - if (refcount_dec_not_one(&nf->nf_ref)) - return; - - /* Try to add it to the LRU. If that fails, decrement. */ - if (nfsd_file_lru_add(nf)) { - /* If it's still hashed, we're done */ - if (test_bit(NFSD_FILE_HASHED, &nf->nf_flags)) { - nfsd_file_schedule_laundrette(); - return; - } - - /* - * We're racing with unhashing, so try to remove it from - * the LRU. If removal fails, then someone else already - * has our reference. - */ - if (!nfsd_file_lru_remove(nf)) - return; - } + set_bit(NFSD_FILE_REFERENCED, &nf->nf_flags); + set_bit(NFSD_FILE_RECENT, &nf->nf_flags); } + if (refcount_dec_and_test(&nf->nf_ref)) nfsd_file_free(nf); } /** * nfsd_file_put_local - put nfsd_file reference and arm nfsd_net_put in caller - * @nf: nfsd_file of which to put the reference + * @pnf: nfsd_file of which to put the reference * * First save the associated net to return to caller, then put * the reference of the nfsd_file. */ struct net * -nfsd_file_put_local(struct nfsd_file *nf) +nfsd_file_put_local(struct nfsd_file __rcu **pnf) { - struct net *net = nf->nf_net; + struct nfsd_file *nf; + struct net *net = NULL; - nfsd_file_put(nf); + nf = unrcu_pointer(xchg(pnf, NULL)); + if (nf) { + net = nf->nf_net; + nfsd_file_put(nf); + } return net; } @@ -432,25 +432,31 @@ nfsd_file_dispose_list(struct list_head *dispose) } /** - * nfsd_file_dispose_list_delayed - move list of dead files to net's freeme list + * nfsd_file_dispose_list_delayed - queue dead files for nfsd thread disposal * @dispose: list of nfsd_files to be disposed * - * Transfers each file to the "freeme" list for its nfsd_net, to eventually - * be disposed of by the per-net garbage collector. + * Transfers each file to the dispose list in its nfsd_net and wakes an nfsd + * thread to do the actual close. This keeps the cost of fput() in the nfsd + * threads rather than in the shrinker or GC worker. + * + * All callers must hold nfsd_gc_lock, so that nfsd_file_cache_shutdown_net() + * can synchronize against them before draining the per-net dispose list. + * This guarantees nf_net is still live when we call net_generic(). */ static void nfsd_file_dispose_list_delayed(struct list_head *dispose) { - while(!list_empty(dispose)) { + lockdep_assert_held(&nfsd_gc_lock); + + while (!list_empty(dispose)) { struct nfsd_file *nf = list_first_entry(dispose, struct nfsd_file, nf_gc); struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id); - struct nfsd_fcache_disposal *l = nn->fcache_disposal; struct svc_serv *serv; - spin_lock(&l->lock); - list_move_tail(&nf->nf_gc, &l->freeme); - spin_unlock(&l->lock); + spin_lock(&nn->fcache_dispose_lock); + list_move_tail(&nf->nf_gc, &nn->fcache_dispose_list); + spin_unlock(&nn->fcache_dispose_lock); /* * The filecache laundrette is shut down after the @@ -474,21 +480,28 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose) */ void nfsd_file_net_dispose(struct nfsd_net *nn) { - struct nfsd_fcache_disposal *l = nn->fcache_disposal; - - if (!list_empty(&l->freeme)) { + if (!list_empty(&nn->fcache_dispose_list)) { LIST_HEAD(dispose); int i; - spin_lock(&l->lock); - for (i = 0; i < 8 && !list_empty(&l->freeme); i++) - list_move(l->freeme.next, &dispose); - spin_unlock(&l->lock); - if (!list_empty(&l->freeme)) - /* Wake up another thread to share the work + spin_lock(&nn->fcache_dispose_lock); + for (i = 0; i < 8 && !list_empty(&nn->fcache_dispose_list); i++) + list_move(nn->fcache_dispose_list.next, &dispose); + spin_unlock(&nn->fcache_dispose_lock); + if (!list_empty(&nn->fcache_dispose_list)) { + /* + * Wake up another thread to share the work * *before* doing any actual disposing. + * + * The filecache laundrette is shut down after + * the nn->nfsd_serv pointer is cleared, but + * before the svc_serv is freed. */ - svc_wake_up(nn->nfsd_serv); + struct svc_serv *serv = nn->nfsd_serv; + + if (serv) + svc_wake_up(serv); + } nfsd_file_dispose_list(&dispose); } } @@ -530,13 +543,12 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru, } /* - * Put the reference held on behalf of the LRU. If it wasn't the last - * one, then just remove it from the LRU and ignore it. + * Put the reference held on behalf of the LRU if it is the last + * reference, else rotate. */ - if (!refcount_dec_and_test(&nf->nf_ref)) { + if (!refcount_dec_if_one(&nf->nf_ref)) { trace_nfsd_file_gc_in_use(nf); - list_lru_isolate(lru, &nf->nf_lru); - return LRU_REMOVED; + return LRU_ROTATE; } /* Refcount went to zero. Unhash it and queue it to the dispose list */ @@ -548,24 +560,57 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru, return LRU_REMOVED; } +static enum lru_status +nfsd_file_gc_cb(struct list_head *item, struct list_lru_one *lru, + void *arg) +{ + struct nfsd_file *nf = list_entry(item, struct nfsd_file, nf_lru); + + if (test_and_clear_bit(NFSD_FILE_RECENT, &nf->nf_flags)) { + /* + * "REFERENCED" really means "should be at the end of the + * LRU. As we are putting it there we can clear the flag. + */ + clear_bit(NFSD_FILE_REFERENCED, &nf->nf_flags); + trace_nfsd_file_gc_aged(nf); + return LRU_ROTATE; + } + return nfsd_file_lru_cb(item, lru, arg); +} + static void nfsd_file_gc(void) { + unsigned long ret = 0; LIST_HEAD(dispose); - unsigned long ret; + int nid; - ret = list_lru_walk(&nfsd_file_lru, nfsd_file_lru_cb, - &dispose, list_lru_count(&nfsd_file_lru)); - trace_nfsd_file_gc_removed(ret, list_lru_count(&nfsd_file_lru)); + spin_lock(&nfsd_gc_lock); + for_each_node_state(nid, N_NORMAL_MEMORY) { + unsigned long remaining = list_lru_count_node(&nfsd_file_lru, nid); + + while (remaining > 0) { + unsigned long nr = min(remaining, NFSD_FILE_GC_BATCH); + + remaining -= nr; + ret += list_lru_walk_node(&nfsd_file_lru, nid, nfsd_file_gc_cb, + &dispose, &nr); + if (nr) + /* walk aborted early */ + remaining = 0; + } + } nfsd_file_dispose_list_delayed(&dispose); + spin_unlock(&nfsd_gc_lock); + trace_nfsd_file_gc_removed(ret, list_lru_count(&nfsd_file_lru)); } static void nfsd_file_gc_worker(struct work_struct *work) { - nfsd_file_gc(); if (list_lru_count(&nfsd_file_lru)) - nfsd_file_schedule_laundrette(); + nfsd_file_gc(); + nfsd_file_schedule_laundrette(); } static unsigned long @@ -580,10 +625,14 @@ nfsd_file_lru_scan(struct shrinker *s, struct shrink_control *sc) LIST_HEAD(dispose); unsigned long ret; + if (!spin_trylock(&nfsd_gc_lock)) + return SHRINK_STOP; + ret = list_lru_shrink_walk(&nfsd_file_lru, sc, nfsd_file_lru_cb, &dispose); - trace_nfsd_file_shrinker_removed(ret, list_lru_count(&nfsd_file_lru)); nfsd_file_dispose_list_delayed(&dispose); + spin_unlock(&nfsd_gc_lock); + trace_nfsd_file_shrinker_removed(ret, list_lru_count(&nfsd_file_lru)); return ret; } @@ -656,11 +705,11 @@ nfsd_file_queue_for_close(struct inode *inode, struct list_head *dispose) } /** - * nfsd_file_close_inode - attempt a delayed close of a nfsd_file + * nfsd_file_close_inode - attempt a deferred close of a nfsd_file * @inode: inode of the file to attempt to remove * * Close out any open nfsd_files that can be reaped for @inode. The - * actual freeing is deferred to the dispose_list_delayed infrastructure. + * actual freeing is deferred to the nfsd service threads. * * This is used by the fsnotify callbacks and setlease notifier. */ @@ -669,8 +718,10 @@ nfsd_file_close_inode(struct inode *inode) { LIST_HEAD(dispose); + spin_lock(&nfsd_gc_lock); nfsd_file_queue_for_close(inode, &dispose); nfsd_file_dispose_list_delayed(&dispose); + spin_unlock(&nfsd_gc_lock); } /** @@ -686,17 +737,58 @@ nfsd_file_close_inode(struct inode *inode) void nfsd_file_close_inode_sync(struct inode *inode) { - struct nfsd_file *nf; LIST_HEAD(dispose); trace_nfsd_file_close(inode); nfsd_file_queue_for_close(inode, &dispose); - while (!list_empty(&dispose)) { - nf = list_first_entry(&dispose, struct nfsd_file, nf_gc); - list_del_init(&nf->nf_gc); - nfsd_file_free(nf); - } + nfsd_file_dispose_list(&dispose); +} + +/** + * nfsd_file_close_export - close cached file handles for an export + * @net: net namespace in which to operate + * @path: export path whose cached files should be closed + * + * Close out GC-managed nfsd_file entries whose underlying file is on + * the same filesystem as, and a descendant of, @path. nfsd_file + * entries do not carry an export reference, so the check uses the + * file's dentry ancestry. False positives (closing a cached handle + * that did not originate from the target export) are harmless -- the + * handle is simply reopened on the next access. + * + * Called from the NFSD_CMD_UNLOCK_EXPORT handler before revoking + * NFSv4 state, to ensure that cached file handles do not hold the + * filesystem busy. + */ +void nfsd_file_close_export(struct net *net, const struct path *path) +{ + struct rhashtable_iter iter; + struct nfsd_file *nf; + LIST_HEAD(dispose); + + rhltable_walk_enter(&nfsd_file_rhltable, &iter); + do { + rhashtable_walk_start(&iter); + + nf = rhashtable_walk_next(&iter); + while (!IS_ERR_OR_NULL(nf)) { + if (nf->nf_net == net && + test_bit(NFSD_FILE_GC, &nf->nf_flags) && + nf->nf_file && + file_inode(nf->nf_file)->i_sb == + path->dentry->d_sb && + is_subdir(nf->nf_file->f_path.dentry, + path->dentry)) + nfsd_file_cond_queue(nf, &dispose); + nf = rhashtable_walk_next(&iter); + } + + rhashtable_walk_stop(&iter); + } while (nf == ERR_PTR(-EAGAIN)); + rhashtable_walk_exit(&iter); + + nfsd_file_dispose_list(&dispose); } static int @@ -741,12 +833,36 @@ nfsd_file_fsnotify_handle_event(struct fsnotify_mark *mark, u32 mask, return 0; } +#ifdef CONFIG_NFSD_V4 +static int +nfsd_dir_fsnotify_handle_event(struct fsnotify_group *group, u32 mask, + const void *data, int data_type, struct inode *dir, + const struct qstr *name, u32 cookie, + struct fsnotify_iter_info *iter_info) +{ + return nfsd_handle_dir_event(mask, dir, data, data_type, name); +} +#else +static int +nfsd_dir_fsnotify_handle_event(struct fsnotify_group *group, u32 mask, + const void *data, int data_type, struct inode *dir, + const struct qstr *name, u32 cookie, + struct fsnotify_iter_info *iter_info) +{ + return 0; +} +#endif static const struct fsnotify_ops nfsd_file_fsnotify_ops = { .handle_inode_event = nfsd_file_fsnotify_handle_event, .free_mark = nfsd_file_mark_free, }; +static const struct fsnotify_ops nfsd_dir_fsnotify_ops = { + .handle_event = nfsd_dir_fsnotify_handle_event, + .free_mark = nfsd_file_mark_free, +}; + int nfsd_file_cache_init(void) { @@ -798,8 +914,7 @@ nfsd_file_cache_init(void) goto out_shrinker; } - nfsd_file_fsnotify_group = fsnotify_alloc_group(&nfsd_file_fsnotify_ops, - 0); + nfsd_file_fsnotify_group = fsnotify_alloc_group(&nfsd_file_fsnotify_ops, 0); if (IS_ERR(nfsd_file_fsnotify_group)) { pr_err("nfsd: unable to create fsnotify group: %ld\n", PTR_ERR(nfsd_file_fsnotify_group)); @@ -808,11 +923,23 @@ nfsd_file_cache_init(void) goto out_notifier; } + nfsd_dir_fsnotify_group = fsnotify_alloc_group(&nfsd_dir_fsnotify_ops, 0); + if (IS_ERR(nfsd_dir_fsnotify_group)) { + pr_err("nfsd: unable to create fsnotify group: %ld\n", + PTR_ERR(nfsd_dir_fsnotify_group)); + ret = PTR_ERR(nfsd_dir_fsnotify_group); + nfsd_dir_fsnotify_group = NULL; + goto out_notify_group; + } + INIT_DELAYED_WORK(&nfsd_filecache_laundrette, nfsd_file_gc_worker); out: if (ret) clear_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags); return ret; +out_notify_group: + fsnotify_put_group(nfsd_file_fsnotify_group); + nfsd_file_fsnotify_group = NULL; out_notifier: lease_unregister_notifier(&nfsd_file_lease_notifier); out_shrinker: @@ -869,42 +996,14 @@ __nfsd_file_cache_purge(struct net *net) nfsd_file_dispose_list(&dispose); } -static struct nfsd_fcache_disposal * -nfsd_alloc_fcache_disposal(void) -{ - struct nfsd_fcache_disposal *l; - - l = kmalloc(sizeof(*l), GFP_KERNEL); - if (!l) - return NULL; - spin_lock_init(&l->lock); - INIT_LIST_HEAD(&l->freeme); - return l; -} - -static void -nfsd_free_fcache_disposal(struct nfsd_fcache_disposal *l) -{ - nfsd_file_dispose_list(&l->freeme); - kfree(l); -} - -static void -nfsd_free_fcache_disposal_net(struct net *net) -{ - struct nfsd_net *nn = net_generic(net, nfsd_net_id); - struct nfsd_fcache_disposal *l = nn->fcache_disposal; - - nfsd_free_fcache_disposal(l); -} - int nfsd_file_cache_start_net(struct net *net) { struct nfsd_net *nn = net_generic(net, nfsd_net_id); - nn->fcache_disposal = nfsd_alloc_fcache_disposal(); - return nn->fcache_disposal ? 0 : -ENOMEM; + spin_lock_init(&nn->fcache_dispose_lock); + INIT_LIST_HEAD(&nn->fcache_dispose_list); + return 0; } /** @@ -923,8 +1022,18 @@ nfsd_file_cache_purge(struct net *net) void nfsd_file_cache_shutdown_net(struct net *net) { + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + nfsd_file_cache_purge(net); - nfsd_free_fcache_disposal_net(net); + /* + * Ensure any in-progress shrinker, GC, or fsnotify/lease callback + * (all of which hold nfsd_gc_lock while calling + * nfsd_file_dispose_list_delayed()) has fully completed before + * draining the per-net dispose list. + */ + spin_lock(&nfsd_gc_lock); + spin_unlock(&nfsd_gc_lock); + nfsd_file_dispose_list(&nn->fcache_dispose_list); } void @@ -948,6 +1057,8 @@ nfsd_file_cache_shutdown(void) rcu_barrier(); fsnotify_put_group(nfsd_file_fsnotify_group); nfsd_file_fsnotify_group = NULL; + fsnotify_put_group(nfsd_dir_fsnotify_group); + nfsd_dir_fsnotify_group = NULL; kmem_cache_destroy(nfsd_file_slab); nfsd_file_slab = NULL; fsnotify_wait_marks_destroyed(); @@ -1027,12 +1138,41 @@ nfsd_file_is_cached(struct inode *inode) } static __be32 +nfsd_file_get_dio_attrs(const struct svc_fh *fhp, struct nfsd_file *nf) +{ + struct inode *inode = file_inode(nf->nf_file); + struct kstat stat; + __be32 status; + + /* Currently only need to get DIO alignment info for regular files */ + if (!S_ISREG(inode->i_mode)) + return nfs_ok; + + status = fh_getattr(fhp, &stat); + if (status != nfs_ok) + return status; + + trace_nfsd_file_get_dio_attrs(inode, &stat); + + if (stat.result_mask & STATX_DIOALIGN) { + nf->nf_dio_mem_align = stat.dio_mem_align; + nf->nf_dio_offset_align = stat.dio_offset_align; + } + if (stat.result_mask & STATX_DIO_READ_ALIGN) + nf->nf_dio_read_offset_align = stat.dio_read_offset_align; + else + nf->nf_dio_read_offset_align = nf->nf_dio_offset_align; + + return nfs_ok; +} + +static __be32 nfsd_file_do_acquire(struct svc_rqst *rqstp, struct net *net, struct svc_cred *cred, struct auth_domain *client, struct svc_fh *fhp, unsigned int may_flags, struct file *file, - struct nfsd_file **pnf, bool want_gc) + umode_t type, bool want_gc, struct nfsd_file **pnf) { unsigned char need = may_flags & NFSD_FILE_MAY_MASK; struct nfsd_file *new, *nf; @@ -1043,13 +1183,13 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct net *net, int ret; retry: - if (rqstp) { - status = fh_verify(rqstp, fhp, S_IFREG, + if (rqstp) + status = fh_verify(rqstp, fhp, type, may_flags|NFSD_MAY_OWNER_OVERRIDE); - } else { - status = fh_verify_local(net, cred, client, fhp, S_IFREG, + else + status = fh_verify_local(net, cred, client, fhp, type, may_flags|NFSD_MAY_OWNER_OVERRIDE); - } + if (status != nfs_ok) return status; inode = d_inode(fhp->fh_dentry); @@ -1058,16 +1198,8 @@ retry: nf = nfsd_file_lookup_locked(net, current_cred(), inode, need, want_gc); rcu_read_unlock(); - if (nf) { - /* - * If the nf is on the LRU then it holds an extra reference - * that must be put if it's removed. It had better not be - * the last one however, since we should hold another. - */ - if (nfsd_file_lru_remove(nf)) - refcount_dec(&nf->nf_ref); + if (nf) goto wait_for_construction; - } new = nfsd_file_alloc(net, inode, need, want_gc); if (!new) { @@ -1130,15 +1262,16 @@ out: open_file: trace_nfsd_file_alloc(nf); + nf->nf_mark = nfsd_file_mark_find_or_create(inode); if (nf->nf_mark) { - if (file) { + if (file && (file->f_mode & FMODE_OPENED)) { get_file(file); nf->nf_file = file; status = nfs_ok; trace_nfsd_file_opened(nf, status); } else { - ret = nfsd_open_verified(fhp, may_flags, &nf->nf_file); + ret = nfsd_open_verified(fhp, type, may_flags, &nf->nf_file); if (ret == -EOPENSTALE && stale_retry) { stale_retry = false; nfsd_file_unhash(nf); @@ -1152,6 +1285,8 @@ open_file: } status = nfserrno(ret); trace_nfsd_file_open(nf, status); + if (status == nfs_ok) + status = nfsd_file_get_dio_attrs(fhp, nf); } } else status = nfserr_jukebox; @@ -1161,6 +1296,9 @@ open_file: */ if (status != nfs_ok || inode->i_nlink == 0) nfsd_file_unhash(nf); + else if (want_gc) + nfsd_file_lru_add(nf); + clear_and_wake_up_bit(NFSD_FILE_PENDING, &nf->nf_flags); if (status == nfs_ok) goto out; @@ -1195,7 +1333,7 @@ nfsd_file_acquire_gc(struct svc_rqst *rqstp, struct svc_fh *fhp, unsigned int may_flags, struct nfsd_file **pnf) { return nfsd_file_do_acquire(rqstp, SVC_NET(rqstp), NULL, NULL, - fhp, may_flags, NULL, pnf, true); + fhp, may_flags, NULL, S_IFREG, true, pnf); } /** @@ -1220,7 +1358,7 @@ nfsd_file_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp, unsigned int may_flags, struct nfsd_file **pnf) { return nfsd_file_do_acquire(rqstp, SVC_NET(rqstp), NULL, NULL, - fhp, may_flags, NULL, pnf, false); + fhp, may_flags, NULL, S_IFREG, false, pnf); } /** @@ -1263,8 +1401,8 @@ nfsd_file_acquire_local(struct net *net, struct svc_cred *cred, const struct cred *save_cred = get_current_cred(); __be32 beres; - beres = nfsd_file_do_acquire(NULL, net, cred, client, - fhp, may_flags, NULL, pnf, false); + beres = nfsd_file_do_acquire(NULL, net, cred, client, fhp, may_flags, + NULL, S_IFREG, false, pnf); put_cred(revert_creds(save_cred)); return beres; } @@ -1274,12 +1412,12 @@ nfsd_file_acquire_local(struct net *net, struct svc_cred *cred, * @rqstp: the RPC transaction being executed * @fhp: the NFS filehandle of the file just created * @may_flags: NFSD_MAY_ settings for the file - * @file: cached, already-open file (may be NULL) + * @file: cached, already-open file (may be NULL or not yet opened) * @pnf: OUT: new or found "struct nfsd_file" object * * Acquire a nfsd_file object that is not GC'ed. If one doesn't already exist, - * and @file is non-NULL, use it to instantiate a new nfsd_file instead of - * opening a new one. + * and @file has FMODE_OPENED set, use it to instantiate a new nfsd_file + * instead of opening a new one. * * Return values: * %nfs_ok - @pnf points to an nfsd_file with its reference @@ -1293,7 +1431,33 @@ nfsd_file_acquire_opened(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfsd_file **pnf) { return nfsd_file_do_acquire(rqstp, SVC_NET(rqstp), NULL, NULL, - fhp, may_flags, file, pnf, false); + fhp, may_flags, file, S_IFREG, false, pnf); +} + +/** + * nfsd_file_acquire_dir - Get a struct nfsd_file with an open directory + * @rqstp: the RPC transaction being executed + * @fhp: the NFS filehandle of the file to be opened + * @pnf: OUT: new or found "struct nfsd_file" object + * + * The nfsd_file_object returned by this API is reference-counted + * but not garbage-collected. The object is unhashed after the + * final nfsd_file_put(). This opens directories only, and only + * in O_RDONLY mode. + * + * Return values: + * %nfs_ok - @pnf points to an nfsd_file with its reference + * count boosted. + * + * On error, an nfsstat value in network byte order is returned. + */ +__be32 +nfsd_file_acquire_dir(struct svc_rqst *rqstp, struct svc_fh *fhp, + struct nfsd_file **pnf) +{ + return nfsd_file_do_acquire(rqstp, SVC_NET(rqstp), NULL, NULL, fhp, + NFSD_MAY_READ|NFSD_MAY_64BIT_COOKIE, + NULL, S_IFDIR, false, pnf); } /* @@ -1348,3 +1512,54 @@ int nfsd_file_cache_stats_show(struct seq_file *m, void *v) seq_printf(m, "mean age (ms): -\n"); return 0; } + +/** + * nfsd_fsnotify_recalc_mask - recalculate the fsnotify mask for a nfsd_file + * @nf: nfsd_file to recalculate the mask on + * + * When a directory nfsd_file has a delegation added or removed, that may + * change the events that nfsd requires from the VFS layer. This function + * recalculates the fsnotify mask based on the leases present. + */ +void nfsd_fsnotify_recalc_mask(struct nfsd_file *nf) +{ + struct inode *inode = file_inode(nf->nf_file); + u32 lease_mask, set = 0, clear = 0; + struct fsnotify_mark *mark; + + /* This is only needed when adding or removing dir delegs */ + if (!S_ISDIR(inode->i_mode) || !nf->nf_mark) + return; + + mark = &nf->nf_mark->nfm_mark; + + /* + * The mark is shared by every nfsd_file on this inode, so concurrent + * delegation add/remove on the same directory can recalc it in + * parallel. Serialize the read of the lease state and the update of + * the mark so that a recalc working from a stale snapshot of the + * lease list can't clobber a concurrent recalc's update. + */ + mutex_lock(&nf->nf_mark->nfm_recalc_mutex); + + /* Set up notifications for any ignored delegation events */ + lease_mask = inode_lease_ignore_mask(inode); + + if (lease_mask & FL_IGN_DIR_CREATE) + set |= FS_CREATE | FS_MOVED_TO; + else + clear |= FS_CREATE | FS_MOVED_TO; + + if (lease_mask & FL_IGN_DIR_DELETE) + set |= FS_DELETE | FS_MOVED_FROM; + else + clear |= FS_DELETE | FS_MOVED_FROM; + + if (lease_mask & FL_IGN_DIR_RENAME) + set |= FS_RENAME; + else + clear |= FS_RENAME; + + fsnotify_modify_mark_mask(mark, set, clear); + mutex_unlock(&nf->nf_mark->nfm_recalc_mutex); +} |
