/* * 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 #include #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; }