summaryrefslogtreecommitdiff
path: root/btrfstune.c
diff options
context:
space:
mode:
Diffstat (limited to 'btrfstune.c')
-rw-r--r--btrfstune.c251
1 files changed, 196 insertions, 55 deletions
diff --git a/btrfstune.c b/btrfstune.c
index 1e378ba1..7e6d0ac2 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;
@@ -91,15 +201,15 @@ static int set_super_incompat_flags(struct btrfs_root *root, u64 flags)
return ret;
}
-static int change_buffer_header_uuid(struct extent_buffer *eb)
+static int change_buffer_header_uuid(struct extent_buffer *eb, uuid_t new_fsid)
{
struct btrfs_fs_info *fs_info = eb->fs_info;
int same_fsid = 1;
int same_chunk_tree_uuid = 1;
int ret;
- same_fsid = !memcmp_extent_buffer(eb, fs_info->new_fsid,
- btrfs_header_fsid(), BTRFS_FSID_SIZE);
+ same_fsid = !memcmp_extent_buffer(eb, new_fsid, btrfs_header_fsid(),
+ BTRFS_FSID_SIZE);
same_chunk_tree_uuid =
!memcmp_extent_buffer(eb, fs_info->new_chunk_tree_uuid,
btrfs_header_chunk_tree_uuid(eb),
@@ -107,7 +217,7 @@ static int change_buffer_header_uuid(struct extent_buffer *eb)
if (same_fsid && same_chunk_tree_uuid)
return 0;
if (!same_fsid)
- write_extent_buffer(eb, fs_info->new_fsid, btrfs_header_fsid(),
+ write_extent_buffer(eb, new_fsid, btrfs_header_fsid(),
BTRFS_FSID_SIZE);
if (!same_chunk_tree_uuid)
write_extent_buffer(eb, fs_info->new_chunk_tree_uuid,
@@ -118,7 +228,7 @@ static int change_buffer_header_uuid(struct extent_buffer *eb)
return ret;
}
-static int change_extents_uuid(struct btrfs_fs_info *fs_info)
+static int change_extents_uuid(struct btrfs_fs_info *fs_info, uuid_t new_fsid)
{
struct btrfs_root *root = fs_info->extent_root;
struct btrfs_path path;
@@ -157,7 +267,7 @@ static int change_extents_uuid(struct btrfs_fs_info *fs_info)
ret = PTR_ERR(eb);
goto out;
}
- ret = change_buffer_header_uuid(eb);
+ ret = change_buffer_header_uuid(eb, new_fsid);
free_extent_buffer(eb);
if (ret < 0) {
error("failed to change uuid of tree block: %llu",
@@ -179,29 +289,28 @@ out:
return ret;
}
-static int change_device_uuid(struct extent_buffer *eb, int slot)
+static int change_device_uuid(struct extent_buffer *eb, int slot,
+ uuid_t new_fsid)
{
struct btrfs_dev_item *di;
struct btrfs_fs_info *fs_info = eb->fs_info;
int ret = 0;
di = btrfs_item_ptr(eb, slot, struct btrfs_dev_item);
- if (!memcmp_extent_buffer(eb, fs_info->new_fsid,
+ if (!memcmp_extent_buffer(eb, new_fsid,
(unsigned long)btrfs_device_fsid(di),
BTRFS_FSID_SIZE))
return ret;
- write_extent_buffer(eb, fs_info->new_fsid,
- (unsigned long)btrfs_device_fsid(di),
+ write_extent_buffer(eb, new_fsid, (unsigned long)btrfs_device_fsid(di),
BTRFS_FSID_SIZE);
ret = write_tree_block(NULL, fs_info, eb);
return ret;
}
-static int change_devices_uuid(struct btrfs_fs_info *fs_info)
+static int change_devices_uuid(struct btrfs_root *root, uuid_t new_fsid)
{
- struct btrfs_root *root = fs_info->chunk_root;
struct btrfs_path path;
struct btrfs_key key = {0, 0, 0};
int ret = 0;
@@ -217,7 +326,8 @@ static int change_devices_uuid(struct btrfs_fs_info *fs_info)
if (key.type != BTRFS_DEV_ITEM_KEY ||
key.objectid != BTRFS_DEV_ITEMS_OBJECTID)
goto next;
- ret = change_device_uuid(path.nodes[0], path.slots[0]);
+ ret = change_device_uuid(path.nodes[0], path.slots[0],
+ new_fsid);
if (ret < 0)
goto out;
next:
@@ -234,7 +344,7 @@ out:
return ret;
}
-static int change_fsid_prepare(struct btrfs_fs_info *fs_info)
+static int change_fsid_prepare(struct btrfs_fs_info *fs_info, uuid_t new_fsid)
{
struct btrfs_root *tree_root = fs_info->tree_root;
u64 flags = btrfs_super_flags(fs_info->super_copy);
@@ -243,11 +353,14 @@ static int change_fsid_prepare(struct btrfs_fs_info *fs_info)
flags |= BTRFS_SUPER_FLAG_CHANGING_FSID;
btrfs_set_super_flags(fs_info->super_copy, flags);
- memcpy(fs_info->super_copy->fsid, fs_info->new_fsid, BTRFS_FSID_SIZE);
+ memcpy(fs_info->super_copy->fsid, new_fsid, BTRFS_FSID_SIZE);
ret = write_all_supers(fs_info);
if (ret < 0)
return ret;
+ /* Also need to change the metadatauuid of the fs info */
+ memcpy(fs_info->fs_devices->metadata_uuid, new_fsid, BTRFS_FSID_SIZE);
+
/* also restore new chunk_tree_id into tree_root for restore */
write_extent_buffer(tree_root->node, fs_info->new_chunk_tree_uuid,
btrfs_header_chunk_tree_uuid(tree_root->node),
@@ -265,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.
@@ -320,10 +413,9 @@ static int change_uuid(struct btrfs_fs_info *fs_info, const char *new_fsid_str)
uuid_generate(new_chunk_id);
}
- fs_info->new_fsid = new_fsid;
fs_info->new_chunk_tree_uuid = new_chunk_id;
- memcpy(old_fsid, (const char*)fs_info->fsid, BTRFS_UUID_SIZE);
+ memcpy(old_fsid, (const char*)fs_info->fs_devices->fsid, BTRFS_UUID_SIZE);
uuid_unparse(old_fsid, uuid_buf);
printf("Current fsid: %s\n", uuid_buf);
@@ -331,13 +423,13 @@ static int change_uuid(struct btrfs_fs_info *fs_info, const char *new_fsid_str)
printf("New fsid: %s\n", uuid_buf);
/* Now we can begin fsid change */
printf("Set superblock flag CHANGING_FSID\n");
- ret = change_fsid_prepare(fs_info);
+ ret = change_fsid_prepare(fs_info, new_fsid);
if (ret < 0)
goto out;
/* Change extents first */
printf("Change fsid in extents\n");
- ret = change_extents_uuid(fs_info);
+ ret = change_extents_uuid(fs_info, new_fsid);
if (ret < 0) {
error("failed to change UUID of metadata: %d", ret);
goto out;
@@ -345,17 +437,15 @@ static int change_uuid(struct btrfs_fs_info *fs_info, const char *new_fsid_str)
/* Then devices */
printf("Change fsid on devices\n");
- ret = change_devices_uuid(fs_info);
+ ret = change_devices_uuid(fs_info->chunk_root, new_fsid);
if (ret < 0) {
error("failed to change UUID of devices: %d", ret);
goto out;
}
/* Last, change fsid in super */
- memcpy(fs_info->fs_devices->fsid, fs_info->new_fsid,
- BTRFS_FSID_SIZE);
- memcpy(fs_info->super_copy->fsid, fs_info->new_fsid,
- BTRFS_FSID_SIZE);
+ memcpy(fs_info->fs_devices->fsid, new_fsid, BTRFS_FSID_SIZE);
+ memcpy(fs_info->super_copy->fsid, new_fsid, BTRFS_FSID_SIZE);
ret = write_all_supers(fs_info);
if (ret < 0)
goto out;
@@ -363,7 +453,6 @@ static int change_uuid(struct btrfs_fs_info *fs_info, const char *new_fsid_str)
/* Now fsid change is done */
printf("Clear superblock flag CHANGING_FSID\n");
ret = change_fsid_done(fs_info);
- fs_info->new_fsid = NULL;
fs_info->new_chunk_tree_uuid = NULL;
printf("Fsid change finished\n");
out:
@@ -373,13 +462,22 @@ out:
static void print_usage(void)
{
printf("usage: btrfstune [options] device\n");
- printf("\t-S value\tpositive value will enable seeding, zero to disable, negative is not allowed\n");
- printf("\t-r \t\tenable extended inode refs\n");
- 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("Tune settings of filesystem features on an unmounted device\n\n");
+ printf("Options:\n");
+ printf(" change feature status:\n");
+ printf("\t-r enable extended inode refs (mkfs: extref, for hardlink limits)\n");
+ printf("\t-x enable skinny metadata extent refs (mkfs: skinny-metadata)\n");
+ printf("\t-n enable no-holes feature (mkfs: no-holes, more efficient sparse file representation)\n");
+ printf("\t-S <0|1> set/unset seeding status of a device\n");
+ printf(" uuid changes:\n");
+ printf("\t-u rewrite fsid, use a random one\n");
+ printf("\t-U UUID rewrite fsid to UUID\n");
+ printf("\t-m change fsid in metadata_uuid to a random UUID\n");
+ printf("\t (incompat change, more lightweight than -u|-U)\n");
+ printf("\t-M UUID change fsid in metadata_uuid to UUID\n");
+ printf(" general:\n");
+ printf("\t-f allow dangerous operations, make sure that you are aware of the dangers\n");
+ printf("\t--help print this help\n");
}
int main(int argc, char *argv[])
@@ -391,6 +489,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;
@@ -401,7 +500,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;
@@ -430,6 +529,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();
@@ -448,7 +556,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;
@@ -495,6 +604,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!");
@@ -519,7 +634,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"