|
@@ -4912,12 +4912,14 @@ long ext4_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
|
|
|
* bug we should fix....
|
|
|
*/
|
|
|
if (ext4_encrypted_inode(inode) &&
|
|
|
- (mode & (FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE)))
|
|
|
+ (mode & (FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_INSERT_RANGE |
|
|
|
+ FALLOC_FL_ZERO_RANGE)))
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
/* Return error if mode is not supported */
|
|
|
if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |
|
|
|
- FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE))
|
|
|
+ FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE |
|
|
|
+ FALLOC_FL_INSERT_RANGE))
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
if (mode & FALLOC_FL_PUNCH_HOLE)
|
|
@@ -4930,6 +4932,9 @@ long ext4_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
|
|
|
if (mode & FALLOC_FL_COLLAPSE_RANGE)
|
|
|
return ext4_collapse_range(inode, offset, len);
|
|
|
|
|
|
+ if (mode & FALLOC_FL_INSERT_RANGE)
|
|
|
+ return ext4_insert_range(inode, offset, len);
|
|
|
+
|
|
|
if (mode & FALLOC_FL_ZERO_RANGE)
|
|
|
return ext4_zero_range(file, offset, len, mode);
|
|
|
|
|
@@ -5224,13 +5229,13 @@ ext4_access_path(handle_t *handle, struct inode *inode,
|
|
|
/*
|
|
|
* ext4_ext_shift_path_extents:
|
|
|
* Shift the extents of a path structure lying between path[depth].p_ext
|
|
|
- * and EXT_LAST_EXTENT(path[depth].p_hdr) downwards, by subtracting shift
|
|
|
- * from starting block for each extent.
|
|
|
+ * and EXT_LAST_EXTENT(path[depth].p_hdr), by @shift blocks. @SHIFT tells
|
|
|
+ * if it is right shift or left shift operation.
|
|
|
*/
|
|
|
static int
|
|
|
ext4_ext_shift_path_extents(struct ext4_ext_path *path, ext4_lblk_t shift,
|
|
|
struct inode *inode, handle_t *handle,
|
|
|
- ext4_lblk_t *start)
|
|
|
+ enum SHIFT_DIRECTION SHIFT)
|
|
|
{
|
|
|
int depth, err = 0;
|
|
|
struct ext4_extent *ex_start, *ex_last;
|
|
@@ -5252,19 +5257,25 @@ ext4_ext_shift_path_extents(struct ext4_ext_path *path, ext4_lblk_t shift,
|
|
|
if (ex_start == EXT_FIRST_EXTENT(path[depth].p_hdr))
|
|
|
update = 1;
|
|
|
|
|
|
- *start = le32_to_cpu(ex_last->ee_block) +
|
|
|
- ext4_ext_get_actual_len(ex_last);
|
|
|
-
|
|
|
while (ex_start <= ex_last) {
|
|
|
- le32_add_cpu(&ex_start->ee_block, -shift);
|
|
|
- /* Try to merge to the left. */
|
|
|
- if ((ex_start >
|
|
|
- EXT_FIRST_EXTENT(path[depth].p_hdr)) &&
|
|
|
- ext4_ext_try_to_merge_right(inode,
|
|
|
- path, ex_start - 1))
|
|
|
+ if (SHIFT == SHIFT_LEFT) {
|
|
|
+ le32_add_cpu(&ex_start->ee_block,
|
|
|
+ -shift);
|
|
|
+ /* Try to merge to the left. */
|
|
|
+ if ((ex_start >
|
|
|
+ EXT_FIRST_EXTENT(path[depth].p_hdr))
|
|
|
+ &&
|
|
|
+ ext4_ext_try_to_merge_right(inode,
|
|
|
+ path, ex_start - 1))
|
|
|
+ ex_last--;
|
|
|
+ else
|
|
|
+ ex_start++;
|
|
|
+ } else {
|
|
|
+ le32_add_cpu(&ex_last->ee_block, shift);
|
|
|
+ ext4_ext_try_to_merge_right(inode, path,
|
|
|
+ ex_last);
|
|
|
ex_last--;
|
|
|
- else
|
|
|
- ex_start++;
|
|
|
+ }
|
|
|
}
|
|
|
err = ext4_ext_dirty(handle, inode, path + depth);
|
|
|
if (err)
|
|
@@ -5279,7 +5290,10 @@ ext4_ext_shift_path_extents(struct ext4_ext_path *path, ext4_lblk_t shift,
|
|
|
if (err)
|
|
|
goto out;
|
|
|
|
|
|
- le32_add_cpu(&path[depth].p_idx->ei_block, -shift);
|
|
|
+ if (SHIFT == SHIFT_LEFT)
|
|
|
+ le32_add_cpu(&path[depth].p_idx->ei_block, -shift);
|
|
|
+ else
|
|
|
+ le32_add_cpu(&path[depth].p_idx->ei_block, shift);
|
|
|
err = ext4_ext_dirty(handle, inode, path + depth);
|
|
|
if (err)
|
|
|
goto out;
|
|
@@ -5297,19 +5311,20 @@ out:
|
|
|
|
|
|
/*
|
|
|
* ext4_ext_shift_extents:
|
|
|
- * All the extents which lies in the range from start to the last allocated
|
|
|
- * block for the file are shifted downwards by shift blocks.
|
|
|
+ * All the extents which lies in the range from @start to the last allocated
|
|
|
+ * block for the @inode are shifted either towards left or right (depending
|
|
|
+ * upon @SHIFT) by @shift blocks.
|
|
|
* On success, 0 is returned, error otherwise.
|
|
|
*/
|
|
|
static int
|
|
|
ext4_ext_shift_extents(struct inode *inode, handle_t *handle,
|
|
|
- ext4_lblk_t start, ext4_lblk_t shift)
|
|
|
+ ext4_lblk_t start, ext4_lblk_t shift,
|
|
|
+ enum SHIFT_DIRECTION SHIFT)
|
|
|
{
|
|
|
struct ext4_ext_path *path;
|
|
|
int ret = 0, depth;
|
|
|
struct ext4_extent *extent;
|
|
|
- ext4_lblk_t stop_block;
|
|
|
- ext4_lblk_t ex_start, ex_end;
|
|
|
+ ext4_lblk_t stop, *iterator, ex_start, ex_end;
|
|
|
|
|
|
/* Let path point to the last extent */
|
|
|
path = ext4_find_extent(inode, EXT_MAX_BLOCKS - 1, NULL, 0);
|
|
@@ -5321,58 +5336,84 @@ ext4_ext_shift_extents(struct inode *inode, handle_t *handle,
|
|
|
if (!extent)
|
|
|
goto out;
|
|
|
|
|
|
- stop_block = le32_to_cpu(extent->ee_block) +
|
|
|
+ stop = le32_to_cpu(extent->ee_block) +
|
|
|
ext4_ext_get_actual_len(extent);
|
|
|
|
|
|
- /* Nothing to shift, if hole is at the end of file */
|
|
|
- if (start >= stop_block)
|
|
|
- goto out;
|
|
|
+ /*
|
|
|
+ * In case of left shift, Don't start shifting extents until we make
|
|
|
+ * sure the hole is big enough to accommodate the shift.
|
|
|
+ */
|
|
|
+ if (SHIFT == SHIFT_LEFT) {
|
|
|
+ path = ext4_find_extent(inode, start - 1, &path, 0);
|
|
|
+ if (IS_ERR(path))
|
|
|
+ return PTR_ERR(path);
|
|
|
+ depth = path->p_depth;
|
|
|
+ extent = path[depth].p_ext;
|
|
|
+ if (extent) {
|
|
|
+ ex_start = le32_to_cpu(extent->ee_block);
|
|
|
+ ex_end = le32_to_cpu(extent->ee_block) +
|
|
|
+ ext4_ext_get_actual_len(extent);
|
|
|
+ } else {
|
|
|
+ ex_start = 0;
|
|
|
+ ex_end = 0;
|
|
|
+ }
|
|
|
|
|
|
- /*
|
|
|
- * Don't start shifting extents until we make sure the hole is big
|
|
|
- * enough to accomodate the shift.
|
|
|
- */
|
|
|
- path = ext4_find_extent(inode, start - 1, &path, 0);
|
|
|
- if (IS_ERR(path))
|
|
|
- return PTR_ERR(path);
|
|
|
- depth = path->p_depth;
|
|
|
- extent = path[depth].p_ext;
|
|
|
- if (extent) {
|
|
|
- ex_start = le32_to_cpu(extent->ee_block);
|
|
|
- ex_end = le32_to_cpu(extent->ee_block) +
|
|
|
- ext4_ext_get_actual_len(extent);
|
|
|
- } else {
|
|
|
- ex_start = 0;
|
|
|
- ex_end = 0;
|
|
|
+ if ((start == ex_start && shift > ex_start) ||
|
|
|
+ (shift > start - ex_end)) {
|
|
|
+ ext4_ext_drop_refs(path);
|
|
|
+ kfree(path);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- if ((start == ex_start && shift > ex_start) ||
|
|
|
- (shift > start - ex_end))
|
|
|
- return -EINVAL;
|
|
|
+ /*
|
|
|
+ * In case of left shift, iterator points to start and it is increased
|
|
|
+ * till we reach stop. In case of right shift, iterator points to stop
|
|
|
+ * and it is decreased till we reach start.
|
|
|
+ */
|
|
|
+ if (SHIFT == SHIFT_LEFT)
|
|
|
+ iterator = &start;
|
|
|
+ else
|
|
|
+ iterator = &stop;
|
|
|
|
|
|
/* Its safe to start updating extents */
|
|
|
- while (start < stop_block) {
|
|
|
- path = ext4_find_extent(inode, start, &path, 0);
|
|
|
+ while (start < stop) {
|
|
|
+ path = ext4_find_extent(inode, *iterator, &path, 0);
|
|
|
if (IS_ERR(path))
|
|
|
return PTR_ERR(path);
|
|
|
depth = path->p_depth;
|
|
|
extent = path[depth].p_ext;
|
|
|
if (!extent) {
|
|
|
EXT4_ERROR_INODE(inode, "unexpected hole at %lu",
|
|
|
- (unsigned long) start);
|
|
|
+ (unsigned long) *iterator);
|
|
|
return -EIO;
|
|
|
}
|
|
|
- if (start > le32_to_cpu(extent->ee_block)) {
|
|
|
+ if (SHIFT == SHIFT_LEFT && *iterator >
|
|
|
+ le32_to_cpu(extent->ee_block)) {
|
|
|
/* Hole, move to the next extent */
|
|
|
if (extent < EXT_LAST_EXTENT(path[depth].p_hdr)) {
|
|
|
path[depth].p_ext++;
|
|
|
} else {
|
|
|
- start = ext4_ext_next_allocated_block(path);
|
|
|
+ *iterator = ext4_ext_next_allocated_block(path);
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ if (SHIFT == SHIFT_LEFT) {
|
|
|
+ extent = EXT_LAST_EXTENT(path[depth].p_hdr);
|
|
|
+ *iterator = le32_to_cpu(extent->ee_block) +
|
|
|
+ ext4_ext_get_actual_len(extent);
|
|
|
+ } else {
|
|
|
+ extent = EXT_FIRST_EXTENT(path[depth].p_hdr);
|
|
|
+ *iterator = le32_to_cpu(extent->ee_block) > 0 ?
|
|
|
+ le32_to_cpu(extent->ee_block) - 1 : 0;
|
|
|
+ /* Update path extent in case we need to stop */
|
|
|
+ while (le32_to_cpu(extent->ee_block) < start)
|
|
|
+ extent++;
|
|
|
+ path[depth].p_ext = extent;
|
|
|
+ }
|
|
|
ret = ext4_ext_shift_path_extents(path, shift, inode,
|
|
|
- handle, &start);
|
|
|
+ handle, SHIFT);
|
|
|
if (ret)
|
|
|
break;
|
|
|
}
|
|
@@ -5485,7 +5526,7 @@ int ext4_collapse_range(struct inode *inode, loff_t offset, loff_t len)
|
|
|
ext4_discard_preallocations(inode);
|
|
|
|
|
|
ret = ext4_ext_shift_extents(inode, handle, punch_stop,
|
|
|
- punch_stop - punch_start);
|
|
|
+ punch_stop - punch_start, SHIFT_LEFT);
|
|
|
if (ret) {
|
|
|
up_write(&EXT4_I(inode)->i_data_sem);
|
|
|
goto out_stop;
|
|
@@ -5510,6 +5551,174 @@ out_mutex:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * ext4_insert_range:
|
|
|
+ * This function implements the FALLOC_FL_INSERT_RANGE flag of fallocate.
|
|
|
+ * The data blocks starting from @offset to the EOF are shifted by @len
|
|
|
+ * towards right to create a hole in the @inode. Inode size is increased
|
|
|
+ * by len bytes.
|
|
|
+ * Returns 0 on success, error otherwise.
|
|
|
+ */
|
|
|
+int ext4_insert_range(struct inode *inode, loff_t offset, loff_t len)
|
|
|
+{
|
|
|
+ struct super_block *sb = inode->i_sb;
|
|
|
+ handle_t *handle;
|
|
|
+ struct ext4_ext_path *path;
|
|
|
+ struct ext4_extent *extent;
|
|
|
+ ext4_lblk_t offset_lblk, len_lblk, ee_start_lblk = 0;
|
|
|
+ unsigned int credits, ee_len;
|
|
|
+ int ret = 0, depth, split_flag = 0;
|
|
|
+ loff_t ioffset;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We need to test this early because xfstests assumes that an
|
|
|
+ * insert range of (0, 1) will return EOPNOTSUPP if the file
|
|
|
+ * system does not support insert range.
|
|
|
+ */
|
|
|
+ if (!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ /* Insert range works only on fs block size aligned offsets. */
|
|
|
+ if (offset & (EXT4_CLUSTER_SIZE(sb) - 1) ||
|
|
|
+ len & (EXT4_CLUSTER_SIZE(sb) - 1))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!S_ISREG(inode->i_mode))
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ trace_ext4_insert_range(inode, offset, len);
|
|
|
+
|
|
|
+ offset_lblk = offset >> EXT4_BLOCK_SIZE_BITS(sb);
|
|
|
+ len_lblk = len >> EXT4_BLOCK_SIZE_BITS(sb);
|
|
|
+
|
|
|
+ /* Call ext4_force_commit to flush all data in case of data=journal */
|
|
|
+ if (ext4_should_journal_data(inode)) {
|
|
|
+ ret = ext4_force_commit(inode->i_sb);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Need to round down to align start offset to page size boundary
|
|
|
+ * for page size > block size.
|
|
|
+ */
|
|
|
+ ioffset = round_down(offset, PAGE_SIZE);
|
|
|
+
|
|
|
+ /* Write out all dirty pages */
|
|
|
+ ret = filemap_write_and_wait_range(inode->i_mapping, ioffset,
|
|
|
+ LLONG_MAX);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ /* Take mutex lock */
|
|
|
+ mutex_lock(&inode->i_mutex);
|
|
|
+
|
|
|
+ /* Currently just for extent based files */
|
|
|
+ if (!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
|
|
|
+ ret = -EOPNOTSUPP;
|
|
|
+ goto out_mutex;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check for wrap through zero */
|
|
|
+ if (inode->i_size + len > inode->i_sb->s_maxbytes) {
|
|
|
+ ret = -EFBIG;
|
|
|
+ goto out_mutex;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Offset should be less than i_size */
|
|
|
+ if (offset >= i_size_read(inode)) {
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto out_mutex;
|
|
|
+ }
|
|
|
+
|
|
|
+ truncate_pagecache(inode, ioffset);
|
|
|
+
|
|
|
+ /* Wait for existing dio to complete */
|
|
|
+ ext4_inode_block_unlocked_dio(inode);
|
|
|
+ inode_dio_wait(inode);
|
|
|
+
|
|
|
+ credits = ext4_writepage_trans_blocks(inode);
|
|
|
+ handle = ext4_journal_start(inode, EXT4_HT_TRUNCATE, credits);
|
|
|
+ if (IS_ERR(handle)) {
|
|
|
+ ret = PTR_ERR(handle);
|
|
|
+ goto out_dio;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Expand file to avoid data loss if there is error while shifting */
|
|
|
+ inode->i_size += len;
|
|
|
+ EXT4_I(inode)->i_disksize += len;
|
|
|
+ inode->i_mtime = inode->i_ctime = ext4_current_time(inode);
|
|
|
+ ret = ext4_mark_inode_dirty(handle, inode);
|
|
|
+ if (ret)
|
|
|
+ goto out_stop;
|
|
|
+
|
|
|
+ down_write(&EXT4_I(inode)->i_data_sem);
|
|
|
+ ext4_discard_preallocations(inode);
|
|
|
+
|
|
|
+ path = ext4_find_extent(inode, offset_lblk, NULL, 0);
|
|
|
+ if (IS_ERR(path)) {
|
|
|
+ up_write(&EXT4_I(inode)->i_data_sem);
|
|
|
+ goto out_stop;
|
|
|
+ }
|
|
|
+
|
|
|
+ depth = ext_depth(inode);
|
|
|
+ extent = path[depth].p_ext;
|
|
|
+ if (extent) {
|
|
|
+ ee_start_lblk = le32_to_cpu(extent->ee_block);
|
|
|
+ ee_len = ext4_ext_get_actual_len(extent);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If offset_lblk is not the starting block of extent, split
|
|
|
+ * the extent @offset_lblk
|
|
|
+ */
|
|
|
+ if ((offset_lblk > ee_start_lblk) &&
|
|
|
+ (offset_lblk < (ee_start_lblk + ee_len))) {
|
|
|
+ if (ext4_ext_is_unwritten(extent))
|
|
|
+ split_flag = EXT4_EXT_MARK_UNWRIT1 |
|
|
|
+ EXT4_EXT_MARK_UNWRIT2;
|
|
|
+ ret = ext4_split_extent_at(handle, inode, &path,
|
|
|
+ offset_lblk, split_flag,
|
|
|
+ EXT4_EX_NOCACHE |
|
|
|
+ EXT4_GET_BLOCKS_PRE_IO |
|
|
|
+ EXT4_GET_BLOCKS_METADATA_NOFAIL);
|
|
|
+ }
|
|
|
+
|
|
|
+ ext4_ext_drop_refs(path);
|
|
|
+ kfree(path);
|
|
|
+ if (ret < 0) {
|
|
|
+ up_write(&EXT4_I(inode)->i_data_sem);
|
|
|
+ goto out_stop;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = ext4_es_remove_extent(inode, offset_lblk,
|
|
|
+ EXT_MAX_BLOCKS - offset_lblk);
|
|
|
+ if (ret) {
|
|
|
+ up_write(&EXT4_I(inode)->i_data_sem);
|
|
|
+ goto out_stop;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * if offset_lblk lies in a hole which is at start of file, use
|
|
|
+ * ee_start_lblk to shift extents
|
|
|
+ */
|
|
|
+ ret = ext4_ext_shift_extents(inode, handle,
|
|
|
+ ee_start_lblk > offset_lblk ? ee_start_lblk : offset_lblk,
|
|
|
+ len_lblk, SHIFT_RIGHT);
|
|
|
+
|
|
|
+ up_write(&EXT4_I(inode)->i_data_sem);
|
|
|
+ if (IS_SYNC(inode))
|
|
|
+ ext4_handle_sync(handle);
|
|
|
+
|
|
|
+out_stop:
|
|
|
+ ext4_journal_stop(handle);
|
|
|
+out_dio:
|
|
|
+ ext4_inode_resume_unlocked_dio(inode);
|
|
|
+out_mutex:
|
|
|
+ mutex_unlock(&inode->i_mutex);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ext4_swap_extents - Swap extents between two inodes
|
|
|
*
|