Skip to content

Commit

Permalink
Merge branch 'integration-4.4' of git://git.kernel.org/pub/scm/linux/…
Browse files Browse the repository at this point in the history
…kernel/git/fdmanana/linux into for-linus-4.4
  • Loading branch information
Chris Mason committed Oct 22, 2015
2 parents a0d58e4 + 0305cd5 commit a408365
Show file tree
Hide file tree
Showing 4 changed files with 407 additions and 88 deletions.
8 changes: 6 additions & 2 deletions fs/btrfs/extent_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -3070,8 +3070,12 @@ static int __do_readpage(struct extent_io_tree *tree,

set_extent_uptodate(tree, cur, cur + iosize - 1,
&cached, GFP_NOFS);
unlock_extent_cached(tree, cur, cur + iosize - 1,
&cached, GFP_NOFS);
if (parent_locked)
free_extent_state(cached);
else
unlock_extent_cached(tree, cur,
cur + iosize - 1,
&cached, GFP_NOFS);
cur = cur + iosize;
pg_offset += iosize;
continue;
Expand Down
82 changes: 68 additions & 14 deletions fs/btrfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -4216,6 +4216,47 @@ static int truncate_space_check(struct btrfs_trans_handle *trans,

}

static int truncate_inline_extent(struct inode *inode,
struct btrfs_path *path,
struct btrfs_key *found_key,
const u64 item_end,
const u64 new_size)
{
struct extent_buffer *leaf = path->nodes[0];
int slot = path->slots[0];
struct btrfs_file_extent_item *fi;
u32 size = (u32)(new_size - found_key->offset);
struct btrfs_root *root = BTRFS_I(inode)->root;

fi = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item);

if (btrfs_file_extent_compression(leaf, fi) != BTRFS_COMPRESS_NONE) {
loff_t offset = new_size;
loff_t page_end = ALIGN(offset, PAGE_CACHE_SIZE);

/*
* Zero out the remaining of the last page of our inline extent,
* instead of directly truncating our inline extent here - that
* would be much more complex (decompressing all the data, then
* compressing the truncated data, which might be bigger than
* the size of the inline extent, resize the extent, etc).
* We release the path because to get the page we might need to
* read the extent item from disk (data not in the page cache).
*/
btrfs_release_path(path);
return btrfs_truncate_page(inode, offset, page_end - offset, 0);
}

btrfs_set_file_extent_ram_bytes(leaf, fi, size);
size = btrfs_file_extent_calc_inline_size(size);
btrfs_truncate_item(root, path, size, 1);

if (test_bit(BTRFS_ROOT_REF_COWS, &root->state))
inode_sub_bytes(inode, item_end + 1 - new_size);

return 0;
}

/*
* this can truncate away extent items, csum items and directory items.
* It starts at a high offset and removes keys until it can't find
Expand Down Expand Up @@ -4410,27 +4451,40 @@ int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans,
* special encodings
*/
if (!del_item &&
btrfs_file_extent_compression(leaf, fi) == 0 &&
btrfs_file_extent_encryption(leaf, fi) == 0 &&
btrfs_file_extent_other_encoding(leaf, fi) == 0) {
u32 size = new_size - found_key.offset;

if (test_bit(BTRFS_ROOT_REF_COWS, &root->state))
inode_sub_bytes(inode, item_end + 1 -
new_size);

/*
* update the ram bytes to properly reflect
* the new size of our item
* Need to release path in order to truncate a
* compressed extent. So delete any accumulated
* extent items so far.
*/
btrfs_set_file_extent_ram_bytes(leaf, fi, size);
size =
btrfs_file_extent_calc_inline_size(size);
btrfs_truncate_item(root, path, size, 1);
if (btrfs_file_extent_compression(leaf, fi) !=
BTRFS_COMPRESS_NONE && pending_del_nr) {
err = btrfs_del_items(trans, root, path,
pending_del_slot,
pending_del_nr);
if (err) {
btrfs_abort_transaction(trans,
root,
err);
goto error;
}
pending_del_nr = 0;
}

err = truncate_inline_extent(inode, path,
&found_key,
item_end,
new_size);
if (err) {
btrfs_abort_transaction(trans,
root, err);
goto error;
}
} else if (test_bit(BTRFS_ROOT_REF_COWS,
&root->state)) {
inode_sub_bytes(inode, item_end + 1 -
found_key.offset);
inode_sub_bytes(inode, item_end + 1 - new_size);
}
}
delete:
Expand Down
195 changes: 152 additions & 43 deletions fs/btrfs/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3327,6 +3327,150 @@ static void clone_update_extent_map(struct inode *inode,
&BTRFS_I(inode)->runtime_flags);
}

