288 lines
8.1 KiB
C
288 lines
8.1 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License v2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 021110-1307, USA.
|
|
*/
|
|
|
|
#include "kerncompat.h"
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include "kernel-shared/accessors.h"
|
|
#include "kernel-shared/uapi/btrfs_tree.h"
|
|
#include "kernel-shared/ctree.h"
|
|
#include "kernel-shared/disk-io.h"
|
|
#include "kernel-shared/file-item.h"
|
|
#include "kernel-shared/transaction.h"
|
|
#include "kernel-shared/free-space-tree.h"
|
|
#include "common/internal.h"
|
|
#include "common/extent-tree-utils.h"
|
|
#include "common/messages.h"
|
|
|
|
/*
|
|
* Search in extent tree to found next meta/data extent. Caller needs to check
|
|
* for no-hole or skinny metadata features.
|
|
*/
|
|
int btrfs_next_extent_item(struct btrfs_root *root, struct btrfs_path *path,
|
|
u64 max_objectid)
|
|
{
|
|
struct btrfs_key found_key;
|
|
int ret;
|
|
|
|
while (1) {
|
|
ret = btrfs_next_item(root, path);
|
|
if (ret)
|
|
return ret;
|
|
btrfs_item_key_to_cpu(path->nodes[0], &found_key, path->slots[0]);
|
|
if (found_key.objectid > max_objectid)
|
|
return 1;
|
|
if (found_key.type == BTRFS_EXTENT_ITEM_KEY ||
|
|
found_key.type == BTRFS_METADATA_ITEM_KEY)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void __get_extent_size(struct btrfs_root *root, struct btrfs_path *path,
|
|
u64 *start, u64 *len)
|
|
{
|
|
struct btrfs_key key;
|
|
|
|
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
|
|
BUG_ON(!(key.type == BTRFS_EXTENT_ITEM_KEY ||
|
|
key.type == BTRFS_METADATA_ITEM_KEY));
|
|
*start = key.objectid;
|
|
if (key.type == BTRFS_EXTENT_ITEM_KEY)
|
|
*len = key.offset;
|
|
else
|
|
*len = root->fs_info->nodesize;
|
|
}
|
|
|
|
/*
|
|
* Find first overlap extent for range [bytenr, bytenr + len).
|
|
*
|
|
* Return 0 for found and point path to it.
|
|
* Return >0 for not found.
|
|
* Return <0 for err
|
|
*/
|
|
static int btrfs_search_overlap_extent(struct btrfs_root *root,
|
|
struct btrfs_path *path, u64 bytenr, u64 len)
|
|
{
|
|
struct btrfs_key key;
|
|
u64 cur_start;
|
|
u64 cur_len;
|
|
int ret;
|
|
|
|
key.objectid = bytenr;
|
|
key.type = BTRFS_EXTENT_DATA_KEY;
|
|
key.offset = (u64)-1;
|
|
|
|
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (ret == 0) {
|
|
error_msg(ERROR_MSG_UNEXPECTED, "EXTENT_DATA found at %llu", bytenr);
|
|
return -EUCLEAN;
|
|
}
|
|
|
|
ret = btrfs_previous_extent_item(root, path, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
/* No previous, check next extent. */
|
|
if (ret > 0)
|
|
goto next;
|
|
__get_extent_size(root, path, &cur_start, &cur_len);
|
|
/* Tail overlap. */
|
|
if (cur_start + cur_len > bytenr)
|
|
return 1;
|
|
|
|
next:
|
|
ret = btrfs_next_extent_item(root, path, bytenr + len);
|
|
if (ret < 0)
|
|
return ret;
|
|
/* No next, prev already checked, no overlap. */
|
|
if (ret > 0)
|
|
return 0;
|
|
__get_extent_size(root, path, &cur_start, &cur_len);
|
|
/* Head overlap.*/
|
|
if (cur_start < bytenr + len)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int __btrfs_record_file_extent(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *root, u64 objectid,
|
|
struct btrfs_inode_item *inode,
|
|
u64 file_pos, u64 disk_bytenr,
|
|
u64 *ret_num_bytes)
|
|
{
|
|
int ret;
|
|
struct btrfs_fs_info *info = root->fs_info;
|
|
struct btrfs_root *extent_root = btrfs_extent_root(info, disk_bytenr);
|
|
struct extent_buffer *leaf;
|
|
struct btrfs_file_extent_item *fi;
|
|
struct btrfs_key ins_key;
|
|
struct btrfs_path *path;
|
|
struct btrfs_extent_item *ei;
|
|
u64 nbytes;
|
|
u64 extent_num_bytes;
|
|
u64 extent_bytenr;
|
|
u64 extent_offset;
|
|
u64 num_bytes = *ret_num_bytes;
|
|
|
|
/*
|
|
* @objectid should be an inode number, thus it must not be smaller
|
|
* than BTRFS_FIRST_FREE_OBJECTID.
|
|
*/
|
|
UASSERT(objectid >= BTRFS_FIRST_FREE_OBJECTID);
|
|
|
|
/*
|
|
* All supported file system should not use its 0 extent. As it's for
|
|
* hole. And hole extent has no size limit, no need to loop.
|
|
*/
|
|
if (disk_bytenr == 0) {
|
|
ret = btrfs_insert_file_extent(trans, root, objectid,
|
|
file_pos, disk_bytenr,
|
|
num_bytes, num_bytes);
|
|
return ret;
|
|
}
|
|
num_bytes = min_t(u64, num_bytes, BTRFS_MAX_EXTENT_SIZE);
|
|
|
|
path = btrfs_alloc_path();
|
|
if (!path)
|
|
return -ENOMEM;
|
|
|
|
/* First to check extent overlap. */
|
|
ret = btrfs_search_overlap_extent(extent_root, path, disk_bytenr, num_bytes);
|
|
if (ret < 0)
|
|
goto fail;
|
|
if (ret > 0) {
|
|
/* Found overlap. */
|
|
u64 cur_start;
|
|
u64 cur_len;
|
|
|
|
__get_extent_size(extent_root, path, &cur_start, &cur_len);
|
|
/* For convert case, this extent should be a subset of existing one. */
|
|
if (disk_bytenr < cur_start) {
|
|
error_msg(ERROR_MSG_UNEXPECTED,
|
|
"invalid range disk_bytenr < cur_start: %llu < %llu",
|
|
disk_bytenr, cur_start);
|
|
ret = -EUCLEAN;
|
|
goto fail;
|
|
}
|
|
|
|
extent_bytenr = cur_start;
|
|
extent_num_bytes = cur_len;
|
|
extent_offset = disk_bytenr - extent_bytenr;
|
|
} else {
|
|
/* No overlap, create new extent. */
|
|
btrfs_release_path(path);
|
|
ins_key.objectid = disk_bytenr;
|
|
ins_key.type = BTRFS_EXTENT_ITEM_KEY;
|
|
ins_key.offset = num_bytes;
|
|
|
|
ret = btrfs_insert_empty_item(trans, extent_root, path,
|
|
&ins_key, sizeof(*ei));
|
|
if (ret == 0) {
|
|
leaf = path->nodes[0];
|
|
ei = btrfs_item_ptr(leaf, path->slots[0],
|
|
struct btrfs_extent_item);
|
|
|
|
btrfs_set_extent_refs(leaf, ei, 0);
|
|
btrfs_set_extent_generation(leaf, ei, trans->transid);
|
|
btrfs_set_extent_flags(leaf, ei,
|
|
BTRFS_EXTENT_FLAG_DATA);
|
|
btrfs_mark_buffer_dirty(leaf);
|
|
|
|
ret = btrfs_update_block_group(trans, disk_bytenr,
|
|
num_bytes, 1, 0);
|
|
if (ret)
|
|
goto fail;
|
|
} else if (ret != -EEXIST) {
|
|
goto fail;
|
|
}
|
|
|
|
ret = remove_from_free_space_tree(trans, disk_bytenr, num_bytes);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
btrfs_run_delayed_refs(trans, -1);
|
|
extent_bytenr = disk_bytenr;
|
|
extent_num_bytes = num_bytes;
|
|
extent_offset = 0;
|
|
}
|
|
btrfs_release_path(path);
|
|
ins_key.objectid = objectid;
|
|
ins_key.type = BTRFS_EXTENT_DATA_KEY;
|
|
ins_key.offset = file_pos;
|
|
ret = btrfs_insert_empty_item(trans, root, path, &ins_key, sizeof(*fi));
|
|
if (ret)
|
|
goto fail;
|
|
leaf = path->nodes[0];
|
|
fi = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_file_extent_item);
|
|
btrfs_set_file_extent_generation(leaf, fi, trans->transid);
|
|
btrfs_set_file_extent_type(leaf, fi, BTRFS_FILE_EXTENT_REG);
|
|
btrfs_set_file_extent_disk_bytenr(leaf, fi, extent_bytenr);
|
|
btrfs_set_file_extent_disk_num_bytes(leaf, fi, extent_num_bytes);
|
|
btrfs_set_file_extent_offset(leaf, fi, extent_offset);
|
|
btrfs_set_file_extent_num_bytes(leaf, fi, num_bytes);
|
|
btrfs_set_file_extent_ram_bytes(leaf, fi, extent_num_bytes);
|
|
btrfs_set_file_extent_compression(leaf, fi, 0);
|
|
btrfs_set_file_extent_encryption(leaf, fi, 0);
|
|
btrfs_set_file_extent_other_encoding(leaf, fi, 0);
|
|
btrfs_mark_buffer_dirty(leaf);
|
|
|
|
nbytes = btrfs_stack_inode_nbytes(inode) + num_bytes;
|
|
btrfs_set_stack_inode_nbytes(inode, nbytes);
|
|
btrfs_release_path(path);
|
|
|
|
ret = btrfs_inc_extent_ref(trans, extent_bytenr, extent_num_bytes,
|
|
0, root->root_key.objectid, objectid,
|
|
file_pos - extent_offset);
|
|
if (ret)
|
|
goto fail;
|
|
ret = 0;
|
|
*ret_num_bytes = min(extent_num_bytes - extent_offset, num_bytes);
|
|
fail:
|
|
btrfs_free_path(path);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Record a file extent. Do all the required works, such as inserting file
|
|
* extent item, inserting extent item and backref item into extent tree and
|
|
* updating block accounting.
|
|
*/
|
|
int btrfs_record_file_extent(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *root, u64 objectid,
|
|
struct btrfs_inode_item *inode,
|
|
u64 file_pos, u64 disk_bytenr,
|
|
u64 num_bytes)
|
|
{
|
|
u64 cur_disk_bytenr = disk_bytenr;
|
|
u64 cur_file_pos = file_pos;
|
|
u64 cur_num_bytes = num_bytes;
|
|
int ret = 0;
|
|
|
|
while (num_bytes > 0) {
|
|
ret = __btrfs_record_file_extent(trans, root, objectid,
|
|
inode, cur_file_pos,
|
|
cur_disk_bytenr,
|
|
&cur_num_bytes);
|
|
if (ret < 0)
|
|
break;
|
|
cur_disk_bytenr += cur_num_bytes;
|
|
cur_file_pos += cur_num_bytes;
|
|
num_bytes -= cur_num_bytes;
|
|
}
|
|
return ret;
|
|
}
|