mirror of
https://github.com/kdave/btrfs-progs
synced 2024-12-18 04:15:32 +00:00
c699820668
Adds a flag to mkfs.btrfs --subvol to allow subvolumes to be created readonly. Signed-off-by: Mark Harmstone <maharmstone@fb.com>
501 lines
14 KiB
C
501 lines
14 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 <time.h>
|
|
#include <uuid/uuid.h>
|
|
#include "common/root-tree-utils.h"
|
|
#include "common/messages.h"
|
|
#include "kernel-shared/disk-io.h"
|
|
#include "kernel-shared/uuid-tree.h"
|
|
|
|
int btrfs_make_root_dir(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *root, u64 objectid)
|
|
{
|
|
int ret;
|
|
struct btrfs_inode_item inode_item;
|
|
time_t now = time(NULL);
|
|
|
|
memset(&inode_item, 0, sizeof(inode_item));
|
|
btrfs_set_stack_inode_generation(&inode_item, trans->transid);
|
|
btrfs_set_stack_inode_size(&inode_item, 0);
|
|
btrfs_set_stack_inode_nlink(&inode_item, 1);
|
|
btrfs_set_stack_inode_nbytes(&inode_item, root->fs_info->nodesize);
|
|
btrfs_set_stack_inode_mode(&inode_item, S_IFDIR | 0755);
|
|
btrfs_set_stack_timespec_sec(&inode_item.atime, now);
|
|
btrfs_set_stack_timespec_nsec(&inode_item.atime, 0);
|
|
btrfs_set_stack_timespec_sec(&inode_item.ctime, now);
|
|
btrfs_set_stack_timespec_nsec(&inode_item.ctime, 0);
|
|
btrfs_set_stack_timespec_sec(&inode_item.mtime, now);
|
|
btrfs_set_stack_timespec_nsec(&inode_item.mtime, 0);
|
|
btrfs_set_stack_timespec_sec(&inode_item.otime, now);
|
|
btrfs_set_stack_timespec_nsec(&inode_item.otime, 0);
|
|
|
|
if (root->fs_info->tree_root == root)
|
|
btrfs_set_super_root_dir(root->fs_info->super_copy, objectid);
|
|
|
|
ret = btrfs_insert_inode(trans, root, objectid, &inode_item);
|
|
if (ret)
|
|
goto error;
|
|
|
|
ret = btrfs_insert_inode_ref(trans, root, "..", 2, objectid, objectid, 0);
|
|
if (ret)
|
|
goto error;
|
|
|
|
btrfs_set_root_dirid(&root->root_item, objectid);
|
|
ret = 0;
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Create a subvolume and initialize its content with the top inode.
|
|
*
|
|
* The created tree root would have its root_ref as 1.
|
|
* Thus for subvolumes caller needs to properly add ROOT_BACKREF items.
|
|
*/
|
|
int btrfs_make_subvolume(struct btrfs_trans_handle *trans, u64 objectid,
|
|
bool readonly)
|
|
{
|
|
struct btrfs_fs_info *fs_info = trans->fs_info;
|
|
struct btrfs_root *root;
|
|
struct btrfs_key key = {
|
|
.objectid = objectid,
|
|
.type = BTRFS_ROOT_ITEM_KEY,
|
|
};
|
|
int ret;
|
|
|
|
/* FSTREE is different and can not be created by this function. */
|
|
UASSERT(objectid != BTRFS_FS_TREE_OBJECTID);
|
|
UASSERT(is_fstree(objectid) || objectid == BTRFS_DATA_RELOC_TREE_OBJECTID);
|
|
|
|
root = btrfs_create_tree(trans, &key);
|
|
if (IS_ERR(root)) {
|
|
ret = PTR_ERR(root);
|
|
goto error;
|
|
}
|
|
/*
|
|
* Free it for now, and re-read it from disk to setup cache and
|
|
* tracking.
|
|
*/
|
|
btrfs_free_fs_root(root);
|
|
root = btrfs_read_fs_root(fs_info, &key);
|
|
if (IS_ERR(root)) {
|
|
ret = PTR_ERR(root);
|
|
goto error;
|
|
}
|
|
ret = btrfs_make_root_dir(trans, root, BTRFS_FIRST_FREE_OBJECTID);
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
btrfs_set_stack_inode_flags(&root->root_item.inode,
|
|
BTRFS_INODE_ROOT_ITEM_INIT);
|
|
|
|
if (readonly)
|
|
btrfs_set_root_flags(&root->root_item, BTRFS_ROOT_SUBVOL_RDONLY);
|
|
|
|
ret = btrfs_update_root(trans, fs_info->tree_root, &root->root_key,
|
|
&root->root_item);
|
|
if (ret < 0)
|
|
goto error;
|
|
return 0;
|
|
error:
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Link subvoume @subvol as @name under directory inode @parent_dir of
|
|
* subvolume @parent_root.
|
|
*/
|
|
int btrfs_link_subvolume(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *parent_root,
|
|
u64 parent_dir, const char *name,
|
|
int namelen, struct btrfs_root *subvol)
|
|
{
|
|
struct btrfs_root *tree_root = trans->fs_info->tree_root;
|
|
struct btrfs_path path = { 0 };
|
|
struct btrfs_key key;
|
|
struct btrfs_inode_item *ii;
|
|
u64 index;
|
|
u64 isize;
|
|
u32 imode;
|
|
int ret;
|
|
|
|
UASSERT(namelen && namelen <= BTRFS_NAME_LEN);
|
|
|
|
/* Make sure @parent_dir is a directory. */
|
|
key.objectid = parent_dir;
|
|
key.type = BTRFS_INODE_ITEM_KEY;
|
|
key.offset = 0;
|
|
ret = btrfs_search_slot(NULL, parent_root, &key, &path, 0, 0);
|
|
if (ret > 0)
|
|
ret = -ENOENT;
|
|
if (ret < 0) {
|
|
btrfs_release_path(&path);
|
|
return ret;
|
|
}
|
|
ii = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_inode_item);
|
|
imode = btrfs_inode_mode(path.nodes[0], ii);
|
|
btrfs_release_path(&path);
|
|
|
|
if (!S_ISDIR(imode)) {
|
|
ret = -EUCLEAN;
|
|
error("%s: inode %llu of subvolume %llu is not a directory",
|
|
__func__, parent_dir, parent_root->root_key.objectid);
|
|
return ret;
|
|
}
|
|
|
|
ret = btrfs_find_free_dir_index(parent_root, parent_dir, &index);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Filename conflicts check. */
|
|
ret = btrfs_check_dir_conflict(parent_root, name, namelen, parent_dir,
|
|
index);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/*
|
|
* Now everything is fine, add the link.
|
|
* From now on, every error would lead to transaction abort.
|
|
*
|
|
* Add the dir_item/index first.
|
|
*/
|
|
ret = btrfs_insert_dir_item(trans, parent_root, name, namelen,
|
|
parent_dir, &subvol->root_key,
|
|
BTRFS_FT_DIR, index);
|
|
if (ret < 0)
|
|
goto abort;
|
|
|
|
/* Update inode size of the parent inode */
|
|
key.objectid = parent_dir;
|
|
key.type = BTRFS_INODE_ITEM_KEY;
|
|
key.offset = 0;
|
|
ret = btrfs_search_slot(trans, parent_root, &key, &path, 1, 1);
|
|
if (ret > 0)
|
|
ret = -ENOENT;
|
|
if (ret < 0) {
|
|
btrfs_release_path(&path);
|
|
goto abort;
|
|
}
|
|
ii = btrfs_item_ptr(path.nodes[0], path.slots[0],
|
|
struct btrfs_inode_item);
|
|
isize = btrfs_inode_size(path.nodes[0], ii);
|
|
isize += namelen * 2;
|
|
btrfs_set_inode_size(path.nodes[0], ii, isize);
|
|
btrfs_mark_buffer_dirty(path.nodes[0]);
|
|
btrfs_release_path(&path);
|
|
|
|
/* Add the root backref. */
|
|
ret = btrfs_add_root_ref(trans, tree_root, subvol->root_key.objectid,
|
|
BTRFS_ROOT_BACKREF_KEY,
|
|
parent_root->root_key.objectid, parent_dir,
|
|
index, name, namelen);
|
|
if (ret < 0)
|
|
goto abort;
|
|
|
|
/* Then forward ref*/
|
|
ret = btrfs_add_root_ref(trans, tree_root,
|
|
parent_root->root_key.objectid,
|
|
BTRFS_ROOT_REF_KEY, subvol->root_key.objectid,
|
|
parent_dir, index, name, namelen);
|
|
if (ret < 0)
|
|
goto abort;
|
|
/* For now, all root should have its refs == 1 already.
|
|
* So no need to update the root refs. */
|
|
UASSERT(btrfs_root_refs(&subvol->root_item) == 1);
|
|
return 0;
|
|
abort:
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int remove_all_tree_items(struct btrfs_root *root)
|
|
{
|
|
struct btrfs_trans_handle *trans;
|
|
struct btrfs_path path = { 0 };
|
|
struct btrfs_key key = { 0 };
|
|
int ret;
|
|
|
|
trans = btrfs_start_transaction(root, 1);
|
|
if (IS_ERR(trans)) {
|
|
ret = PTR_ERR(trans);
|
|
errno = -ret;
|
|
error_msg(ERROR_MSG_START_TRANS,
|
|
"remove all items for tree %lld: %m",
|
|
root->root_key.objectid);
|
|
return ret;
|
|
}
|
|
while (true) {
|
|
int nr_items;
|
|
|
|
ret = btrfs_search_slot(trans, root, &key, &path, -1, 1);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error("failed to locate the first key of root %lld: %m",
|
|
root->root_key.objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
if (ret == 0) {
|
|
ret = -EUCLEAN;
|
|
errno = -ret;
|
|
error("unexpected all zero key found in root %lld",
|
|
root->root_key.objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
nr_items = btrfs_header_nritems(path.nodes[0]);
|
|
/* The tree is empty. */
|
|
if (nr_items == 0) {
|
|
btrfs_release_path(&path);
|
|
break;
|
|
}
|
|
ret = btrfs_del_items(trans, root, &path, 0, nr_items);
|
|
btrfs_release_path(&path);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error("failed to empty the first leaf of root %lld: %m",
|
|
root->root_key.objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
ret = btrfs_commit_transaction(trans, root);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error_msg(ERROR_MSG_COMMIT_TRANS,
|
|
"removal all items for tree %lld: %m",
|
|
root->root_key.objectid);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int rescan_subvol_uuid(struct btrfs_trans_handle *trans,
|
|
struct btrfs_key *subvol_key)
|
|
{
|
|
struct btrfs_fs_info *fs_info = trans->fs_info;
|
|
struct btrfs_root *subvol;
|
|
int ret;
|
|
|
|
UASSERT(is_fstree(subvol_key->objectid));
|
|
|
|
/*
|
|
* Read out the subvolume root and updates root::root_item.
|
|
* This is to avoid de-sync between in-memory and on-disk root_items.
|
|
*/
|
|
subvol = btrfs_read_fs_root(fs_info, subvol_key);
|
|
if (IS_ERR(subvol)) {
|
|
ret = PTR_ERR(subvol);
|
|
error("failed to read subvolume %llu: %m",
|
|
subvol_key->objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
/* The uuid is not set, regenerate one. */
|
|
if (uuid_is_null(subvol->root_item.uuid)) {
|
|
uuid_generate(subvol->root_item.uuid);
|
|
ret = btrfs_update_root(trans, fs_info->tree_root, &subvol->root_key,
|
|
&subvol->root_item);
|
|
if (ret < 0) {
|
|
error("failed to update subvolume %llu: %m",
|
|
subvol_key->objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
ret = btrfs_uuid_tree_add(trans, subvol->root_item.uuid,
|
|
BTRFS_UUID_KEY_SUBVOL,
|
|
subvol->root_key.objectid);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error("failed to add uuid for subvolume %llu: %m",
|
|
subvol_key->objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
if (!uuid_is_null(subvol->root_item.received_uuid)) {
|
|
ret = btrfs_uuid_tree_add(trans, subvol->root_item.uuid,
|
|
BTRFS_UUID_KEY_RECEIVED_SUBVOL,
|
|
subvol->root_key.objectid);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error("failed to add received_uuid for subvol %llu: %m",
|
|
subvol->root_key.objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int rescan_uuid_tree(struct btrfs_fs_info *fs_info)
|
|
{
|
|
struct btrfs_root *tree_root = fs_info->tree_root;
|
|
struct btrfs_root *uuid_root = fs_info->uuid_root;
|
|
struct btrfs_trans_handle *trans;
|
|
struct btrfs_path path = { 0 };
|
|
struct btrfs_key key = { 0 };
|
|
int ret;
|
|
|
|
UASSERT(uuid_root);
|
|
trans = btrfs_start_transaction(uuid_root, 1);
|
|
if (IS_ERR(trans)) {
|
|
ret = PTR_ERR(trans);
|
|
errno = -ret;
|
|
error_msg(ERROR_MSG_START_TRANS, "rescan uuid tree: %m");
|
|
return ret;
|
|
}
|
|
key.objectid = BTRFS_LAST_FREE_OBJECTID;
|
|
key.type = BTRFS_ROOT_ITEM_KEY;
|
|
key.offset = (u64)-1;
|
|
/* Iterate through all subvolumes except fs tree. */
|
|
while (true) {
|
|
struct btrfs_key found_key;
|
|
struct extent_buffer *leaf;
|
|
int slot;
|
|
|
|
/* No more subvolume. */
|
|
if (key.objectid < BTRFS_FIRST_FREE_OBJECTID) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error_msg(ERROR_MSG_READ, "iterate subvolumes: %m");
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
if (ret > 0) {
|
|
ret = btrfs_previous_item(tree_root, &path,
|
|
BTRFS_FIRST_FREE_OBJECTID,
|
|
BTRFS_ROOT_ITEM_KEY);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
btrfs_release_path(&path);
|
|
error_msg(ERROR_MSG_READ, "iterate subvolumes: %m");
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
/* No more subvolume. */
|
|
if (ret > 0) {
|
|
ret = 0;
|
|
btrfs_release_path(&path);
|
|
break;
|
|
}
|
|
}
|
|
leaf = path.nodes[0];
|
|
slot = path.slots[0];
|
|
btrfs_item_key_to_cpu(leaf, &found_key, slot);
|
|
btrfs_release_path(&path);
|
|
key.objectid = found_key.objectid - 1;
|
|
|
|
ret = rescan_subvol_uuid(trans, &found_key);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error("failed to rescan the uuid of subvolume %llu: %m",
|
|
found_key.objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Update fs tree uuid. */
|
|
key.objectid = BTRFS_FS_TREE_OBJECTID;
|
|
key.type = BTRFS_ROOT_ITEM_KEY;
|
|
key.offset = 0;
|
|
ret = rescan_subvol_uuid(trans, &key);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error("failed to rescan the uuid of subvolume %llu: %m",
|
|
key.objectid);
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
ret = btrfs_commit_transaction(trans, uuid_root);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error_msg(ERROR_MSG_COMMIT_TRANS, "rescan uuid tree: %m");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Rebuild the whole uuid tree.
|
|
*
|
|
* If no uuid tree is present, create a new one.
|
|
* If there is an existing uuid tree, all items will be deleted first.
|
|
*
|
|
* For all existing subvolumes (except fs tree), any uninitialized uuid
|
|
* (all zero) will be generated using a random uuid, and inserted into the new
|
|
* tree.
|
|
* And if a subvolume has its UUID initialized, it will not be touched and
|
|
* added to the new uuid tree.
|
|
*/
|
|
int btrfs_rebuild_uuid_tree(struct btrfs_fs_info *fs_info)
|
|
{
|
|
struct btrfs_root *uuid_root;
|
|
struct btrfs_key key;
|
|
int ret;
|
|
|
|
if (!fs_info->uuid_root) {
|
|
struct btrfs_trans_handle *trans;
|
|
|
|
trans = btrfs_start_transaction(fs_info->tree_root, 1);
|
|
if (IS_ERR(trans)) {
|
|
ret = PTR_ERR(trans);
|
|
errno = -ret;
|
|
error_msg(ERROR_MSG_START_TRANS, "create uuid tree: %m");
|
|
return ret;
|
|
}
|
|
key.objectid = BTRFS_UUID_TREE_OBJECTID;
|
|
key.type = BTRFS_ROOT_ITEM_KEY;
|
|
key.offset = 0;
|
|
uuid_root = btrfs_create_tree(trans, &key);
|
|
if (IS_ERR(uuid_root)) {
|
|
ret = PTR_ERR(uuid_root);
|
|
errno = -ret;
|
|
error("failed to create uuid root: %m");
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
add_root_to_dirty_list(uuid_root);
|
|
fs_info->uuid_root = uuid_root;
|
|
ret = btrfs_commit_transaction(trans, fs_info->tree_root);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error_msg(ERROR_MSG_COMMIT_TRANS, "create uuid tree: %m");
|
|
return ret;
|
|
}
|
|
} else {
|
|
ret = remove_all_tree_items(fs_info->uuid_root);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error("failed to clear the existing uuid tree: %m");
|
|
return ret;
|
|
}
|
|
}
|
|
UASSERT(fs_info->uuid_root);
|
|
ret = rescan_uuid_tree(fs_info);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
error("failed to rescan the uuid tree: %m");
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|