diff --git a/btrfstune.c b/btrfstune.c index ad70772b..3d0e8f61 100644 --- a/btrfstune.c +++ b/btrfstune.c @@ -73,6 +73,116 @@ static int update_seeding_flag(struct btrfs_root *root, int set_flag) return ret; } +/* + * Return 0 for no unfinished fsid change. + * Return >0 for unfinished fsid change, and restore unfinished fsid/ + * chunk_tree_id into fsid_ret/chunk_id_ret. + */ +static int check_unfinished_fsid_change(struct btrfs_fs_info *fs_info, + uuid_t fsid_ret, uuid_t chunk_id_ret) +{ + struct btrfs_root *tree_root = fs_info->tree_root; + u64 flags = btrfs_super_flags(fs_info->super_copy); + + if (flags & (BTRFS_SUPER_FLAG_CHANGING_FSID | + BTRFS_SUPER_FLAG_CHANGING_FSID_V2)) { + memcpy(fsid_ret, fs_info->super_copy->fsid, BTRFS_FSID_SIZE); + read_extent_buffer(tree_root->node, chunk_id_ret, + btrfs_header_chunk_tree_uuid(tree_root->node), + BTRFS_UUID_SIZE); + return 1; + } + return 0; +} + +static int set_metadata_uuid(struct btrfs_root *root, const char *uuid_string) +{ + struct btrfs_super_block *disk_super; + uuid_t new_fsid, unused1, unused2; + struct btrfs_trans_handle *trans; + bool new_uuid = true; + u64 incompat_flags; + bool uuid_changed; + u64 super_flags; + int ret; + + disk_super = root->fs_info->super_copy; + super_flags = btrfs_super_flags(disk_super); + incompat_flags = btrfs_super_incompat_flags(disk_super); + uuid_changed = incompat_flags & BTRFS_FEATURE_INCOMPAT_METADATA_UUID; + + if (super_flags & BTRFS_SUPER_FLAG_SEEDING) { + fprintf(stderr, "cannot set metadata UUID on a seed device\n"); + return 1; + } + + if (check_unfinished_fsid_change(root->fs_info, unused1, unused2)) { + fprintf(stderr, + "UUID rewrite in progress, cannot change fsid\n"); + return 1; + } + + if (uuid_string) + uuid_parse(uuid_string, new_fsid); + else + uuid_generate(new_fsid); + + new_uuid = (memcmp(new_fsid, disk_super->fsid, BTRFS_FSID_SIZE) != 0); + + /* Step 1 sets the in progress flag */ + trans = btrfs_start_transaction(root, 1); + super_flags |= BTRFS_SUPER_FLAG_CHANGING_FSID_V2; + btrfs_set_super_flags(disk_super, super_flags); + ret = btrfs_commit_transaction(trans, root); + if (ret < 0) + return ret; + + if (new_uuid && uuid_changed && memcmp(disk_super->metadata_uuid, + new_fsid, BTRFS_FSID_SIZE) == 0) { + /* + * Changing fsid to be the same as metadata uuid, so just + * disable the flag + */ + memcpy(disk_super->fsid, &new_fsid, BTRFS_FSID_SIZE); + incompat_flags &= ~BTRFS_FEATURE_INCOMPAT_METADATA_UUID; + btrfs_set_super_incompat_flags(disk_super, incompat_flags); + memset(disk_super->metadata_uuid, 0, BTRFS_FSID_SIZE); + } else if (new_uuid && uuid_changed && memcmp(disk_super->metadata_uuid, + new_fsid, BTRFS_FSID_SIZE)) { + /* + * Changing fsid on an already changed FS, in this case we + * only change the fsid and don't touch metadata uuid as it + * has already the correct value + */ + memcpy(disk_super->fsid, &new_fsid, BTRFS_FSID_SIZE); + } else if (new_uuid && !uuid_changed) { + /* + * First time changing the fsid, copy the fsid to metadata_uuid + */ + incompat_flags |= BTRFS_FEATURE_INCOMPAT_METADATA_UUID; + btrfs_set_super_incompat_flags(disk_super, incompat_flags); + memcpy(disk_super->metadata_uuid, disk_super->fsid, + BTRFS_FSID_SIZE); + memcpy(disk_super->fsid, &new_fsid, BTRFS_FSID_SIZE); + } else { + /* Setting the same fsid as current, do nothing */ + return 0; + } + + trans = btrfs_start_transaction(root, 1); + + /* + * Step 2 is to write the metadata_uuid, set the incompat flag and + * clear the in progress flag + */ + super_flags &= ~BTRFS_SUPER_FLAG_CHANGING_FSID_V2; + btrfs_set_super_flags(disk_super, super_flags); + + /* Then actually copy the metadata uuid and set the incompat bit */ + + return btrfs_commit_transaction(trans, root); +} + static int set_super_incompat_flags(struct btrfs_root *root, u64 flags) { struct btrfs_trans_handle *trans; @@ -268,26 +378,6 @@ static int change_fsid_done(struct btrfs_fs_info *fs_info) return write_all_supers(fs_info); } -/* - * Return 0 for no unfinished fsid change. - * Return >0 for unfinished fsid change, and restore unfinished fsid/ - * chunk_tree_id into fsid_ret/chunk_id_ret. - */ -static int check_unfinished_fsid_change(struct btrfs_fs_info *fs_info, - uuid_t fsid_ret, uuid_t chunk_id_ret) -{ - struct btrfs_root *tree_root = fs_info->tree_root; - u64 flags = btrfs_super_flags(fs_info->super_copy); - - if (flags & BTRFS_SUPER_FLAG_CHANGING_FSID) { - memcpy(fsid_ret, fs_info->super_copy->fsid, BTRFS_FSID_SIZE); - read_extent_buffer(tree_root->node, chunk_id_ret, - btrfs_header_chunk_tree_uuid(tree_root->node), - BTRFS_UUID_SIZE); - return 1; - } - return 0; -} /* * Change fsid of a given fs. @@ -381,8 +471,10 @@ static void print_usage(void) printf("\t-x \t\tenable skinny metadata extent refs\n"); printf("\t-n \t\tenable no-holes feature (more efficient sparse file representation)\n"); printf("\t-f \t\tforce to do dangerous operation, make sure that you are aware of the dangers\n"); - printf("\t-u \t\tchange fsid, use a random one\n"); - printf("\t-U UUID\t\tchange fsid to UUID\n"); + printf("\t-u \t\trewrite fsid, use a random one\n"); + printf("\t-U UUID\t\trewrite fsid to UUID\n"); + printf("\t-m \t\tchange only metadata uuid (more lightweight than -u|-U)\n"); + printf("\t-M UUID\t\tchange fsid to UUID\n"); } int main(int argc, char *argv[]) @@ -394,6 +486,7 @@ int main(int argc, char *argv[]) int seeding_flag = 0; u64 seeding_value = 0; int random_fsid = 0; + int change_metadata_uuid = 0; char *new_fsid_str = NULL; int ret; u64 super_flags = 0; @@ -404,7 +497,7 @@ int main(int argc, char *argv[]) { "help", no_argument, NULL, GETOPT_VAL_HELP}, { NULL, 0, NULL, 0 } }; - int c = getopt_long(argc, argv, "S:rxfuU:n", long_options, NULL); + int c = getopt_long(argc, argv, "S:rxfuU:nmM:", long_options, NULL); if (c < 0) break; @@ -433,6 +526,15 @@ int main(int argc, char *argv[]) ctree_flags |= OPEN_CTREE_IGNORE_FSID_MISMATCH; random_fsid = 1; break; + case 'M': + ctree_flags |= OPEN_CTREE_IGNORE_FSID_MISMATCH; + change_metadata_uuid = 1; + new_fsid_str = optarg; + break; + case 'm': + ctree_flags |= OPEN_CTREE_IGNORE_FSID_MISMATCH; + change_metadata_uuid = 1; + break; case GETOPT_VAL_HELP: default: print_usage(); @@ -451,7 +553,8 @@ int main(int argc, char *argv[]) error("random fsid can't be used with specified fsid"); return 1; } - if (!super_flags && !seeding_flag && !(random_fsid || new_fsid_str)) { + if (!super_flags && !seeding_flag && !(random_fsid || new_fsid_str) && + !change_metadata_uuid) { error("at least one option should be specified"); print_usage(); return 1; @@ -498,6 +601,12 @@ int main(int argc, char *argv[]) } if (seeding_flag) { + if (btrfs_fs_incompat(root->fs_info, METADATA_UUID)) { + fprintf(stderr, "SEED flag cannot be changed on a metadata-uuid changed fs\n"); + ret = 1; + goto out; + } + if (!seeding_value && !force) { warning( "this is dangerous, clearing the seeding flag may cause the derived device not to be mountable!"); @@ -522,7 +631,33 @@ int main(int argc, char *argv[]) total++; } - if (random_fsid || new_fsid_str) { + if (change_metadata_uuid) { + if (seeding_flag) { + fprintf(stderr, + "Not allowed to set both seeding flag and uuid metadata\n"); + ret = 1; + goto out; + } + + if (new_fsid_str) + ret = set_metadata_uuid(root, new_fsid_str); + else + ret = set_metadata_uuid(root, NULL); + + if (!ret) + success++; + total++; + } + + if (random_fsid || (new_fsid_str && !change_metadata_uuid)) { + if (btrfs_fs_incompat(root->fs_info, METADATA_UUID)) { + fprintf(stderr, + "Cannot rewrite fsid while METADATA_UUID flag is active. \n" + "Ensure fsid and metadata_uuid match before retrying.\n"); + ret = 1; + goto out; + } + if (!force) { warning( "it's recommended to run 'btrfs check --readonly' before this operation.\n" diff --git a/ctree.h b/ctree.h index 45ef02e7..be5ecf8f 100644 --- a/ctree.h +++ b/ctree.h @@ -330,6 +330,7 @@ static inline unsigned long btrfs_chunk_item_size(int num_stripes) #define BTRFS_SUPER_FLAG_METADUMP (1ULL << 33) #define BTRFS_SUPER_FLAG_METADUMP_V2 (1ULL << 34) #define BTRFS_SUPER_FLAG_CHANGING_FSID (1ULL << 35) +#define BTRFS_SUPER_FLAG_CHANGING_FSID_V2 (1ULL << 36) #define BTRFS_BACKREF_REV_MAX 256 #define BTRFS_BACKREF_REV_SHIFT 56