diff options
| author | Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> | 2009-06-05 00:56:49 -0400 |
|---|---|---|
| committer | Theodore Ts'o <tytso@mit.edu> | 2009-06-05 00:56:49 -0400 |
| commit | f8514083cd61daef12fba5ef883ad9352c450428 (patch) | |
| tree | 46cbf0c28ea112229c3ee9209750c8017f1b9385 | |
| parent | 1938a150c25bf7c2c47182e753a1038945b70b0e (diff) | |
ext4: truncate the file properly if we fail to copy data from userspace
In generic_perform_write if we fail to copy the user data we don't
update the inode->i_size. We should truncate the file in the above
case so that we don't have blocks allocated outside inode->i_size. Add
the inode to orphan list in the same transaction as block allocation
This ensures that if we crash in between the recovery would do the
truncate.
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
CC: Jan Kara <jack@suse.cz>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
| -rw-r--r-- | fs/ext4/inode.c | 128 |
1 files changed, 102 insertions, 26 deletions
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 8d215881172f..2c10d346f7a3 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c | |||
| @@ -1549,6 +1549,52 @@ static int write_end_fn(handle_t *handle, struct buffer_head *bh) | |||
| 1549 | return ext4_handle_dirty_metadata(handle, NULL, bh); | 1549 | return ext4_handle_dirty_metadata(handle, NULL, bh); |
| 1550 | } | 1550 | } |
| 1551 | 1551 | ||
| 1552 | static int ext4_generic_write_end(struct file *file, | ||
| 1553 | struct address_space *mapping, | ||
| 1554 | loff_t pos, unsigned len, unsigned copied, | ||
| 1555 | struct page *page, void *fsdata) | ||
| 1556 | { | ||
| 1557 | int i_size_changed = 0; | ||
| 1558 | struct inode *inode = mapping->host; | ||
| 1559 | handle_t *handle = ext4_journal_current_handle(); | ||
| 1560 | |||
| 1561 | copied = block_write_end(file, mapping, pos, len, copied, page, fsdata); | ||
| 1562 | |||
| 1563 | /* | ||
| 1564 | * No need to use i_size_read() here, the i_size | ||
| 1565 | * cannot change under us because we hold i_mutex. | ||
| 1566 | * | ||
| 1567 | * But it's important to update i_size while still holding page lock: | ||
| 1568 | * page writeout could otherwise come in and zero beyond i_size. | ||
| 1569 | */ | ||
| 1570 | if (pos + copied > inode->i_size) { | ||
| 1571 | i_size_write(inode, pos + copied); | ||
| 1572 | i_size_changed = 1; | ||
| 1573 | } | ||
| 1574 | |||
| 1575 | if (pos + copied > EXT4_I(inode)->i_disksize) { | ||
| 1576 | /* We need to mark inode dirty even if | ||
| 1577 | * new_i_size is less that inode->i_size | ||
| 1578 | * bu greater than i_disksize.(hint delalloc) | ||
| 1579 | */ | ||
| 1580 | ext4_update_i_disksize(inode, (pos + copied)); | ||
| 1581 | i_size_changed = 1; | ||
| 1582 | } | ||
| 1583 | unlock_page(page); | ||
| 1584 | page_cache_release(page); | ||
| 1585 | |||
| 1586 | /* | ||
| 1587 | * Don't mark the inode dirty under page lock. First, it unnecessarily | ||
| 1588 | * makes the holding time of page lock longer. Second, it forces lock | ||
| 1589 | * ordering of page lock and transaction start for journaling | ||
| 1590 | * filesystems. | ||
| 1591 | */ | ||
| 1592 | if (i_size_changed) | ||
| 1593 | ext4_mark_inode_dirty(handle, inode); | ||
| 1594 | |||
| 1595 | return copied; | ||
| 1596 | } | ||
| 1597 | |||
| 1552 | /* | 1598 | /* |
| 1553 | * We need to pick up the new inode size which generic_commit_write gave us | 1599 | * We need to pick up the new inode size which generic_commit_write gave us |
| 1554 | * `file' can be NULL - eg, when called from page_symlink(). | 1600 | * `file' can be NULL - eg, when called from page_symlink(). |
| @@ -1572,21 +1618,15 @@ static int ext4_ordered_write_end(struct file *file, | |||
| 1572 | ret = ext4_jbd2_file_inode(handle, inode); | 1618 | ret = ext4_jbd2_file_inode(handle, inode); |
| 1573 | 1619 | ||
| 1574 | if (ret == 0) { | 1620 | if (ret == 0) { |
| 1575 | loff_t new_i_size; | 1621 | ret2 = ext4_generic_write_end(file, mapping, pos, len, copied, |
| 1576 | |||
| 1577 | new_i_size = pos + copied; | ||
| 1578 | if (new_i_size > EXT4_I(inode)->i_disksize) { | ||
| 1579 | ext4_update_i_disksize(inode, new_i_size); | ||
| 1580 | /* We need to mark inode dirty even if | ||
| 1581 | * new_i_size is less that inode->i_size | ||
| 1582 | * bu greater than i_disksize.(hint delalloc) | ||
| 1583 | */ | ||
| 1584 | ext4_mark_inode_dirty(handle, inode); | ||
| 1585 | } | ||
| 1586 | |||
| 1587 | ret2 = generic_write_end(file, mapping, pos, len, copied, | ||
| 1588 | page, fsdata); | 1622 | page, fsdata); |
| 1589 | copied = ret2; | 1623 | copied = ret2; |
| 1624 | if (pos + len > inode->i_size) | ||
| 1625 | /* if we have allocated more blocks and copied | ||
| 1626 | * less. We will have blocks allocated outside | ||
| 1627 | * inode->i_size. So truncate them | ||
| 1628 | */ | ||
| 1629 | ext4_orphan_add(handle, inode); | ||
| 1590 | if (ret2 < 0) | 1630 | if (ret2 < 0) |
| 1591 | ret = ret2; | 1631 | ret = ret2; |
| 1592 | } | 1632 | } |
| @@ -1594,6 +1634,18 @@ static int ext4_ordered_write_end(struct file *file, | |||
| 1594 | if (!ret) | 1634 | if (!ret) |
| 1595 | ret = ret2; | 1635 | ret = ret2; |
| 1596 | 1636 | ||
| 1637 | if (pos + len > inode->i_size) { | ||
| 1638 | vmtruncate(inode, inode->i_size); | ||
| 1639 | /* | ||
| 1640 | * If vmtruncate failed early the inode might still be | ||
| 1641 | * on the orphan list; we need to make sure the inode | ||
| 1642 | * is removed from the orphan list in that case. | ||
| 1643 | */ | ||
| 1644 | if (inode->i_nlink) | ||
| 1645 | ext4_orphan_del(NULL, inode); | ||
| 1646 | } | ||
| 1647 | |||
| 1648 | |||
| 1597 | return ret ? ret : copied; | 1649 | return ret ? ret : copied; |
| 1598 | } | 1650 | } |
| 1599 | 1651 | ||
| @@ -1605,25 +1657,21 @@ static int ext4_writeback_write_end(struct file *file, | |||
| 1605 | handle_t *handle = ext4_journal_current_handle(); | 1657 | handle_t *handle = ext4_journal_current_handle(); |
| 1606 | struct inode *inode = mapping->host; | 1658 | struct inode *inode = mapping->host; |
| 1607 | int ret = 0, ret2; | 1659 | int ret = 0, ret2; |
| 1608 | loff_t new_i_size; | ||
| 1609 | 1660 | ||
| 1610 | trace_mark(ext4_writeback_write_end, | 1661 | trace_mark(ext4_writeback_write_end, |
| 1611 | "dev %s ino %lu pos %llu len %u copied %u", | 1662 | "dev %s ino %lu pos %llu len %u copied %u", |
| 1612 | inode->i_sb->s_id, inode->i_ino, | 1663 | inode->i_sb->s_id, inode->i_ino, |
| 1613 | (unsigned long long) pos, len, copied); | 1664 | (unsigned long long) pos, len, copied); |
| 1614 | new_i_size = pos + copied; | 1665 | ret2 = ext4_generic_write_end(file, mapping, pos, len, copied, |
| 1615 | if (new_i_size > EXT4_I(inode)->i_disksize) { | ||
| 1616 | ext4_update_i_disksize(inode, new_i_size); | ||
| 1617 | /* We need to mark inode dirty even if | ||
| 1618 | * new_i_size is less that inode->i_size | ||
| 1619 | * bu greater than i_disksize.(hint delalloc) | ||
| 1620 | */ | ||
| 1621 | ext4_mark_inode_dirty(handle, inode); | ||
| 1622 | } | ||
| 1623 | |||
| 1624 | ret2 = generic_write_end(file, mapping, pos, len, copied, | ||
| 1625 | page, fsdata); | 1666 | page, fsdata); |
| 1626 | copied = ret2; | 1667 | copied = ret2; |
| 1668 | if (pos + len > inode->i_size) | ||
| 1669 | /* if we have allocated more blocks and copied | ||
| 1670 | * less. We will have blocks allocated outside | ||
| 1671 | * inode->i_size. So truncate them | ||
| 1672 | */ | ||
| 1673 | ext4_orphan_add(handle, inode); | ||
| 1674 | |||
| 1627 | if (ret2 < 0) | 1675 | if (ret2 < 0) |
| 1628 | ret = ret2; | 1676 | ret = ret2; |
| 1629 | 1677 | ||
| @@ -1631,6 +1679,17 @@ static int ext4_writeback_write_end(struct file *file, | |||
| 1631 | if (!ret) | 1679 | if (!ret) |
| 1632 | ret = ret2; | 1680 | ret = ret2; |
| 1633 | 1681 | ||
| 1682 | if (pos + len > inode->i_size) { | ||
| 1683 | vmtruncate(inode, inode->i_size); | ||
| 1684 | /* | ||
| 1685 | * If vmtruncate failed early the inode might still be | ||
| 1686 | * on the orphan list; we need to make sure the inode | ||
| 1687 | * is removed from the orphan list in that case. | ||
| 1688 | */ | ||
| 1689 | if (inode->i_nlink) | ||
| 1690 | ext4_orphan_del(NULL, inode); | ||
| 1691 | } | ||
| 1692 | |||
| 1634 | return ret ? ret : copied; | 1693 | return ret ? ret : copied; |
| 1635 | } | 1694 | } |
| 1636 | 1695 | ||
| @@ -1675,10 +1734,27 @@ static int ext4_journalled_write_end(struct file *file, | |||
| 1675 | } | 1734 | } |
| 1676 | 1735 | ||
| 1677 | unlock_page(page); | 1736 | unlock_page(page); |
| 1737 | page_cache_release(page); | ||
| 1738 | if (pos + len > inode->i_size) | ||
| 1739 | /* if we have allocated more blocks and copied | ||
| 1740 | * less. We will have blocks allocated outside | ||
| 1741 | * inode->i_size. So truncate them | ||
| 1742 | */ | ||
| 1743 | ext4_orphan_add(handle, inode); | ||
| 1744 | |||
| 1678 | ret2 = ext4_journal_stop(handle); | 1745 | ret2 = ext4_journal_stop(handle); |
| 1679 | if (!ret) | 1746 | if (!ret) |
| 1680 | ret = ret2; | 1747 | ret = ret2; |
| 1681 | page_cache_release(page); | 1748 | if (pos + len > inode->i_size) { |
| 1749 | vmtruncate(inode, inode->i_size); | ||
| 1750 | /* | ||
| 1751 | * If vmtruncate failed early the inode might still be | ||
| 1752 | * on the orphan list; we need to make sure the inode | ||
| 1753 | * is removed from the orphan list in that case. | ||
| 1754 | */ | ||
| 1755 | if (inode->i_nlink) | ||
| 1756 | ext4_orphan_del(NULL, inode); | ||
| 1757 | } | ||
| 1682 | 1758 | ||
| 1683 | return ret ? ret : copied; | 1759 | return ret ? ret : copied; |
| 1684 | } | 1760 | } |
