diff options
Diffstat (limited to 'fs/namei.c')
-rw-r--r-- | fs/namei.c | 118 |
1 files changed, 52 insertions, 66 deletions
diff --git a/fs/namei.c b/fs/namei.c index e412421210cc..409a441ba2ae 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -494,50 +494,6 @@ static inline void unlock_rcu_walk(void) br_read_unlock(&vfsmount_lock); } -/* - * When we move over from the RCU domain to properly refcounted - * long-lived dentries, we need to check the sequence numbers - * we got before lookup very carefully. - * - * We cannot blindly increment a dentry refcount - even if it - * is not locked - if it is zero, because it may have gone - * through the final d_kill() logic already. - * - * So for a zero refcount, we need to get the spinlock (which is - * safe even for a dead dentry because the de-allocation is - * RCU-delayed), and check the sequence count under the lock. - * - * Once we have checked the sequence count, we know it is live, - * and since we hold the spinlock it cannot die from under us. - * - * In contrast, if the reference count wasn't zero, we can just - * increment the lockref without having to take the spinlock. - * Even if the sequence number ends up being stale, we haven't - * gone through the final dput() and killed the dentry yet. - */ -static inline int d_rcu_to_refcount(struct dentry *dentry, seqcount_t *validate, unsigned seq) -{ - int gotref; - - gotref = lockref_get_or_lock(&dentry->d_lockref); - - /* Does the sequence number still match? */ - if (read_seqcount_retry(validate, seq)) { - if (gotref) - dput(dentry); - else - spin_unlock(&dentry->d_lock); - return -ECHILD; - } - - /* Get the ref now, if we couldn't get it originally */ - if (!gotref) { - dentry->d_lockref.count++; - spin_unlock(&dentry->d_lock); - } - return 0; -} - /** * unlazy_walk - try to switch to ref-walk mode. * @nd: nameidata pathwalk data @@ -552,16 +508,29 @@ static int unlazy_walk(struct nameidata *nd, struct dentry *dentry) { struct fs_struct *fs = current->fs; struct dentry *parent = nd->path.dentry; - int want_root = 0; BUG_ON(!(nd->flags & LOOKUP_RCU)); - if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) { - want_root = 1; - spin_lock(&fs->lock); - if (nd->root.mnt != fs->root.mnt || - nd->root.dentry != fs->root.dentry) - goto err_root; - } + + /* + * Get a reference to the parent first: we're + * going to make "path_put(nd->path)" valid in + * non-RCU context for "terminate_walk()". + * + * If this doesn't work, return immediately with + * RCU walking still active (and then we will do + * the RCU walk cleanup in terminate_walk()). + */ + if (!lockref_get_not_dead(&parent->d_lockref)) + return -ECHILD; + + /* + * After the mntget(), we terminate_walk() will do + * the right thing for non-RCU mode, and all our + * subsequent exit cases should unlock_rcu_walk() + * before returning. + */ + mntget(nd->path.mnt); + nd->flags &= ~LOOKUP_RCU; /* * For a negative lookup, the lookup sequence point is the parents @@ -575,30 +544,42 @@ static int unlazy_walk(struct nameidata *nd, struct dentry *dentry) * be valid if the child sequence number is still valid. */ if (!dentry) { - if (d_rcu_to_refcount(parent, &parent->d_seq, nd->seq) < 0) - goto err_root; + if (read_seqcount_retry(&parent->d_seq, nd->seq)) + goto out; BUG_ON(nd->inode != parent->d_inode); } else { - if (d_rcu_to_refcount(dentry, &dentry->d_seq, nd->seq) < 0) - goto err_root; - if (d_rcu_to_refcount(parent, &dentry->d_seq, nd->seq) < 0) - goto err_parent; + if (!lockref_get_not_dead(&dentry->d_lockref)) + goto out; + if (read_seqcount_retry(&dentry->d_seq, nd->seq)) + goto drop_dentry; } - if (want_root) { + + /* + * Sequence counts matched. Now make sure that the root is + * still valid and get it if required. + */ + if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) { + spin_lock(&fs->lock); + if (nd->root.mnt != fs->root.mnt || nd->root.dentry != fs->root.dentry) + goto unlock_and_drop_dentry; path_get(&nd->root); spin_unlock(&fs->lock); } - mntget(nd->path.mnt); unlock_rcu_walk(); - nd->flags &= ~LOOKUP_RCU; return 0; -err_parent: +unlock_and_drop_dentry: + spin_unlock(&fs->lock); +drop_dentry: + unlock_rcu_walk(); dput(dentry); -err_root: - if (want_root) - spin_unlock(&fs->lock); + goto drop_root_mnt; +out: + unlock_rcu_walk(); +drop_root_mnt: + if (!(nd->flags & LOOKUP_ROOT)) + nd->root.mnt = NULL; return -ECHILD; } @@ -627,8 +608,13 @@ static int complete_walk(struct nameidata *nd) if (!(nd->flags & LOOKUP_ROOT)) nd->root.mnt = NULL; - if (d_rcu_to_refcount(dentry, &dentry->d_seq, nd->seq) < 0) { + if (unlikely(!lockref_get_not_dead(&dentry->d_lockref))) { + unlock_rcu_walk(); + return -ECHILD; + } + if (read_seqcount_retry(&dentry->d_seq, nd->seq)) { unlock_rcu_walk(); + dput(dentry); return -ECHILD; } mntget(nd->path.mnt); |