From patchwork Thu Dec 24 02:18:57 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: TARUISI Hiroaki X-Patchwork-Id: 69673 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.3/8.14.2) with ESMTP id nBO2IpBJ028109 for ; Thu, 24 Dec 2009 02:18:51 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1757318AbZLXCSs (ORCPT ); Wed, 23 Dec 2009 21:18:48 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1751574AbZLXCSs (ORCPT ); Wed, 23 Dec 2009 21:18:48 -0500 Received: from fgwmail6.fujitsu.co.jp ([192.51.44.36]:57396 "EHLO fgwmail6.fujitsu.co.jp" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751108AbZLXCSq (ORCPT ); Wed, 23 Dec 2009 21:18:46 -0500 Received: from m6.gw.fujitsu.co.jp ([10.0.50.76]) by fgwmail6.fujitsu.co.jp (Fujitsu Gateway) with ESMTP id nBO2Ijpw025813 for (envelope-from taruishi.hiroak@jp.fujitsu.com); Thu, 24 Dec 2009 11:18:45 +0900 Received: from smail (m6 [127.0.0.1]) by outgoing.m6.gw.fujitsu.co.jp (Postfix) with ESMTP id 30D6E45DE51 for ; Thu, 24 Dec 2009 11:18:45 +0900 (JST) Received: from s6.gw.fujitsu.co.jp (s6.gw.fujitsu.co.jp [10.0.50.96]) by m6.gw.fujitsu.co.jp (Postfix) with ESMTP id 1624445DE50 for ; Thu, 24 Dec 2009 11:18:45 +0900 (JST) Received: from s6.gw.fujitsu.co.jp (localhost.localdomain [127.0.0.1]) by s6.gw.fujitsu.co.jp (Postfix) with ESMTP id EB24F1DB8038 for ; Thu, 24 Dec 2009 11:18:44 +0900 (JST) Received: from m020.s.css.fujitsu.com (m020.s.css.fujitsu.com [10.0.81.60]) by s6.gw.fujitsu.co.jp (Postfix) with ESMTP id 9024B1DB8049 for ; Thu, 24 Dec 2009 11:18:41 +0900 (JST) Received: from m020.css.fujitsu.com (m020 [127.0.0.1]) by m020.s.css.fujitsu.com (Postfix) with ESMTP id 7E05648000E for ; Thu, 24 Dec 2009 11:18:41 +0900 (JST) Received: from [127.0.0.1] (unknown [10.124.100.186]) by m020.s.css.fujitsu.com (Postfix) with ESMTP id 534DE180B8 for ; Thu, 24 Dec 2009 11:18:41 +0900 (JST) X-SecurityPolicyCheck-FJ: OK by FujitsuOutboundMailChecker v1.4.0 Received: from paxd3.soft.fujitsu.com[10.124.100.186] by paxd3.soft.fujitsu.com (FujitsuOutboundMailChecker v1.4.0/9992[10.124.100.186]); Thu, 24 Dec 2009 11:19:05 +0900 (JST) Message-ID: <4B32CF91.8080908@jp.fujitsu.com> Date: Thu, 24 Dec 2009 11:18:57 +0900 From: TARUISI Hiroaki User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.1.5) Gecko/20091204 Thunderbird/3.0 MIME-Version: 1.0 To: linux-btrfs@vger.kernel.org Subject: [PATCH] btrfs-progs: New utility to swap subvolumes References: <4B32CF4D.90406@jp.fujitsu.com> In-Reply-To: <4B32CF4D.90406@jp.fujitsu.com> X-Enigmail-Version: 1.0 Sender: linux-btrfs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-btrfs@vger.kernel.org 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 +#include +#include +#include +#include +#include +#include +#include +#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 + +#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; +}