diff options
Diffstat (limited to 'fs/btrfs/file.c')
-rw-r--r-- | fs/btrfs/file.c | 148 |
1 files changed, 147 insertions, 1 deletions
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index fa4ef18b66b1..bd4d061c6e4d 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c | |||
@@ -1664,8 +1664,154 @@ out: | |||
1664 | return ret; | 1664 | return ret; |
1665 | } | 1665 | } |
1666 | 1666 | ||
1667 | static int find_desired_extent(struct inode *inode, loff_t *offset, int origin) | ||
1668 | { | ||
1669 | struct btrfs_root *root = BTRFS_I(inode)->root; | ||
1670 | struct extent_map *em; | ||
1671 | struct extent_state *cached_state = NULL; | ||
1672 | u64 lockstart = *offset; | ||
1673 | u64 lockend = i_size_read(inode); | ||
1674 | u64 start = *offset; | ||
1675 | u64 orig_start = *offset; | ||
1676 | u64 len = i_size_read(inode); | ||
1677 | u64 last_end = 0; | ||
1678 | int ret = 0; | ||
1679 | |||
1680 | lockend = max_t(u64, root->sectorsize, lockend); | ||
1681 | if (lockend <= lockstart) | ||
1682 | lockend = lockstart + root->sectorsize; | ||
1683 | |||
1684 | len = lockend - lockstart + 1; | ||
1685 | |||
1686 | len = max_t(u64, len, root->sectorsize); | ||
1687 | if (inode->i_size == 0) | ||
1688 | return -ENXIO; | ||
1689 | |||
1690 | lock_extent_bits(&BTRFS_I(inode)->io_tree, lockstart, lockend, 0, | ||
1691 | &cached_state, GFP_NOFS); | ||
1692 | |||
1693 | /* | ||
1694 | * Delalloc is such a pain. If we have a hole and we have pending | ||
1695 | * delalloc for a portion of the hole we will get back a hole that | ||
1696 | * exists for the entire range since it hasn't been actually written | ||
1697 | * yet. So to take care of this case we need to look for an extent just | ||
1698 | * before the position we want in case there is outstanding delalloc | ||
1699 | * going on here. | ||
1700 | */ | ||
1701 | if (origin == SEEK_HOLE && start != 0) { | ||
1702 | if (start <= root->sectorsize) | ||
1703 | em = btrfs_get_extent_fiemap(inode, NULL, 0, 0, | ||
1704 | root->sectorsize, 0); | ||
1705 | else | ||
1706 | em = btrfs_get_extent_fiemap(inode, NULL, 0, | ||
1707 | start - root->sectorsize, | ||
1708 | root->sectorsize, 0); | ||
1709 | if (IS_ERR(em)) { | ||
1710 | ret = -ENXIO; | ||
1711 | goto out; | ||
1712 | } | ||
1713 | last_end = em->start + em->len; | ||
1714 | if (em->block_start == EXTENT_MAP_DELALLOC) | ||
1715 | last_end = min_t(u64, last_end, inode->i_size); | ||
1716 | free_extent_map(em); | ||
1717 | } | ||
1718 | |||
1719 | while (1) { | ||
1720 | em = btrfs_get_extent_fiemap(inode, NULL, 0, start, len, 0); | ||
1721 | if (IS_ERR(em)) { | ||
1722 | ret = -ENXIO; | ||
1723 | break; | ||
1724 | } | ||
1725 | |||
1726 | if (em->block_start == EXTENT_MAP_HOLE) { | ||
1727 | if (test_bit(EXTENT_FLAG_VACANCY, &em->flags)) { | ||
1728 | if (last_end <= orig_start) { | ||
1729 | free_extent_map(em); | ||
1730 | ret = -ENXIO; | ||
1731 | break; | ||
1732 | } | ||
1733 | } | ||
1734 | |||
1735 | if (origin == SEEK_HOLE) { | ||
1736 | *offset = start; | ||
1737 | free_extent_map(em); | ||
1738 | break; | ||
1739 | } | ||
1740 | } else { | ||
1741 | if (origin == SEEK_DATA) { | ||
1742 | if (em->block_start == EXTENT_MAP_DELALLOC) { | ||
1743 | if (start >= inode->i_size) { | ||
1744 | free_extent_map(em); | ||
1745 | ret = -ENXIO; | ||
1746 | break; | ||
1747 | } | ||
1748 | } | ||
1749 | |||
1750 | *offset = start; | ||
1751 | free_extent_map(em); | ||
1752 | break; | ||
1753 | } | ||
1754 | } | ||
1755 | |||
1756 | start = em->start + em->len; | ||
1757 | last_end = em->start + em->len; | ||
1758 | |||
1759 | if (em->block_start == EXTENT_MAP_DELALLOC) | ||
1760 | last_end = min_t(u64, last_end, inode->i_size); | ||
1761 | |||
1762 | if (test_bit(EXTENT_FLAG_VACANCY, &em->flags)) { | ||
1763 | free_extent_map(em); | ||
1764 | ret = -ENXIO; | ||
1765 | break; | ||
1766 | } | ||
1767 | free_extent_map(em); | ||
1768 | cond_resched(); | ||
1769 | } | ||
1770 | if (!ret) | ||
1771 | *offset = min(*offset, inode->i_size); | ||
1772 | out: | ||
1773 | unlock_extent_cached(&BTRFS_I(inode)->io_tree, lockstart, lockend, | ||
1774 | &cached_state, GFP_NOFS); | ||
1775 | return ret; | ||
1776 | } | ||
1777 | |||
1778 | static loff_t btrfs_file_llseek(struct file *file, loff_t offset, int origin) | ||
1779 | { | ||
1780 | struct inode *inode = file->f_mapping->host; | ||
1781 | int ret; | ||
1782 | |||
1783 | mutex_lock(&inode->i_mutex); | ||
1784 | switch (origin) { | ||
1785 | case SEEK_END: | ||
1786 | case SEEK_CUR: | ||
1787 | offset = generic_file_llseek_unlocked(file, offset, origin); | ||
1788 | goto out; | ||
1789 | case SEEK_DATA: | ||
1790 | case SEEK_HOLE: | ||
1791 | ret = find_desired_extent(inode, &offset, origin); | ||
1792 | if (ret) { | ||
1793 | mutex_unlock(&inode->i_mutex); | ||
1794 | return ret; | ||
1795 | } | ||
1796 | } | ||
1797 | |||
1798 | if (offset < 0 && !(file->f_mode & FMODE_UNSIGNED_OFFSET)) | ||
1799 | return -EINVAL; | ||
1800 | if (offset > inode->i_sb->s_maxbytes) | ||
1801 | return -EINVAL; | ||
1802 | |||
1803 | /* Special lock needed here? */ | ||
1804 | if (offset != file->f_pos) { | ||
1805 | file->f_pos = offset; | ||
1806 | file->f_version = 0; | ||
1807 | } | ||
1808 | out: | ||
1809 | mutex_unlock(&inode->i_mutex); | ||
1810 | return offset; | ||
1811 | } | ||
1812 | |||
1667 | const struct file_operations btrfs_file_operations = { | 1813 | const struct file_operations btrfs_file_operations = { |
1668 | .llseek = generic_file_llseek, | 1814 | .llseek = btrfs_file_llseek, |
1669 | .read = do_sync_read, | 1815 | .read = do_sync_read, |
1670 | .write = do_sync_write, | 1816 | .write = do_sync_write, |
1671 | .aio_read = generic_file_aio_read, | 1817 | .aio_read = generic_file_aio_read, |