diff mbox

btrfs-progs: New utility to swap subvolumes

Message ID 4B32CF91.8080908@jp.fujitsu.com (mailing list archive)
State New, archived
Headers show

Commit Message

TARUISI Hiroaki Dec. 24, 2009, 2:18 a.m. UTC
None
diff mbox

Patch

Index: b/Makefile
===================================================================
--- a/Makefile	2009-12-24 10:45:24.000000000 +0900
+++ b/Makefile	2009-12-24 10:45:43.000000000 +0900
@@ -17,7 +17,7 @@  bindir = $(prefix)/bin
 LIBS=-luuid

 progs = btrfsctl mkfs.btrfs btrfs-debug-tree btrfs-show btrfs-vol btrfsck \
-	btrfs-map-logical
+	btrfs-map-logical btrfsrevert

 # make C=1 to enable sparse
 ifdef C
@@ -63,6 +63,9 @@  btrfs-map-logical: $(objects) btrfs-map-
 btrfs-image: $(objects) btrfs-image.o
 	gcc $(CFLAGS) -o btrfs-image $(objects) btrfs-image.o -lpthread -lz $(LDFLAGS) $(LIBS)

+btrfsrevert: $(objects) btrfsrevert.o
+	gcc $(CFLAGS) -o btrfsrevert $(objects) btrfsrevert.o $(LDFLAGS) $(LIBS)
+
 dir-test: $(objects) dir-test.o
 	gcc $(CFLAGS) -o dir-test $(objects) dir-test.o $(LDFLAGS) $(LIBS)

