diff options
Diffstat (limited to 'fs/btrfs/tree-log.c')
-rw-r--r-- | fs/btrfs/tree-log.c | 64 |
1 files changed, 60 insertions, 4 deletions
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index 9c45431e69ab..cb5666e7c3f9 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c | |||
@@ -1613,6 +1613,9 @@ static bool name_in_log_ref(struct btrfs_root *log_root, | |||
1613 | * not exist in the FS, it is skipped. fsyncs on directories | 1613 | * not exist in the FS, it is skipped. fsyncs on directories |
1614 | * do not force down inodes inside that directory, just changes to the | 1614 | * do not force down inodes inside that directory, just changes to the |
1615 | * names or unlinks in a directory. | 1615 | * names or unlinks in a directory. |
1616 | * | ||
1617 | * Returns < 0 on error, 0 if the name wasn't replayed (dentry points to a | ||
1618 | * non-existing inode) and 1 if the name was replayed. | ||
1616 | */ | 1619 | */ |
1617 | static noinline int replay_one_name(struct btrfs_trans_handle *trans, | 1620 | static noinline int replay_one_name(struct btrfs_trans_handle *trans, |
1618 | struct btrfs_root *root, | 1621 | struct btrfs_root *root, |
@@ -1631,6 +1634,7 @@ static noinline int replay_one_name(struct btrfs_trans_handle *trans, | |||
1631 | int exists; | 1634 | int exists; |
1632 | int ret = 0; | 1635 | int ret = 0; |
1633 | bool update_size = (key->type == BTRFS_DIR_INDEX_KEY); | 1636 | bool update_size = (key->type == BTRFS_DIR_INDEX_KEY); |
1637 | bool name_added = false; | ||
1634 | 1638 | ||
1635 | dir = read_one_inode(root, key->objectid); | 1639 | dir = read_one_inode(root, key->objectid); |
1636 | if (!dir) | 1640 | if (!dir) |
@@ -1708,6 +1712,8 @@ out: | |||
1708 | } | 1712 | } |
1709 | kfree(name); | 1713 | kfree(name); |
1710 | iput(dir); | 1714 | iput(dir); |
1715 | if (!ret && name_added) | ||
1716 | ret = 1; | ||
1711 | return ret; | 1717 | return ret; |
1712 | 1718 | ||
1713 | insert: | 1719 | insert: |
@@ -1723,6 +1729,8 @@ insert: | |||
1723 | name, name_len, log_type, &log_key); | 1729 | name, name_len, log_type, &log_key); |
1724 | if (ret && ret != -ENOENT && ret != -EEXIST) | 1730 | if (ret && ret != -ENOENT && ret != -EEXIST) |
1725 | goto out; | 1731 | goto out; |
1732 | if (!ret) | ||
1733 | name_added = true; | ||
1726 | update_size = false; | 1734 | update_size = false; |
1727 | ret = 0; | 1735 | ret = 0; |
1728 | goto out; | 1736 | goto out; |
@@ -1740,12 +1748,13 @@ static noinline int replay_one_dir_item(struct btrfs_trans_handle *trans, | |||
1740 | struct extent_buffer *eb, int slot, | 1748 | struct extent_buffer *eb, int slot, |
1741 | struct btrfs_key *key) | 1749 | struct btrfs_key *key) |
1742 | { | 1750 | { |
1743 | int ret; | 1751 | int ret = 0; |
1744 | u32 item_size = btrfs_item_size_nr(eb, slot); | 1752 | u32 item_size = btrfs_item_size_nr(eb, slot); |
1745 | struct btrfs_dir_item *di; | 1753 | struct btrfs_dir_item *di; |
1746 | int name_len; | 1754 | int name_len; |
1747 | unsigned long ptr; | 1755 | unsigned long ptr; |
1748 | unsigned long ptr_end; | 1756 | unsigned long ptr_end; |
1757 | struct btrfs_path *fixup_path = NULL; | ||
1749 | 1758 | ||
1750 | ptr = btrfs_item_ptr_offset(eb, slot); | 1759 | ptr = btrfs_item_ptr_offset(eb, slot); |
1751 | ptr_end = ptr + item_size; | 1760 | ptr_end = ptr + item_size; |
@@ -1755,12 +1764,59 @@ static noinline int replay_one_dir_item(struct btrfs_trans_handle *trans, | |||
1755 | return -EIO; | 1764 | return -EIO; |
1756 | name_len = btrfs_dir_name_len(eb, di); | 1765 | name_len = btrfs_dir_name_len(eb, di); |
1757 | ret = replay_one_name(trans, root, path, eb, di, key); | 1766 | ret = replay_one_name(trans, root, path, eb, di, key); |
1758 | if (ret) | 1767 | if (ret < 0) |
1759 | return ret; | 1768 | break; |
1760 | ptr = (unsigned long)(di + 1); | 1769 | ptr = (unsigned long)(di + 1); |
1761 | ptr += name_len; | 1770 | ptr += name_len; |
1771 | |||
1772 | /* | ||
1773 | * If this entry refers to a non-directory (directories can not | ||
1774 | * have a link count > 1) and it was added in the transaction | ||
1775 | * that was not committed, make sure we fixup the link count of | ||
1776 | * the inode it the entry points to. Otherwise something like | ||
1777 | * the following would result in a directory pointing to an | ||
1778 | * inode with a wrong link that does not account for this dir | ||
1779 | * entry: | ||
1780 | * | ||
1781 | * mkdir testdir | ||
1782 | * touch testdir/foo | ||
1783 | * touch testdir/bar | ||
1784 | * sync | ||
1785 | * | ||
1786 | * ln testdir/bar testdir/bar_link | ||
1787 | * ln testdir/foo testdir/foo_link | ||
1788 | * xfs_io -c "fsync" testdir/bar | ||
1789 | * | ||
1790 | * <power failure> | ||
1791 | * | ||
1792 | * mount fs, log replay happens | ||
1793 | * | ||
1794 | * File foo would remain with a link count of 1 when it has two | ||
1795 | * entries pointing to it in the directory testdir. This would | ||
1796 | * make it impossible to ever delete the parent directory has | ||
1797 | * it would result in stale dentries that can never be deleted. | ||
1798 | */ | ||
1799 | if (ret == 1 && btrfs_dir_type(eb, di) != BTRFS_FT_DIR) { | ||
1800 | struct btrfs_key di_key; | ||
1801 | |||
1802 | if (!fixup_path) { | ||
1803 | fixup_path = btrfs_alloc_path(); | ||
1804 | if (!fixup_path) { | ||
1805 | ret = -ENOMEM; | ||
1806 | break; | ||
1807 | } | ||
1808 | } | ||
1809 | |||
1810 | btrfs_dir_item_key_to_cpu(eb, di, &di_key); | ||
1811 | ret = link_to_fixup_dir(trans, root, fixup_path, | ||
1812 | di_key.objectid); | ||
1813 | if (ret) | ||
1814 | break; | ||
1815 | } | ||
1816 | ret = 0; | ||
1762 | } | 1817 | } |
1763 | return 0; | 1818 | btrfs_free_path(fixup_path); |
1819 | return ret; | ||
1764 | } | 1820 | } |
1765 | 1821 | ||
1766 | /* | 1822 | /* |