diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2006-10-21 13:24:20 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-10-21 16:35:06 -0400 |
commit | 9eaef27b36a6b716384948da94b8fc5bfba7b712 (patch) | |
tree | ab69cf9c3863bb7ca564574c914e5b6bf8ad3162 | |
parent | 3f7705eab6722ad1a346d748c4aad55755d6c241 (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.c | 137 | ||||
-rw-r--r-- | fs/nfs/dir.c | 7 |
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 | 1480 | static void d_move_locked(struct dentry * dentry, struct dentry * target) | |
1481 | void 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 | |||
1548 | void 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 | */ | ||
1558 | static 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 | */ | ||
1580 | static 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; | ||
1602 | out_unalias: | ||
1603 | d_move_locked(alias, dentry); | ||
1604 | ret = alias; | ||
1605 | out_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 | */ |
1582 | struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode) | 1654 | struct 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 | 1704 | out_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 */ | ||
1641 | connect_mountpoint: | ||
1642 | __d_materialise_dentry(dentry, alias); | ||
1643 | |||
1644 | /* Replace the candidate dentry with the alias in the tree */ | ||
1645 | replace_with_alias: | ||
1646 | __d_drop(alias); | ||
1647 | actual = alias; | ||
1648 | goto found; | ||
1649 | |||
1650 | shouldnt_be_hashed: | 1713 | shouldnt_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 | ||
936 | no_entry: | 936 | no_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)); |
942 | out_unlock: | 945 | out_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 | ||