Commit 5d32c2e0 authored by Eryu Guan's avatar Eryu Guan Committed by Luis Henriques

ext4: be more strict when migrating to non-extent based file

commit d6f123a9 upstream.

Currently the check in ext4_ind_migrate() is not enough before doing the
real conversion:

a) delayed allocated extents could bypass the check on eh->eh_entries
   and eh->eh_depth

This can be demonstrated by this script

  xfs_io -fc "pwrite 0 4k" -c "pwrite 8k 4k" /mnt/ext4/testfile
  chattr -e /mnt/ext4/testfile

where testfile has two extents but still be converted to non-extent
based file format.

b) only extent length is checked but not the offset, which would result
   in data lose (delalloc) or fs corruption (nodelalloc), because
   non-extent based file only supports at most (12 + 2^10 + 2^20 + 2^30)
   blocks

This can be demostrated by

  xfs_io -fc "pwrite 5T 4k" /mnt/ext4/testfile
  chattr -e /mnt/ext4/testfile
  sync

If delalloc is enabled, dmesg prints
  EXT4-fs warning (device dm-4): ext4_block_to_path:105: block 1342177280 > max in inode 53
  EXT4-fs (dm-4): Delayed block allocation failed for inode 53 at logical offset 1342177280 with max blocks 1 with error 5
  EXT4-fs (dm-4): This should not happen!! Data will be lost

If delalloc is disabled, e2fsck -nf shows corruption
  Inode 53, i_size is 5497558142976, should be 4096.  Fix? no

Fix the two issues by

a) forcing all delayed allocation blocks to be allocated before checking
   eh->eh_depth and eh->eh_entries
b) limiting the last logical block of the extent is within direct map
Signed-off-by: default avatarEryu Guan <guaneryu@gmail.com>
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
Signed-off-by: default avatarLuis Henriques <luis.henriques@canonical.com>
parent e7f70bb6
...@@ -616,6 +616,7 @@ int ext4_ind_migrate(struct inode *inode) ...@@ -616,6 +616,7 @@ int ext4_ind_migrate(struct inode *inode)
struct ext4_inode_info *ei = EXT4_I(inode); struct ext4_inode_info *ei = EXT4_I(inode);
struct ext4_extent *ex; struct ext4_extent *ex;
unsigned int i, len; unsigned int i, len;
ext4_lblk_t end;
ext4_fsblk_t blk; ext4_fsblk_t blk;
handle_t *handle; handle_t *handle;
int ret; int ret;
...@@ -629,6 +630,14 @@ int ext4_ind_migrate(struct inode *inode) ...@@ -629,6 +630,14 @@ int ext4_ind_migrate(struct inode *inode)
EXT4_FEATURE_RO_COMPAT_BIGALLOC)) EXT4_FEATURE_RO_COMPAT_BIGALLOC))
return -EOPNOTSUPP; return -EOPNOTSUPP;
/*
* In order to get correct extent info, force all delayed allocation
* blocks to be allocated, otherwise delayed allocation blocks may not
* be reflected and bypass the checks on extent header.
*/
if (test_opt(inode->i_sb, DELALLOC))
ext4_alloc_da_blocks(inode);
handle = ext4_journal_start(inode, EXT4_HT_MIGRATE, 1); handle = ext4_journal_start(inode, EXT4_HT_MIGRATE, 1);
if (IS_ERR(handle)) if (IS_ERR(handle))
return PTR_ERR(handle); return PTR_ERR(handle);
...@@ -650,7 +659,8 @@ int ext4_ind_migrate(struct inode *inode) ...@@ -650,7 +659,8 @@ int ext4_ind_migrate(struct inode *inode)
else { else {
len = le16_to_cpu(ex->ee_len); len = le16_to_cpu(ex->ee_len);
blk = ext4_ext_pblock(ex); blk = ext4_ext_pblock(ex);
if (len > EXT4_NDIR_BLOCKS) { end = le32_to_cpu(ex->ee_block) + len - 1;
if (end >= EXT4_NDIR_BLOCKS) {
ret = -EOPNOTSUPP; ret = -EOPNOTSUPP;
goto errout; goto errout;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment