mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge tag 'pull-dcache' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull dcache updates from Al Viro:
- d_alloc_parallel() API change (Neil's with my changes)
- NORCU fixes
- Reorganization and simplification of dentry eviction logic
- Simplifying rcu_read_lock() scopes in fs/dcache.c
- Secondary roots work - getting rid of NFS fake root dentries and
dealing with remaining shrink_dcache_for_umount() and
shrink_dentry_list() races
- making cursors NORCU (surprisingly easy)
* tag 'pull-dcache' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: (22 commits)
make cursors NORCU
nfs: get rid of fake root dentries
wind ->s_roots via ->d_sib instead of ->d_hash
shrink_dentry_tree(): unify the calls of shrink_dentry_list()
shrinking rcu_read_lock() scope in d_alloc_parallel()
d_walk(): shrink rcu_read_lock() scope
document dentry_kill()
adjust calling conventions of lock_for_kill(), fold __dentry_kill() into dentry_kill()
Document rcu_read_lock() use in select_collect2()
Shift rcu_read_{,un}lock() inside fast_dput()
simplify safety for lock_for_kill() slowpath
fold lock_for_kill() and __dentry_kill() into common helper
fold lock_for_kill() into shrink_kill()
shrink_dentry_list(): start with removing from shrink list
d_prune_aliases(): make sure to skip NORCU aliases
kill d_dispose_if_unused()
make to_shrink_list() return whether it has moved dentry to list
select_collect(): ignore dentries on shrink lists if they have positive refcounts
find_acceptable_alias(): skip NORCU aliases with zero refcount
fix a race between d_find_any_alias() and final dput() of NORCU dentries
...
This commit is contained in:
@@ -1384,3 +1384,20 @@ for_each_alias(dentry, inode) instead of hlist_for_each_entry; better
|
||||
yet, see if any of the exported primitives could be used instead of
|
||||
the entire loop. You still need to hold ->i_lock of the inode over
|
||||
either form of manual loop.
|
||||
|
||||
---
|
||||
|
||||
**mandatory**
|
||||
|
||||
d_alloc_parallel() no longer requires a waitqueue_head.
|
||||
|
||||
---
|
||||
|
||||
**mandatory**
|
||||
|
||||
d_dispose_if_unused() is gone; use __move_to_shrink_list() if you really
|
||||
need that functionality, but watch out for memory safety issues - just
|
||||
as with d_dispose_if_unused() these are not trivial; with this variant
|
||||
of API it's more explicit, since grabbing ->d_lock is caller-side, but
|
||||
d_dispose_if_unused() had all the same issues. It's a low-level primitive;
|
||||
use only if you have no alternative.
|
||||
|
||||
+1
-3
@@ -248,13 +248,11 @@ int afs_silly_iput(struct dentry *dentry, struct inode *inode)
|
||||
struct dentry *alias;
|
||||
int ret;
|
||||
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
|
||||
_enter("%p{%pd},%llx", dentry, dentry, vnode->fid.vnode);
|
||||
|
||||
down_read(&dvnode->rmdir_lock);
|
||||
|
||||
alias = d_alloc_parallel(dentry->d_parent, &dentry->d_name, &wq);
|
||||
alias = d_alloc_parallel(dentry->d_parent, &dentry->d_name);
|
||||
if (IS_ERR(alias)) {
|
||||
up_read(&dvnode->rmdir_lock);
|
||||
return 0;
|
||||
|
||||
+324
-225
File diff suppressed because it is too large
Load Diff
+4
-5
@@ -53,10 +53,10 @@ find_acceptable_alias(struct dentry *result,
|
||||
inode = result->d_inode;
|
||||
spin_lock(&inode->i_lock);
|
||||
for_each_alias(dentry, inode) {
|
||||
dget(dentry);
|
||||
if (!dget_alias_ilocked(dentry))
|
||||
continue;
|
||||
spin_unlock(&inode->i_lock);
|
||||
if (toput)
|
||||
dput(toput);
|
||||
dput(toput);
|
||||
if (dentry != result && acceptable(context, dentry)) {
|
||||
dput(result);
|
||||
return dentry;
|
||||
@@ -66,8 +66,7 @@ find_acceptable_alias(struct dentry *result,
|
||||
}
|
||||
spin_unlock(&inode->i_lock);
|
||||
|
||||
if (toput)
|
||||
dput(toput);
|
||||
dput(toput);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -402,6 +402,8 @@ static struct file *alloc_file(const struct path *path, int flags,
|
||||
static inline int alloc_path_pseudo(const char *name, struct inode *inode,
|
||||
struct vfsmount *mnt, struct path *path)
|
||||
{
|
||||
if (WARN_ON_ONCE(S_ISDIR(inode->i_mode)))
|
||||
return -EINVAL;
|
||||
path->dentry = d_alloc_pseudo(mnt->mnt_sb, &QSTR(name));
|
||||
if (!path->dentry)
|
||||
return -ENOMEM;
|
||||
|
||||
+1
-1
@@ -177,8 +177,8 @@ static void fuse_dentry_tree_work(struct work_struct *work)
|
||||
spin_lock(&fd->dentry->d_lock);
|
||||
/* If dentry is still referenced, let next dput release it */
|
||||
fd->dentry->d_flags |= DCACHE_OP_DELETE;
|
||||
__move_to_shrink_list(fd->dentry, &dispose);
|
||||
spin_unlock(&fd->dentry->d_lock);
|
||||
d_dispose_if_unused(fd->dentry, &dispose);
|
||||
if (need_resched()) {
|
||||
spin_unlock(&dentry_hash[i].lock);
|
||||
cond_resched();
|
||||
|
||||
+1
-2
@@ -164,7 +164,6 @@ static int fuse_direntplus_link(struct file *file,
|
||||
struct inode *dir = d_inode(parent);
|
||||
struct fuse_conn *fc;
|
||||
struct inode *inode;
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
int epoch;
|
||||
|
||||
if (!o->nodeid) {
|
||||
@@ -201,7 +200,7 @@ static int fuse_direntplus_link(struct file *file,
|
||||
dentry = d_lookup(parent, &name);
|
||||
if (!dentry) {
|
||||
retry:
|
||||
dentry = d_alloc_parallel(parent, &name, &wq);
|
||||
dentry = d_alloc_parallel(parent, &name);
|
||||
if (IS_ERR(dentry))
|
||||
return PTR_ERR(dentry);
|
||||
}
|
||||
|
||||
+2
-4
@@ -1896,13 +1896,12 @@ static struct dentry *__lookup_slow(const struct qstr *name,
|
||||
{
|
||||
struct dentry *dentry, *old;
|
||||
struct inode *inode = dir->d_inode;
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
|
||||
/* Don't go there if it's already dead */
|
||||
if (unlikely(IS_DEADDIR(inode)))
|
||||
return ERR_PTR(-ENOENT);
|
||||
again:
|
||||
dentry = d_alloc_parallel(dir, name, &wq);
|
||||
dentry = d_alloc_parallel(dir, name);
|
||||
if (IS_ERR(dentry))
|
||||
return dentry;
|
||||
if (unlikely(!d_in_lookup(dentry))) {
|
||||
@@ -4413,7 +4412,6 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
|
||||
struct dentry *dentry;
|
||||
int error, create_error = 0;
|
||||
umode_t mode = op->mode;
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
|
||||
if (unlikely(IS_DEADDIR(dir_inode)))
|
||||
return ERR_PTR(-ENOENT);
|
||||
@@ -4422,7 +4420,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
|
||||
dentry = d_lookup(dir, &nd->last);
|
||||
for (;;) {
|
||||
if (!dentry) {
|
||||
dentry = d_alloc_parallel(dir, &nd->last, &wq);
|
||||
dentry = d_alloc_parallel(dir, &nd->last);
|
||||
if (IS_ERR(dentry))
|
||||
return dentry;
|
||||
}
|
||||
|
||||
+2
-4
@@ -726,7 +726,6 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry,
|
||||
unsigned long dir_verifier)
|
||||
{
|
||||
struct qstr filename = QSTR_INIT(entry->name, entry->len);
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
struct dentry *dentry;
|
||||
struct dentry *alias;
|
||||
struct inode *inode;
|
||||
@@ -755,7 +754,7 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry,
|
||||
dentry = d_lookup(parent, &filename);
|
||||
again:
|
||||
if (!dentry) {
|
||||
dentry = d_alloc_parallel(parent, &filename, &wq);
|
||||
dentry = d_alloc_parallel(parent, &filename);
|
||||
if (IS_ERR(dentry))
|
||||
return;
|
||||
}
|
||||
@@ -2106,7 +2105,6 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
|
||||
struct file *file, unsigned open_flags,
|
||||
umode_t mode)
|
||||
{
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
struct nfs_open_context *ctx;
|
||||
struct dentry *res;
|
||||
struct iattr attr = { .ia_valid = ATTR_OPEN };
|
||||
@@ -2162,7 +2160,7 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
|
||||
d_drop(dentry);
|
||||
switched = true;
|
||||
dentry = d_alloc_parallel(dentry->d_parent,
|
||||
&dentry->d_name, &wq);
|
||||
&dentry->d_name);
|
||||
if (IS_ERR(dentry))
|
||||
return PTR_ERR(dentry);
|
||||
if (unlikely(!d_in_lookup(dentry)))
|
||||
|
||||
+2
-33
@@ -32,35 +32,6 @@
|
||||
|
||||
#define NFSDBG_FACILITY NFSDBG_CLIENT
|
||||
|
||||
/*
|
||||
* Set the superblock root dentry.
|
||||
* Note that this function frees the inode in case of error.
|
||||
*/
|
||||
static int nfs_superblock_set_dummy_root(struct super_block *sb, struct inode *inode)
|
||||
{
|
||||
/* The mntroot acts as the dummy root dentry for this superblock */
|
||||
if (sb->s_root == NULL) {
|
||||
sb->s_root = d_make_root(inode);
|
||||
if (sb->s_root == NULL)
|
||||
return -ENOMEM;
|
||||
ihold(inode);
|
||||
/*
|
||||
* Ensure that this dentry is invisible to d_find_alias().
|
||||
* Otherwise, it may be spliced into the tree by
|
||||
* d_splice_alias if a parent directory from the same
|
||||
* filesystem gets mounted at a later time.
|
||||
* This again causes shrink_dcache_for_umount_subtree() to
|
||||
* Oops, since the test for IS_ROOT() will fail.
|
||||
*/
|
||||
spin_lock(&d_inode(sb->s_root)->i_lock);
|
||||
spin_lock(&sb->s_root->d_lock);
|
||||
hlist_del_init(&sb->s_root->d_alias);
|
||||
spin_unlock(&sb->s_root->d_lock);
|
||||
spin_unlock(&d_inode(sb->s_root)->i_lock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* get a root dentry from the root filehandle
|
||||
*/
|
||||
@@ -99,10 +70,6 @@ int nfs_get_root(struct super_block *s, struct fs_context *fc)
|
||||
goto out_fattr;
|
||||
}
|
||||
|
||||
error = nfs_superblock_set_dummy_root(s, inode);
|
||||
if (error != 0)
|
||||
goto out_fattr;
|
||||
|
||||
/* root dentries normally start off anonymous and get spliced in later
|
||||
* if the dentry tree reaches them; however if the dentry already
|
||||
* exists, we'll pick it up at this point and use it as the root
|
||||
@@ -123,6 +90,8 @@ int nfs_get_root(struct super_block *s, struct fs_context *fc)
|
||||
name = NULL;
|
||||
}
|
||||
spin_unlock(&root->d_lock);
|
||||
if (!s->s_root)
|
||||
s->s_root = dget(root);
|
||||
fc->root = root;
|
||||
if (server->caps & NFS_CAP_SECURITY_LABEL)
|
||||
kflags |= SECURITY_LSM_NATIVE_LABELS;
|
||||
|
||||
+1
-2
@@ -124,7 +124,7 @@ static int nfs_call_unlink(struct dentry *dentry, struct inode *inode, struct nf
|
||||
struct dentry *alias;
|
||||
|
||||
down_read_non_owner(&NFS_I(dir)->rmdir_sem);
|
||||
alias = d_alloc_parallel(dentry->d_parent, &data->args.name, &data->wq);
|
||||
alias = d_alloc_parallel(dentry->d_parent, &data->args.name);
|
||||
if (IS_ERR(alias)) {
|
||||
up_read_non_owner(&NFS_I(dir)->rmdir_sem);
|
||||
return 0;
|
||||
@@ -185,7 +185,6 @@ nfs_async_unlink(struct dentry *dentry, const struct qstr *name)
|
||||
|
||||
data->cred = get_current_cred();
|
||||
data->res.dir_attr = &data->dir_attr;
|
||||
init_waitqueue_head(&data->wq);
|
||||
|
||||
status = -EBUSY;
|
||||
spin_lock(&dentry->d_lock);
|
||||
|
||||
+1
-2
@@ -2111,8 +2111,7 @@ bool proc_fill_cache(struct file *file, struct dir_context *ctx,
|
||||
goto end_instantiate;
|
||||
|
||||
if (!child) {
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
child = d_alloc_parallel(dir, &qname, &wq);
|
||||
child = d_alloc_parallel(dir, &qname);
|
||||
if (IS_ERR(child))
|
||||
goto end_instantiate;
|
||||
if (d_in_lookup(child)) {
|
||||
|
||||
@@ -692,8 +692,7 @@ static bool proc_sys_fill_cache(struct file *file,
|
||||
|
||||
child = d_lookup(dir, &qname);
|
||||
if (!child) {
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
child = d_alloc_parallel(dir, &qname, &wq);
|
||||
child = d_alloc_parallel(dir, &qname);
|
||||
if (IS_ERR(child))
|
||||
return false;
|
||||
if (d_in_lookup(child)) {
|
||||
|
||||
@@ -73,7 +73,6 @@ cifs_prime_dcache(struct dentry *parent, struct qstr *name,
|
||||
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
|
||||
bool posix = cifs_sb_master_tcon(cifs_sb)->posix_extensions;
|
||||
bool reparse_need_reval = false;
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
||||
int rc;
|
||||
|
||||
cifs_dbg(FYI, "%s: for %s\n", __func__, name->name);
|
||||
@@ -105,7 +104,7 @@ retry:
|
||||
(fattr->cf_flags & CIFS_FATTR_NEED_REVAL))
|
||||
return;
|
||||
|
||||
dentry = d_alloc_parallel(parent, name, &wq);
|
||||
dentry = d_alloc_parallel(parent, name);
|
||||
}
|
||||
if (IS_ERR(dentry))
|
||||
return;
|
||||
|
||||
@@ -359,6 +359,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
|
||||
s->s_iflags |= SB_I_NODEV;
|
||||
INIT_HLIST_NODE(&s->s_instances);
|
||||
INIT_HLIST_BL_HEAD(&s->s_roots);
|
||||
spin_lock_init(&s->s_roots_lock);
|
||||
mutex_init(&s->s_sync_lock);
|
||||
INIT_LIST_HEAD(&s->s_inodes);
|
||||
spin_lock_init(&s->s_inode_list_lock);
|
||||
|
||||
+24
-7
@@ -116,10 +116,7 @@ struct dentry {
|
||||
* possible!
|
||||
*/
|
||||
|
||||
union {
|
||||
struct list_head d_lru; /* LRU list */
|
||||
wait_queue_head_t *d_wait; /* in-lookup ones only */
|
||||
};
|
||||
struct list_head d_lru; /* LRU list */
|
||||
struct hlist_node d_sib; /* child of parent list */
|
||||
struct hlist_head d_children; /* our children */
|
||||
/*
|
||||
@@ -210,6 +207,9 @@ enum dentry_flags {
|
||||
DCACHE_REFERENCED = BIT(6), /* Recently used, don't discard. */
|
||||
DCACHE_DONTCACHE = BIT(7), /* Purge from memory on final dput() */
|
||||
DCACHE_CANT_MOUNT = BIT(8),
|
||||
DCACHE_LOOKUP_WAITERS = BIT(9), /* A thread is waiting for
|
||||
* PAR_LOOKUP to clear
|
||||
*/
|
||||
DCACHE_SHRINK_LIST = BIT(10),
|
||||
DCACHE_OP_WEAK_REVALIDATE = BIT(11),
|
||||
/*
|
||||
@@ -256,8 +256,7 @@ extern void d_delete(struct dentry *);
|
||||
/* allocate/de-allocate */
|
||||
extern struct dentry * d_alloc(struct dentry *, const struct qstr *);
|
||||
extern struct dentry * d_alloc_anon(struct super_block *);
|
||||
extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *,
|
||||
wait_queue_head_t *);
|
||||
extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *);
|
||||
extern struct dentry * d_splice_alias(struct inode *, struct dentry *);
|
||||
/* weird procfs mess; *NOT* exported */
|
||||
extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *,
|
||||
@@ -281,7 +280,7 @@ extern void d_tmpfile(struct file *, struct inode *);
|
||||
|
||||
extern struct dentry *d_find_alias(struct inode *);
|
||||
extern void d_prune_aliases(struct inode *);
|
||||
extern void d_dispose_if_unused(struct dentry *, struct list_head *);
|
||||
extern bool __move_to_shrink_list(struct dentry *, struct list_head *);
|
||||
extern void shrink_dentry_list(struct list_head *);
|
||||
|
||||
extern struct dentry *d_find_alias_rcu(struct inode *);
|
||||
@@ -366,6 +365,24 @@ static inline struct dentry *dget(struct dentry *dentry)
|
||||
return dentry;
|
||||
}
|
||||
|
||||
/* dentry->d_inode->i_lock must be held by caller */
|
||||
static inline bool dget_alias_ilocked(struct dentry *dentry)
|
||||
{
|
||||
if (likely(!(READ_ONCE(dentry->d_flags) & DCACHE_NORCU))) {
|
||||
lockref_get(&dentry->d_lockref);
|
||||
return true;
|
||||
}
|
||||
// NORCU dentries with zero refcount MUST NOT be grabbed
|
||||
spin_lock(&dentry->d_lock);
|
||||
if (dentry->d_lockref.count > 0) {
|
||||
dget_dlock(dentry);
|
||||
spin_unlock(&dentry->d_lock);
|
||||
return true;
|
||||
}
|
||||
spin_unlock(&dentry->d_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
extern struct dentry *dget_parent(struct dentry *dentry);
|
||||
|
||||
/**
|
||||
|
||||
@@ -162,7 +162,8 @@ struct super_block {
|
||||
struct unicode_map *s_encoding;
|
||||
__u16 s_encoding_flags;
|
||||
#endif
|
||||
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
|
||||
struct hlist_head s_roots; /* alternate root dentries for NFS */
|
||||
spinlock_t s_roots_lock;
|
||||
struct mount *s_mounts; /* list of mounts; _not_ for fs use */
|
||||
struct block_device *s_bdev; /* can go away once we use an accessor for @s_bdev_file */
|
||||
struct file *s_bdev_file;
|
||||
|
||||
@@ -1745,7 +1745,6 @@ struct nfs_unlinkdata {
|
||||
struct nfs_removeargs args;
|
||||
struct nfs_removeres res;
|
||||
struct dentry *dentry;
|
||||
wait_queue_head_t wq;
|
||||
const struct cred *cred;
|
||||
struct nfs_fattr dir_attr;
|
||||
long timeout;
|
||||
|
||||
Reference in New Issue
Block a user