diff --git a/fs/dcache.c b/fs/dcache.c index fd74753e7715..43149c7849d9 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "internal.h" #include "mount.h" @@ -2756,8 +2757,16 @@ static void d_wait_lookup(struct dentry *dentry) } } -struct dentry *d_alloc_parallel(struct dentry *parent, - const struct qstr *name) +/* What to do when __d_alloc_parallel finds a d_in_lookup dentry */ +enum alloc_para { + ALLOC_PARA_WAIT, + ALLOC_PARA_FAIL, +}; + +static inline +struct dentry *__d_alloc_parallel(struct dentry *parent, + const struct qstr *name, + enum alloc_para how) { unsigned int hash = name->hash; struct hlist_bl_head *b = in_lookup_hash(parent, hash); @@ -2830,6 +2839,12 @@ struct dentry *d_alloc_parallel(struct dentry *parent, spin_unlock(&dentry->d_lock); goto retry; } + if (unlikely(how == ALLOC_PARA_FAIL)) { + /* mustn't wait for concurrent lookup to complete */ + spin_unlock(&dentry->d_lock); + dput(new); + return ERR_PTR(-EWOULDBLOCK); + } /* * somebody is likely to be still doing lookup for it; * pin it and wait for them to finish @@ -2863,8 +2878,70 @@ struct dentry *d_alloc_parallel(struct dentry *parent, dput(dentry); goto retry; } + +/** + * d_alloc_parallel() - allocate a new dentry and ensure uniqueness + * @parent: dentry of the parent + * @name: name of the dentry within that parent. + * + * A new dentry is allocated and, providing it is unique, added to the + * relevant index. + * If an existing dentry is found with the same parent/name that is + * not d_in_lookup(), then that is returned instead. + * If the existing dentry is d_in_lookup(), d_alloc_parallel() waits for + * that lookup to complete before returning the dentry and then ensures the + * match is still valid. + * Thus if the returned dentry is d_in_lookup() then the caller has + * exclusive access until it completes the lookup. + * If the returned dentry is not d_in_lookup() then a lookup has + * already completed. + * + * The @name must already have ->hash set, as can be achieved + * by e.g. try_lookup_noperm(). + * + * Returns: the dentry, whether found or allocated, or an error %-ENOMEM. + */ +struct dentry *d_alloc_parallel(struct dentry *parent, + const struct qstr *name) +{ + return __d_alloc_parallel(parent, name, ALLOC_PARA_WAIT); +} EXPORT_SYMBOL(d_alloc_parallel); +/** + * d_alloc_trylock() - find or allocate a new dentry + * @parent: dentry of the parent + * @name: name of the dentry within that parent. + * + * A new dentry is allocated and, providing it is unique, added to the + * relevant index. + * If an existing dentry is found with the same parent/name that is + * not d_in_lookup() then that is returned instead. + * If the existing dentry is d_in_lookup(), d_alloc_trylock() + * returns with error %-EWOULDBLOCK. + * Thus if the returned dentry is d_in_lookup() then the caller has + * exclusive access until it completes the lookup. + * If the returned dentry is not d_in_lookup() then a lookup has + * already completed. + * + * The @name need not already have ->hash set. + * + * Returns: the dentry, whether found or allocated, or an error + * %-ENOMEM, %-EWOULDBLOCK, %-EACCES (for a bad name) or + * anything returned by ->d_hash(). + */ +struct dentry *d_alloc_trylock(struct dentry *parent, + struct qstr *name) +{ + struct dentry *de; + + de = try_lookup_noperm(name, parent); + if (!de) + de = __d_alloc_parallel(parent, name, ALLOC_PARA_FAIL); + return de; +} +EXPORT_SYMBOL(d_alloc_trylock); + /* * Move dentry from in-lookup state to busy-negative one. * diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 4b1ff99608e0..7afe16d4664d 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -257,6 +257,7 @@ extern void d_delete(struct dentry *); 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 *); +extern struct dentry * d_alloc_trylock(struct dentry *, 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 *,