Index: b/btrfsrevert.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ b/btrfsrevert.c	2009-12-24 10:45:43.000000000 +0900
@@ -0,0 +1,778 @@ 
+/*
+ * Copyright (C) 2008 Oracle.  All rights reserved.
+ *
+ * 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.
+ */
+
+#define _XOPEN_SOURCE 500
+#define _GNU_SOURCE 1
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <uuid/uuid.h>
+#include "kerncompat.h"
+#include "ctree.h"
+#include "disk-io.h"
+#include "transaction.h"
+#include "utils.h"
+#include "version.h"
+#include "ioctl.h"
+#include "hash.h"
+#include <mcheck.h>
+
+#define BTRFS_REVERTED_TREE_LOCATION ".old_trees"
+
+struct tree_info {
+	u64 id, parent_treeid;
+	u64 parent_dirid, index;
+	int name_len;
+	char *name;
+};
+
+struct revert_packet {
+	struct tree_info source, target, backup;
+};
+
+static void print_usage(void)
+{
+	fprintf(stderr, "usage: btrfsrevert [options] device\n");
+	fprintf(stderr, "\t-s source tree\n");
+	fprintf(stderr, "\t-t target tree\n");
+}
+
+static int find_file_tree(struct btrfs_root *root, struct tree_info *tinfo)
+{
+	struct btrfs_path *path;
+	struct btrfs_key key;
+	struct btrfs_root_ref *ref;
+	int ret;
+
+	if (tinfo->id == BTRFS_FS_TREE_OBJECTID) {
+		tinfo->name = malloc(9);
+		strcpy(tinfo->name, "default");
+		tinfo->name_len = 8;
+		return 0;
+	}
+	if (tinfo->id < BTRFS_FIRST_FREE_OBJECTID ||
+		BTRFS_LAST_FREE_OBJECTID < tinfo->id)
+			return 2;
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+
+	key.objectid = tinfo->id;
+	key.type = BTRFS_ROOT_BACKREF_KEY;
+	key.offset = (u64)-1;
+
+	ret = btrfs_search_slot(NULL, root->fs_info->tree_root, &key,
+		path, 0, 0);
+	if (ret < 0)
+		goto out;
+
+	BUG_ON(ret == 0);
+	if (path->slots[0] == 0) {
+		ret = btrfs_prev_leaf(root->fs_info->tree_root, path);
+
+		if (ret)
+			goto out;
+	} else
+		path->slots[0]--;
+
+	btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
+	if (key.objectid == tinfo->id && key.type == BTRFS_ROOT_BACKREF_KEY) {
+		ret = 0;
+		tinfo->parent_treeid = key.offset;
+		ref = btrfs_item_ptr(path->nodes[0], path->slots[0],
+			struct btrfs_root_ref);
+		tinfo->parent_dirid = btrfs_root_ref_dirid(path->nodes[0], ref);
+		tinfo->index = btrfs_root_ref_sequence(path->nodes[0], ref);
+		tinfo->name_len = btrfs_root_ref_name_len(path->nodes[0], ref);
+		tinfo->name = malloc(tinfo->name_len + 1);
+		if (tinfo->name) {
+			tinfo->name[tinfo->name_len] = '\0';
+			read_extent_buffer(path->nodes[0], tinfo->name,
+				(long)(ref + 1), tinfo->name_len);
+		} else
+			ret = -ENOMEM;
+	} else
+		ret = 1;
+
+out:
+	btrfs_free_path(path);
+	return ret;
+}
+
+static int btrfs_revert_root(struct btrfs_fs_info *fs_info,
+		struct revert_packet *rvp)
+{
+	printf("Root swap is not implemented yet.\n");
+	return 0;
+}
+
+static int btrfs_update_root_ref(struct btrfs_trans_handle *trans,
+		struct btrfs_root *root, struct btrfs_key *ref_key,
+		struct tree_info *f_tinfo, struct tree_info *t_tinfo,
+		char *new_name)
+{
+	struct btrfs_root *tree_root = root->fs_info->tree_root;
+	struct btrfs_root_ref *oldref;
+	struct btrfs_path *path;
+	struct btrfs_key key;
+	int ret, name_len, new_name_len = strlen(new_name);
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+	if (f_tinfo->parent_treeid != t_tinfo->parent_treeid) {
+		if (ref_key->type == BTRFS_ROOT_REF_KEY) {
+			ret = btrfs_add_root_ref(trans, root->fs_info->tree_root,
+				t_tinfo->parent_treeid, BTRFS_ROOT_REF_KEY, f_tinfo->id,
+				t_tinfo->parent_dirid, t_tinfo->index, new_name,
+				strlen(new_name));
+			if (ret)
+				goto error;
+			ref_key->objectid = t_tinfo->parent_treeid;
+			ref_key->offset = f_tinfo->id;
+
+			key.objectid = f_tinfo->parent_treeid;
+			key.type = BTRFS_ROOT_REF_KEY;
+			key.offset = f_tinfo->id;
+			ret = btrfs_search_slot(trans, tree_root, &key, path,
+				-(sizeof(struct btrfs_root_ref) + f_tinfo->name_len), 1);
+			if (ret)
+				goto error;
+			ret = btrfs_del_item(trans, tree_root, path);
+			if (ret)
+				goto error;
+		} else if (ref_key->type == BTRFS_ROOT_BACKREF_KEY) {
+			ret = btrfs_add_root_ref(trans, root->fs_info->tree_root,
+				f_tinfo->id, BTRFS_ROOT_BACKREF_KEY, t_tinfo->parent_treeid,
+				t_tinfo->parent_dirid, t_tinfo->index, new_name,
+				strlen(new_name));
+			if (ret)
+				goto error;
+			ref_key->objectid = f_tinfo->id;
+			ref_key->offset = t_tinfo->parent_treeid;
+
+			key.objectid = f_tinfo->id;
+			key.type = BTRFS_ROOT_BACKREF_KEY;
+			key.offset = f_tinfo->parent_treeid;
+			ret = btrfs_search_slot(trans, tree_root, &key, path,
+				-(sizeof(struct btrfs_root_ref) + f_tinfo->name_len), 1);
+			if (ret)
+				goto error;
+			ret = btrfs_del_item(trans, tree_root, path);
+			if (ret)
+				goto error;
+		}
+		btrfs_release_path(tree_root, path);
+	}
+
+	ret = btrfs_search_slot(trans, tree_root, ref_key, path,
+		strlen(new_name) - f_tinfo->name_len, 1);
+	if (ret)
+		goto error;
+
+	oldref = btrfs_item_ptr(path->nodes[0], path->slots[0],
+		struct btrfs_root_ref);
+	name_len = btrfs_root_ref_name_len(path->nodes[0], oldref);
+
+	if (name_len < new_name_len) {
+		ret = btrfs_extend_item(trans, tree_root, path,
+			new_name_len - name_len);
+		if (ret)
+			goto error;
+	} else {
+		ret = btrfs_truncate_item(trans, tree_root, path,
+			sizeof(*oldref) + new_name_len, 1);
+		if (ret)
+			goto error;
+	}
+	oldref = btrfs_item_ptr(path->nodes[0], path->slots[0],
+		struct btrfs_root_ref);
+	write_extent_buffer(path->nodes[0], new_name,
+		(unsigned long)(oldref + 1), new_name_len);
+	btrfs_set_root_ref_name_len(path->nodes[0], oldref, new_name_len);
+	btrfs_set_root_ref_dirid(path->nodes[0], oldref, t_tinfo->parent_dirid);
+	btrfs_set_root_ref_sequence(path->nodes[0], oldref, t_tinfo->index);
+
+	btrfs_free_path(path);
+	return 0;
+
+error:
+	btrfs_free_path(path);
+	return 1;
+}
+
+static int btrfs_find_highest_index(struct btrfs_root *fs_root,
+		u64 i_no, u64 *index)
+{
+	struct btrfs_path *path;
+	struct btrfs_key key;
+	int ret;
+
+	key.objectid = i_no;
+	key.type = BTRFS_DIR_INDEX_KEY;
+	key.offset = (u64)-1;
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+	ret = btrfs_search_slot(NULL, fs_root, &key, path, 0, 0);
+	if (ret <0)
+		goto out;
+	BUG_ON(ret == 0);
+	if (path->slots[0] == 0) {
+		*index = 1ULL;
+	} else {
+		path->slots[0]--;
+		btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
+		if (key.objectid == i_no && key.type == BTRFS_DIR_INDEX_KEY)
+			*index = key.offset;
+		else {
+			*index = 1ULL;
+		}
+	}
+	ret = 0;
+out:
+	btrfs_free_path(path);
+	return ret;
+}
+
+static int btrfs_update_parent_dir_size(struct btrfs_trans_handle *trans,
+		struct btrfs_root *fs_root, struct btrfs_path *path, u64 parent_id,
+		int name_len)
+{
+	struct btrfs_key parent_key = {parent_id, BTRFS_INODE_ITEM_KEY, 0ULL};;
+	struct btrfs_inode_item *parent_inode;
+	u64 dir_size;
+	int ret;
+
+	ret = btrfs_search_slot(trans, fs_root, &parent_key, path, 0, 1);
+	BUG_ON(ret);
+
+	parent_inode = btrfs_item_ptr(path->nodes[0], path->slots[0],
+			struct btrfs_inode_item);
+
+	dir_size = btrfs_inode_size(path->nodes[0], parent_inode);
+	btrfs_set_inode_size(path->nodes[0], parent_inode, dir_size+name_len*2);
+	btrfs_mark_buffer_dirty(path->nodes[0]);
+
+	return 0;
+}
+
+static int btrfs_create_dir(struct btrfs_root *root, u64 dirid,
+		char *dir_name, struct revert_packet *rvp)
+{
+	struct btrfs_inode_item inode_item;
+	struct btrfs_dir_item *dir_item;
+	struct btrfs_path *path;
+	struct btrfs_trans_handle *trans;
+	struct btrfs_key location, found_key;
+	int ret;
+	u64 objectid, index = 0;
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+
+	dir_item = btrfs_lookup_dir_item(NULL, root, path, dirid,
+		dir_name, strlen(dir_name), 0);
+
+	if (dir_item) {
+		btrfs_dir_item_key_to_cpu(path->nodes[0], dir_item, &found_key);
+		if (rvp) {
+			rvp->backup.id = root->root_key.objectid;
+			rvp->backup.parent_dirid = found_key.objectid;
+			rvp->backup.parent_treeid = root->root_key.objectid;
+		}
+		btrfs_free_path(path);
+		return 0;
+	}
+
+	btrfs_release_path(root, path);
+
+	ret = btrfs_find_highest_inode(root, &objectid);
+	if (ret)
+		return -1;
+
+	ret = btrfs_find_highest_index(root, BTRFS_FIRST_FREE_OBJECTID, &index);
+	if (ret)
+		return -1;
+
+	trans = btrfs_start_transaction(root, 1);
+	if (!trans)
+		return -ENOMEM;
+
+	objectid++;
+	index++;
+	if (rvp) {
+		rvp->backup.id = root->root_key.objectid;
+		rvp->backup.parent_dirid = objectid;
+		rvp->backup.parent_treeid = root->root_key.objectid;
+	}
+
+	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->leafsize);
+	btrfs_set_stack_inode_mode(&inode_item, S_IFDIR | 0555);
+	ret = btrfs_insert_inode(trans, root, objectid, &inode_item);
+	if (ret)
+		goto out;
+	ret = btrfs_insert_inode_ref(trans, root, dir_name, strlen(dir_name),
+			objectid, BTRFS_FIRST_FREE_OBJECTID, index);
+	if (ret)
+		goto out;
+
+	location.objectid = objectid;
+	location.offset = 0ULL;
+	location.type = BTRFS_INODE_ITEM_KEY;
+
+	ret = btrfs_insert_dir_item(trans, root, dir_name, strlen(dir_name),
+			BTRFS_FIRST_FREE_OBJECTID, &location, BTRFS_FT_DIR, index);
+	if (ret)
+		goto out;
+	ret = btrfs_update_parent_dir_size(trans, root, path,
+		BTRFS_FIRST_FREE_OBJECTID, strlen(dir_name));
+
+	btrfs_commit_transaction(trans, root);
+out:
+	btrfs_free_path(path);
+	return 0;
+}
+
+static char *generate_uuid_string(char *name)
+{
+	uuid_t uuid;
+	int len = strlen(name);
+	char *ret_name;
+
+	if (len + 37 > BTRFS_VOL_NAME_MAX)
+		len = BTRFS_VOL_NAME_MAX - 37;
+	ret_name = malloc(38+len);
+	if (!ret_name)
+		return NULL;
+	ret_name[37+len]='\0';
+	strncpy(ret_name, name, len);
+	ret_name[len] = '_';
+	uuid_generate_time(uuid);
+	uuid_unparse(uuid, ret_name + len + 1);
+
+	return ret_name;
+}
+
+static int btrfs_create_backup_link(struct btrfs_trans_handle *trans,
+		struct btrfs_root *backup_root, char *name, struct revert_packet *rvp)
+{
+	struct btrfs_dir_item *dir_item;
+	struct btrfs_path *path;
+	struct btrfs_key location, ref_key;
+	struct btrfs_root *tree_root = backup_root->fs_info->tree_root;
+	int ret;
+
+	if (!name)
+		return -1;
+	path = btrfs_alloc_path();
+	if (!path) {
+		return -ENOMEM;
+	}
+
+	dir_item = btrfs_lookup_dir_item(NULL, backup_root, path,
+		rvp->backup.parent_dirid, name, strlen(name), 0);
+	if (dir_item)
+		goto error;
+
+	btrfs_release_path(backup_root, path);
+
+	ret = btrfs_find_highest_index(backup_root, rvp->backup.parent_dirid,
+		&rvp->backup.index);
+	if (ret)
+		goto error;
+
+	rvp->backup.index++;
+
+	location.objectid = rvp->target.id;
+	location.type = BTRFS_ROOT_ITEM_KEY;
+	location.offset = (u64)-1;
+
+	ret = btrfs_insert_dir_item(trans, backup_root, name, strlen(name),
+			rvp->backup.parent_dirid, &location, BTRFS_FT_DIR,
+			rvp->backup.index);
+	if (ret)
+		goto error;
+
+	ret = btrfs_update_parent_dir_size(trans, backup_root, path,
+		rvp->backup.parent_dirid, strlen(name));
+	if (ret)
+		goto error;
+
+	btrfs_free_path(path);
+
+	ref_key.objectid = rvp->target.id;
+	ref_key.type = BTRFS_ROOT_BACKREF_KEY;
+	ref_key.offset = rvp->target.parent_treeid;
+	ret = btrfs_update_root_ref(trans, tree_root, &ref_key, &rvp->target,
+		&rvp->backup, name);
+	if (ret)
+		return -1;
+
+	ref_key.objectid = rvp->target.parent_treeid;
+	ref_key.type = BTRFS_ROOT_REF_KEY;
+	ref_key.offset = rvp->target.id;
+	ret = btrfs_update_root_ref(trans, tree_root, &ref_key, &rvp->target,
+		&rvp->backup, name);
+	if (ret)
+		return -1;
+
+	return 0;
+
+error:
+	btrfs_free_path(path);
+	return -1;
+}
+
+static u64 btrfs_find_current_root(struct btrfs_root *root)
+{
+	struct btrfs_path *path;
+	struct btrfs_key key;
+	struct btrfs_dir_item *dir_item;
+	struct btrfs_root *tree_root = root->fs_info->tree_root;
+	int ret;
+	char *default_name = "default";
+
+	key.objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+	key.type = BTRFS_DIR_ITEM_KEY;
+	key.offset = btrfs_name_hash(default_name, strlen(default_name));
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return 0ULL;
+	ret = btrfs_search_slot(NULL, tree_root, &key, path, 0, 0);
+	BUG_ON(ret);
+
+	dir_item = btrfs_item_ptr(path->nodes[0], path->slots[0],
+		struct btrfs_dir_item);
+	BUG_ON(IS_ERR(dir_item));
+
+	btrfs_dir_item_key_to_cpu(path->nodes[0], dir_item, &key);
+
+	btrfs_free_path(path);
+	return key.objectid;
+}
+
+static struct btrfs_root *btrfs_read_fs_root_by_objectid(
+		struct btrfs_fs_info *fs_info, u64 tree_id)
+{
+	struct btrfs_key key;
+
+	key.objectid = tree_id;
+	key.type = BTRFS_ROOT_ITEM_KEY;
+	key.offset = (u64)-1;
+
+	return btrfs_read_fs_root(fs_info, &key);
+}
+
+static void btrfs_update_dir_location(struct btrfs_trans_handle *trans,
+		struct btrfs_path *path, u64 tree_id)
+{
+	struct btrfs_dir_item *di_ref;
+	struct btrfs_key location;
+	struct btrfs_disk_key disk_location;
+
+	di_ref = btrfs_item_ptr(path->nodes[0], path->slots[0],
+		struct btrfs_dir_item);
+	BUG_ON(!di_ref);
+	location.objectid = tree_id;
+	location.type = BTRFS_ROOT_ITEM_KEY;
+	location.offset = (u64)-1;
+
+	btrfs_cpu_key_to_disk(&disk_location, &location);
+	btrfs_set_dir_item_key(path->nodes[0], di_ref, &disk_location);
+
+	btrfs_mark_buffer_dirty(path->nodes[0]);
+}
+
+static int btrfs_switch_dir_item(struct btrfs_trans_handle *trans,
+		struct btrfs_root *root, struct revert_packet *rvp)
+{
+	struct btrfs_root *tree_root, *file_root;
+	struct btrfs_key key;
+	struct btrfs_path *path;
+	int ret;
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+	file_root = btrfs_read_fs_root_by_objectid(root->fs_info,
+		rvp->target.parent_treeid);
+	if (IS_ERR(file_root))
+		return -EINVAL;
+	tree_root = btrfs_read_fs_root_by_objectid(root->fs_info,
+		BTRFS_ROOT_TREE_OBJECTID);
+	BUG_ON(IS_ERR(tree_root));
+
+	key.objectid = rvp->target.parent_dirid;
+	key.type = BTRFS_DIR_ITEM_KEY;
+	key.offset = btrfs_name_hash(rvp->target.name, rvp->target.name_len);
+
+	ret = btrfs_search_slot(trans, file_root, &key, path, 0, 1);
+	BUG_ON(ret);
+
+	btrfs_update_dir_location(trans, path, rvp->source.id);
+	btrfs_release_path(file_root, path);
+
+	key.objectid = rvp->target.parent_dirid;
+	key.type = BTRFS_DIR_INDEX_KEY;
+	key.offset = rvp->target.index;
+	ret = btrfs_search_slot(trans, file_root, &key, path, 0, 1);
+	BUG_ON(ret);
+
+	btrfs_update_dir_location(trans, path, rvp->source.id);
+	btrfs_free_path(path);
+
+	key.objectid = rvp->source.parent_treeid;
+	key.type = BTRFS_ROOT_REF_KEY;
+	key.offset = rvp->source.id;
+	ret = btrfs_update_root_ref(trans, tree_root, &key, &rvp->source,
+		&rvp->target, rvp->target.name);
+	if (ret)
+		return ret;
+
+	key.objectid = rvp->source.id;
+	key.type = BTRFS_ROOT_BACKREF_KEY;
+	key.offset = rvp->source.parent_treeid;
+	ret = btrfs_update_root_ref(trans, tree_root, &key, &rvp->source,
+		&rvp->target, rvp->target.name);
+
+	return ret;
+}
+
+static int btrfs_del_dir_item(struct btrfs_trans_handle *trans,
+		struct btrfs_root *root, struct tree_info *tinfo)
+{
+	struct btrfs_path *path;
+	struct btrfs_key key;
+	int ret, del_size;
+
+	path = btrfs_alloc_path();
+	key.objectid = tinfo->parent_dirid;
+	key.type = BTRFS_DIR_ITEM_KEY;
+	key.offset = btrfs_name_hash(tinfo->name, tinfo->name_len);
+	del_size = sizeof(struct btrfs_dir_item) + tinfo->name_len;
+
+	ret = btrfs_search_slot(trans, root, &key, path, -del_size, 1);
+	BUG_ON(ret);
+	ret = btrfs_del_item(trans, root, path);
+	if (ret)
+		goto out;
+	btrfs_release_path(root, path);
+
+	key.objectid = tinfo->parent_dirid;
+	key.type = BTRFS_DIR_INDEX_KEY;
+	key.offset = tinfo->index;
+
+	ret = btrfs_search_slot(trans, root, &key, path, -del_size, 1);
+	BUG_ON(ret);
+	ret = btrfs_del_item(trans, root, path);
+	if (ret)
+		goto out;
+	btrfs_release_path(root, path);
+
+	ret = btrfs_update_parent_dir_size(trans, root, path,
+		tinfo->parent_dirid, -tinfo->name_len);
+out:
+	btrfs_free_path(path);
+	return ret;
+}
+
+static int btrfs_revert_directory(struct btrfs_fs_info *fs_info,
+		struct revert_packet *rvp)
+{
+	struct btrfs_trans_handle *trans;
+	struct btrfs_root *backup_root, *target_root, *source_root;
+	int ret;
+	char *store_name;
+
+	store_name = generate_uuid_string(rvp->target.name);
+	printf("\tOld target subvolume is moved to default filesystem root\n");
+	printf("\t  (path = /%s/%s)\n",
+		BTRFS_REVERTED_TREE_LOCATION, store_name);
+
+	ret = btrfs_create_dir(fs_info->fs_root, BTRFS_FIRST_FREE_OBJECTID,
+		BTRFS_REVERTED_TREE_LOCATION, rvp);
+	if(ret)
+		goto out_error;
+
+	backup_root = btrfs_read_fs_root_by_objectid(fs_info, rvp->backup.id);
+	if (!backup_root)
+		goto out_error;
+	trans = btrfs_start_transaction(backup_root, 1);
+	if (!trans)
+		goto out_error;
+	ret = btrfs_create_backup_link(trans, backup_root, store_name, rvp);
+	if (ret)
+		goto out_error;
+	btrfs_commit_transaction(trans, backup_root);
+
+	target_root = btrfs_read_fs_root_by_objectid(fs_info,
+		rvp->target.parent_treeid);
+	if (!target_root)
+		goto out_error;
+	trans = btrfs_start_transaction(target_root, 1);
+	if (!trans)
+		goto out_error;
+	ret = btrfs_switch_dir_item(trans, backup_root, rvp);
+	if (ret)
+		goto out_error;
+	btrfs_commit_transaction(trans, target_root);
+
+	source_root = btrfs_read_fs_root_by_objectid(fs_info,
+		rvp->source.parent_treeid);
+	if (!source_root)
+		goto out_error;
+	trans = btrfs_start_transaction(source_root, 1);
+	if (!trans)
+		goto out_error;
+	ret = btrfs_del_dir_item(trans, source_root, &rvp->source);
+	if (ret)
+		goto out_error;
+	btrfs_commit_transaction(trans, source_root);
+
+	free(store_name);
+	return 0;
+
+out_error:
+	free(store_name);
+	return -1;
+}
+
+static char *device;
+
+int main(int argc, char *argv[])
+{
+	struct btrfs_root *root = NULL;
+	struct revert_packet rv_packet;
+	u64 cur_root;
+	int ret = 1;
+
+	rv_packet.source.id = 0ULL;
+	rv_packet.target.id = 0ULL;
+	rv_packet.source.name = NULL;
+	rv_packet.target.name = NULL;
+	while(1) {
+		int c = getopt(argc, argv, "s:t:");
+		if (c < 0)
+			break;
+		switch(c) {
+		case 's':
+			rv_packet.source.id = atoll(optarg);
+			break;
+		case 't':
+			rv_packet.target.id = atoll(optarg);
+			break;
+		default:
+			print_usage();
+			ret = 1;
+			goto out;
+		}
+	}
+
+	if (rv_packet.source.id == 0ULL) {
+		fprintf(stderr, "source tree is not specified\n");
+		goto error;
+	}
+	if (rv_packet.target.id == 0ULL) {
+		fprintf(stderr, "target tree is not specified\n");
+		goto error;
+	}
+	if (rv_packet.source.id == rv_packet.target.id) {
+		fprintf(stderr, "source tree and target tree are a same tree\n");
+		goto error;
+	}
+
+	argc = argc - optind;
+	device = argv[optind];
+	if (argc != 1) {
+		fprintf(stderr, "device not specified\n");
+		goto error;
+	}
+
+	if (check_mounted(device)) {
+		fprintf(stderr, "%s is mounted\n", device);
+		goto error;
+	}
+
+	root = open_ctree(device, 0, 1);
+	if (!root) {
+		printf("ctree open error.\n");
+		goto error;
+	}
+
+	cur_root = btrfs_find_current_root(root);
+
+	if (find_file_tree(root, &rv_packet.source)) {
+		printf("source tree not exist\n");
+		goto error_out;
+	}
+	if (find_file_tree(root, &rv_packet.target)) {
+		printf("target tree not exist\n");
+		goto error_out;
+	}
+	if (rv_packet.source.id == BTRFS_FS_TREE_OBJECTID)
+		if(rv_packet.target.id != cur_root) {
+			printf("filesystem root(id=%llu) cannot replaces non-root tree(id=%llu)\n",
+				rv_packet.source.id, rv_packet.target.id);
+			goto error_out;
+		}
+
+	printf("tree reverting utilitiy\n");
+	printf("\t subvolume\t: \"%s\"\n", rv_packet.target.name);
+	printf("\t subvolume id\t: [before] %llu -> [after] %llu\n",
+		rv_packet.target.id, rv_packet.source.id);
+
+	if (rv_packet.target.id == cur_root) {
+		ret = btrfs_revert_root(root->fs_info, &rv_packet);
+	} else {
+ 		ret = btrfs_revert_directory(root->fs_info, &rv_packet);
+	}
+
+	printf("operation complete\n");
+close_out:
+	close_ctree(root);
+
+out:
+	if (rv_packet.source.name)
+		free(rv_packet.source.name);
+	if (rv_packet.target.name)
+		free(rv_packet.target.name);
+
+	printf("%s\n",BTRFS_BUILD_VERSION);
+	return ret;
+
+error_out:
+	ret = 1;
+	goto close_out;
+
+error:
+	ret = 1;
+	goto out;
+}