aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrond Myklebust <Trond.Myklebust@netapp.com>2006-10-21 13:24:20 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2006-10-21 16:35:06 -0400
commit9eaef27b36a6b716384948da94b8fc5bfba7b712 (patch)
treeab69cf9c3863bb7ca564574c914e5b6bf8ad3162
parent3f7705eab6722ad1a346d748c4aad55755d6c241 (diff)
[PATCH] VFS: Make d_materialise_unique() enforce directory uniqueness
If the caller tries to instantiate a directory using an inode that already has a dentry alias, then we attempt to rename the existing dentry instead of instantiating a new one. Fail with an ELOOP error if the rename would affect one of our parent directories. This behaviour is needed in order to avoid issues such as http://bugzilla.kernel.org/show_bug.cgi?id=7178 Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com> Cc: Miklos Szeredi <miklos@szeredi.hu> Cc: Maneesh Soni <maneesh@in.ibm.com> Cc: Dipankar Sarma <dipankar@in.ibm.com> Cc: Neil Brown <neilb@cse.unsw.edu.au> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Christoph Hellwig <hch@lst.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--fs/dcache.c137
-rw-r--r--fs/nfs/dir.c7
2 files changed, 106 insertions, 38 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index 2bac4ba1d1d3..a1ff91eef108 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1469,23 +1469,21 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
1469 * deleted it. 1469 * deleted it.
1470 */ 1470 */
1471 1471
1472/** 1472/*
1473 * d_move - move a dentry 1473 * d_move_locked - move a dentry
1474 * @dentry: entry to move 1474 * @dentry: entry to move
1475 * @target: new dentry 1475 * @target: new dentry
1476 * 1476 *
1477 * Update the dcache to reflect the move of a file name. Negative 1477 * Update the dcache to reflect the move of a file name. Negative
1478 * dcache entries should not be moved in this way. 1478 * dcache entries should not be moved in this way.
1479 */ 1479 */
1480 1480static void d_move_locked(struct dentry * dentry, struct dentry * target)
1481void d_move(struct dentry * dentry, struct dentry * target)
1482{ 1481{
1483 struct hlist_head *list; 1482 struct hlist_head *list;
1484 1483
1485 if (!dentry->d_inode) 1484 if (!dentry->d_inode)
1486 printk(KERN_WARNING "VFS: moving negative dcache entry\n"); 1485 printk(KERN_WARNING "VFS: moving negative dcache entry\n");
1487 1486
1488 spin_lock(&dcache_lock);
1489 write_seqlock(&rename_lock); 1487 write_seqlock(&rename_lock);
1490 /* 1488 /*
1491 * XXXX: do we really need to take target->d_lock? 1489 * XXXX: do we really need to take target->d_lock?
@@ -1536,10 +1534,84 @@ already_unhashed:
1536 fsnotify_d_move(dentry); 1534 fsnotify_d_move(dentry);
1537 spin_unlock(&dentry->d_lock); 1535 spin_unlock(&dentry->d_lock);
1538 write_sequnlock(&rename_lock); 1536 write_sequnlock(&rename_lock);
1537}
1538
1539/**
1540 * d_move - move a dentry
1541 * @dentry: entry to move
1542 * @target: new dentry
1543 *
1544 * Update the dcache to reflect the move of a file name. Negative
1545 * dcache entries should not be moved in this way.
1546 */
1547
1548void d_move(struct dentry * dentry, struct dentry * target)
1549{
1550 spin_lock(&dcache_lock);
1551 d_move_locked(dentry, target);
1539 spin_unlock(&dcache_lock); 1552 spin_unlock(&dcache_lock);
1540} 1553}
1541 1554
1542/* 1555/*
1556 * Helper that returns 1 if p1 is a parent of p2, else 0
1557 */
1558static int d_isparent(struct dentry *p1, struct dentry *p2)
1559{
1560 struct dentry *p;
1561
1562 for (p = p2; p->d_parent != p; p = p->d_parent) {
1563 if (p->d_parent == p1)
1564 return 1;
1565 }
1566 return 0;
1567}
1568
1569/*
1570 * This helper attempts to cope with remotely renamed directories
1571 *
1572 * It assumes that the caller is already holding
1573 * dentry->d_parent->d_inode->i_mutex and the dcache_lock
1574 *
1575 * Note: If ever the locking in lock_rename() changes, then please
1576 * remember to update this too...
1577 *
1578 * On return, dcache_lock will have been unlocked.
1579 */
1580static struct dentry *__d_unalias(struct dentry *dentry, struct dentry *alias)
1581{
1582 struct mutex *m1 = NULL, *m2 = NULL;
1583 struct dentry *ret;
1584
1585 /* If alias and dentry share a parent, then no extra locks required */
1586 if (alias->d_parent == dentry->d_parent)
1587 goto out_unalias;
1588
1589 /* Check for loops */
1590 ret = ERR_PTR(-ELOOP);
1591 if (d_isparent(alias, dentry))
1592 goto out_err;
1593
1594 /* See lock_rename() */
1595 ret = ERR_PTR(-EBUSY);
1596 if (!mutex_trylock(&dentry->d_sb->s_vfs_rename_mutex))
1597 goto out_err;
1598 m1 = &dentry->d_sb->s_vfs_rename_mutex;
1599 if (!mutex_trylock(&alias->d_parent->d_inode->i_mutex))
1600 goto out_err;
1601 m2 = &alias->d_parent->d_inode->i_mutex;
1602out_unalias:
1603 d_move_locked(alias, dentry);
1604 ret = alias;
1605out_err:
1606 spin_unlock(&dcache_lock);
1607 if (m2)
1608 mutex_unlock(m2);
1609 if (m1)
1610 mutex_unlock(m1);
1611 return ret;
1612}
1613
1614/*
1543 * Prepare an anonymous dentry for life in the superblock's dentry tree as a 1615 * Prepare an anonymous dentry for life in the superblock's dentry tree as a
1544 * named dentry in place of the dentry to be replaced. 1616 * named dentry in place of the dentry to be replaced.
1545 */ 1617 */
@@ -1581,7 +1653,7 @@ static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon)
1581 */ 1653 */
1582struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode) 1654struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
1583{ 1655{
1584 struct dentry *alias, *actual; 1656 struct dentry *actual;
1585 1657
1586 BUG_ON(!d_unhashed(dentry)); 1658 BUG_ON(!d_unhashed(dentry));
1587 1659
@@ -1593,26 +1665,27 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
1593 goto found_lock; 1665 goto found_lock;
1594 } 1666 }
1595 1667
1596 /* See if a disconnected directory already exists as an anonymous root 1668 if (S_ISDIR(inode->i_mode)) {
1597 * that we should splice into the tree instead */ 1669 struct dentry *alias;
1598 if (S_ISDIR(inode->i_mode) && (alias = __d_find_alias(inode, 1))) { 1670
1599 spin_lock(&alias->d_lock); 1671 /* Does an aliased dentry already exist? */
1600 1672 alias = __d_find_alias(inode, 0);
1601 /* Is this a mountpoint that we could splice into our tree? */ 1673 if (alias) {
1602 if (IS_ROOT(alias)) 1674 actual = alias;
1603 goto connect_mountpoint; 1675 /* Is this an anonymous mountpoint that we could splice
1604 1676 * into our tree? */
1605 if (alias->d_name.len == dentry->d_name.len && 1677 if (IS_ROOT(alias)) {
1606 alias->d_parent == dentry->d_parent && 1678 spin_lock(&alias->d_lock);
1607 memcmp(alias->d_name.name, 1679 __d_materialise_dentry(dentry, alias);
1608 dentry->d_name.name, 1680 __d_drop(alias);
1609 dentry->d_name.len) == 0) 1681 goto found;
1610 goto replace_with_alias; 1682 }
1611 1683 /* Nope, but we must(!) avoid directory aliasing */
1612 spin_unlock(&alias->d_lock); 1684 actual = __d_unalias(dentry, alias);
1613 1685 if (IS_ERR(actual))
1614 /* Doh! Seem to be aliasing directories for some reason... */ 1686 dput(alias);
1615 dput(alias); 1687 goto out_nolock;
1688 }
1616 } 1689 }
1617 1690
1618 /* Add a unique reference */ 1691 /* Add a unique reference */
@@ -1628,7 +1701,7 @@ found:
1628 _d_rehash(actual); 1701 _d_rehash(actual);
1629 spin_unlock(&actual->d_lock); 1702 spin_unlock(&actual->d_lock);
1630 spin_unlock(&dcache_lock); 1703 spin_unlock(&dcache_lock);
1631 1704out_nolock:
1632 if (actual == dentry) { 1705 if (actual == dentry) {
1633 security_d_instantiate(dentry, inode); 1706 security_d_instantiate(dentry, inode);
1634 return NULL; 1707 return NULL;
@@ -1637,16 +1710,6 @@ found:
1637 iput(inode); 1710 iput(inode);
1638 return actual; 1711 return actual;
1639 1712
1640 /* Convert the anonymous/root alias into an ordinary dentry */
1641connect_mountpoint:
1642 __d_materialise_dentry(dentry, alias);
1643
1644 /* Replace the candidate dentry with the alias in the tree */
1645replace_with_alias:
1646 __d_drop(alias);
1647 actual = alias;
1648 goto found;
1649
1650shouldnt_be_hashed: 1713shouldnt_be_hashed:
1651 spin_unlock(&dcache_lock); 1714 spin_unlock(&dcache_lock);
1652 BUG(); 1715 BUG();
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 4133ef5264e5..27b5a1051b1c 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -935,8 +935,11 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru
935 935
936no_entry: 936no_entry:
937 res = d_materialise_unique(dentry, inode); 937 res = d_materialise_unique(dentry, inode);
938 if (res != NULL) 938 if (res != NULL) {
939 if (IS_ERR(res))
940 goto out_unlock;
939 dentry = res; 941 dentry = res;
942 }
940 nfs_renew_times(dentry); 943 nfs_renew_times(dentry);
941 nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); 944 nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
942out_unlock: 945out_unlock:
@@ -1132,6 +1135,8 @@ static struct dentry *nfs_readdir_lookup(nfs_readdir_descriptor_t *desc)
1132 alias = d_materialise_unique(dentry, inode); 1135 alias = d_materialise_unique(dentry, inode);
1133 if (alias != NULL) { 1136 if (alias != NULL) {
1134 dput(dentry); 1137 dput(dentry);
1138 if (IS_ERR(alias))
1139 return NULL;
1135 dentry = alias; 1140 dentry = alias;
1136 } 1141 }
1137 1142