/* * 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 #include #include #include "kernel-lib/sizes.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/volumes.h" #include "kernel-shared/file-item.h" #include "kernel-shared/extent_io.h" #include "kernel-shared/transaction.h" #include "kernel-shared/tree-checker.h" #include "common/messages.h" #include "common/utils.h" #include "common/inject-error.h" #include "common/extent-tree-utils.h" #include "tune/tune.h" static int check_csum_change_requreiment(struct btrfs_fs_info *fs_info, u16 new_csum_type) { struct btrfs_root *tree_root = fs_info->tree_root; struct btrfs_root *dev_root = fs_info->dev_root; struct btrfs_path path = { 0 }; struct btrfs_key key; int ret; if (btrfs_super_log_root(fs_info->super_copy)) { error("dirty log tree detected, please replay the log or zero it."); return -EINVAL; } if (btrfs_fs_incompat(fs_info, EXTENT_TREE_V2)) { error("no csum change support for extent-tree-v2 feature yet."); return -EOPNOTSUPP; } key.objectid = BTRFS_BALANCE_OBJECTID; key.type = BTRFS_TEMPORARY_ITEM_KEY; key.offset = 0; ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0); btrfs_release_path(&path); if (ret < 0) { errno = -ret; error("failed to check the balance status: %m"); return ret; } if (ret == 0) { error("running balance detected, please finish or cancel it."); return -EINVAL; } key.objectid = 0; key.type = BTRFS_DEV_REPLACE_KEY; key.offset = 0; ret = btrfs_search_slot(NULL, dev_root, &key, &path, 0, 0); if (ret < 0) { btrfs_release_path(&path); errno = -ret; error("failed to check the dev-replace status: %m"); return ret; } if (ret == 0) { struct btrfs_dev_replace_item *ptr; u64 state; ptr = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_dev_replace_item); state = btrfs_dev_replace_replace_state(path.nodes[0], ptr); if (state == BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED || state == BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED) { btrfs_release_path(&path); error( "running/suspended dev-replace detected, please finish or cancel it"); return -EINVAL; } } btrfs_release_path(&path); if (fs_info->csum_type == new_csum_type) { error("the fs is already using csum type %s (%u)", btrfs_super_csum_name(new_csum_type), new_csum_type); return -EINVAL; } return 0; } static int get_last_csum_bytenr(struct btrfs_fs_info *fs_info, u64 *result) { struct btrfs_root *csum_root = btrfs_csum_root(fs_info, 0); struct btrfs_path path = { 0 }; struct btrfs_key key; int ret; key.objectid = BTRFS_EXTENT_CSUM_OBJECTID; key.type = BTRFS_EXTENT_CSUM_KEY; key.offset = (u64)-1; ret = btrfs_search_slot(NULL, csum_root, &key, &path, 0, 0); if (ret < 0) return ret; UASSERT(ret > 0); ret = btrfs_previous_item(csum_root, &path, BTRFS_EXTENT_CSUM_OBJECTID, BTRFS_EXTENT_CSUM_KEY); if (ret < 0) return ret; /* * Empty csum tree, set last csum byte to 0 so we can skip new data * csum generation. */ if (ret > 0) { *result = 0; btrfs_release_path(&path); return 0; } btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); *result = key.offset + btrfs_item_size(path.nodes[0], path.slots[0]) / fs_info->csum_size * fs_info->sectorsize; btrfs_release_path(&path); return 0; } static int read_verify_one_data_sector(struct btrfs_fs_info *fs_info, u64 logical, void *data_buf, const void *old_csums, u16 old_csum_type, bool output_error) { const u32 sectorsize = fs_info->sectorsize; int num_copies = btrfs_num_copies(fs_info, logical, sectorsize); bool found_good = false; for (int mirror = 1; mirror <= num_copies; mirror++) { u8 csum_has[BTRFS_CSUM_SIZE]; u64 readlen = sectorsize; int ret; ret = read_data_from_disk(fs_info, data_buf, logical, &readlen, mirror); if (ret < 0) { errno = -ret; error("failed to read logical %llu: %m", logical); continue; } btrfs_csum_data(fs_info, fs_info->csum_type, data_buf, csum_has, sectorsize); if (memcmp(csum_has, old_csums, fs_info->csum_size) == 0) { found_good = true; break; } else if (output_error){ char found[BTRFS_CSUM_STRING_LEN]; char want[BTRFS_CSUM_STRING_LEN]; btrfs_format_csum(fs_info->csum_type, old_csums, want); btrfs_format_csum(fs_info->csum_type, csum_has, found); error("csum mismatch for logical %llu mirror %u, has %s expected %s", logical, mirror, found, want); } } if (!found_good) return -EIO; return 0; } static int generate_new_csum_range(struct btrfs_trans_handle *trans, u64 logical, u64 length, u16 new_csum_type, const void *old_csums) { struct btrfs_fs_info *fs_info = trans->fs_info; const u32 sectorsize = fs_info->sectorsize; int ret = 0; void *buf; buf = malloc(fs_info->sectorsize); if (!buf) return -ENOMEM; for (u64 cur = logical; cur < logical + length; cur += sectorsize) { ret = read_verify_one_data_sector(fs_info, cur, buf, old_csums + (cur - logical) / sectorsize * fs_info->csum_size, fs_info->csum_type, true); if (ret < 0) { error("failed to recover a good copy for data at logical %llu", logical); goto out; } /* Calculate new csum and insert it into the csum tree. */ ret = btrfs_csum_file_block(trans, cur, BTRFS_CSUM_CHANGE_OBJECTID, new_csum_type, buf); if (ret < 0) { errno = -ret; error("failed to insert new csum for data at logical %llu: %m", cur); goto out; } } out: free(buf); return ret; } /* * After reading this many bytes of data, commit the current transaction. * * Only a soft cap, we can exceed the threshold if hitting a large enough csum * item. */ #define CSUM_CHANGE_BYTES_THRESHOLD (SZ_2M) static unsigned int calc_csum_change_nr_items(struct btrfs_fs_info *fs_info, u16 new_csum_type) { const u32 new_csum_size = btrfs_csum_type_size(new_csum_type); const u32 csum_item_size = CSUM_CHANGE_BYTES_THRESHOLD / fs_info->sectorsize * new_csum_size; return round_up(csum_item_size, fs_info->nodesize) / fs_info->nodesize * 2; } static int generate_new_data_csums_range(struct btrfs_fs_info *fs_info, u64 start, u16 new_csum_type) { const unsigned int nr_items = calc_csum_change_nr_items(fs_info, new_csum_type); struct btrfs_root *csum_root = btrfs_csum_root(fs_info, 0); struct btrfs_trans_handle *trans; struct btrfs_path path = { 0 }; struct btrfs_key key; void *csum_buffer; u64 converted_bytes = 0; u64 last_csum; u64 cur = start; int ret; ret = get_last_csum_bytenr(fs_info, &last_csum); if (ret < 0) { errno = -ret; error("failed to get the last csum item: %m"); return ret; } csum_buffer = malloc(fs_info->nodesize); if (!csum_buffer) return -ENOMEM; trans = btrfs_start_transaction(csum_root, nr_items); if (IS_ERR(trans)) { ret = PTR_ERR(trans); errno = -ret; error("failed to start transaction: %m"); return ret; } while (cur < last_csum) { u64 csum_start; u64 len; u32 item_size; key.objectid = BTRFS_EXTENT_CSUM_OBJECTID; key.type = BTRFS_EXTENT_CSUM_KEY; key.offset = cur; ret = btrfs_search_slot(NULL, csum_root, &key, &path, 0, 0); if (ret < 0) goto out; if (ret > 0 && path.slots[0] >= btrfs_header_nritems(path.nodes[0])) { ret = btrfs_next_leaf(csum_root, &path); if (ret > 0) { ret = 0; btrfs_release_path(&path); break; } if (ret < 0) { btrfs_release_path(&path); goto out; } } btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); UASSERT(key.offset >= cur); item_size = btrfs_item_size(path.nodes[0], path.slots[0]); csum_start = key.offset; len = item_size / fs_info->csum_size * fs_info->sectorsize; read_extent_buffer(path.nodes[0], csum_buffer, btrfs_item_ptr_offset(path.nodes[0], path.slots[0]), item_size); btrfs_release_path(&path); ret = generate_new_csum_range(trans, csum_start, len, new_csum_type, csum_buffer); if (ret < 0) goto out; converted_bytes += len; if (converted_bytes >= CSUM_CHANGE_BYTES_THRESHOLD) { converted_bytes = 0; ret = btrfs_commit_transaction(trans, csum_root); if (inject_error(0xfc35ae54)) return -EUCLEAN; if (ret < 0) goto out; trans = btrfs_start_transaction(csum_root, nr_items); if (IS_ERR(trans)) { ret = PTR_ERR(trans); goto out; } } cur = csum_start + len; } ret = btrfs_commit_transaction(trans, csum_root); if (inject_error(0x4de02239)) return -EUCLEAN; out: free(csum_buffer); return ret; } static int generate_new_data_csums(struct btrfs_fs_info *fs_info, u16 new_csum_type) { struct btrfs_root *tree_root = fs_info->tree_root; struct btrfs_trans_handle *trans; struct btrfs_path path = { 0 }; struct btrfs_key key; int ret; trans = btrfs_start_transaction(tree_root, 1); if (IS_ERR(trans)) { ret = PTR_ERR(trans); errno = -ret; error("failed to start transaction: %m"); return ret; } key.objectid = BTRFS_CSUM_CHANGE_OBJECTID; key.type = BTRFS_TEMPORARY_ITEM_KEY; key.offset = new_csum_type; ret = btrfs_insert_empty_item(trans, tree_root, &path, &key, 0); btrfs_release_path(&path); if (ret < 0) { errno = -ret; error("failed to insert csum change item: %m"); btrfs_abort_transaction(trans, ret); return ret; } btrfs_set_super_flags(fs_info->super_copy, btrfs_super_flags(fs_info->super_copy) | BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM); ret = btrfs_commit_transaction(trans, tree_root); if (inject_error(0x3964edd9)) return -EUCLEAN; if (ret < 0) { errno = -ret; error("failed to commit the initial transaction: %m"); return ret; } return generate_new_data_csums_range(fs_info, 0, new_csum_type); } /* After deleting/modifying this many leaves, commit a transaction. */ #define CSUM_CHANGE_LEAVES_THRESHOLD 32 static int delete_old_data_csums(struct btrfs_fs_info *fs_info) { struct btrfs_root *csum_root = btrfs_csum_root(fs_info, 0); struct btrfs_trans_handle *trans = 0; struct btrfs_path path = { 0 }; struct btrfs_key last_key; unsigned int deleted_leaves = 0; int ret; last_key.objectid = BTRFS_EXTENT_CSUM_OBJECTID; last_key.type = BTRFS_EXTENT_CSUM_KEY; last_key.offset = (u64)-1; while (true) { int start_slot; int nr; if (deleted_leaves >= CSUM_CHANGE_LEAVES_THRESHOLD) { UASSERT(trans); ret = btrfs_commit_transaction(trans, csum_root); if (ret < 0) { errno = -ret; error_msg(ERROR_MSG_COMMIT_TRANS, "deleting old data csums: %m"); return ret; } trans = NULL; deleted_leaves = 0; } if (!trans) { trans = btrfs_start_transaction(csum_root, 1); if (IS_ERR(trans)) { ret = PTR_ERR(trans); errno = -ret; error_msg(ERROR_MSG_START_TRANS, "deleting old data csums: %m"); return ret; } } ret = btrfs_search_slot(trans, csum_root, &last_key, &path, -1, 1); if (ret < 0) { errno = -ret; error("failed to search the last old csum item: %m"); btrfs_abort_transaction(trans, ret); return ret; } UASSERT(ret > 0); nr = btrfs_header_nritems(path.nodes[0]); /* No item left (empty csum tree), exit. */ if (!nr) break; for (start_slot = 0; start_slot < nr; start_slot++) { struct btrfs_key found_key; btrfs_item_key_to_cpu(path.nodes[0], &found_key, start_slot); /* Break from the for loop, we found the first old csum. */ if (found_key.objectid == BTRFS_EXTENT_CSUM_OBJECTID) break; } /* No more old csum item detected, exit. */ if (start_slot == nr) break; /* Delete items starting from @start_slot to the end. */ ret = btrfs_del_items(trans, csum_root, &path, start_slot, nr - start_slot); if (ret < 0) { errno = -ret; error("failed to delete items: %m"); break; } btrfs_release_path(&path); deleted_leaves++; } btrfs_release_path(&path); if (ret < 0) btrfs_abort_transaction(trans, ret); ret = btrfs_commit_transaction(trans, csum_root); if (inject_error(0x55fb4d13)) return -EUCLEAN; if (ret < 0) { errno = -ret; error("failed to commit transaction after deleting the old data csums: %m"); } return ret; } static int change_csum_objectids(struct btrfs_fs_info *fs_info) { struct btrfs_root *csum_root = btrfs_csum_root(fs_info, 0); struct btrfs_trans_handle *trans = NULL; struct btrfs_path path = { 0 }; struct btrfs_key last_key; unsigned int changed_leaves = 0; u64 super_flags; int ret = 0; last_key.objectid = BTRFS_CSUM_CHANGE_OBJECTID; last_key.type = BTRFS_EXTENT_CSUM_KEY; last_key.offset = (u64)-1; while (true) { struct btrfs_key found_key; int nr; if (changed_leaves >= CSUM_CHANGE_LEAVES_THRESHOLD) { UASSERT(trans); ret = btrfs_commit_transaction(trans, csum_root); if (ret < 0) { errno = -ret; error_msg(ERROR_MSG_COMMIT_TRANS, "changing data csum objectid: %m"); return ret; } trans = NULL; changed_leaves = 0; } if (!trans) { trans = btrfs_start_transaction(csum_root, 1); if (IS_ERR(trans)) { ret = PTR_ERR(trans); errno = -ret; error_msg(ERROR_MSG_START_TRANS, "changing data csum objectid: %m"); return ret; } } ret = btrfs_search_slot(trans, csum_root, &last_key, &path, 0, 1); if (ret < 0) goto out; UASSERT(ret > 0); nr = btrfs_header_nritems(path.nodes[0]); /* No item left (empty csum tree), exit. */ if (!nr) goto out; /* No more temporary csum items, all converted, exit. */ if (path.slots[0] == 0) goto out; /* All csum items should be new csums. */ btrfs_item_key_to_cpu(path.nodes[0], &found_key, 0); UASSERT(found_key.objectid == BTRFS_CSUM_CHANGE_OBJECTID); /* * Start changing the objectids, since EXTENT_CSUM (-10) is * larger than CSUM_CHANGE (-13), we always change from the tail. */ for (int i = nr - 1; i >= 0; i--) { btrfs_item_key_to_cpu(path.nodes[0], &found_key, i); found_key.objectid = BTRFS_EXTENT_CSUM_OBJECTID; path.slots[0] = i; btrfs_set_item_key_safe(fs_info, &path, &found_key); } btrfs_release_path(&path); } out: btrfs_release_path(&path); if (ret < 0) { btrfs_abort_transaction(trans, ret); return ret; } /* * All data csum items has been changed to the new type, we can clear * the superblock flag for data csum change, and go to the metadata csum * change phase. */ super_flags = btrfs_super_flags(fs_info->super_copy); super_flags &= ~BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM; super_flags |= BTRFS_SUPER_FLAG_CHANGING_META_CSUM; btrfs_set_super_flags(fs_info->super_copy, super_flags); ret = btrfs_commit_transaction(trans, csum_root); if (inject_error(0x2628b3fe)) return -EUCLEAN; if (ret < 0) { errno = -ret; error("failed to commit transaction after changing data csum objectids: %m"); } return ret; } static int rewrite_tree_block_csum(struct btrfs_fs_info *fs_info, u64 logical, u16 new_csum_type) { struct extent_buffer *eb; struct btrfs_tree_parent_check check = { 0 }; u8 result_old[BTRFS_CSUM_SIZE]; u8 result_new[BTRFS_CSUM_SIZE]; int ret; eb = alloc_dummy_extent_buffer(fs_info, logical, fs_info->nodesize); if (!eb) return -ENOMEM; ret = btrfs_read_extent_buffer(eb, &check); if (ret < 0) { errno = -ret; error("failed to read tree block at logical %llu: %m", logical); goto out; } /* Verify the csum first. */ btrfs_csum_data(fs_info, fs_info->csum_type, (u8 *)eb->data + BTRFS_CSUM_SIZE, result_old, fs_info->nodesize - BTRFS_CSUM_SIZE); btrfs_csum_data(fs_info, new_csum_type, (u8 *)eb->data + BTRFS_CSUM_SIZE, result_new, fs_info->nodesize - BTRFS_CSUM_SIZE); /* Matches old csum, rewrite. */ if (memcmp_extent_buffer(eb, result_old, 0, fs_info->csum_size) == 0) { write_extent_buffer(eb, result_new, 0, btrfs_csum_type_size(new_csum_type)); ret = write_data_to_disk(fs_info, eb->data, eb->start, fs_info->nodesize); if (ret < 0) { errno = -ret; error("failed to write tree block at logical %llu: %m", logical); } goto out; } /* Already new csum. */ if (memcmp_extent_buffer(eb, result_new, 0, fs_info->csum_size) == 0) goto out; /* Csum doesn't match either old or new csum type, bad tree block. */ ret = -EIO; error("tree block csum mismatch at logical %llu", logical); out: free_extent_buffer(eb); return ret; } static int change_meta_csums(struct btrfs_fs_info *fs_info, u16 new_csum_type) { struct btrfs_root *extent_root = btrfs_extent_root(fs_info, 0); struct btrfs_path path = { 0 }; struct btrfs_key key; u64 super_flags; int ret; /* Re-set the super flags, this is for resume cases. */ super_flags = btrfs_super_flags(fs_info->super_copy); super_flags &= ~BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM; super_flags |= BTRFS_SUPER_FLAG_CHANGING_META_CSUM; btrfs_set_super_flags(fs_info->super_copy, super_flags); ret = write_all_supers(fs_info); if (ret < 0) { errno = -ret; error("failed to update super flags: %m"); } /* * Disable metadata csum checks first, as we may hit tree blocks with * either old or new csums. * We will manually check the meta csums here. */ fs_info->skip_csum_check = true; key.objectid = 0; key.type = 0; key.offset = 0; ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0); if (ret < 0) { errno = -ret; error("failed to get the first tree block of extent tree: %m"); return ret; } UASSERT(ret > 0); while (true) { btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); if (key.type != BTRFS_EXTENT_ITEM_KEY && key.type != BTRFS_METADATA_ITEM_KEY) goto next; if (key.type == BTRFS_EXTENT_ITEM_KEY) { struct btrfs_extent_item *ei; ei = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_extent_item); if (btrfs_extent_flags(path.nodes[0], ei) & BTRFS_EXTENT_FLAG_DATA) goto next; } ret = rewrite_tree_block_csum(fs_info, key.objectid, new_csum_type); if (ret < 0) { errno = -ret; error("failed to rewrite csum for tree block %llu: %m", key.offset); goto out; } next: ret = btrfs_next_extent_item(extent_root, &path, U64_MAX); if (ret < 0) { errno = -ret; error("failed to get next extent item: %m"); } if (ret > 0) { ret = 0; goto out; } } out: btrfs_release_path(&path); /* * Finish the change by clearing the csum change flag, update the superblock * csum type, and delete the csum change item in the fs with new csum type. */ if (ret == 0) { struct btrfs_root *tree_root = fs_info->tree_root; struct btrfs_trans_handle *trans; super_flags = btrfs_super_flags(fs_info->super_copy); btrfs_set_super_csum_type(fs_info->super_copy, new_csum_type); super_flags &= ~(BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM | BTRFS_SUPER_FLAG_CHANGING_META_CSUM); btrfs_set_super_flags(fs_info->super_copy, super_flags); fs_info->csum_type = new_csum_type; fs_info->csum_size = btrfs_csum_type_size(new_csum_type); fs_info->skip_csum_check = 0; trans = btrfs_start_transaction(tree_root, 1); if (IS_ERR(trans)) { ret = PTR_ERR(trans); errno = -ret; error("failed to start new transaction with new csum type: %m"); return ret; } key.objectid = BTRFS_CSUM_CHANGE_OBJECTID; key.type = BTRFS_TEMPORARY_ITEM_KEY; key.offset = new_csum_type; ret = btrfs_search_slot(trans, tree_root, &key, &path, -1, 1); if (ret > 0) ret = -ENOENT; if (ret < 0) { errno = -ret; error("failed to locate the csum change item: %m"); btrfs_release_path(&path); btrfs_abort_transaction(trans, ret); return ret; } ret = btrfs_del_item(trans, tree_root, &path); if (ret < 0) { errno = -ret; error("failed to delete the csum change item: %m"); btrfs_release_path(&path); btrfs_abort_transaction(trans, ret); return ret; } btrfs_release_path(&path); ret = btrfs_commit_transaction(trans, tree_root); if (inject_error(0x9f0ca518)) return -EUCLEAN; if (ret < 0) { errno = -ret; error("failed to finalize the csum change: %m"); } } return ret; } /* * Get the first and last csum items which has @objectid as their objectid. * * This would be called to handle data csum resume, which may have both old * and new csums co-exist in the same csum tree. * * Return >0 if there is no such EXTENT_CSUM with given @objectid. * Return 0 if there is such EXTENT_CSUM and populate @first_ret and @last_ret. * Return <0 for errors. */ static int get_csum_items_range(struct btrfs_fs_info *fs_info, u64 objectid, u64 *first_ret, u64 *last_ret, u32 *last_item_size) { struct btrfs_root *csum_root = btrfs_csum_root(fs_info, 0); struct btrfs_path path = { 0 }; struct btrfs_key key; int ret; key.objectid = objectid; key.type = BTRFS_EXTENT_CSUM_KEY; key.offset = 0; ret = btrfs_search_slot(NULL, csum_root, &key, &path, 0, 0); if (ret < 0) { errno = -ret; error("failed to search csum tree: %m"); return ret; } if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) { ret = btrfs_next_leaf(csum_root, &path); if (ret < 0) { errno = -ret; error("failed to search csum tree: %m"); btrfs_release_path(&path); return ret; } /* * There is no next leaf, meaning we didn't find any csum item * with given objectid. */ if (ret > 0) { btrfs_release_path(&path); return ret; } } btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); btrfs_release_path(&path); if (key.objectid != objectid) return 1; *first_ret = key.offset; key.objectid = objectid; key.type = BTRFS_EXTENT_CSUM_KEY; key.offset = (u64)-1; ret = btrfs_search_slot(NULL, csum_root, &key, &path, 0, 0); if (ret < 0) { errno = -ret; error("failed to search csum tree: %m"); return ret; } UASSERT(ret > 0); ret = btrfs_previous_item(csum_root, &path, objectid, BTRFS_EXTENT_CSUM_KEY); if (ret < 0) { errno = -ret; error("failed to search csum tree: %m"); btrfs_release_path(&path); return ret; } if (ret > 0) { btrfs_release_path(&path); return 1; } btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); *last_item_size = btrfs_item_size(path.nodes[0], path.slots[0]); btrfs_release_path(&path); *last_ret = key.offset; return 0; } /* * Verify one data sector to determine which csum type matches the csum. * * Return >0 if the current csum type doesn't pass the check (including csum * item too small compared to csum type). * Return 0 if the current csum type passes the check. * Return <0 for other errors. */ static int determine_csum_type(struct btrfs_fs_info *fs_info, u64 logical, u16 csum_type) { struct btrfs_root *csum_root = btrfs_csum_root(fs_info, logical); struct btrfs_path path = { 0 }; struct btrfs_key key; u16 csum_size = btrfs_csum_type_size(csum_type); u8 csum_expected[BTRFS_CSUM_SIZE]; void *buf; int ret; key.objectid = BTRFS_EXTENT_CSUM_OBJECTID; key.type = BTRFS_EXTENT_CSUM_KEY; key.offset = logical; ret = btrfs_search_slot(NULL, csum_root, &key, &path, 0, 0); if (ret > 0) ret = -ENOENT; if (ret < 0) { errno = -ret; error("failed to search csum tree: %m"); btrfs_release_path(&path); return ret; } /* * The csum item size is smaller than expected csum size, no * more need to check. */ if (btrfs_item_size(path.nodes[0], path.slots[0]) < csum_size) { btrfs_release_path(&path); return 1; } read_extent_buffer(path.nodes[0], csum_expected, btrfs_item_ptr_offset(path.nodes[0], path.slots[0]), csum_size); btrfs_release_path(&path); buf = malloc(fs_info->sectorsize); if (!buf) return -ENOMEM; ret = read_verify_one_data_sector(fs_info, logical, buf, csum_expected, csum_type, false); if (ret < 0) ret = 1; free(buf); return ret; } static int resume_data_csum_change(struct btrfs_fs_info *fs_info, u16 new_csum_type) { u64 old_csum_first; u64 old_csum_last; u64 new_csum_first; u64 new_csum_last; bool old_csum_found = false; bool new_csum_found = false; u32 old_last_size; u32 new_last_size; u64 resume_start; int ret; ret = get_csum_items_range(fs_info, BTRFS_EXTENT_CSUM_OBJECTID, &old_csum_first, &old_csum_last, &old_last_size); if (ret < 0) return ret; if (ret == 0) old_csum_found = true; ret = get_csum_items_range(fs_info, BTRFS_CSUM_CHANGE_OBJECTID, &new_csum_first, &new_csum_last, &new_last_size); if (ret < 0) return ret; if (ret == 0) new_csum_found = true; /* * No csum item found at all, this fs has empty csum tree. * Just go metadata change. */ if (!old_csum_found && !new_csum_found) goto new_meta_csum; /* * Only old csums exists. This can be one of the two cases: * - Only the csum change item inserted, no new csum generated. * - All data csum is converted to the new type. * * Here we need to check if the csum item is in old or new type. */ if (old_csum_found && !new_csum_found) { ret = determine_csum_type(fs_info, old_csum_first, fs_info->csum_type); if (ret == 0) { /* All old data csums, restart generation. */ resume_start = 0; goto new_data_csums; } ret = determine_csum_type(fs_info, old_csum_first, new_csum_type); if (ret == 0) { /* * All new data csums, just go metadata csum change, which * would drop the CHANGING_DATA_CSUM flag for us. */ goto new_meta_csum; } error("The data checksum for logical %llu doesn't match either old or new csum type, unable to resume", old_csum_first); return -EUCLEAN; } /* * Both old and new csum exist, and new csum is only a subset of the * old ones. * * This means we're still generating new data csums. */ if (old_csum_found && new_csum_found && old_csum_first <= new_csum_first && old_csum_last >= new_csum_last) { resume_start = new_csum_last + new_last_size / btrfs_csum_type_size(new_csum_type) * fs_info->sectorsize; goto new_data_csums; } /* * Both old and new csum exist, and old csum is a subset of the new ones. * * This means we're deleting the old csums. */ if (old_csum_found && new_csum_found && new_csum_first <= old_csum_first && new_csum_last >= old_csum_last) goto delete_old; /* * Both csums exist but not covering each other, or only new csum exists. * * This means we have already deleted all the old csums, is going to or * have already started objectid change. */ if ((old_csum_found && new_csum_found && old_csum_last <= new_csum_first) && (!old_csum_found && new_csum_found)) goto change; /* The remaining cases should not be possible. */ error("unexpected resume condition:"); error("old csum found=%d start=%llu last=%llu new csum found=%d start=%llu last=%llu", old_csum_found, old_csum_found ? old_csum_first : 0, old_csum_found ? old_csum_last : 0, new_csum_found, new_csum_found ? new_csum_first : 0, new_csum_found ? new_csum_last : 0); return -EUCLEAN; new_data_csums: ret = generate_new_data_csums_range(fs_info, resume_start, new_csum_type); if (ret < 0) { errno = -ret; error("failed to generate new data csums: %m"); return ret; } delete_old: ret = delete_old_data_csums(fs_info); if (ret < 0) return ret; change: ret = change_csum_objectids(fs_info); if (ret < 0) return ret; new_meta_csum: ret = change_meta_csums(fs_info, new_csum_type); return ret; } static int resume_csum_change(struct btrfs_fs_info *fs_info, u16 new_csum_type) { const u64 super_flags = btrfs_super_flags(fs_info->super_copy); struct btrfs_root *tree_root = fs_info->tree_root; struct btrfs_path path = { 0 }; struct btrfs_key key; int ret; if ((super_flags & (BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM | BTRFS_SUPER_FLAG_CHANGING_META_CSUM)) == (BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM | BTRFS_SUPER_FLAG_CHANGING_META_CSUM)) { error( "invalid super flags, only one bit of CHANGING_DATA_CSUM or CHANGING_META_CSUM can be set"); return -EUCLEAN; } key.objectid = BTRFS_CSUM_CHANGE_OBJECTID; key.type = BTRFS_TEMPORARY_ITEM_KEY; key.offset = (u64)-1; ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0); if (ret < 0) { errno = -ret; error("failed to locate the csum change item: %m"); return ret; } UASSERT(ret > 0); ret = btrfs_previous_item(tree_root, &path, BTRFS_CSUM_CHANGE_OBJECTID, BTRFS_TEMPORARY_ITEM_KEY); if (ret > 0) ret = -ENOENT; if (ret < 0) { errno = -ret; error("failed to locate the csum change item: %m"); btrfs_release_path(&path); return ret; } btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); btrfs_release_path(&path); if (new_csum_type != key.offset) { ret = -EINVAL; error( "target csum type mismatch with interrupted csum type, has %s (%u) expect %s (%llu)", btrfs_super_csum_name(new_csum_type), new_csum_type, btrfs_super_csum_name(key.offset), key.offset); return ret; } if (super_flags & BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM) { ret = resume_data_csum_change(fs_info, new_csum_type); if (ret < 0) { errno = -ret; error("failed to resume data checksum change: %m"); } return ret; } /* * For metadata resume, just call the same change_meta_csums(), as we * have no record on previous converted metadata, thus have to go * through all metadata anyway. */ ret = change_meta_csums(fs_info, new_csum_type); if (ret < 0) { errno = -ret; error("failed to resume metadata csum change: %m"); } return ret; } int btrfs_change_csum_type(struct btrfs_fs_info *fs_info, u16 new_csum_type) { u16 old_csum_type = fs_info->csum_type; int ret; /* Phase 0, check conflicting features. */ ret = check_csum_change_requreiment(fs_info, new_csum_type); if (ret < 0) return ret; if (btrfs_super_flags(fs_info->super_copy) & (BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM | BTRFS_SUPER_FLAG_CHANGING_META_CSUM)) { ret = resume_csum_change(fs_info, new_csum_type); if (ret < 0) { errno = -ret; error("failed to resume unfinished csum change: %m"); return ret; } printf("converted csum type from %s (%u) to %s (%u)\n", btrfs_super_csum_name(old_csum_type), old_csum_type, btrfs_super_csum_name(new_csum_type), new_csum_type); return ret; } /* * Phase 1, generate new data csums. * * The new data csums would have a different key objectid, and there * will be a temporary item in root tree to indicate the new checksum * algo. */ ret = generate_new_data_csums(fs_info, new_csum_type); if (ret < 0) { errno = -ret; error("failed to generate new data csums: %m"); return ret; } /* Phase 2, delete the old data csums. */ ret = delete_old_data_csums(fs_info); if (ret < 0) return ret; /* Phase 3, change the new csum key objectid */ ret = change_csum_objectids(fs_info); if (ret < 0) return ret; /* * Phase 4, change the csums for metadata. * * This has to be done in-place, as we don't have a good method * like relocation in progs. * Thus we have to support reading a tree block with either csum. */ ret = change_meta_csums(fs_info, new_csum_type); if (ret == 0) printf("converted csum type from %s (%u) to %s (%u)\n", btrfs_super_csum_name(old_csum_type), old_csum_type, btrfs_super_csum_name(new_csum_type), new_csum_type); return ret; }