|
@@ -69,6 +69,7 @@ struct dentry_stat_t dentry_stat = {
|
|
|
|
|
|
static void __d_free(struct dentry *dentry)
|
|
static void __d_free(struct dentry *dentry)
|
|
{
|
|
{
|
|
|
|
+ WARN_ON(!list_empty(&dentry->d_alias));
|
|
if (dname_external(dentry))
|
|
if (dname_external(dentry))
|
|
kfree(dentry->d_name.name);
|
|
kfree(dentry->d_name.name);
|
|
kmem_cache_free(dentry_cache, dentry);
|
|
kmem_cache_free(dentry_cache, dentry);
|
|
@@ -174,9 +175,12 @@ static struct dentry *d_kill(struct dentry *dentry)
|
|
dentry_stat.nr_dentry--; /* For d_free, below */
|
|
dentry_stat.nr_dentry--; /* For d_free, below */
|
|
/*drops the locks, at that point nobody can reach this dentry */
|
|
/*drops the locks, at that point nobody can reach this dentry */
|
|
dentry_iput(dentry);
|
|
dentry_iput(dentry);
|
|
- parent = dentry->d_parent;
|
|
|
|
|
|
+ if (IS_ROOT(dentry))
|
|
|
|
+ parent = NULL;
|
|
|
|
+ else
|
|
|
|
+ parent = dentry->d_parent;
|
|
d_free(dentry);
|
|
d_free(dentry);
|
|
- return dentry == parent ? NULL : parent;
|
|
|
|
|
|
+ return parent;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -666,11 +670,12 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
|
|
BUG();
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
|
|
- parent = dentry->d_parent;
|
|
|
|
- if (parent == dentry)
|
|
|
|
|
|
+ if (IS_ROOT(dentry))
|
|
parent = NULL;
|
|
parent = NULL;
|
|
- else
|
|
|
|
|
|
+ else {
|
|
|
|
+ parent = dentry->d_parent;
|
|
atomic_dec(&parent->d_count);
|
|
atomic_dec(&parent->d_count);
|
|
|
|
+ }
|
|
|
|
|
|
list_del(&dentry->d_u.d_child);
|
|
list_del(&dentry->d_u.d_child);
|
|
detached++;
|
|
detached++;
|
|
@@ -977,6 +982,15 @@ struct dentry *d_alloc_name(struct dentry *parent, const char *name)
|
|
return d_alloc(parent, &q);
|
|
return d_alloc(parent, &q);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/* the caller must hold dcache_lock */
|
|
|
|
+static void __d_instantiate(struct dentry *dentry, struct inode *inode)
|
|
|
|
+{
|
|
|
|
+ if (inode)
|
|
|
|
+ list_add(&dentry->d_alias, &inode->i_dentry);
|
|
|
|
+ dentry->d_inode = inode;
|
|
|
|
+ fsnotify_d_instantiate(dentry, inode);
|
|
|
|
+}
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* d_instantiate - fill in inode information for a dentry
|
|
* d_instantiate - fill in inode information for a dentry
|
|
* @entry: dentry to complete
|
|
* @entry: dentry to complete
|
|
@@ -996,10 +1010,7 @@ void d_instantiate(struct dentry *entry, struct inode * inode)
|
|
{
|
|
{
|
|
BUG_ON(!list_empty(&entry->d_alias));
|
|
BUG_ON(!list_empty(&entry->d_alias));
|
|
spin_lock(&dcache_lock);
|
|
spin_lock(&dcache_lock);
|
|
- if (inode)
|
|
|
|
- list_add(&entry->d_alias, &inode->i_dentry);
|
|
|
|
- entry->d_inode = inode;
|
|
|
|
- fsnotify_d_instantiate(entry, inode);
|
|
|
|
|
|
+ __d_instantiate(entry, inode);
|
|
spin_unlock(&dcache_lock);
|
|
spin_unlock(&dcache_lock);
|
|
security_d_instantiate(entry, inode);
|
|
security_d_instantiate(entry, inode);
|
|
}
|
|
}
|
|
@@ -1029,7 +1040,7 @@ static struct dentry *__d_instantiate_unique(struct dentry *entry,
|
|
unsigned int hash = entry->d_name.hash;
|
|
unsigned int hash = entry->d_name.hash;
|
|
|
|
|
|
if (!inode) {
|
|
if (!inode) {
|
|
- entry->d_inode = NULL;
|
|
|
|
|
|
+ __d_instantiate(entry, NULL);
|
|
return NULL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1048,9 +1059,7 @@ static struct dentry *__d_instantiate_unique(struct dentry *entry,
|
|
return alias;
|
|
return alias;
|
|
}
|
|
}
|
|
|
|
|
|
- list_add(&entry->d_alias, &inode->i_dentry);
|
|
|
|
- entry->d_inode = inode;
|
|
|
|
- fsnotify_d_instantiate(entry, inode);
|
|
|
|
|
|
+ __d_instantiate(entry, inode);
|
|
return NULL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1111,69 +1120,71 @@ static inline struct hlist_head *d_hash(struct dentry *parent,
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * d_alloc_anon - allocate an anonymous dentry
|
|
|
|
|
|
+ * d_obtain_alias - find or allocate a dentry for a given inode
|
|
* @inode: inode to allocate the dentry for
|
|
* @inode: inode to allocate the dentry for
|
|
*
|
|
*
|
|
- * This is similar to d_alloc_root. It is used by filesystems when
|
|
|
|
- * creating a dentry for a given inode, often in the process of
|
|
|
|
- * mapping a filehandle to a dentry. The returned dentry may be
|
|
|
|
- * anonymous, or may have a full name (if the inode was already
|
|
|
|
- * in the cache). The file system may need to make further
|
|
|
|
- * efforts to connect this dentry into the dcache properly.
|
|
|
|
|
|
+ * Obtain a dentry for an inode resulting from NFS filehandle conversion or
|
|
|
|
+ * similar open by handle operations. The returned dentry may be anonymous,
|
|
|
|
+ * or may have a full name (if the inode was already in the cache).
|
|
*
|
|
*
|
|
- * When called on a directory inode, we must ensure that
|
|
|
|
- * the inode only ever has one dentry. If a dentry is
|
|
|
|
- * found, that is returned instead of allocating a new one.
|
|
|
|
|
|
+ * When called on a directory inode, we must ensure that the inode only ever
|
|
|
|
+ * has one dentry. If a dentry is found, that is returned instead of
|
|
|
|
+ * allocating a new one.
|
|
*
|
|
*
|
|
* On successful return, the reference to the inode has been transferred
|
|
* On successful return, the reference to the inode has been transferred
|
|
- * to the dentry. If %NULL is returned (indicating kmalloc failure),
|
|
|
|
- * the reference on the inode has not been released.
|
|
|
|
|
|
+ * to the dentry. In case of an error the reference on the inode is released.
|
|
|
|
+ * To make it easier to use in export operations a %NULL or IS_ERR inode may
|
|
|
|
+ * be passed in and will be the error will be propagate to the return value,
|
|
|
|
+ * with a %NULL @inode replaced by ERR_PTR(-ESTALE).
|
|
*/
|
|
*/
|
|
-
|
|
|
|
-struct dentry * d_alloc_anon(struct inode *inode)
|
|
|
|
|
|
+struct dentry *d_obtain_alias(struct inode *inode)
|
|
{
|
|
{
|
|
static const struct qstr anonstring = { .name = "" };
|
|
static const struct qstr anonstring = { .name = "" };
|
|
struct dentry *tmp;
|
|
struct dentry *tmp;
|
|
struct dentry *res;
|
|
struct dentry *res;
|
|
|
|
|
|
- if ((res = d_find_alias(inode))) {
|
|
|
|
- iput(inode);
|
|
|
|
- return res;
|
|
|
|
- }
|
|
|
|
|
|
+ if (!inode)
|
|
|
|
+ return ERR_PTR(-ESTALE);
|
|
|
|
+ if (IS_ERR(inode))
|
|
|
|
+ return ERR_CAST(inode);
|
|
|
|
|
|
- tmp = d_alloc(NULL, &anonstring);
|
|
|
|
- if (!tmp)
|
|
|
|
- return NULL;
|
|
|
|
|
|
+ res = d_find_alias(inode);
|
|
|
|
+ if (res)
|
|
|
|
+ goto out_iput;
|
|
|
|
|
|
|
|
+ tmp = d_alloc(NULL, &anonstring);
|
|
|
|
+ if (!tmp) {
|
|
|
|
+ res = ERR_PTR(-ENOMEM);
|
|
|
|
+ goto out_iput;
|
|
|
|
+ }
|
|
tmp->d_parent = tmp; /* make sure dput doesn't croak */
|
|
tmp->d_parent = tmp; /* make sure dput doesn't croak */
|
|
-
|
|
|
|
|
|
+
|
|
spin_lock(&dcache_lock);
|
|
spin_lock(&dcache_lock);
|
|
res = __d_find_alias(inode, 0);
|
|
res = __d_find_alias(inode, 0);
|
|
- if (!res) {
|
|
|
|
- /* attach a disconnected dentry */
|
|
|
|
- res = tmp;
|
|
|
|
- tmp = NULL;
|
|
|
|
- spin_lock(&res->d_lock);
|
|
|
|
- res->d_sb = inode->i_sb;
|
|
|
|
- res->d_parent = res;
|
|
|
|
- res->d_inode = inode;
|
|
|
|
- res->d_flags |= DCACHE_DISCONNECTED;
|
|
|
|
- res->d_flags &= ~DCACHE_UNHASHED;
|
|
|
|
- list_add(&res->d_alias, &inode->i_dentry);
|
|
|
|
- hlist_add_head(&res->d_hash, &inode->i_sb->s_anon);
|
|
|
|
- spin_unlock(&res->d_lock);
|
|
|
|
-
|
|
|
|
- inode = NULL; /* don't drop reference */
|
|
|
|
|
|
+ if (res) {
|
|
|
|
+ spin_unlock(&dcache_lock);
|
|
|
|
+ dput(tmp);
|
|
|
|
+ goto out_iput;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ /* attach a disconnected dentry */
|
|
|
|
+ spin_lock(&tmp->d_lock);
|
|
|
|
+ tmp->d_sb = inode->i_sb;
|
|
|
|
+ tmp->d_inode = inode;
|
|
|
|
+ tmp->d_flags |= DCACHE_DISCONNECTED;
|
|
|
|
+ tmp->d_flags &= ~DCACHE_UNHASHED;
|
|
|
|
+ list_add(&tmp->d_alias, &inode->i_dentry);
|
|
|
|
+ hlist_add_head(&tmp->d_hash, &inode->i_sb->s_anon);
|
|
|
|
+ spin_unlock(&tmp->d_lock);
|
|
|
|
+
|
|
spin_unlock(&dcache_lock);
|
|
spin_unlock(&dcache_lock);
|
|
|
|
+ return tmp;
|
|
|
|
|
|
- if (inode)
|
|
|
|
- iput(inode);
|
|
|
|
- if (tmp)
|
|
|
|
- dput(tmp);
|
|
|
|
|
|
+ out_iput:
|
|
|
|
+ iput(inode);
|
|
return res;
|
|
return res;
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+EXPORT_SYMBOL_GPL(d_obtain_alias);
|
|
|
|
|
|
/**
|
|
/**
|
|
* d_splice_alias - splice a disconnected dentry into the tree if one exists
|
|
* d_splice_alias - splice a disconnected dentry into the tree if one exists
|
|
@@ -1200,17 +1211,14 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
|
|
new = __d_find_alias(inode, 1);
|
|
new = __d_find_alias(inode, 1);
|
|
if (new) {
|
|
if (new) {
|
|
BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
|
|
BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
|
|
- fsnotify_d_instantiate(new, inode);
|
|
|
|
spin_unlock(&dcache_lock);
|
|
spin_unlock(&dcache_lock);
|
|
security_d_instantiate(new, inode);
|
|
security_d_instantiate(new, inode);
|
|
d_rehash(dentry);
|
|
d_rehash(dentry);
|
|
d_move(new, dentry);
|
|
d_move(new, dentry);
|
|
iput(inode);
|
|
iput(inode);
|
|
} else {
|
|
} else {
|
|
- /* d_instantiate takes dcache_lock, so we do it by hand */
|
|
|
|
- list_add(&dentry->d_alias, &inode->i_dentry);
|
|
|
|
- dentry->d_inode = inode;
|
|
|
|
- fsnotify_d_instantiate(dentry, inode);
|
|
|
|
|
|
+ /* already taking dcache_lock, so d_add() by hand */
|
|
|
|
+ __d_instantiate(dentry, inode);
|
|
spin_unlock(&dcache_lock);
|
|
spin_unlock(&dcache_lock);
|
|
security_d_instantiate(dentry, inode);
|
|
security_d_instantiate(dentry, inode);
|
|
d_rehash(dentry);
|
|
d_rehash(dentry);
|
|
@@ -1293,8 +1301,7 @@ struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
|
|
* d_instantiate() by hand because it takes dcache_lock which
|
|
* d_instantiate() by hand because it takes dcache_lock which
|
|
* we already hold.
|
|
* we already hold.
|
|
*/
|
|
*/
|
|
- list_add(&found->d_alias, &inode->i_dentry);
|
|
|
|
- found->d_inode = inode;
|
|
|
|
|
|
+ __d_instantiate(found, inode);
|
|
spin_unlock(&dcache_lock);
|
|
spin_unlock(&dcache_lock);
|
|
security_d_instantiate(found, inode);
|
|
security_d_instantiate(found, inode);
|
|
return found;
|
|
return found;
|
|
@@ -1456,8 +1463,6 @@ out:
|
|
* d_validate - verify dentry provided from insecure source
|
|
* d_validate - verify dentry provided from insecure source
|
|
* @dentry: The dentry alleged to be valid child of @dparent
|
|
* @dentry: The dentry alleged to be valid child of @dparent
|
|
* @dparent: The parent dentry (known to be valid)
|
|
* @dparent: The parent dentry (known to be valid)
|
|
- * @hash: Hash of the dentry
|
|
|
|
- * @len: Length of the name
|
|
|
|
*
|
|
*
|
|
* An insecure source has sent us a dentry, here we verify it and dget() it.
|
|
* An insecure source has sent us a dentry, here we verify it and dget() it.
|
|
* This is used by ncpfs in its readdir implementation.
|
|
* This is used by ncpfs in its readdir implementation.
|
|
@@ -1714,18 +1719,23 @@ void d_move(struct dentry * dentry, struct dentry * target)
|
|
spin_unlock(&dcache_lock);
|
|
spin_unlock(&dcache_lock);
|
|
}
|
|
}
|
|
|
|
|
|
-/*
|
|
|
|
- * Helper that returns 1 if p1 is a parent of p2, else 0
|
|
|
|
|
|
+/**
|
|
|
|
+ * d_ancestor - search for an ancestor
|
|
|
|
+ * @p1: ancestor dentry
|
|
|
|
+ * @p2: child dentry
|
|
|
|
+ *
|
|
|
|
+ * Returns the ancestor dentry of p2 which is a child of p1, if p1 is
|
|
|
|
+ * an ancestor of p2, else NULL.
|
|
*/
|
|
*/
|
|
-static int d_isparent(struct dentry *p1, struct dentry *p2)
|
|
|
|
|
|
+struct dentry *d_ancestor(struct dentry *p1, struct dentry *p2)
|
|
{
|
|
{
|
|
struct dentry *p;
|
|
struct dentry *p;
|
|
|
|
|
|
- for (p = p2; p->d_parent != p; p = p->d_parent) {
|
|
|
|
|
|
+ for (p = p2; !IS_ROOT(p); p = p->d_parent) {
|
|
if (p->d_parent == p1)
|
|
if (p->d_parent == p1)
|
|
- return 1;
|
|
|
|
|
|
+ return p;
|
|
}
|
|
}
|
|
- return 0;
|
|
|
|
|
|
+ return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1749,7 +1759,7 @@ static struct dentry *__d_unalias(struct dentry *dentry, struct dentry *alias)
|
|
|
|
|
|
/* Check for loops */
|
|
/* Check for loops */
|
|
ret = ERR_PTR(-ELOOP);
|
|
ret = ERR_PTR(-ELOOP);
|
|
- if (d_isparent(alias, dentry))
|
|
|
|
|
|
+ if (d_ancestor(alias, dentry))
|
|
goto out_err;
|
|
goto out_err;
|
|
|
|
|
|
/* See lock_rename() */
|
|
/* See lock_rename() */
|
|
@@ -1822,7 +1832,7 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
|
|
|
|
|
|
if (!inode) {
|
|
if (!inode) {
|
|
actual = dentry;
|
|
actual = dentry;
|
|
- dentry->d_inode = NULL;
|
|
|
|
|
|
+ __d_instantiate(dentry, NULL);
|
|
goto found_lock;
|
|
goto found_lock;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2149,32 +2159,27 @@ out:
|
|
* Caller must ensure that "new_dentry" is pinned before calling is_subdir()
|
|
* Caller must ensure that "new_dentry" is pinned before calling is_subdir()
|
|
*/
|
|
*/
|
|
|
|
|
|
-int is_subdir(struct dentry * new_dentry, struct dentry * old_dentry)
|
|
|
|
|
|
+int is_subdir(struct dentry *new_dentry, struct dentry *old_dentry)
|
|
{
|
|
{
|
|
int result;
|
|
int result;
|
|
- struct dentry * saved = new_dentry;
|
|
|
|
unsigned long seq;
|
|
unsigned long seq;
|
|
|
|
|
|
- /* need rcu_readlock to protect against the d_parent trashing due to
|
|
|
|
- * d_move
|
|
|
|
|
|
+ /* FIXME: This is old behavior, needed? Please check callers. */
|
|
|
|
+ if (new_dentry == old_dentry)
|
|
|
|
+ return 1;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Need rcu_readlock to protect against the d_parent trashing
|
|
|
|
+ * due to d_move
|
|
*/
|
|
*/
|
|
rcu_read_lock();
|
|
rcu_read_lock();
|
|
- do {
|
|
|
|
|
|
+ do {
|
|
/* for restarting inner loop in case of seq retry */
|
|
/* for restarting inner loop in case of seq retry */
|
|
- new_dentry = saved;
|
|
|
|
- result = 0;
|
|
|
|
seq = read_seqbegin(&rename_lock);
|
|
seq = read_seqbegin(&rename_lock);
|
|
- for (;;) {
|
|
|
|
- if (new_dentry != old_dentry) {
|
|
|
|
- struct dentry * parent = new_dentry->d_parent;
|
|
|
|
- if (parent == new_dentry)
|
|
|
|
- break;
|
|
|
|
- new_dentry = parent;
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ if (d_ancestor(old_dentry, new_dentry))
|
|
result = 1;
|
|
result = 1;
|
|
- break;
|
|
|
|
- }
|
|
|
|
|
|
+ else
|
|
|
|
+ result = 0;
|
|
} while (read_seqretry(&rename_lock, seq));
|
|
} while (read_seqretry(&rename_lock, seq));
|
|
rcu_read_unlock();
|
|
rcu_read_unlock();
|
|
|
|
|
|
@@ -2344,7 +2349,6 @@ void __init vfs_caches_init(unsigned long mempages)
|
|
}
|
|
}
|
|
|
|
|
|
EXPORT_SYMBOL(d_alloc);
|
|
EXPORT_SYMBOL(d_alloc);
|
|
-EXPORT_SYMBOL(d_alloc_anon);
|
|
|
|
EXPORT_SYMBOL(d_alloc_root);
|
|
EXPORT_SYMBOL(d_alloc_root);
|
|
EXPORT_SYMBOL(d_delete);
|
|
EXPORT_SYMBOL(d_delete);
|
|
EXPORT_SYMBOL(d_find_alias);
|
|
EXPORT_SYMBOL(d_find_alias);
|