summaryrefslogtreecommitdiff
path: root/fs/nfsd/filecache.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/nfsd/filecache.c')
-rw-r--r--fs/nfsd/filecache.c507
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);
+}