ext4: Add flag to files with blocks intentionally past EOF
authorJiaying Zhang <jiayingz@google.com>
Wed, 24 Feb 2010 14:52:53 +0000 (09:52 -0500)
committerTheodore Ts'o <tytso@mit.edu>
Wed, 24 Feb 2010 14:52:53 +0000 (09:52 -0500)
fallocate() may potentially instantiate blocks past EOF, depending
on the flags used when it is called.

e2fsck currently has a test for blocks past i_size, and it
sometimes trips up - noticeably on xfstests 013 which runs fsstress.

This patch from Jiayang does fix it up - it (along with
e2fsprogs updates and other patches recently from Aneesh) has
survived many fsstress runs in a row.

Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Jiaying Zhang <jiayingz@google.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
fs/ext4/ext4.h
fs/ext4/extents.c
fs/ext4/inode.c
fs/ext4/ioctl.c

index 509437f..74664ca 100644 (file)
@@ -285,10 +285,11 @@ struct flex_groups {
 #define EXT4_HUGE_FILE_FL               0x00040000 /* Set to each huge file */
 #define EXT4_EXTENTS_FL                        0x00080000 /* Inode uses extents */
 #define EXT4_EA_INODE_FL               0x00200000 /* Inode used for large EA */
+#define EXT4_EOFBLOCKS_FL              0x00400000 /* Blocks allocated beyond EOF */
 #define EXT4_RESERVED_FL               0x80000000 /* reserved for ext4 lib */
 
-#define EXT4_FL_USER_VISIBLE           0x000BDFFF /* User visible flags */
-#define EXT4_FL_USER_MODIFIABLE                0x000B80FF /* User modifiable flags */
+#define EXT4_FL_USER_VISIBLE           0x004BDFFF /* User visible flags */
+#define EXT4_FL_USER_MODIFIABLE                0x004B80FF /* User modifiable flags */
 
 /* Flags that should be inherited by new inodes from their parent. */
 #define EXT4_FL_INHERITED (EXT4_SECRM_FL | EXT4_UNRM_FL | EXT4_COMPR_FL |\
index 7d54850..a2c21aa 100644 (file)
@@ -3186,7 +3186,7 @@ int ext4_ext_get_blocks(handle_t *handle, struct inode *inode,
 {
        struct ext4_ext_path *path = NULL;
        struct ext4_extent_header *eh;
-       struct ext4_extent newex, *ex;
+       struct ext4_extent newex, *ex, *last_ex;
        ext4_fsblk_t newblock;
        int err = 0, depth, ret, cache_type;
        unsigned int allocated = 0;
@@ -3367,6 +3367,19 @@ int ext4_ext_get_blocks(handle_t *handle, struct inode *inode,
                                                     EXT4_STATE_DIO_UNWRITTEN);
                }
        }
+
+       if (unlikely(EXT4_I(inode)->i_flags & EXT4_EOFBLOCKS_FL)) {
+               if (eh->eh_entries) {
+                       last_ex = EXT_LAST_EXTENT(eh);
+                       if (iblock + ar.len > le32_to_cpu(last_ex->ee_block)
+                                           + ext4_ext_get_actual_len(last_ex))
+                               EXT4_I(inode)->i_flags &= ~EXT4_EOFBLOCKS_FL;
+               } else {
+                       WARN_ON(eh->eh_entries == 0);
+                       ext4_error(inode->i_sb, __func__,
+                               "inode#%lu, eh->eh_entries = 0!", inode->i_ino);
+                       }
+       }
        err = ext4_ext_insert_extent(handle, inode, path, &newex, flags);
        if (err) {
                /* free data blocks we just allocated */
@@ -3500,6 +3513,13 @@ static void ext4_falloc_update_inode(struct inode *inode,
                        i_size_write(inode, new_size);
                if (new_size > EXT4_I(inode)->i_disksize)
                        ext4_update_i_disksize(inode, new_size);
+       } else {
+               /*
+                * Mark that we allocate beyond EOF so the subsequent truncate
+                * can proceed even if the new size is the same as i_size.
+                */
+               if (new_size > i_size_read(inode))
+                       EXT4_I(inode)->i_flags |= EXT4_EOFBLOCKS_FL;
        }
 
 }
index ecac8c5..edb7edc 100644 (file)
@@ -4456,6 +4456,8 @@ void ext4_truncate(struct inode *inode)
        if (!ext4_can_truncate(inode))
                return;
 
+       EXT4_I(inode)->i_flags &= ~EXT4_EOFBLOCKS_FL;
+
        if (inode->i_size == 0 && !test_opt(inode->i_sb, NO_AUTO_DA_ALLOC))
                ext4_set_inode_state(inode, EXT4_STATE_DA_ALLOC_CLOSE);
 
@@ -5305,7 +5307,9 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)
        }
 
        if (S_ISREG(inode->i_mode) &&
-           attr->ia_valid & ATTR_SIZE && attr->ia_size < inode->i_size) {
+           attr->ia_valid & ATTR_SIZE &&
+           (attr->ia_size < inode->i_size ||
+            (EXT4_I(inode)->i_flags & EXT4_EOFBLOCKS_FL))) {
                handle_t *handle;
 
                handle = ext4_journal_start(inode, 3);
@@ -5336,6 +5340,9 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)
                                goto err_out;
                        }
                }
+               /* ext4_truncate will clear the flag */
+               if ((EXT4_I(inode)->i_flags & EXT4_EOFBLOCKS_FL))
+                       ext4_truncate(inode);
        }
 
        rc = inode_setattr(inode, attr);
index b63d193..2220feb 100644 (file)
@@ -92,6 +92,15 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                        flags &= ~EXT4_EXTENTS_FL;
                }
 
+               if (flags & EXT4_EOFBLOCKS_FL) {
+                       /* we don't support adding EOFBLOCKS flag */
+                       if (!(oldflags & EXT4_EOFBLOCKS_FL)) {
+                               err = -EOPNOTSUPP;
+                               goto flags_out;
+                       }
+               } else if (oldflags & EXT4_EOFBLOCKS_FL)
+                       ext4_truncate(inode);
+
                handle = ext4_journal_start(inode, 1);
                if (IS_ERR(handle)) {
                        err = PTR_ERR(handle);