Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull vfs fixes from Al Viro: "A couple more of d_walk()/d_subdirs reordering fixes (stable fodder; ought to solve that crap for good) and a fix for a brown paperbag bug in d_alloc_parallel() (this cycle)" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: fix idiotic braino in d_alloc_parallel() autofs races much milder d_walk() race
This commit is contained in:
commit
67016f6cdf
@ -70,9 +70,13 @@ struct autofs_info {
|
||||
};
|
||||
|
||||
#define AUTOFS_INF_EXPIRING (1<<0) /* dentry in the process of expiring */
|
||||
#define AUTOFS_INF_NO_RCU (1<<1) /* the dentry is being considered
|
||||
#define AUTOFS_INF_WANT_EXPIRE (1<<1) /* the dentry is being considered
|
||||
* for expiry, so RCU_walk is
|
||||
* not permitted
|
||||
* not permitted. If it progresses to
|
||||
* actual expiry attempt, the flag is
|
||||
* not cleared when EXPIRING is set -
|
||||
* in that case it gets cleared only
|
||||
* when it comes to clearing EXPIRING.
|
||||
*/
|
||||
#define AUTOFS_INF_PENDING (1<<2) /* dentry pending mount */
|
||||
|
||||
|
@ -316,19 +316,17 @@ struct dentry *autofs4_expire_direct(struct super_block *sb,
|
||||
if (ino->flags & AUTOFS_INF_PENDING)
|
||||
goto out;
|
||||
if (!autofs4_direct_busy(mnt, root, timeout, do_now)) {
|
||||
ino->flags |= AUTOFS_INF_NO_RCU;
|
||||
ino->flags |= AUTOFS_INF_WANT_EXPIRE;
|
||||
spin_unlock(&sbi->fs_lock);
|
||||
synchronize_rcu();
|
||||
spin_lock(&sbi->fs_lock);
|
||||
if (!autofs4_direct_busy(mnt, root, timeout, do_now)) {
|
||||
ino->flags |= AUTOFS_INF_EXPIRING;
|
||||
smp_mb();
|
||||
ino->flags &= ~AUTOFS_INF_NO_RCU;
|
||||
init_completion(&ino->expire_complete);
|
||||
spin_unlock(&sbi->fs_lock);
|
||||
return root;
|
||||
}
|
||||
ino->flags &= ~AUTOFS_INF_NO_RCU;
|
||||
ino->flags &= ~AUTOFS_INF_WANT_EXPIRE;
|
||||
}
|
||||
out:
|
||||
spin_unlock(&sbi->fs_lock);
|
||||
@ -446,7 +444,7 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
|
||||
while ((dentry = get_next_positive_subdir(dentry, root))) {
|
||||
spin_lock(&sbi->fs_lock);
|
||||
ino = autofs4_dentry_ino(dentry);
|
||||
if (ino->flags & AUTOFS_INF_NO_RCU)
|
||||
if (ino->flags & AUTOFS_INF_WANT_EXPIRE)
|
||||
expired = NULL;
|
||||
else
|
||||
expired = should_expire(dentry, mnt, timeout, how);
|
||||
@ -455,7 +453,7 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
|
||||
continue;
|
||||
}
|
||||
ino = autofs4_dentry_ino(expired);
|
||||
ino->flags |= AUTOFS_INF_NO_RCU;
|
||||
ino->flags |= AUTOFS_INF_WANT_EXPIRE;
|
||||
spin_unlock(&sbi->fs_lock);
|
||||
synchronize_rcu();
|
||||
spin_lock(&sbi->fs_lock);
|
||||
@ -465,7 +463,7 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
|
||||
goto found;
|
||||
}
|
||||
|
||||
ino->flags &= ~AUTOFS_INF_NO_RCU;
|
||||
ino->flags &= ~AUTOFS_INF_WANT_EXPIRE;
|
||||
if (expired != dentry)
|
||||
dput(expired);
|
||||
spin_unlock(&sbi->fs_lock);
|
||||
@ -475,17 +473,8 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
|
||||
found:
|
||||
pr_debug("returning %p %pd\n", expired, expired);
|
||||
ino->flags |= AUTOFS_INF_EXPIRING;
|
||||
smp_mb();
|
||||
ino->flags &= ~AUTOFS_INF_NO_RCU;
|
||||
init_completion(&ino->expire_complete);
|
||||
spin_unlock(&sbi->fs_lock);
|
||||
spin_lock(&sbi->lookup_lock);
|
||||
spin_lock(&expired->d_parent->d_lock);
|
||||
spin_lock_nested(&expired->d_lock, DENTRY_D_LOCK_NESTED);
|
||||
list_move(&expired->d_parent->d_subdirs, &expired->d_child);
|
||||
spin_unlock(&expired->d_lock);
|
||||
spin_unlock(&expired->d_parent->d_lock);
|
||||
spin_unlock(&sbi->lookup_lock);
|
||||
return expired;
|
||||
}
|
||||
|
||||
@ -496,7 +485,7 @@ int autofs4_expire_wait(struct dentry *dentry, int rcu_walk)
|
||||
int status;
|
||||
|
||||
/* Block on any pending expire */
|
||||
if (!(ino->flags & (AUTOFS_INF_EXPIRING | AUTOFS_INF_NO_RCU)))
|
||||
if (!(ino->flags & AUTOFS_INF_WANT_EXPIRE))
|
||||
return 0;
|
||||
if (rcu_walk)
|
||||
return -ECHILD;
|
||||
@ -554,7 +543,7 @@ int autofs4_expire_run(struct super_block *sb,
|
||||
ino = autofs4_dentry_ino(dentry);
|
||||
/* avoid rapid-fire expire attempts if expiry fails */
|
||||
ino->last_used = now;
|
||||
ino->flags &= ~AUTOFS_INF_EXPIRING;
|
||||
ino->flags &= ~(AUTOFS_INF_EXPIRING|AUTOFS_INF_WANT_EXPIRE);
|
||||
complete_all(&ino->expire_complete);
|
||||
spin_unlock(&sbi->fs_lock);
|
||||
|
||||
@ -583,7 +572,7 @@ int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt,
|
||||
spin_lock(&sbi->fs_lock);
|
||||
/* avoid rapid-fire expire attempts if expiry fails */
|
||||
ino->last_used = now;
|
||||
ino->flags &= ~AUTOFS_INF_EXPIRING;
|
||||
ino->flags &= ~(AUTOFS_INF_EXPIRING|AUTOFS_INF_WANT_EXPIRE);
|
||||
complete_all(&ino->expire_complete);
|
||||
spin_unlock(&sbi->fs_lock);
|
||||
dput(dentry);
|
||||
|
@ -458,7 +458,7 @@ static int autofs4_d_manage(struct dentry *dentry, bool rcu_walk)
|
||||
*/
|
||||
struct inode *inode;
|
||||
|
||||
if (ino->flags & (AUTOFS_INF_EXPIRING | AUTOFS_INF_NO_RCU))
|
||||
if (ino->flags & AUTOFS_INF_WANT_EXPIRE)
|
||||
return 0;
|
||||
if (d_mountpoint(dentry))
|
||||
return 0;
|
||||
|
75
fs/dcache.c
75
fs/dcache.c
@ -507,6 +507,44 @@ void d_drop(struct dentry *dentry)
|
||||
}
|
||||
EXPORT_SYMBOL(d_drop);
|
||||
|
||||
static inline void dentry_unlist(struct dentry *dentry, struct dentry *parent)
|
||||
{
|
||||
struct dentry *next;
|
||||
/*
|
||||
* Inform d_walk() and shrink_dentry_list() that we are no longer
|
||||
* attached to the dentry tree
|
||||
*/
|
||||
dentry->d_flags |= DCACHE_DENTRY_KILLED;
|
||||
if (unlikely(list_empty(&dentry->d_child)))
|
||||
return;
|
||||
__list_del_entry(&dentry->d_child);
|
||||
/*
|
||||
* Cursors can move around the list of children. While we'd been
|
||||
* a normal list member, it didn't matter - ->d_child.next would've
|
||||
* been updated. However, from now on it won't be and for the
|
||||
* things like d_walk() it might end up with a nasty surprise.
|
||||
* Normally d_walk() doesn't care about cursors moving around -
|
||||
* ->d_lock on parent prevents that and since a cursor has no children
|
||||
* of its own, we get through it without ever unlocking the parent.
|
||||
* There is one exception, though - if we ascend from a child that
|
||||
* gets killed as soon as we unlock it, the next sibling is found
|
||||
* using the value left in its ->d_child.next. And if _that_
|
||||
* pointed to a cursor, and cursor got moved (e.g. by lseek())
|
||||
* before d_walk() regains parent->d_lock, we'll end up skipping
|
||||
* everything the cursor had been moved past.
|
||||
*
|
||||
* Solution: make sure that the pointer left behind in ->d_child.next
|
||||
* points to something that won't be moving around. I.e. skip the
|
||||
* cursors.
|
||||
*/
|
||||
while (dentry->d_child.next != &parent->d_subdirs) {
|
||||
next = list_entry(dentry->d_child.next, struct dentry, d_child);
|
||||
if (likely(!(next->d_flags & DCACHE_DENTRY_CURSOR)))
|
||||
break;
|
||||
dentry->d_child.next = next->d_child.next;
|
||||
}
|
||||
}
|
||||
|
||||
static void __dentry_kill(struct dentry *dentry)
|
||||
{
|
||||
struct dentry *parent = NULL;
|
||||
@ -532,12 +570,7 @@ static void __dentry_kill(struct dentry *dentry)
|
||||
}
|
||||
/* if it was on the hash then remove it */
|
||||
__d_drop(dentry);
|
||||
__list_del_entry(&dentry->d_child);
|
||||
/*
|
||||
* Inform d_walk() that we are no longer attached to the
|
||||
* dentry tree
|
||||
*/
|
||||
dentry->d_flags |= DCACHE_DENTRY_KILLED;
|
||||
dentry_unlist(dentry, parent);
|
||||
if (parent)
|
||||
spin_unlock(&parent->d_lock);
|
||||
dentry_iput(dentry);
|
||||
@ -1203,6 +1236,9 @@ resume:
|
||||
struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
|
||||
next = tmp->next;
|
||||
|
||||
if (unlikely(dentry->d_flags & DCACHE_DENTRY_CURSOR))
|
||||
continue;
|
||||
|
||||
spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
|
||||
|
||||
ret = enter(data, dentry);
|
||||
@ -1651,6 +1687,16 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
|
||||
}
|
||||
EXPORT_SYMBOL(d_alloc);
|
||||
|
||||
struct dentry *d_alloc_cursor(struct dentry * parent)
|
||||
{
|
||||
struct dentry *dentry = __d_alloc(parent->d_sb, NULL);
|
||||
if (dentry) {
|
||||
dentry->d_flags |= DCACHE_RCUACCESS | DCACHE_DENTRY_CURSOR;
|
||||
dentry->d_parent = dget(parent);
|
||||
}
|
||||
return dentry;
|
||||
}
|
||||
|
||||
/**
|
||||
* d_alloc_pseudo - allocate a dentry (for lookup-less filesystems)
|
||||
* @sb: the superblock
|
||||
@ -2457,7 +2503,6 @@ retry:
|
||||
rcu_read_unlock();
|
||||
goto retry;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
/*
|
||||
* No changes for the parent since the beginning of d_lookup().
|
||||
* Since all removals from the chain happen with hlist_bl_lock(),
|
||||
@ -2470,8 +2515,6 @@ retry:
|
||||
continue;
|
||||
if (dentry->d_parent != parent)
|
||||
continue;
|
||||
if (d_unhashed(dentry))
|
||||
continue;
|
||||
if (parent->d_flags & DCACHE_OP_COMPARE) {
|
||||
int tlen = dentry->d_name.len;
|
||||
const char *tname = dentry->d_name.name;
|
||||
@ -2483,9 +2526,18 @@ retry:
|
||||
if (dentry_cmp(dentry, str, len))
|
||||
continue;
|
||||
}
|
||||
dget(dentry);
|
||||
hlist_bl_unlock(b);
|
||||
/* somebody is doing lookup for it right now; wait for it */
|
||||
/* now we can try to grab a reference */
|
||||
if (!lockref_get_not_dead(&dentry->d_lockref)) {
|
||||
rcu_read_unlock();
|
||||
goto retry;
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
/*
|
||||
* somebody is likely to be still doing lookup for it;
|
||||
* wait for them to finish
|
||||
*/
|
||||
spin_lock(&dentry->d_lock);
|
||||
d_wait_lookup(dentry);
|
||||
/*
|
||||
@ -2516,6 +2568,7 @@ retry:
|
||||
dput(new);
|
||||
return dentry;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
/* we can't take ->d_lock here; it's OK, though. */
|
||||
new->d_flags |= DCACHE_PAR_LOOKUP;
|
||||
new->d_wait = wq;
|
||||
|
@ -130,6 +130,7 @@ extern int invalidate_inodes(struct super_block *, bool);
|
||||
extern struct dentry *__d_alloc(struct super_block *, const struct qstr *);
|
||||
extern int d_set_mounted(struct dentry *dentry);
|
||||
extern long prune_dcache_sb(struct super_block *sb, struct shrink_control *sc);
|
||||
extern struct dentry *d_alloc_cursor(struct dentry *);
|
||||
|
||||
/*
|
||||
* read_write.c
|
||||
|
@ -71,9 +71,7 @@ EXPORT_SYMBOL(simple_lookup);
|
||||
|
||||
int dcache_dir_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
static struct qstr cursor_name = QSTR_INIT(".", 1);
|
||||
|
||||
file->private_data = d_alloc(file->f_path.dentry, &cursor_name);
|
||||
file->private_data = d_alloc_cursor(file->f_path.dentry);
|
||||
|
||||
return file->private_data ? 0 : -ENOMEM;
|
||||
}
|
||||
|
@ -212,6 +212,7 @@ struct dentry_operations {
|
||||
#define DCACHE_OP_REAL 0x08000000
|
||||
|
||||
#define DCACHE_PAR_LOOKUP 0x10000000 /* being looked up (with parent locked shared) */
|
||||
#define DCACHE_DENTRY_CURSOR 0x20000000
|
||||
|
||||
extern seqlock_t rename_lock;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user