/*
* Make sure we do not end up inserting an inline extent into a file that has
* already other (non-inline) extents. If a file has an inline extent it can
* not have any other extents and the (single) inline extent must start at the
* file offset 0. Failing to respect these rules will lead to file corruption,
* resulting in EIO errors on read/write operations, hitting BUG_ON's in mm, etc
*
* We can have extents that have been already written to disk or we can have
* dirty ranges still in delalloc, in which case the extent maps and items are
* created only when we run delalloc, and the delalloc ranges might fall outside
* the range we are currently locking in the inode's io tree. So we check the
* inode's i_size because of that (i_size updates are done while holding the
* i_mutex, which we are holding here).
* We also check to see if the inode has a size not greater than "datal" but has
* extents beyond it, due to an fallocate with FALLOC_FL_KEEP_SIZE (and we are
* protected against such concurrent fallocate calls by the i_mutex).
*
* If the file has no extents but a size greater than datal, do not allow the
* copy because we would need turn the inline extent into a non-inline one (even
* with NO_HOLES enabled). If we find our destination inode only has one inline
* extent, just overwrite it with the source inline extent if its size is less
* than the source extent's size, or we could copy the source inline extent's
* data into the destination inode's inline extent if the later is greater then
* the former.
*/
static int clone_copy_inline_extent(struct inode *src,
struct inode *dst,
struct btrfs_trans_handle *trans,
struct btrfs_path *path,
struct btrfs_key *new_key,
const u64 drop_start,
const u64 datal,
const u64 skip,
const u64 size,
char *inline_data)
{
struct btrfs_root *root = BTRFS_I(dst)->root;
const u64 aligned_end = ALIGN(new_key->offset + datal,
root->sectorsize);
int ret;
struct btrfs_key key;

if (new_key->offset > 0)
return -EOPNOTSUPP;

key.objectid = btrfs_ino(dst);
key.type = BTRFS_EXTENT_DATA_KEY;
key.offset = 0;
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
if (ret < 0) {
return ret;
} else if (ret > 0) {
if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) {
ret = btrfs_next_leaf(root, path);
if (ret < 0)
return ret;
else if (ret > 0)
goto copy_inline_extent;
}
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
if (key.objectid == btrfs_ino(dst) &&
key.type == BTRFS_EXTENT_DATA_KEY) {
ASSERT(key.offset > 0);
return -EOPNOTSUPP;
}
} else if (i_size_read(dst) <= datal) {
struct btrfs_file_extent_item *ei;
u64 ext_len;

/*
* If the file size is <= datal, make sure there are no other
* extents following (can happen do to an fallocate call with
* the flag FALLOC_FL_KEEP_SIZE).
*/
ei = btrfs_item_ptr(path->nodes[0], path->slots[0],
struct btrfs_file_extent_item);
/*
* If it's an inline extent, it can not have other extents
* following it.
*/
if (btrfs_file_extent_type(path->nodes[0], ei) ==
BTRFS_FILE_EXTENT_INLINE)
goto copy_inline_extent;

ext_len = btrfs_file_extent_num_bytes(path->nodes[0], ei);
if (ext_len > aligned_end)
return -EOPNOTSUPP;

ret = btrfs_next_item(root, path);
if (ret < 0) {
return ret;
} else if (ret == 0) {
btrfs_item_key_to_cpu(path->nodes[0], &key,
path->slots[0]);
if (key.objectid == btrfs_ino(dst) &&
key.type == BTRFS_EXTENT_DATA_KEY)
return -EOPNOTSUPP;
}
}

copy_inline_extent:
/*
* We have no extent items, or we have an extent at offset 0 which may
* or may not be inlined. All these cases are dealt the same way.
*/
if (i_size_read(dst) > datal) {
/*
* If the destination inode has an inline extent...
* This would require copying the data from the source inline
* extent into the beginning of the destination's inline extent.
* But this is really complex, both extents can be compressed
* or just one of them, which would require decompressing and
* re-compressing data (which could increase the new compressed
* size, not allowing the compressed data to fit anymore in an
* inline extent).
* So just don't support this case for now (it should be rare,
* we are not really saving space when cloning inline extents).
*/
return -EOPNOTSUPP;
}

btrfs_release_path(path);
ret = btrfs_drop_extents(trans, root, dst, drop_start, aligned_end, 1);
if (ret)
return ret;
ret = btrfs_insert_empty_item(trans, root, path, new_key, size);
if (ret)
return ret;

if (skip) {
const u32 start = btrfs_file_extent_calc_inline_size(0);

memmove(inline_data + start, inline_data + start + skip, datal);
}

write_extent_buffer(path->nodes[0], inline_data,
btrfs_item_ptr_offset(path->nodes[0],
path->slots[0]),
size);
inode_add_bytes(dst, datal);

return 0;
}

/**
* btrfs_clone() - clone a range from inode file to another
*
Expand Down Expand Up @@ -3593,21 +3737,6 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
} else if (type == BTRFS_FILE_EXTENT_INLINE) {
u64 skip = 0;
u64 trim = 0;
u64 aligned_end = 0;

/*
* Don't copy an inline extent into an offset
* greater than zero. Having an inline extent
* at such an offset results in chaos as btrfs
* isn't prepared for such cases. Just skip
* this case for the same reasons as commented
* at btrfs_ioctl_clone().
*/
if (last_dest_end > 0) {
ret = -EOPNOTSUPP;
btrfs_end_transaction(trans, root);
goto out;
}

if (off > key.offset) {
skip = off - key.offset;
Expand All @@ -3625,42 +3754,22 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
size -= skip + trim;
datal -= skip + trim;

aligned_end = ALIGN(new_key.offset + datal,
root->sectorsize);
ret = btrfs_drop_extents(trans, root, inode,
drop_start,
aligned_end,
1);
ret = clone_copy_inline_extent(src, inode,
trans, path,
&new_key,
drop_start,
datal,
skip, size, buf);
if (ret) {
if (ret != -EOPNOTSUPP)
btrfs_abort_transaction(trans,
root, ret);
btrfs_end_transaction(trans, root);
goto out;
}

ret = btrfs_insert_empty_item(trans, root, path,
&new_key, size);
if (ret) {
btrfs_abort_transaction(trans, root,
ret);
root,
ret);
btrfs_end_transaction(trans, root);
goto out;
}

if (skip) {
u32 start =
btrfs_file_extent_calc_inline_size(0);
memmove(buf+start, buf+start+skip,
datal);
}

leaf = path->nodes[0];
slot = path->slots[0];
write_extent_buffer(leaf, buf,
btrfs_item_ptr_offset(leaf, slot),
size);
inode_add_bytes(inode, datal);
}

/* If we have an implicit hole (NO_HOLES feature). */
Expand Down
Loading

0 comments on commit a408365

Please sign in to comment.