diff options
Diffstat (limited to 'fs/namei.c')
-rw-r--r-- | fs/namei.c | 102 |
1 files changed, 49 insertions, 53 deletions
diff --git a/fs/namei.c b/fs/namei.c index cc4bcfaa8624..56e4f4d537d0 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -494,37 +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) -{ - if (likely(lockref_get_not_dead(&dentry->d_lockref))) { - if (!read_seqcount_retry(validate, seq)) - return 0; - dput(dentry); - } - return -ECHILD; -} - /** * unlazy_walk - try to switch to ref-walk mode. * @nd: nameidata pathwalk data @@ -539,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 @@ -562,30 +544,39 @@ 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); + return -ECHILD; +out: + unlock_rcu_walk(); return -ECHILD; } @@ -614,10 +605,15 @@ 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); unlock_rcu_walk(); } |