[sheepdog] [PATCH v4 1/3] rename collie as dog

Liu Yuan namei.unix at gmail.com
Tue Aug 13 08:09:48 CEST 2013


Signed-off-by: Liu Yuan <namei.unix at gmail.com>
---
 .gitignore                       |    6 +-
 INSTALL                          |    2 +-
 Makefile.am                      |   10 +-
 README                           |   12 +-
 collie/Makefile.am               |   56 -
 collie/cluster.c                 |  524 ---------
 collie/collie.c                  |  433 --------
 collie/collie.h                  |   97 --
 collie/common.c                  |  324 ------
 collie/farm/farm.c               |  410 --------
 collie/farm/farm.h               |   83 --
 collie/farm/object_tree.c        |  124 ---
 collie/farm/sha1_file.c          |  148 ---
 collie/farm/slice.c              |  109 --
 collie/farm/snap.c               |  127 ---
 collie/farm/trunk.c              |   84 --
 collie/node.c                    |  417 --------
 collie/trace.c                   |  387 -------
 collie/treeview.c                |  188 ----
 collie/treeview.h                |   21 -
 collie/vdi.c                     | 2160 --------------------------------------
 configure.ac                     |    6 +-
 debian/sheepdog.bash-completion  |    2 +-
 dog/Makefile.am                  |   56 +
 dog/cluster.c                    |  524 +++++++++
 dog/common.c                     |  324 ++++++
 dog/dog.c                        |  433 ++++++++
 dog/dog.h                        |   97 ++
 dog/farm/farm.c                  |  410 ++++++++
 dog/farm/farm.h                  |   83 ++
 dog/farm/object_tree.c           |  124 +++
 dog/farm/sha1_file.c             |  148 +++
 dog/farm/slice.c                 |  109 ++
 dog/farm/snap.c                  |  127 +++
 dog/farm/trunk.c                 |   84 ++
 dog/node.c                       |  417 ++++++++
 dog/trace.c                      |  387 +++++++
 dog/treeview.c                   |  188 ++++
 dog/treeview.h                   |   21 +
 dog/vdi.c                        | 2160 ++++++++++++++++++++++++++++++++++++++
 include/internal_proto.h         |    4 +-
 lib/logger.c                     |    2 +-
 man/Makefile.am                  |   10 +-
 man/collie.8.in                  |   37 -
 man/dog.8.in                     |   37 +
 man/sheep.8.in                   |    4 +-
 man/sheepfs.8.in                 |    4 +-
 script/Makefile.am               |    2 +-
 script/bash_completion_collie    |  303 ------
 script/bash_completion_dog       |  303 ++++++
 sheep/sheep.c                    |    2 +-
 sheep/store.c                    |    2 +-
 sheepdog.spec.in                 |    4 +-
 sheepfs/cluster.c                |    2 +-
 sheepfs/node.c                   |    4 +-
 sheepfs/vdi.c                    |    2 +-
 sheepfs/volume.c                 |    2 +-
 tests/dynamorio/journaling/01.sh |    4 +-
 tests/functional/001             |    2 +-
 tests/functional/002             |    2 +-
 tests/functional/003             |    2 +-
 tests/functional/004             |    2 +-
 tests/functional/005             |    4 +-
 tests/functional/006             |    2 +-
 tests/functional/007             |    6 +-
 tests/functional/008             |    6 +-
 tests/functional/009             |    6 +-
 tests/functional/010             |   32 +-
 tests/functional/011             |    2 +-
 tests/functional/012             |    2 +-
 tests/functional/014             |   12 +-
 tests/functional/015             |   48 +-
 tests/functional/016             |   10 +-
 tests/functional/017             |    2 +-
 tests/functional/018             |    8 +-
 tests/functional/019             |    6 +-
 tests/functional/020             |    4 +-
 tests/functional/022             |    2 +-
 tests/functional/023             |    4 +-
 tests/functional/024             |    2 +-
 tests/functional/025             |    4 +-
 tests/functional/026             |    6 +-
 tests/functional/027             |    4 +-
 tests/functional/028             |   18 +-
 tests/functional/029             |   12 +-
 tests/functional/030             |   62 +-
 tests/functional/031             |    4 +-
 tests/functional/032             |    8 +-
 tests/functional/033             |    8 +-
 tests/functional/034             |   10 +-
 tests/functional/035             |   12 +-
 tests/functional/036             |    6 +-
 tests/functional/037             |    8 +-
 tests/functional/038             |    8 +-
 tests/functional/039             |   34 +-
 tests/functional/040             |    2 +-
 tests/functional/041             |   46 +-
 tests/functional/042             |   14 +-
 tests/functional/043             |   24 +-
 tests/functional/044             |   42 +-
 tests/functional/045             |    8 +-
 tests/functional/046             |   24 +-
 tests/functional/047             |   10 +-
 tests/functional/048             |   20 +-
 tests/functional/049             |    6 +-
 tests/functional/050             |    8 +-
 tests/functional/051             |    6 +-
 tests/functional/052             |   16 +-
 tests/functional/053             |   10 +-
 tests/functional/054             |    6 +-
 tests/functional/055             |   26 +-
 tests/functional/056             |   18 +-
 tests/functional/057             |   24 +-
 tests/functional/058             |    8 +-
 tests/functional/059             |    8 +-
 tests/functional/060             |   16 +-
 tests/functional/061             |    4 +-
 tests/functional/062             |   36 +-
 tests/functional/063             |   12 +-
 tests/functional/064             |   16 +-
 tests/functional/065             |    6 +-
 tests/functional/066             |   12 +-
 tests/functional/067             |    4 +-
 tests/functional/068             |    6 +-
 tests/functional/069             |    6 +-
 tests/functional/070             |    4 +-
 tests/functional/071             |    6 +-
 tests/functional/072             |    6 +-
 tests/functional/check           |    2 +-
 tests/functional/common.config   |    4 +-
 tests/functional/common.filter   |    2 +-
 tests/functional/common.rc       |   24 +-
 tests/functional/group           |    4 +-
 tests/unit/Makefile.am           |    2 +-
 tests/unit/collie/Makefile.am    |   23 -
 tests/unit/collie/mock_collie.c  |   25 -
 tests/unit/collie/test_common.c  |   56 -
 tests/unit/dog/Makefile.am       |   23 +
 tests/unit/dog/mock_dog.c        |   25 +
 tests/unit/dog/test_common.c     |   56 +
 140 files changed, 6613 insertions(+), 6613 deletions(-)
 delete mode 100644 collie/Makefile.am
 delete mode 100644 collie/cluster.c
 delete mode 100644 collie/collie.c
 delete mode 100644 collie/collie.h
 delete mode 100644 collie/common.c
 delete mode 100644 collie/farm/farm.c
 delete mode 100644 collie/farm/farm.h
 delete mode 100644 collie/farm/object_tree.c
 delete mode 100644 collie/farm/sha1_file.c
 delete mode 100644 collie/farm/slice.c
 delete mode 100644 collie/farm/snap.c
 delete mode 100644 collie/farm/trunk.c
 delete mode 100644 collie/node.c
 delete mode 100644 collie/trace.c
 delete mode 100644 collie/treeview.c
 delete mode 100644 collie/treeview.h
 delete mode 100644 collie/vdi.c
 create mode 100644 dog/Makefile.am
 create mode 100644 dog/cluster.c
 create mode 100644 dog/common.c
 create mode 100644 dog/dog.c
 create mode 100644 dog/dog.h
 create mode 100644 dog/farm/farm.c
 create mode 100644 dog/farm/farm.h
 create mode 100644 dog/farm/object_tree.c
 create mode 100644 dog/farm/sha1_file.c
 create mode 100644 dog/farm/slice.c
 create mode 100644 dog/farm/snap.c
 create mode 100644 dog/farm/trunk.c
 create mode 100644 dog/node.c
 create mode 100644 dog/trace.c
 create mode 100644 dog/treeview.c
 create mode 100644 dog/treeview.h
 create mode 100644 dog/vdi.c
 delete mode 100644 man/collie.8.in
 create mode 100644 man/dog.8.in
 delete mode 100644 script/bash_completion_collie
 create mode 100644 script/bash_completion_dog
 delete mode 100644 tests/unit/collie/Makefile.am
 delete mode 100644 tests/unit/collie/mock_collie.c
 delete mode 100644 tests/unit/collie/test_common.c
 create mode 100644 tests/unit/dog/Makefile.am
 create mode 100644 tests/unit/dog/mock_dog.c
 create mode 100644 tests/unit/dog/test_common.c

diff --git a/.gitignore b/.gitignore
index fdbf367..69aca9d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,12 +31,12 @@ GSYMS
 #
 # programs
 #
-collie/collie
+dog/dog
 sheep/sheep
 sheepfs/sheepfs
 shepherd/shepherd
 tools/zk_control
-tests/unit/collie/test_common
+tests/unit/dog/test_common
 tests/unit/sheep/test_vdi
 tests/unit/sheep/test_cluster_driver
 
@@ -73,7 +73,7 @@ tests/*.out.bad
 *.patch
 
 man/sheep.8
-man/collie.8
+man/dog.8
 man/sheepfs.8
 
 *.deb
diff --git a/INSTALL b/INSTALL
index 1a23091..28fbb55 100644
--- a/INSTALL
+++ b/INSTALL
@@ -65,7 +65,7 @@ Installing from source
    $ make rpm
    $ sudo rpm -ivh x86_64/sheepdog-0.*
 
-Please read the README file, the sheep(8), collie(8) or sheepfs(8) man page for
+Please read the README file, the sheep(8), dog(8) or sheepfs(8) man page for
 further usage instructions.
 
 ===============================================================================
diff --git a/Makefile.am b/Makefile.am
index beafa1c..915ce54 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,7 +17,7 @@ sheepdogsysconfdir	= ${SHEEPDOGCONFDIR}
 
 sheepdogsysconf_DATA	= 
 
-SUBDIRS			= lib collie sheep include script shepherd tools
+SUBDIRS			= lib dog sheep include script shepherd tools
 
 if BUILD_SHEEPFS
 SUBDIRS			+= sheepfs
@@ -92,7 +92,7 @@ sparse:
 
 CHECK_STYLE=../script/checkpatch.pl -f --no-summary --terse
 check-style:
-	@for dir in lib collie sheep include sheepfs; do \
+	@for dir in lib dog sheep include sheepfs; do \
 		make -C $$dir check-style CHECK_STYLE="$(CHECK_STYLE)"; \
 	done
 
@@ -100,12 +100,12 @@ if BUILD_COVERAGE
 coverage: clean check
 	@rm -rf coverage
 
-	@for dir in collie sheep tests/unit/collie tests/unit/sheep ; do\
+	@for dir in dog sheep tests/unit/dog tests/unit/sheep ; do\
 		$(MAKE) -C $$dir coverage;				\
 	done
 
-	@lcov -a collie/collie.info -a sheep/sheep.info			\
-	-a tests/unit/collie/collie.info -a tests/unit/sheep/sheep.info	\
+	@lcov -a dog/dog.info -a sheep/sheep.info			\
+	-a tests/unit/dog/dog.info -a tests/unit/sheep/sheep.info	\
 	-o sheep.info &&						\
 	lcov -r sheep.info /usr/include/\* -o sheep.info &&		\
 	lcov -r sheep.info tests/unit/\* -o sheep.info &&		\
diff --git a/README b/README
index f130e86..8561db3 100644
--- a/README
+++ b/README
@@ -82,7 +82,7 @@ Usage
 
    2. Make fs
 
-      $ collie cluster format --copies=3
+      $ dog cluster format --copies=3
 
       --copies specifies the number of default data redundancy. In this case,
       the replicated data is stored on three machines.
@@ -91,7 +91,7 @@ Usage
 
       Following list shows that Sheepdog is running on 32 nodes.
 
-      $ collie node list
+      $ dog node list
         Idx	Node id (FNV-1a) - Host:Port
       ------------------------------------------------
         0	0308164db75cff7e - 10.68.13.15:7000
@@ -138,7 +138,7 @@ Usage
 
    3. See Sheepdog images by the following command.
 
-      $ collie vdi list
+      $ dog vdi list
         name        id    size    used  shared    creation time  object id
       --------------------------------------------------------------------
         Bob          0  2.0 GB  1.6 GB  0.0 MB 2010-03-23 16:16      80000
@@ -151,7 +151,7 @@ Usage
 
    2. Following command checks used images.
 
-      $ collie vm list
+      $ dog vm list
       Name            |Vdi size |Allocated| Shared  | Status
       ----------------+---------+---------+---------+------------
       Bob             |   2.0 GB|   1.6 GB|   0.0 MB| running on xx.xx.xx.xx
@@ -167,7 +167,7 @@ Usage
    2. After getting snapshot, a new virtual machine images are added as a not-
       current image.
 
-      $ collie vdi list
+      $ dog vdi list
         name        id    size    used  shared    creation time  object id
       --------------------------------------------------------------------
         Bob          0  2.0 GB  1.6 GB  0.0 MB 2010-03-23 16:16      80000
@@ -185,7 +185,7 @@ Usage
 
    2. Charlie's image is added to the virtual machine list.
 
-      $ collie vdi list
+      $ dog vdi list
         name        id    size    used  shared    creation time  object id
       --------------------------------------------------------------------
         Bob          0  2.0 GB  1.6 GB  0.0 MB 2010-03-23 16:16      80000
diff --git a/collie/Makefile.am b/collie/Makefile.am
deleted file mode 100644
index 2b57f5a..0000000
--- a/collie/Makefile.am
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Copyright 2010 Red Hat, Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License 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; see the file COPYING.  If not, write to
-# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-
-MAINTAINERCLEANFILES	= Makefile.in
-
-AM_CFLAGS		=
-
-INCLUDES		= -I$(top_builddir)/include -I$(top_srcdir)/include
-
-sbin_PROGRAMS		= collie
-
-collie_SOURCES		= farm/object_tree.c farm/sha1_file.c farm/snap.c \
-			  farm/trunk.c farm/farm.c farm/slice.c \
-			  collie.c common.c treeview.c vdi.c node.c cluster.c
-
-if BUILD_TRACE
-collie_SOURCES          += trace.c
-override CFLAGS         := $(subst -pg,,$(CFLAGS))
-endif
-
-collie_LDADD		= ../lib/libsheepdog.a -lpthread
-collie_DEPENDENCIES	= ../lib/libsheepdog.a
-
-noinst_HEADERS		= treeview.h collie.h farm/farm.h
-
-EXTRA_DIST		=
-
-all-local:
-	@echo Built collie
-
-clean-local:
-	rm -f collie *.o gmon.out *.da *.bb *.bbg
-
-# support for GNU Flymake
-check-syntax:
-	$(COMPILE) -fsyntax-only $(CHK_SOURCES)
-
-check-style:
-	@$(CHECK_STYLE) $(collie_SOURCES) $(noinst_HEADERS)
-
-coverage:
-	@lcov -d . -c -o collie.info
diff --git a/collie/cluster.c b/collie/cluster.c
deleted file mode 100644
index ef87bc4..0000000
--- a/collie/cluster.c
+++ /dev/null
@@ -1,524 +0,0 @@
-/*
- * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <time.h>
-#include <string.h>
-#include <ctype.h>
-#include <sys/time.h>
-
-#include "collie.h"
-#include "farm/farm.h"
-
-static struct sd_option cluster_options[] = {
-	{'b', "store", true, "specify backend store"},
-	{'c', "copies", true, "specify the default data redundancy (number of copies)"},
-	{'f', "force", false, "do not prompt for confirmation"},
-
-	{ 0, NULL, false, NULL },
-};
-
-static struct cluster_cmd_data {
-	int copies;
-	bool force;
-	char name[STORE_LEN];
-} cluster_cmd_data;
-
-#define DEFAULT_STORE	"plain"
-
-static int list_store(void)
-{
-	int ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	char buf[512] = { 0 };
-
-	sd_init_req(&hdr, SD_OP_GET_STORE_LIST);
-	hdr.data_length = 512;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, buf);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Restore failed: %s", sd_strerror(rsp->result));
-		return EXIT_FAILURE;
-	}
-
-	printf("Available stores:\n");
-	printf("---------------------------------------\n");
-	printf("%s\n", buf);
-	return EXIT_SYSFAIL;
-}
-
-static bool no_vdi(const unsigned long *vdis)
-{
-	return find_next_bit(vdis, SD_NR_VDIS, 0) == SD_NR_VDIS;
-}
-
-#define FORMAT_PRINT				\
-	"    __\n"				\
-	"   ()'`;\n"				\
-	"   /\\|`\n"				\
-	"  /  |   Caution! The cluster is not empty.\n" \
-	"(/_)_|_  Are you sure you want to continue? [yes/no]: "
-
-static int cluster_format(int argc, char **argv)
-{
-	int ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	struct timeval tv;
-	char store_name[STORE_LEN];
-	static DECLARE_BITMAP(vdi_inuse, SD_NR_VDIS);
-
-	sd_init_req(&hdr, SD_OP_READ_VDIS);
-	hdr.data_length = sizeof(vdi_inuse);
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, &vdi_inuse);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (!no_vdi(vdi_inuse))
-		confirm(FORMAT_PRINT);
-
-	gettimeofday(&tv, NULL);
-
-	sd_init_req(&hdr, SD_OP_MAKE_FS);
-	hdr.cluster.copies = cluster_cmd_data.copies;
-	hdr.cluster.ctime = (uint64_t) tv.tv_sec << 32 | tv.tv_usec * 1000;
-
-	if (strlen(cluster_cmd_data.name))
-		pstrcpy(store_name, STORE_LEN, cluster_cmd_data.name);
-	else
-		pstrcpy(store_name, STORE_LEN, DEFAULT_STORE);
-	hdr.data_length = strlen(store_name) + 1;
-	hdr.flags |= SD_FLAG_CMD_WRITE;
-
-	printf("using backend %s store\n", store_name);
-	ret = collie_exec_req(sdhost, sdport, &hdr, store_name);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Format failed: %s", sd_strerror(rsp->result));
-		if (rsp->result == SD_RES_NO_STORE)
-			return list_store();
-		else
-			return EXIT_SYSFAIL;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int cluster_info(int argc, char **argv)
-{
-	int i, ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	struct epoch_log *logs;
-	int nr_logs, log_length;
-	time_t ti, ct;
-	struct tm tm;
-	char time_str[128];
-
-	log_length = sd_epoch * sizeof(struct epoch_log);
-	logs = xmalloc(log_length);
-
-	sd_init_req(&hdr, SD_OP_STAT_CLUSTER);
-	hdr.data_length = log_length;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, logs);
-	if (ret < 0)
-		goto error;
-
-	if (!raw_output)
-		printf("Cluster status: ");
-	if (rsp->result == SD_RES_SUCCESS)
-		printf("running, auto-recovery %s\n", logs->disable_recovery ?
-		       "disabled" : "enabled");
-	else
-		printf("%s\n", sd_strerror(rsp->result));
-
-	if (!raw_output && rsp->data_length > 0) {
-		ct = logs[0].ctime >> 32;
-		printf("\nCluster created at %s\n", ctime(&ct));
-		printf("Epoch Time           Version\n");
-	}
-
-	nr_logs = rsp->data_length / sizeof(struct epoch_log);
-	for (i = 0; i < nr_logs; i++) {
-		int j;
-		const struct sd_node *entry;
-
-		ti = logs[i].time;
-		if (raw_output) {
-			snprintf(time_str, sizeof(time_str), "%" PRIu64, (uint64_t) ti);
-		} else {
-			localtime_r(&ti, &tm);
-			strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &tm);
-		}
-
-		printf(raw_output ? "%s %d" : "%s %6d", time_str, logs[i].epoch);
-		printf(" [");
-		for (j = 0; j < logs[i].nr_nodes; j++) {
-			entry = logs[i].nodes + j;
-			printf("%s%s",
-			       (j == 0) ? "" : ", ",
-			       addr_to_str(entry->nid.addr, entry->nid.port));
-		}
-		printf("]\n");
-	}
-
-	free(logs);
-	return EXIT_SUCCESS;
-error:
-	free(logs);
-	return EXIT_SYSFAIL;
-}
-
-static int cluster_shutdown(int argc, char **argv)
-{
-	int ret;
-	struct sd_req hdr;
-
-	sd_init_req(&hdr, SD_OP_SHUTDOWN);
-
-	ret = send_light_req(&hdr, sdhost, sdport);
-	if (ret) {
-		sd_err("failed to execute request");
-		return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static void print_list(void *buf, unsigned len)
-{
-	struct snap_log *log_buf = (struct snap_log *)buf;
-	unsigned nr = len / sizeof(struct snap_log);
-
-	printf("Index\t\tTag\t\tSnapshot Time\n");
-	for (unsigned i = 0; i < nr; i++, log_buf++) {
-		time_t *t = (time_t *)&log_buf->time;
-		printf("%d\t\t", log_buf->idx);
-		printf("%s\t\t", log_buf->tag);
-		printf("%s", ctime(t));
-	}
-}
-
-static int list_snapshot(int argc, char **argv)
-{
-	char *path = argv[optind++];
-	void *buf = NULL;
-	int log_nr;
-	int ret = EXIT_SYSFAIL;
-
-	if (farm_init(path) != SD_RES_SUCCESS)
-		goto out;
-
-	buf = snap_log_read(&log_nr);
-	if (!buf)
-		goto out;
-
-	print_list(buf, log_nr * sizeof(struct snap_log));
-	ret = EXIT_SUCCESS;
-out:
-	if (ret)
-		sd_err("Fail to list snapshot.");
-	free(buf);
-	return ret;
-}
-
-static void fill_object_tree(uint32_t vid, const char *name, const char *tag,
-			  uint32_t snapid, uint32_t flags,
-			  const struct sd_inode *i, void *data)
-{
-	uint64_t vdi_oid = vid_to_vdi_oid(vid), vmstate_oid;
-	int nr_vmstate_object;
-
-	/* ignore active vdi */
-	if (!vdi_is_snapshot(i))
-		return;
-
-	/* fill vdi object id */
-	object_tree_insert(vdi_oid, i->nr_copies);
-
-	/* fill data object id */
-	for (uint64_t idx = 0; idx < MAX_DATA_OBJS; idx++) {
-		if (i->data_vdi_id[idx]) {
-			uint64_t oid = vid_to_data_oid(i->data_vdi_id[idx],
-						       idx);
-			object_tree_insert(oid, i->nr_copies);
-		}
-	}
-
-	/* fill vmstate object id */
-	nr_vmstate_object = DIV_ROUND_UP(i->vm_state_size, SD_DATA_OBJ_SIZE);
-	for (int idx = 0; idx < nr_vmstate_object; idx++) {
-		vmstate_oid = vid_to_vmstate_oid(vid, idx);
-		object_tree_insert(vmstate_oid, i->nr_copies);
-	}
-}
-
-static int save_snapshot(int argc, char **argv)
-{
-	char *tag = argv[optind++];
-	char *path, *p;
-	int ret = EXIT_SYSFAIL, uninitialized_var(unused);
-
-	unused = strtol(tag, &p, 10);
-	if (tag != p) {
-		sd_err("Tag should not start with number.");
-		return EXIT_USAGE;
-	}
-
-	if (!argv[optind]) {
-		sd_err("Please specify the path to save snapshot.");
-		return EXIT_USAGE;
-	}
-	path = argv[optind];
-
-	if (farm_init(path) != SD_RES_SUCCESS)
-		goto out;
-
-	if (farm_contain_snapshot(0, tag)) {
-		sd_err("Snapshot tag has already been used for another"
-		       " snapshot, please, use another one.");
-		goto out;
-	}
-
-	if (parse_vdi(fill_object_tree, SD_INODE_SIZE, NULL) != SD_RES_SUCCESS)
-		goto out;
-
-	if (farm_save_snapshot(tag) != SD_RES_SUCCESS)
-		goto out;
-
-	ret = EXIT_SUCCESS;
-out:
-	if (ret)
-		sd_err("Fail to save snapshot to path: %s.", path);
-	object_tree_free();
-	return ret;
-}
-
-static int load_snapshot(int argc, char **argv)
-{
-	char *tag = argv[optind++];
-	char *path, *p;
-	uint32_t idx;
-	int ret = EXIT_SYSFAIL;
-
-	idx = strtol(tag, &p, 10);
-	if (tag == p)
-		idx = 0;
-
-	if (!argv[optind]) {
-		sd_err("Please specify the path to save snapshot.");
-		return EXIT_USAGE;
-	}
-	path = argv[optind];
-
-	if (farm_init(path) != SD_RES_SUCCESS)
-		goto out;
-
-	if (!farm_contain_snapshot(idx, tag)) {
-		sd_err("Snapshot index or tag does not exist.");
-		goto out;
-	}
-
-	if (cluster_format(0, NULL) != SD_RES_SUCCESS)
-		goto out;
-
-	if (farm_load_snapshot(idx, tag) != SD_RES_SUCCESS)
-		goto out;
-
-	ret = EXIT_SUCCESS;
-out:
-	if (ret)
-		sd_err("Fail to load snapshot");
-	return ret;
-}
-
-#define RECOVER_PRINT \
-	"Caution! Please try starting all the cluster nodes normally before\n" \
-	"running this command.\n\n" \
-	"The cluster may need to be force recovered if:\n" \
-	"  - the master node fails to start because of epoch mismatch; or\n" \
-	"  - some nodes fail to start after a cluster shutdown.\n\n" \
-	"Are you sure you want to continue? [yes/no]: "
-
-static int cluster_force_recover(int argc, char **argv)
-{
-	int ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	char str[123] = {'\0'};
-	struct sd_node nodes[SD_MAX_NODES];
-
-	if (!cluster_cmd_data.force) {
-		int i, l;
-		printf(RECOVER_PRINT);
-		ret = scanf("%s", str);
-		if (ret < 0)
-			return EXIT_SYSFAIL;
-		l = strlen(str);
-		for (i = 0; i < l; i++)
-			str[i] = tolower(str[i]);
-		if (strncmp(str, "yes", 3) != 0)
-			return EXIT_SUCCESS;
-	}
-
-	sd_init_req(&hdr, SD_OP_FORCE_RECOVER);
-	hdr.data_length = sizeof(nodes);
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, nodes);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("failed to execute request, %s",
-		       sd_strerror(rsp->result));
-		return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int cluster_disable_recover(int argc, char **argv)
-{
-	int ret;
-	struct sd_req hdr;
-
-	sd_init_req(&hdr, SD_OP_DISABLE_RECOVER);
-
-	ret = send_light_req(&hdr, sdhost, sdport);
-	if (ret)
-		return EXIT_FAILURE;
-
-	printf("Cluster recovery: disable\n");
-	return EXIT_SUCCESS;
-}
-
-static int cluster_enable_recover(int argc, char **argv)
-{
-	int ret;
-	struct sd_req hdr;
-
-	sd_init_req(&hdr, SD_OP_ENABLE_RECOVER);
-
-	ret = send_light_req(&hdr, sdhost, sdport);
-	if (ret)
-		return EXIT_FAILURE;
-
-	printf("Cluster recovery: enable\n");
-	return EXIT_SUCCESS;
-}
-
-/* Subcommand list of recover */
-static struct subcommand cluster_recover_cmd[] = {
-	{"force", NULL, NULL, "force recover cluster immediately",
-	 NULL, 0, cluster_force_recover},
-	{"enable", NULL, NULL, "enable automatic recovery and "
-				"run once recover if necessary",
-	 NULL, 0, cluster_enable_recover},
-	{"disable", NULL, NULL, "disable automatic recovery",
-	 NULL, 0, cluster_disable_recover},
-	{NULL},
-};
-
-static int cluster_recover(int argc, char **argv)
-{
-	return do_generic_subcommand(cluster_recover_cmd, argc, argv);
-}
-
-/* Subcommand list of snapshot */
-static struct subcommand cluster_snapshot_cmd[] = {
-	{"save", NULL, "h", "save snapshot to localpath",
-	 NULL, CMD_NEED_ARG|CMD_NEED_NODELIST,
-	 save_snapshot, NULL},
-	{"list", NULL, "h", "list snapshot of localpath",
-	 NULL, CMD_NEED_ARG, list_snapshot, NULL},
-	{"load", NULL, "h", "load snapshot from localpath",
-	 NULL, CMD_NEED_ARG, load_snapshot, NULL},
-	{NULL},
-};
-
-static int cluster_snapshot(int argc, char **argv)
-{
-	return do_generic_subcommand(cluster_snapshot_cmd, argc, argv);
-}
-
-static int cluster_reweight(int argc, char **argv)
-{
-	int ret;
-	struct sd_req hdr;
-
-	sd_init_req(&hdr, SD_OP_REWEIGHT);
-	ret = send_light_req(&hdr, sdhost, sdport);
-	if (ret)
-		return EXIT_FAILURE;
-	return EXIT_SUCCESS;
-}
-
-static struct subcommand cluster_cmd[] = {
-	{"info", NULL, "aprh", "show cluster information",
-	 NULL, CMD_NEED_NODELIST, cluster_info, cluster_options},
-	{"format", NULL, "bcaph", "create a Sheepdog store",
-	 NULL, 0, cluster_format, cluster_options},
-	{"shutdown", NULL, "aph", "stop Sheepdog",
-	 NULL, 0, cluster_shutdown, cluster_options},
-	{"snapshot", "<tag|idx> <path>", "aph", "snapshot/restore the cluster",
-	 cluster_snapshot_cmd, CMD_NEED_ARG,
-	 cluster_snapshot, cluster_options},
-	{"recover", NULL, "afph",
-	 "See 'collie cluster recover' for more information",
-	 cluster_recover_cmd, CMD_NEED_ARG,
-	 cluster_recover, cluster_options},
-	{"reweight", NULL, "aph", "reweight the cluster", NULL, 0,
-	 cluster_reweight, cluster_options},
-	{NULL,},
-};
-
-static int cluster_parser(int ch, char *opt)
-{
-	int copies;
-	char *p;
-
-	switch (ch) {
-	case 'b':
-		pstrcpy(cluster_cmd_data.name, sizeof(cluster_cmd_data.name),
-			opt);
-		break;
-	case 'c':
-		copies = strtol(opt, &p, 10);
-		if (opt == p || copies < 1) {
-			sd_err("There must be at least one copy of data");
-			exit(EXIT_FAILURE);
-		} else if (copies > SD_MAX_COPIES) {
-			sd_err("Redundancy may not exceed %d copies",
-			       SD_MAX_COPIES);
-			exit(EXIT_FAILURE);
-		}
-		cluster_cmd_data.copies = copies;
-		break;
-	case 'f':
-		cluster_cmd_data.force = true;
-		break;
-	}
-
-	return 0;
-}
-
-struct command cluster_command = {
-	"cluster",
-	cluster_cmd,
-	cluster_parser
-};
diff --git a/collie/collie.c b/collie/collie.c
deleted file mode 100644
index f95f3c5..0000000
--- a/collie/collie.c
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2009-2011 Nippon Telegraph and Telephone Corporation.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <signal.h>
-
-#include "sheepdog_proto.h"
-#include "sheep.h"
-#include "collie.h"
-#include "util.h"
-#include "sockfd_cache.h"
-
-#define EPOLL_SIZE 4096
-
-static const char program_name[] = "collie";
-/* default sdhost is "127.0.0.1" */
-uint8_t sdhost[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1 };
-int sdport = SD_LISTEN_PORT;
-bool highlight = true;
-bool raw_output;
-bool verbose;
-
-static const struct sd_option collie_options[] = {
-
-	/* common options for all collie commands */
-	{'a', "address", true, "specify the daemon address (default: localhost)"},
-	{'p', "port", true, "specify the daemon port"},
-	{'r', "raw", false, "raw output mode: omit headers, separate fields with\n"
-	 "                          single spaces and print all sizes in decimal bytes"},
-	{'v', "verbose", false, "print more information than default"},
-	{'h', "help", false, "display this help and exit"},
-
-	{ 0, NULL, false, NULL },
-};
-
-static void usage(const struct command *commands, int status);
-
-uint32_t sd_epoch;
-
-struct sd_node sd_nodes[SD_MAX_NODES];
-struct sd_vnode sd_vnodes[SD_MAX_VNODES];
-int sd_nodes_nr, sd_vnodes_nr;
-
-int update_node_list(int max_nodes)
-{
-	int ret;
-	unsigned int size;
-	char *buf = NULL;
-	struct sd_node *ent;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-
-	size = sizeof(*ent) * max_nodes;
-	buf = xzalloc(size);
-	sd_init_req(&hdr, SD_OP_GET_NODE_LIST);
-
-	hdr.data_length = size;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, buf);
-	if (ret < 0)
-		goto out;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Failed to update node list: %s",
-		       sd_strerror(rsp->result));
-		ret = -1;
-		goto out;
-	}
-
-	size = rsp->data_length;
-	sd_nodes_nr = size / sizeof(*ent);
-	if (sd_nodes_nr == 0) {
-		sd_err("There are no active sheep daemons");
-		exit(EXIT_FAILURE);
-	}
-
-	/* FIXME */
-	if (sd_nodes_nr > max_nodes) {
-		ret = -1;
-		goto out;
-	}
-
-	memcpy(sd_nodes, buf, size);
-	sd_vnodes_nr = nodes_to_vnodes(sd_nodes, sd_nodes_nr, sd_vnodes);
-	sd_epoch = hdr.epoch;
-out:
-	if (buf)
-		free(buf);
-
-	return ret;
-}
-
-static int (*command_parser)(int, char *);
-static int (*command_fn)(int, char **);
-static const char *command_opts;
-static const char *command_arg;
-static const char *command_desc;
-static struct sd_option *command_options;
-
-static const struct sd_option *find_opt(int ch)
-{
-	const struct sd_option *opt;
-
-	/* search for common options */
-	sd_for_each_option(opt, collie_options) {
-		if (opt->ch == ch)
-			return opt;
-	}
-
-	/* search for self options */
-	if (command_options) {
-		sd_for_each_option(opt, command_options) {
-			if (opt->ch == ch)
-				return opt;
-		}
-	}
-
-	sd_err("Internal error");
-	exit(EXIT_SYSFAIL);
-}
-
-static void init_commands(const struct command **commands)
-{
-	static struct command *cmds;
-	struct command command_list[] = {
-		vdi_command,
-		node_command,
-		cluster_command,
-		trace_command,
-		{NULL,}
-	};
-
-	if (!cmds) {
-		cmds = (struct command *)xmalloc(sizeof(command_list));
-		memcpy(cmds, command_list, sizeof(command_list));
-	}
-
-	*commands = cmds;
-	return;
-}
-
-static const struct subcommand *find_subcmd(const char *cmd, const char *subcmd)
-{
-	int i, j;
-	const struct command *commands;
-	const struct subcommand *sub;
-
-	init_commands(&commands);
-
-	for (i = 0; commands[i].name; i++) {
-		if (!strcmp(commands[i].name, cmd)) {
-			sub = commands[i].sub;
-			for (j = 0; sub[j].name; j++) {
-				if (!strcmp(sub[j].name, subcmd))
-					return &sub[j];
-			}
-		}
-	}
-
-	return NULL;
-}
-
-static unsigned long setup_commands(const struct command *commands,
-				    char *cmd, char *subcmd)
-{
-	int i;
-	bool found = false;
-	struct subcommand *s;
-	unsigned long flags = 0;
-
-	for (i = 0; commands[i].name; i++) {
-		if (!strcmp(commands[i].name, cmd)) {
-			found = true;
-			if (commands[i].parser)
-				command_parser = commands[i].parser;
-			break;
-		}
-	}
-
-	if (!found) {
-		if (cmd && strcmp(cmd, "help") && strcmp(cmd, "--help") &&
-		    strcmp(cmd, "-h")) {
-			sd_err("Invalid command '%s'", cmd);
-			usage(commands, EXIT_USAGE);
-		}
-		usage(commands, 0);
-	}
-
-	for (s = commands[i].sub; subcmd && s->name; s++) {
-		if (!strcmp(s->name, subcmd)) {
-			command_fn = s->fn;
-			command_opts = s->opts;
-			command_arg = s->arg;
-			command_desc = s->desc;
-			command_options = s->options;
-			flags = s->flags;
-			break;
-		}
-	}
-
-	if (!command_fn) {
-		if (subcmd && strcmp(subcmd, "help") &&
-		    strcmp(subcmd, "--help") && strcmp(subcmd, "-h"))
-			sd_err("Invalid command '%s %s'", cmd, subcmd);
-		sd_err("Available %s commands:", cmd);
-		for (s = commands[i].sub; s->name; s++)
-			sd_err("  %s %s", cmd, s->name);
-		exit(EXIT_USAGE);
-	}
-
-	return flags;
-}
-
-static void usage(const struct command *commands, int status)
-{
-	int i;
-	struct subcommand *s;
-	char name[64];
-
-	if (status)
-		sd_err("Try '%s --help' for more information.", program_name);
-	else {
-		printf("Sheepdog administrator utility\n");
-		printf("Usage: %s <command> <subcommand> [options]\n", program_name);
-		printf("\nAvailable commands:\n");
-		for (i = 0; commands[i].name; i++) {
-			for (s = commands[i].sub; s->name; s++) {
-				snprintf(name, sizeof(name), "%s %s",
-					 commands[i].name, s->name);
-				printf("  %-24s%s\n", name, s->desc);
-			}
-		}
-		printf("\n");
-		printf("For more information, run "
-		       "'%s <command> <subcommand> --help'.\n", program_name);
-	}
-	exit(status);
-}
-
-void subcommand_usage(char *cmd, char *subcmd, int status)
-{
-	int i, n, len = strlen(command_opts);
-	const struct sd_option *sd_opt;
-	const struct subcommand *sub, *subsub;
-	char name[64];
-
-	printf("Usage: %s %s %s", program_name, cmd, subcmd);
-
-	/* Show subcmd's subcommands if necessary */
-	sub = find_subcmd(cmd, subcmd);
-	subsub = sub->sub;
-	if (subsub) {
-		n = 0;
-		while (subsub[n].name)
-			n++;
-		if (n == 1)
-			printf(" %s", subsub[0].name);
-		else if (n > 1) {
-			printf(" {%s", subsub[0].name);
-			for (i = 1; i < n; i++)
-				printf("|%s", subsub[i].name);
-			printf("}");
-		}
-	}
-
-	for (i = 0; i < len; i++) {
-		sd_opt = find_opt(command_opts[i]);
-		if (sd_opt->has_arg)
-			printf(" [-%c %s]", sd_opt->ch, sd_opt->name);
-		else
-			printf(" [-%c]", sd_opt->ch);
-	}
-	if (command_arg)
-		printf(" %s", command_arg);
-
-	printf("\n");
-	if (subsub) {
-		printf("Available subcommands:\n");
-		for (i = 0; subsub[i].name; i++)
-			printf("  %-24s%s\n", subsub[i].name, subsub[i].desc);
-
-	}
-
-	printf("Options:\n");
-	for (i = 0; i < len; i++) {
-		sd_opt = find_opt(command_opts[i]);
-		snprintf(name, sizeof(name), "-%c, --%s",
-			 sd_opt->ch, sd_opt->name);
-		printf("  %-24s%s\n", name, sd_opt->desc);
-	}
-
-	exit(status);
-}
-
-static const struct sd_option *build_sd_options(const char *opts)
-{
-	static struct sd_option sd_opts[256], *p;
-	int i, len = strlen(opts);
-
-	p = sd_opts;
-	for (i = 0; i < len; i++)
-		*p++ = *find_opt(opts[i]);
-	memset(p, 0, sizeof(struct sd_option));
-
-	return sd_opts;
-}
-
-static void crash_handler(int signo)
-{
-	sd_err("collie exits unexpectedly (%s).", strsignal(signo));
-
-	sd_backtrace();
-
-	/*
-	 * OOM raises SIGABRT in xmalloc but the administrator expects
-	 * that collie exits with EXIT_SYSFAIL.  We have to give up
-	 * dumping a core file in this case.
-	 */
-	if (signo == SIGABRT)
-		exit(EXIT_SYSFAIL);
-
-	reraise_crash_signal(signo, EXIT_SYSFAIL);
-}
-
-static size_t get_nr_nodes(void)
-{
-	return sd_nodes_nr;
-}
-
-int main(int argc, char **argv)
-{
-	int ch, longindex, ret;
-	unsigned long flags;
-	struct option *long_options;
-	const struct command *commands;
-	const char *short_options;
-	char *p;
-	const struct sd_option *sd_opts;
-
-	install_crash_handler(crash_handler);
-
-	init_commands(&commands);
-
-	if (argc < 2)
-		usage(commands, 0);
-
-	flags = setup_commands(commands, argv[1], argv[2]);
-
-	optind = 3;
-
-	sd_opts = build_sd_options(command_opts);
-	long_options = build_long_options(sd_opts);
-	short_options = build_short_options(sd_opts);
-
-	while ((ch = getopt_long(argc, argv, short_options, long_options,
-				&longindex)) >= 0) {
-
-		switch (ch) {
-		case 'a':
-			if (!str_to_addr(optarg, sdhost)) {
-				sd_err("Invalid ip address %s", optarg);
-				return EXIT_FAILURE;
-			}
-			break;
-		case 'p':
-			sdport = strtol(optarg, &p, 10);
-			if (optarg == p || sdport < 1 || sdport > UINT16_MAX) {
-				sd_err("Invalid port number '%s'", optarg);
-				exit(EXIT_USAGE);
-			}
-			break;
-		case 'r':
-			raw_output = true;
-			break;
-		case 'v':
-			verbose = true;
-			break;
-		case 'h':
-			subcommand_usage(argv[1], argv[2], EXIT_SUCCESS);
-			break;
-		case '?':
-			usage(commands, EXIT_USAGE);
-			break;
-		default:
-			if (command_parser)
-				command_parser(ch, optarg);
-			else
-				usage(commands, EXIT_USAGE);
-			break;
-		}
-	}
-
-	if (!is_stdout_console() || raw_output)
-		highlight = false;
-
-	if (flags & CMD_NEED_NODELIST) {
-		ret = update_node_list(SD_MAX_NODES);
-		if (ret < 0) {
-			sd_err("Failed to get node list");
-			exit(EXIT_SYSFAIL);
-		}
-	}
-
-	if (flags & CMD_NEED_ARG && argc == optind)
-		subcommand_usage(argv[1], argv[2], EXIT_USAGE);
-
-	if (init_event(EPOLL_SIZE) < 0)
-		exit(EXIT_SYSFAIL);
-
-	if (init_work_queue(get_nr_nodes) != 0) {
-		sd_err("Failed to init work queue");
-		exit(EXIT_SYSFAIL);
-	}
-
-	if (sockfd_init()) {
-		sd_err("sockfd_init() failed");
-		exit(EXIT_SYSFAIL);
-	}
-
-	ret = command_fn(argc, argv);
-	if (ret == EXIT_USAGE)
-		subcommand_usage(argv[1], argv[2], EXIT_USAGE);
-	return ret;
-}
diff --git a/collie/collie.h b/collie/collie.h
deleted file mode 100644
index 5f6797d..0000000
--- a/collie/collie.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef __COLLIE_H__
-#define __COLLIE_H__
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/ioctl.h>
-
-#include "sheepdog_proto.h"
-#include "sheep.h"
-#include "exits.h"
-#include "option.h"
-#include "work.h"
-#include "event.h"
-#include "config.h"
-
-#define CMD_NEED_NODELIST (1 << 0)
-#define CMD_NEED_ARG (1 << 1)
-
-#define UINT64_DECIMAL_SIZE 21
-
-struct command {
-	const char *name;
-	struct subcommand *sub;
-	int (*parser)(int, char *);
-};
-
-struct subcommand {
-	const char *name;
-	const char *arg;
-	const char *opts;
-	const char *desc;
-	struct subcommand *sub;
-	unsigned long flags;
-	int (*fn)(int, char **);
-	struct sd_option *options;
-};
-void subcommand_usage(char *cmd, char *subcmd, int status);
-
-extern uint8_t sdhost[16];
-extern int sdport;
-extern bool highlight;
-extern bool raw_output;
-extern bool verbose;
-
-extern uint32_t sd_epoch;
-extern struct sd_node sd_nodes[SD_MAX_NODES];
-extern struct sd_vnode sd_vnodes[SD_MAX_VNODES];
-extern int sd_nodes_nr, sd_vnodes_nr;
-
-bool is_current(const struct sd_inode *i);
-char *size_to_str(uint64_t _size, char *str, int str_size);
-typedef void (*vdi_parser_func_t)(uint32_t vid, const char *name,
-				  const char *tag, uint32_t snapid,
-				  uint32_t flags,
-				  const struct sd_inode *i, void *data);
-int parse_vdi(vdi_parser_func_t func, size_t size, void *data);
-int sd_read_object(uint64_t oid, void *data, unsigned int datalen,
-		   uint64_t offset, bool direct);
-int sd_write_object(uint64_t oid, uint64_t cow_oid, void *data,
-		    unsigned int datalen, uint64_t offset, uint32_t flags,
-		    int copies, bool create, bool direct);
-int collie_exec_req(const uint8_t *addr, int port, struct sd_req *hdr,
-		    void *data);
-int send_light_req(struct sd_req *hdr, const uint8_t *addr, int port);
-int do_generic_subcommand(struct subcommand *sub, int argc, char **argv);
-int update_node_list(int max_nodes);
-void confirm(const char *message);
-void work_queue_wait(struct work_queue *q);
-int do_vdi_create(const char *vdiname, int64_t vdi_size,
-		  uint32_t base_vid, uint32_t *vdi_id, bool snapshot,
-		  int nr_copies);
-void show_progress(uint64_t done, uint64_t total, bool raw);
-
-extern struct command vdi_command;
-extern struct command node_command;
-extern struct command cluster_command;
-
-#ifdef HAVE_TRACE
-  extern struct command trace_command;
-#else
-  #define trace_command {}
-#endif /* HAVE_TRACE */
-
-#endif
diff --git a/collie/common.c b/collie/common.c
deleted file mode 100644
index 3237608..0000000
--- a/collie/common.c
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "collie.h"
-#include "sha1.h"
-#include "sockfd_cache.h"
-
-char *size_to_str(uint64_t _size, char *str, int str_size)
-{
-	const char *units[] = {"MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
-	int i = 0;
-	double size;
-
-	if (raw_output) {
-		snprintf(str, str_size, "%" PRIu64, _size);
-		return str;
-	}
-
-	size = (double)_size;
-	size /= 1024 * 1024;
-	while (i < ARRAY_SIZE(units) - 1 && size >= 1024) {
-		i++;
-		size /= 1024;
-	}
-
-	if (size >= 10)
-		snprintf(str, str_size, "%.0lf %s", size, units[i]);
-	else
-		snprintf(str, str_size, "%.1lf %s", size, units[i]);
-
-	return str;
-}
-
-int sd_read_object(uint64_t oid, void *data, unsigned int datalen,
-		   uint64_t offset, bool direct)
-{
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	int ret;
-
-	sd_init_req(&hdr, SD_OP_READ_OBJ);
-	hdr.data_length = datalen;
-
-	hdr.obj.oid = oid;
-	hdr.obj.offset = offset;
-	if (direct)
-		hdr.flags |= SD_FLAG_CMD_DIRECT;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, data);
-	if (ret < 0) {
-		sd_err("Failed to read object %" PRIx64, oid);
-		return SD_RES_EIO;
-	}
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Failed to read object %" PRIx64 " %s", oid,
-		       sd_strerror(rsp->result));
-		return rsp->result;
-	}
-
-	untrim_zero_blocks(data, rsp->obj.offset, rsp->data_length, datalen);
-
-	return SD_RES_SUCCESS;
-}
-
-int sd_write_object(uint64_t oid, uint64_t cow_oid, void *data,
-		    unsigned int datalen, uint64_t offset, uint32_t flags,
-		    int copies, bool create, bool direct)
-{
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	int ret;
-
-	if (create)
-		sd_init_req(&hdr, SD_OP_CREATE_AND_WRITE_OBJ);
-	else
-		sd_init_req(&hdr, SD_OP_WRITE_OBJ);
-
-	hdr.data_length = datalen;
-	hdr.flags = flags | SD_FLAG_CMD_WRITE;
-	if (cow_oid)
-		hdr.flags |= SD_FLAG_CMD_COW;
-	if (direct)
-		hdr.flags |= SD_FLAG_CMD_DIRECT;
-
-	hdr.obj.copies = copies;
-	hdr.obj.oid = oid;
-	hdr.obj.cow_oid = cow_oid;
-	hdr.obj.offset = offset;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, data);
-	if (ret < 0) {
-		sd_err("Failed to write object %" PRIx64, oid);
-		return SD_RES_EIO;
-	}
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Failed to write object %" PRIx64 ": %s", oid,
-		       sd_strerror(rsp->result));
-		return rsp->result;
-	}
-
-	return SD_RES_SUCCESS;
-}
-
-#define FOR_EACH_VDI(nr, vdis) FOR_EACH_BIT(nr, vdis, SD_NR_VDIS)
-
-int parse_vdi(vdi_parser_func_t func, size_t size, void *data)
-{
-	int ret;
-	unsigned long nr;
-	static struct sd_inode i;
-	struct sd_req req;
-	static DECLARE_BITMAP(vdi_inuse, SD_NR_VDIS);
-	unsigned int rlen = sizeof(vdi_inuse);
-
-	sd_init_req(&req, SD_OP_READ_VDIS);
-	req.data_length = sizeof(vdi_inuse);
-
-	ret = collie_exec_req(sdhost, sdport, &req, &vdi_inuse);
-	if (ret < 0)
-		goto out;
-
-	FOR_EACH_VDI(nr, vdi_inuse) {
-		uint64_t oid;
-		uint32_t snapid;
-
-		oid = vid_to_vdi_oid(nr);
-
-		memset(&i, 0, sizeof(i));
-		ret = sd_read_object(oid, &i, SD_INODE_HEADER_SIZE, 0, true);
-		if (ret != SD_RES_SUCCESS) {
-			sd_err("Failed to read inode header");
-			continue;
-		}
-
-		if (i.name[0] == '\0') /* this VDI has been deleted */
-			continue;
-
-		if (size > SD_INODE_HEADER_SIZE) {
-			rlen = DIV_ROUND_UP(i.vdi_size, SD_DATA_OBJ_SIZE) *
-				sizeof(i.data_vdi_id[0]);
-			if (rlen > size - SD_INODE_HEADER_SIZE)
-				rlen = size - SD_INODE_HEADER_SIZE;
-
-			ret = sd_read_object(oid, ((char *)&i) + SD_INODE_HEADER_SIZE,
-					     rlen, SD_INODE_HEADER_SIZE, true);
-
-			if (ret != SD_RES_SUCCESS) {
-				sd_err("Failed to read inode");
-				continue;
-			}
-		}
-
-		snapid = vdi_is_snapshot(&i) ? i.snap_id : 0;
-		func(i.vdi_id, i.name, i.tag, snapid, 0, &i, data);
-	}
-
-out:
-	return ret;
-}
-
-int collie_exec_req(const uint8_t *addr, int port, struct sd_req *hdr,
-		    void *buf)
-{
-	struct node_id nid = {};
-	struct sockfd *sfd;
-	int ret;
-
-	memcpy(nid.addr, addr, sizeof(nid.addr));
-	nid.port = port;
-
-	sfd = sockfd_cache_get(&nid);
-	if (!sfd)
-		return -1;
-
-	/*
-	 * Retry forever for collie because
-	 * 1. We can't get the newest epoch
-	 * 2. Some operations might take unexpected long time
-	 */
-	ret = exec_req(sfd->fd, hdr, buf, NULL, 0, UINT32_MAX);
-
-	sockfd_cache_put(&nid, sfd);
-
-	return ret ? -1 : 0;
-}
-
-/* Light request only contains header, without body content. */
-int send_light_req(struct sd_req *hdr, const uint8_t *addr, int port)
-{
-	int ret = collie_exec_req(addr, port, hdr, NULL);
-
-	if (ret == -1)
-		return -1;
-
-	if (ret != SD_RES_SUCCESS) {
-		sd_err("Response's result: %s", sd_strerror(ret));
-		return -1;
-	}
-
-	return 0;
-}
-
-int do_generic_subcommand(struct subcommand *sub, int argc, char **argv)
-{
-	int i, ret;
-
-	for (i = 0; sub[i].name; i++) {
-		if (!strcmp(sub[i].name, argv[optind])) {
-			unsigned long flags = sub[i].flags;
-
-			if (flags & CMD_NEED_NODELIST) {
-				ret = update_node_list(SD_MAX_NODES);
-				if (ret < 0) {
-					sd_err("Failed to get node list");
-					exit(EXIT_SYSFAIL);
-				}
-			}
-
-			if (flags & CMD_NEED_ARG && argc < 5)
-				subcommand_usage(argv[1], argv[2], EXIT_USAGE);
-			optind++;
-			ret = sub[i].fn(argc, argv);
-			if (ret == EXIT_USAGE)
-				subcommand_usage(argv[1], argv[2], EXIT_USAGE);
-			return ret;
-		}
-	}
-
-	subcommand_usage(argv[1], argv[2], EXIT_FAILURE);
-	return EXIT_FAILURE;
-}
-
-void confirm(const char *message)
-{
-	char input[8] = "";
-	char *ret;
-
-	printf("%s", message);
-	ret = fgets(input, sizeof(input), stdin);
-	if (ret == NULL || strncasecmp(input, "yes", 3) != 0)
-		exit(EXIT_SUCCESS);
-}
-
-void work_queue_wait(struct work_queue *q)
-{
-	while (!work_queue_empty(q))
-		event_loop(-1);
-}
-
-#define DEFAULT_SCREEN_WIDTH 80
-
-static int get_screen_width(void)
-{
-	struct winsize wsz;
-
-	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz) < 0)
-		return DEFAULT_SCREEN_WIDTH;
-
-	return wsz.ws_col;
-}
-
-/*
- * Show prograss bar as follows.
- *
- *  45.0 % [===============>                  ] 180 MB / 400 MB
- */
-void show_progress(uint64_t done, uint64_t total, bool raw)
-{
-	char done_str[256], total_str[256];
-	int screen_width = get_screen_width();
-	int bar_length = screen_width - 30;
-	char *buf;
-
-	if (!is_stdout_console())
-		return;
-	if (screen_width <= 0)
-		return;
-
-	printf("\r"); /* move to the beginning of the line */
-
-	if (raw) {
-		snprintf(done_str, sizeof(done_str), "%"PRIu64, done);
-		snprintf(total_str, sizeof(total_str), "%"PRIu64, total);
-	} else {
-		size_to_str(done, done_str, sizeof(done_str));
-		size_to_str(total, total_str, sizeof(total_str));
-	}
-
-	buf = xmalloc(screen_width + 1);
-	snprintf(buf, screen_width, "%5.1lf %% [", (double)done / total * 100);
-
-	for (int i = 0; i < bar_length; i++) {
-		if (total * (i + 1) / bar_length <= done)
-			strcat(buf, "=");
-		else if (total * i / bar_length <= done &&
-			 done < total * (i + 1) / bar_length)
-			strcat(buf, ">");
-		else
-			strcat(buf, " ");
-	}
-	snprintf(buf + strlen(buf), screen_width - strlen(buf),
-		 "] %s / %s", done_str, total_str);
-
-	/* fill the rest of buffer with blank characters */
-	memset(buf + strlen(buf), ' ', screen_width - strlen(buf));
-	buf[screen_width] = '\0';
-	printf("%s", buf);
-
-	if (done == total)
-		printf("\n");
-
-	fflush(stdout);
-
-	free(buf);
-}
diff --git a/collie/farm/farm.c b/collie/farm/farm.c
deleted file mode 100644
index ff8be39..0000000
--- a/collie/farm/farm.c
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2011 Taobao Inc.
- * Copyright (C) 2013 Zelin.io
- *
- * Liu Yuan <namei.unix at gmail.com>
- * Kai Zhang <kyle at zelin.io>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <pthread.h>
-
-#include "farm.h"
-#include "list.h"
-
-static char farm_object_dir[PATH_MAX];
-static char farm_dir[PATH_MAX];
-
-static struct sd_lock vdi_list_lock = SD_LOCK_INITIALIZER;
-struct vdi_entry {
-	char name[SD_MAX_VDI_LEN];
-	uint64_t vdi_size;
-	uint32_t vdi_id;
-	uint32_t snap_id;
-	uint8_t  nr_copies;
-	struct list_head list;
-};
-static LIST_HEAD(last_vdi_list);
-
-struct snapshot_work {
-	struct trunk_entry entry;
-	struct strbuf *trunk_buf;
-	struct work work;
-};
-static struct work_queue *wq;
-static uatomic_bool work_error;
-
-static struct vdi_entry *find_vdi(const char *name)
-{
-	struct vdi_entry *vdi;
-
-	list_for_each_entry(vdi, &last_vdi_list, list) {
-		if (!strcmp(vdi->name, name))
-			return vdi;
-	}
-	return NULL;
-}
-
-static struct vdi_entry *new_vdi(const char *name, uint64_t vdi_size,
-				 uint32_t vdi_id, uint32_t snap_id,
-				 uint8_t nr_copies)
-{
-	struct vdi_entry *vdi;
-	vdi = xmalloc(sizeof(struct vdi_entry));
-	pstrcpy(vdi->name, sizeof(vdi->name), name);
-	vdi->vdi_size = vdi_size;
-	vdi->vdi_id = vdi_id;
-	vdi->snap_id = snap_id;
-	vdi->nr_copies = nr_copies;
-	INIT_LIST_HEAD(&vdi->list);
-	return vdi;
-}
-
-static void insert_vdi(struct sd_inode *new)
-{
-	struct vdi_entry *vdi;
-	vdi = find_vdi(new->name);
-	if (!vdi) {
-		vdi = new_vdi(new->name,
-			      new->vdi_size,
-			      new->vdi_id,
-			      new->snap_id,
-			      new->nr_copies);
-		list_add(&vdi->list, &last_vdi_list);
-	} else if (vdi->snap_id < new->snap_id) {
-		vdi->vdi_size = new->vdi_size;
-		vdi->vdi_id = new->vdi_id;
-		vdi->snap_id = new->snap_id;
-		vdi->nr_copies = new->nr_copies;
-	}
-}
-
-static int create_active_vdis(void)
-{
-	struct vdi_entry *vdi;
-	uint32_t new_vid;
-	list_for_each_entry(vdi, &last_vdi_list, list) {
-		if (do_vdi_create(vdi->name,
-				  vdi->vdi_size,
-				  vdi->vdi_id, &new_vid,
-				  false, vdi->nr_copies) < 0)
-			return -1;
-	}
-	return 0;
-}
-
-static void free_vdi_list(void)
-{
-	struct vdi_entry *vdi, *next;
-	list_for_each_entry_safe(vdi, next, &last_vdi_list, list)
-		free(vdi);
-}
-
-char *get_object_directory(void)
-{
-	return farm_object_dir;
-}
-
-static int create_directory(const char *p)
-{
-	int ret = -1;
-	struct strbuf buf = STRBUF_INIT;
-
-	strbuf_addstr(&buf, p);
-	if (xmkdir(buf.buf, 0755) < 0) {
-		if (errno == EEXIST)
-			sd_err("Path is not a directory: %s", p);
-		goto out;
-	}
-
-	if (!strlen(farm_dir))
-		strbuf_copyout(&buf, farm_dir, sizeof(farm_dir));
-
-	strbuf_addstr(&buf, "/objects");
-	if (xmkdir(buf.buf, 0755) < 0)
-		goto out;
-
-	for (int i = 0; i < 256; i++) {
-		strbuf_addf(&buf, "/%02x", i);
-		if (xmkdir(buf.buf, 0755) < 0)
-			goto out;
-
-		strbuf_remove(&buf, buf.len - 3, 3);
-	}
-
-	if (!strlen(farm_object_dir))
-		strbuf_copyout(&buf, farm_object_dir, sizeof(farm_object_dir));
-
-	ret = 0;
-out:
-	if (ret)
-		sd_err("Fail to create directory: %m");
-	strbuf_release(&buf);
-	return ret;
-}
-
-static int get_trunk_sha1(uint32_t idx, const char *tag, unsigned char *outsha1)
-{
-	int nr_logs = -1, ret = -1;
-	struct snap_log *log_buf, *log_free = NULL;
-	struct snap_file *snap_buf = NULL;
-
-	log_free = log_buf = snap_log_read(&nr_logs);
-	if (nr_logs < 0)
-		goto out;
-
-	for (int i = 0; i < nr_logs; i++, log_buf++) {
-		if (log_buf->idx != idx && strcmp(log_buf->tag, tag))
-			continue;
-		snap_buf = snap_file_read(log_buf->sha1);
-		if (!snap_buf)
-			goto out;
-		memcpy(outsha1, snap_buf->trunk_sha1, SHA1_DIGEST_SIZE);
-		ret = 0;
-		goto out;
-	}
-out:
-	free(log_free);
-	free(snap_buf);
-	return ret;
-}
-
-static int notify_vdi_add(uint32_t vdi_id, uint32_t nr_copies)
-{
-	int ret = -1;
-	struct sd_req hdr;
-	char *buf = NULL;
-
-	sd_init_req(&hdr, SD_OP_NOTIFY_VDI_ADD);
-	hdr.vdi_state.new_vid = vdi_id;
-	hdr.vdi_state.copies = nr_copies;
-	hdr.vdi_state.set_bitmap = true;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, buf);
-
-	if (ret)
-		sd_err("Fail to notify vdi add event(%"PRIx32", %d)", vdi_id,
-		       nr_copies);
-
-	free(buf);
-	return ret;
-}
-
-int farm_init(const char *path)
-{
-	int ret = -1;
-
-	if (create_directory(path) < 0)
-		goto out;
-	if (snap_init(farm_dir) < 0)
-		goto out;
-	return 0;
-out:
-	if (ret)
-		sd_err("Fail to init farm.");
-	return ret;
-}
-
-bool farm_contain_snapshot(uint32_t idx, const char *tag)
-{
-	unsigned char trunk_sha1[SHA1_DIGEST_SIZE];
-	return (get_trunk_sha1(idx, tag, trunk_sha1) == 0);
-}
-
-static void do_save_object(struct work *work)
-{
-	void *buf;
-	size_t size;
-	struct snapshot_work *sw;
-
-	if (uatomic_is_true(&work_error))
-		return;
-
-	sw = container_of(work, struct snapshot_work, work);
-
-	size = get_objsize(sw->entry.oid);
-	buf = xmalloc(size);
-
-	if (sd_read_object(sw->entry.oid, buf, size, 0, true) < 0)
-		goto error;
-
-	if (slice_write(buf, size, sw->entry.sha1) < 0)
-		goto error;
-
-	free(buf);
-	return;
-error:
-	free(buf);
-	sd_err("Fail to save object, oid %"PRIx64, sw->entry.oid);
-	uatomic_set_true(&work_error);
-}
-
-static void farm_show_progress(uint64_t done, uint64_t total)
-{
-	return show_progress(done, total, true);
-}
-
-static void save_object_done(struct work *work)
-{
-	struct snapshot_work *sw = container_of(work, struct snapshot_work,
-						work);
-	static unsigned long saved;
-
-	if (uatomic_is_true(&work_error))
-		goto out;
-
-	strbuf_add(sw->trunk_buf, &sw->entry, sizeof(struct trunk_entry));
-	farm_show_progress(uatomic_add_return(&saved, 1), object_tree_size());
-out:
-	free(sw);
-}
-
-static int queue_save_snapshot_work(uint64_t oid, int nr_copies, void *data)
-{
-	struct snapshot_work *sw = xzalloc(sizeof(struct snapshot_work));
-	struct strbuf *trunk_buf = data;
-
-	sw->entry.oid = oid;
-	sw->entry.nr_copies = nr_copies;
-	sw->trunk_buf = trunk_buf;
-	sw->work.fn = do_save_object;
-	sw->work.done = save_object_done;
-	queue_work(wq, &sw->work);
-
-	return 0;
-}
-
-int farm_save_snapshot(const char *tag)
-{
-	unsigned char snap_sha1[SHA1_DIGEST_SIZE];
-	unsigned char trunk_sha1[SHA1_DIGEST_SIZE];
-	struct strbuf trunk_buf;
-	void *snap_log = NULL;
-	int log_nr, idx, ret = -1;
-	uint64_t nr_objects = object_tree_size();
-
-	snap_log = snap_log_read(&log_nr);
-	if (!snap_log)
-		goto out;
-
-	idx = log_nr + 1;
-
-	strbuf_init(&trunk_buf, sizeof(struct trunk_entry) * nr_objects);
-
-	wq = create_work_queue("save snapshot", WQ_ORDERED);
-	if (for_each_object_in_tree(queue_save_snapshot_work,
-				    &trunk_buf) < 0)
-		goto out;
-
-	work_queue_wait(wq);
-	if (uatomic_is_true(&work_error))
-		goto out;
-
-	if (trunk_file_write(nr_objects, (struct trunk_entry *)trunk_buf.buf,
-			     trunk_sha1) < 0)
-		goto out;
-
-	if (snap_file_write(idx, trunk_sha1, snap_sha1) < 0)
-		goto out;
-
-	if (snap_log_write(idx, tag, snap_sha1) < 0)
-		goto out;
-
-	ret = 0;
-out:
-	strbuf_release(&trunk_buf);
-	free(snap_log);
-	return ret;
-}
-
-static void do_load_object(struct work *work)
-{
-	void *buffer = NULL;
-	size_t size;
-	struct snapshot_work *sw;
-	static unsigned long loaded;
-
-	if (uatomic_is_true(&work_error))
-		return;
-
-	sw = container_of(work, struct snapshot_work, work);
-
-	buffer = slice_read(sw->entry.sha1, &size);
-
-	if (!buffer)
-		goto error;
-
-	if (sd_write_object(sw->entry.oid, 0, buffer, size, 0, 0,
-			    sw->entry.nr_copies, true, true) != 0)
-		goto error;
-
-	if (is_vdi_obj(sw->entry.oid)) {
-		if (notify_vdi_add(oid_to_vid(sw->entry.oid),
-				   sw->entry.nr_copies) < 0)
-			goto error;
-
-		sd_write_lock(&vdi_list_lock);
-		insert_vdi(buffer);
-		sd_unlock(&vdi_list_lock);
-	}
-
-	farm_show_progress(uatomic_add_return(&loaded, 1), trunk_get_count());
-	free(buffer);
-	return;
-error:
-	free(buffer);
-	sd_err("Fail to load object, oid %"PRIx64, sw->entry.oid);
-	uatomic_set_true(&work_error);
-}
-
-static void load_object_done(struct work *work)
-{
-	struct snapshot_work *sw = container_of(work, struct snapshot_work,
-						work);
-
-	free(sw);
-}
-
-static int queue_load_snapshot_work(struct trunk_entry *entry, void *data)
-{
-	struct snapshot_work *sw = xzalloc(sizeof(struct snapshot_work));
-
-	memcpy(&sw->entry, entry, sizeof(struct trunk_entry));
-	sw->work.fn = do_load_object;
-	sw->work.done = load_object_done;
-	queue_work(wq, &sw->work);
-
-	return 0;
-}
-
-int farm_load_snapshot(uint32_t idx, const char *tag)
-{
-	int ret = -1;
-	unsigned char trunk_sha1[SHA1_DIGEST_SIZE];
-
-	if (get_trunk_sha1(idx, tag, trunk_sha1) < 0)
-		goto out;
-
-	wq = create_work_queue("load snapshot", WQ_DYNAMIC);
-	if (for_each_entry_in_trunk(trunk_sha1, queue_load_snapshot_work,
-				    NULL) < 0)
-		goto out;
-
-	work_queue_wait(wq);
-	if (uatomic_is_true(&work_error))
-		goto out;
-
-	if (create_active_vdis() < 0)
-		goto out;
-
-	ret = 0;
-out:
-	free_vdi_list();
-	return ret;
-}
diff --git a/collie/farm/farm.h b/collie/farm/farm.h
deleted file mode 100644
index 4c03a83..0000000
--- a/collie/farm/farm.h
+++ /dev/null
@@ -1,83 +0,0 @@
-#ifndef FARM_H
-#define FARM_H
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <inttypes.h>
-#include <memory.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <sys/mman.h>
-#include <linux/limits.h>
-
-#include "collie.h"
-#include "sheep.h"
-#include "strbuf.h"
-#include "sha1.h"
-
-struct trunk_entry {
-	uint64_t oid;
-	int nr_copies;
-	unsigned char sha1[SHA1_DIGEST_SIZE];
-};
-
-struct trunk_file {
-	uint64_t nr_entries;
-	struct trunk_entry *entries;
-};
-
-struct snap_file {
-	int idx;
-	unsigned char trunk_sha1[SHA1_DIGEST_SIZE];
-};
-
-struct snap_log {
-	uint32_t idx;
-	char tag[SD_MAX_SNAPSHOT_TAG_LEN];
-	uint64_t time;
-	unsigned char sha1[SHA1_DIGEST_SIZE];
-};
-
-/* farm.c */
-int farm_init(const char *path);
-bool farm_contain_snapshot(uint32_t idx, const char *tag);
-int farm_save_snapshot(const char *tag);
-int farm_load_snapshot(uint32_t idx, const char *tag);
-char *get_object_directory(void);
-
-/* trunk.c */
-int trunk_init(void);
-int trunk_file_write(uint64_t nr_entries, struct trunk_entry *entries,
-		     unsigned char *trunk_sha1);
-int for_each_entry_in_trunk(unsigned char *trunk_sha1,
-			    int (*func)(struct trunk_entry *entry, void *data),
-			    void *data);
-uint64_t trunk_get_count(void);
-
-/* snap.c */
-int snap_init(const char *path);
-struct snap_file *snap_file_read(unsigned char *sha1);
-int snap_file_write(uint32_t idx, unsigned char *trunk_sha1,
-		    unsigned char *outsha1);
-void *snap_log_read(int *out_nr);
-int snap_log_write(uint32_t idx, const char *tag, unsigned char *sha1);
-
-/* sha1_file.c */
-int sha1_file_write(void *buf, size_t len, unsigned char *sha1);
-void *sha1_file_read(const unsigned char *sha1, size_t *size);
-
-/* object_tree.c */
-int object_tree_size(void);
-void object_tree_insert(uint64_t oid, int nr_copies);
-void object_tree_free(void);
-void object_tree_print(void);
-int for_each_object_in_tree(int (*func)(uint64_t oid, int nr_copies,
-					void *data), void *data);
-/* slice.c */
-int slice_write(void *buf, size_t len, unsigned char *outsha1);
-void *slice_read(const unsigned char *sha1, size_t *outsize);
-
-#endif
diff --git a/collie/farm/object_tree.c b/collie/farm/object_tree.c
deleted file mode 100644
index 00cad2d..0000000
--- a/collie/farm/object_tree.c
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2013 Zelin.io
- *
- * Kai Zhang <kyle at zelin.io>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "farm.h"
-#include "rbtree.h"
-
-struct object_tree_entry {
-	uint64_t oid;
-	int nr_copies;
-	struct rb_node node;
-	struct list_head list;
-};
-
-struct object_tree {
-	int nr_objs;
-	struct rb_root root;
-	struct list_head list;
-};
-
-static struct object_tree tree = {
-	.nr_objs = 0,
-	.root = RB_ROOT,
-	.list = LIST_HEAD_INIT(tree.list)
-};
-static struct object_tree_entry *cached_entry;
-
-static struct object_tree_entry *do_insert(struct rb_root *root,
-				      struct object_tree_entry *new)
-{
-	struct rb_node **p = &root->rb_node;
-	struct rb_node *parent = NULL;
-	struct object_tree_entry *entry;
-
-	while (*p) {
-		parent = *p;
-		entry = rb_entry(parent, struct object_tree_entry, node);
-
-		if (new->oid < entry->oid)
-			p = &(*p)->rb_left;
-		else if (new->oid > entry->oid)
-			p = &(*p)->rb_right;
-		else
-			return entry; /* already has this entry */
-	}
-	rb_link_node(&new->node, parent, p);
-	rb_insert_color(&new->node, root);
-
-	return NULL; /* insert sucessfully */
-}
-
-void object_tree_insert(uint64_t oid, int nr_copies)
-{
-	struct rb_root *root = &tree.root;
-	struct object_tree_entry *p = NULL;
-
-	if (!cached_entry)
-		cached_entry = xzalloc(sizeof(*cached_entry));
-	cached_entry->oid = oid;
-	cached_entry->nr_copies = nr_copies;
-	rb_init_node(&cached_entry->node);
-	p = do_insert(root, cached_entry);
-	if (!p) {
-		list_add(&cached_entry->list, &tree.list);
-		tree.nr_objs++;
-		cached_entry = NULL;
-	}
-}
-
-void object_tree_print(void)
-{
-	struct rb_node *p = rb_first(&tree.root);
-	struct object_tree_entry *entry;
-	printf("nr_objs: %d\n", tree.nr_objs);
-
-	while (p) {
-		entry = rb_entry(p, struct object_tree_entry, node);
-		printf("Obj id: %"PRIx64"\n", entry->oid);
-		p = rb_next(p);
-	}
-}
-
-void object_tree_free(void)
-{
-	struct object_tree_entry *entry, *next;
-	list_for_each_entry_safe(entry, next, &tree.list, list)
-		free(entry);
-
-	free(cached_entry);
-}
-
-int object_tree_size(void)
-{
-	return tree.nr_objs;
-}
-
-int for_each_object_in_tree(int (*func)(uint64_t oid, int nr_copies,
-					void *data), void *data)
-{
-	struct rb_node *p = rb_first(&tree.root);
-	struct object_tree_entry *entry;
-	int ret = -1;
-
-	while (p) {
-		entry = rb_entry(p, struct object_tree_entry, node);
-
-		if (func(entry->oid, entry->nr_copies, data) < 0)
-			goto out;
-
-		p = rb_next(p);
-	}
-	ret = 0;
-out:
-	return ret;
-}
diff --git a/collie/farm/sha1_file.c b/collie/farm/sha1_file.c
deleted file mode 100644
index 3ba2519..0000000
--- a/collie/farm/sha1_file.c
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2011 Taobao Inc.
- *
- * Liu Yuan <namei.unix at gmail.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- *   sha1_file provide us some useful features:
- *
- *   - Regardless of object type, all objects are all in deflated with zlib,
- *     and have a header that not only specifies their tag, but also size
- *     information about the data in the object.
- *
- *   - the general consistency of an object can always be tested independently
- *     of the contents or the type of the object: all objects can be validated
- *     by verifying that their hashes match the content of the file.
- */
-#include <sys/types.h>
-
-#include "farm.h"
-#include "util.h"
-
-static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
-{
-	int i;
-	for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
-		static const char hex[] = "0123456789abcdef";
-		unsigned int val = sha1[i];
-		char *pos = pathbuf + i*2 + (i > 0);
-		*pos++ = hex[val >> 4];
-		*pos = hex[val & 0xf];
-	}
-}
-
-static char *sha1_to_path(const unsigned char *sha1)
-{
-	static __thread char buf[PATH_MAX];
-	const char *objdir;
-	int len;
-
-	objdir = get_object_directory();
-	len = strlen(objdir);
-
-	/* '/' + sha1(2) + '/' + sha1(38) + '\0' */
-	memcpy(buf, objdir, len);
-	buf[len] = '/';
-	buf[len+3] = '/';
-	buf[len+42] = '\0';
-	fill_sha1_path(buf + len + 1, sha1);
-	return buf;
-}
-
-static int sha1_buffer_write(const unsigned char *sha1,
-			     void *buf, unsigned int size)
-{
-	char *filename = sha1_to_path(sha1);
-	int fd, ret = 0, len;
-
-	fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
-	if (fd < 0) {
-		if (errno != EEXIST) {
-			sd_err("failed to open file %s with error: %m",
-			       filename);
-			ret = -1;
-		}
-		goto err_open;
-	}
-	len = xwrite(fd, buf, size);
-	if (len != size) {
-		sd_err("%m");
-		close(fd);
-		return -1;
-	}
-
-	close(fd);
-err_open:
-	return ret;
-}
-
-int sha1_file_write(void *buf, size_t len, unsigned char *outsha1)
-{
-	unsigned char sha1[SHA1_DIGEST_SIZE];
-
-	sha1_from_buffer(buf, len, sha1);
-	if (sha1_buffer_write(sha1, buf, len) < 0)
-		return -1;
-	if (outsha1)
-		memcpy(outsha1, sha1, SHA1_DIGEST_SIZE);
-	return 0;
-}
-
-static int verify_sha1_file(const unsigned char *sha1,
-			    void *buf, unsigned long len)
-{
-	unsigned char tmp[SHA1_DIGEST_SIZE];
-
-	sha1_from_buffer(buf, len, tmp);
-	if (memcmp((char *)tmp, (char *)sha1, SHA1_DIGEST_SIZE) != 0) {
-		sd_err("failed, %s != %s", sha1_to_hex(sha1), sha1_to_hex(tmp));
-		return -1;
-	}
-	return 0;
-}
-
-void *sha1_file_read(const unsigned char *sha1, size_t *size)
-{
-	char *filename = sha1_to_path(sha1);
-	int fd = open(filename, O_RDONLY);
-	struct stat st;
-	void *buf = NULL;
-
-	if (fd < 0) {
-		perror(filename);
-		return NULL;
-	}
-	if (fstat(fd, &st) < 0) {
-		sd_err("%m");
-		goto out;
-	}
-
-	buf = xmalloc(st.st_size);
-	if (!buf)
-		goto out;
-
-	if (xread(fd, buf, st.st_size) != st.st_size) {
-		free(buf);
-		buf = NULL;
-		goto out;
-	}
-
-	if (verify_sha1_file(sha1, buf, st.st_size) < 0) {
-		free(buf);
-		buf = NULL;
-		goto out;
-	}
-
-	*size = st.st_size;
-out:
-	close(fd);
-	return buf;
-}
diff --git a/collie/farm/slice.c b/collie/farm/slice.c
deleted file mode 100644
index 53941c1..0000000
--- a/collie/farm/slice.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * copyright (c) 2013 taobao inc.
- *
- * liu yuan <namei.unix at gmail.com>
- *
- * this program is free software; you can redistribute it and/or
- * modify it under the terms of the gnu general public license version
- * 2 as published by the free software foundation.
- *
- * you should have received a copy of the gnu general public license
- * along with this program. if not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- * Slice is a fixed chunk of one object to be stored in farm. We slice
- * the object into smaller chunks to get better deduplication.
- */
-
-#include <pthread.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "farm.h"
-#include "strbuf.h"
-#include "util.h"
-#include "sheepdog_proto.h"
-
-struct slice {
-	unsigned char sha1[SHA1_DIGEST_SIZE];
-};
-
-struct slice_file {
-	uint32_t nr_slices;
-	struct slice *slices;
-};
-
-/* 128k, best empirical value from some tests, but no rationale */
-#define SLICE_SIZE (1024*128)
-
-int slice_write(void *buf, size_t len, unsigned char *outsha1)
-{
-	int count = DIV_ROUND_UP(len, SLICE_SIZE);
-	size_t slen = count * SHA1_DIGEST_SIZE;
-	char *sbuf = xmalloc(slen);
-	char *p = buf;
-
-	for (int i = 0; i < count; i++, p += SLICE_SIZE) {
-		unsigned char sha1[SHA1_DIGEST_SIZE];
-		size_t wlen = (ssize_t)len - SLICE_SIZE > 0 ? SLICE_SIZE : len;
-		len -= SLICE_SIZE;
-
-		if (sha1_file_write(p, wlen, sha1) < 0)
-			goto err;
-		memcpy(sbuf + i * SHA1_DIGEST_SIZE, sha1, SHA1_DIGEST_SIZE);
-	}
-
-	if (sha1_file_write(sbuf, slen, outsha1) < 0)
-		goto err;
-	free(sbuf);
-	return 0;
-err:
-	free(sbuf);
-	return -1;
-}
-
-static struct slice_file *slice_file_read(const unsigned char *sha1)
-{
-	size_t size;
-	struct slice_file *slice_file = NULL;
-	void *buf = sha1_file_read(sha1, &size);
-
-	if (!buf)
-		return NULL;
-	slice_file = xmalloc(sizeof(struct slice_file));
-	slice_file->nr_slices = size / SHA1_DIGEST_SIZE;
-	slice_file->slices = buf;
-
-	return slice_file;
-}
-
-void *slice_read(const unsigned char *sha1, size_t *outsize)
-{
-	struct slice_file *file = slice_file_read(sha1);
-	struct strbuf buf = STRBUF_INIT;
-	void *object;
-
-	if (!file)
-		goto err;
-
-	*outsize = 0;
-	for (uint32_t i = 0; i < file->nr_slices; i++) {
-		size_t size;
-		void *sbuf;
-
-		sbuf = sha1_file_read(file->slices[i].sha1, &size);
-		if (!sbuf)
-			goto err;
-		strbuf_add(&buf, sbuf, size);
-		*outsize += size;
-	}
-
-	object = xmalloc(*outsize);
-	strbuf_copyout(&buf, object, *outsize);
-	strbuf_release(&buf);
-	return object;
-err:
-	strbuf_release(&buf);
-	return NULL;
-}
diff --git a/collie/farm/snap.c b/collie/farm/snap.c
deleted file mode 100644
index 8c46484..0000000
--- a/collie/farm/snap.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2011 Taobao Inc.
- * Copyright (C) 2013 Zelin.io
- *
- * Liu Yuan <namei.unix at gmail.com>
- * Kai Zhang <kyle at zelin.io>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/* Snap object is the meta data that describes the cluster snapshot. */
-
-#include <time.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "farm.h"
-
-static char snap_log_path[PATH_MAX];
-
-int snap_init(const char *farm_dir)
-{
-	int fd, ret = -1;
-	struct strbuf buf = STRBUF_INIT;
-
-	strbuf_addstr(&buf, farm_dir);
-	strbuf_addf(&buf, "/%s", "user_snap");
-
-	if (!strlen(snap_log_path))
-		strbuf_copyout(&buf, snap_log_path, sizeof(snap_log_path));
-
-	fd = open(snap_log_path, O_CREAT | O_EXCL, 0666);
-	if (fd < 0) {
-		if (errno != EEXIST) {
-			sd_err("%m");
-			goto out;
-		}
-	}
-
-	ret = 0;
-	close(fd);
-out:
-	strbuf_release(&buf);
-	return ret;
-}
-
-int snap_log_write(uint32_t idx, const char *tag, unsigned char *sha1)
-{
-	int fd, ret = -1;
-	struct strbuf buf = STRBUF_INIT;
-	struct snap_log log = { .idx = idx,
-				.time = time(NULL) };
-	pstrcpy(log.tag, SD_MAX_SNAPSHOT_TAG_LEN, tag);
-	memcpy(log.sha1, sha1, SHA1_DIGEST_SIZE);
-
-	fd = open(snap_log_path, O_WRONLY | O_APPEND);
-	if (fd < 0) {
-		sd_err("%m");
-		goto out;
-	}
-
-	strbuf_reset(&buf);
-	strbuf_add(&buf, &log, sizeof(log));
-	ret = xwrite(fd, buf.buf, buf.len);
-	if (ret != buf.len)
-		goto out_close;
-
-	ret = 0;
-out_close:
-	close(fd);
-out:
-	strbuf_release(&buf);
-	return ret;
-}
-
-void *snap_log_read(int *out_nr)
-{
-	struct stat st;
-	void *buffer = NULL;
-	int len, fd;
-
-	fd = open(snap_log_path, O_RDONLY);
-	if (fd < 0) {
-		sd_err("%m");
-		goto out;
-	}
-	if (fstat(fd, &st) < 0) {
-		sd_err("%m");
-		goto out_close;
-	}
-
-	len = st.st_size;
-	buffer = xmalloc(len);
-	len = xread(fd, buffer, len);
-	if (len != st.st_size) {
-		free(buffer);
-		buffer = NULL;
-		goto out_close;
-	}
-	*out_nr = len / sizeof(struct snap_log);
-out_close:
-	close(fd);
-out:
-	return buffer;
-}
-
-struct snap_file *snap_file_read(unsigned char *sha1)
-{
-	size_t size;
-	return sha1_file_read(sha1, &size);
-}
-
-int snap_file_write(uint32_t idx, unsigned char *trunk_sha1,
-		    unsigned char *outsha1)
-{
-	struct snap_file snap;
-	snap.idx = idx;
-	memcpy(snap.trunk_sha1, trunk_sha1, SHA1_DIGEST_SIZE);
-
-	return sha1_file_write(&snap, sizeof(struct snap_file),
-			       outsha1);
-}
diff --git a/collie/farm/trunk.c b/collie/farm/trunk.c
deleted file mode 100644
index 91f293e..0000000
--- a/collie/farm/trunk.c
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2011 Taobao Inc.
- * Copyright (C) 2013 Zelin.io
- *
- * Liu Yuan <namei.unix at gmail.com>
- * Kai Zhang <kyle at zelin.io>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- * Trunk object is meta data that describes the structure of the data objects
- * at the time of snapshot being taken. It ties data objects together into a
- * flat directory structure.
- */
-#include <pthread.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "farm.h"
-#include "strbuf.h"
-#include "list.h"
-#include "util.h"
-#include "sheepdog_proto.h"
-
-static uint64_t total_count;
-
-int trunk_file_write(uint64_t nr_entries, struct trunk_entry *entries,
-		     unsigned char *trunk_sha1)
-{
-	size_t size = sizeof(struct trunk_entry) * nr_entries;
-	return sha1_file_write(entries, size, trunk_sha1);
-}
-
-static struct trunk_file *trunk_file_read(unsigned char *sha1)
-{
-	size_t size;
-	struct trunk_file *trunk = NULL;
-	void *buf = sha1_file_read(sha1, &size);
-
-	if (!buf)
-		return NULL;
-	trunk = xmalloc(sizeof(struct trunk_file));
-	trunk->nr_entries = size / sizeof(struct trunk_entry);
-	trunk->entries = buf;
-
-	return trunk;
-}
-
-int for_each_entry_in_trunk(unsigned char *trunk_sha1,
-			    int (*func)(struct trunk_entry *entry, void *data),
-			    void *data)
-{
-	struct trunk_file *trunk;
-	struct trunk_entry *entry;
-	int ret = -1;
-
-	trunk = trunk_file_read(trunk_sha1);
-	if (!trunk)
-		goto out;
-
-	total_count = trunk->nr_entries;
-	entry = trunk->entries;
-	for (uint64_t i = 0; i < trunk->nr_entries; i++, entry++) {
-		if (func(entry, data) < 0)
-			goto out;
-	}
-
-	ret = 0;
-out:
-	free(trunk->entries);
-	free(trunk);
-	return ret;
-}
-
-uint64_t trunk_get_count(void)
-{
-	return total_count;
-}
diff --git a/collie/node.c b/collie/node.c
deleted file mode 100644
index 028d804..0000000
--- a/collie/node.c
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "collie.h"
-
-static struct node_cmd_data {
-	bool all_nodes;
-	bool recovery_progress;
-} node_cmd_data;
-
-static void cal_total_vdi_size(uint32_t vid, const char *name, const char *tag,
-			       uint32_t snapid, uint32_t flags,
-			       const struct sd_inode *i, void *data)
-{
-	uint64_t *size = data;
-
-	if (!vdi_is_snapshot(i))
-		*size += i->vdi_size;
-}
-
-static int node_list(int argc, char **argv)
-{
-	int i;
-
-	if (!raw_output)
-		printf("  Id   Host:Port         V-Nodes       Zone\n");
-	for (i = 0; i < sd_nodes_nr; i++) {
-		const char *host = addr_to_str(sd_nodes[i].nid.addr,
-					       sd_nodes[i].nid.port);
-
-		printf(raw_output ? "%d %s %d %u\n" : "%4d   %-20s\t%2d%11u\n",
-		       i, host, sd_nodes[i].nr_vnodes, sd_nodes[i].zone);
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int node_info(int argc, char **argv)
-{
-	int i, ret, success = 0;
-	uint64_t total_size = 0, total_avail = 0, total_vdi_size = 0;
-	char total_str[UINT64_DECIMAL_SIZE], use_str[UINT64_DECIMAL_SIZE],
-	     avail_str[UINT64_DECIMAL_SIZE], vdi_size_str[UINT64_DECIMAL_SIZE];
-
-	if (!raw_output)
-		printf("Id\tSize\tUsed\tAvail\tUse%%\n");
-
-	for (i = 0; i < sd_nodes_nr; i++) {
-		struct sd_req req;
-		struct sd_rsp *rsp = (struct sd_rsp *)&req;
-		char store_str[UINT64_DECIMAL_SIZE],
-		     used_str[UINT64_DECIMAL_SIZE],
-		     free_str[UINT64_DECIMAL_SIZE];
-
-		sd_init_req(&req, SD_OP_STAT_SHEEP);
-
-		ret = send_light_req(&req, sd_nodes[i].nid.addr,
-				     sd_nodes[i].nid.port);
-
-		size_to_str(rsp->node.store_size, store_str, sizeof(store_str));
-		size_to_str(rsp->node.store_free, free_str, sizeof(free_str));
-		size_to_str(rsp->node.store_size - rsp->node.store_free,
-			    used_str, sizeof(used_str));
-		if (!ret) {
-			int ratio = (int)(((double)(rsp->node.store_size -
-						    rsp->node.store_free) /
-					   rsp->node.store_size) * 100);
-			printf(raw_output ? "%d %s %s %s %d%%\n" :
-					"%2d\t%s\t%s\t%s\t%3d%%\n",
-			       i, store_str, used_str, free_str,
-			       rsp->node.store_size == 0 ? 0 : ratio);
-			success++;
-		}
-
-		total_size += rsp->node.store_size;
-		total_avail += rsp->node.store_free;
-	}
-
-	if (success == 0) {
-		sd_err("Cannot get information from any nodes");
-		return EXIT_SYSFAIL;
-	}
-
-	if (parse_vdi(cal_total_vdi_size, SD_INODE_HEADER_SIZE,
-			&total_vdi_size) < 0)
-		return EXIT_SYSFAIL;
-
-	size_to_str(total_size, total_str, sizeof(total_str));
-	size_to_str(total_avail, avail_str, sizeof(avail_str));
-	size_to_str(total_size - total_avail, use_str, sizeof(use_str));
-	size_to_str(total_vdi_size, vdi_size_str, sizeof(vdi_size_str));
-	printf(raw_output ? "Total %s %s %s %d%% %s\n"
-			  : "Total\t%s\t%s\t%s\t%3d%%\n\n"
-			  "Total virtual image size\t%s\n",
-	       total_str, use_str, avail_str,
-	       (int)(((double)(total_size - total_avail) / total_size) * 100),
-	       vdi_size_str);
-
-	return EXIT_SUCCESS;
-}
-
-static int get_recovery_state(struct recovery_state *state)
-{
-	int ret;
-	struct sd_req req;
-
-	sd_init_req(&req, SD_OP_STAT_RECOVERY);
-	req.data_length = sizeof(*state);
-
-	ret = collie_exec_req(sdhost, sdport, &req, state);
-	if (ret < 0) {
-		sd_err("Failed to execute request");
-		return -1;
-	}
-
-	return 0;
-}
-
-static int node_recovery_progress(void)
-{
-	int result;
-	unsigned int prev_nr_total;
-	struct recovery_state rstate;
-
-	/*
-	 * ToDos
-	 *
-	 * 1. Calculate size of actually copied objects.
-	 *    For doing this, not so trivial changes for recovery process are
-	 *    required.
-	 *
-	 * 2. Print remaining physical time.
-	 *    Even if it is not so acculate, the information is helpful for
-	 *    administrators.
-	 */
-
-	result = get_recovery_state(&rstate);
-	if (result < 0)
-		return EXIT_SYSFAIL;
-
-	if (!rstate.in_recovery)
-		return EXIT_SUCCESS;
-
-	do {
-		prev_nr_total = rstate.nr_total;
-
-		result = get_recovery_state(&rstate);
-		if (result < 0)
-			break;
-
-		if (!rstate.in_recovery) {
-			show_progress(prev_nr_total, prev_nr_total, true);
-			break;
-		}
-
-		switch (rstate.state) {
-		case RW_PREPARE_LIST:
-			printf("\rpreparing a checked object list...");
-			break;
-		case RW_NOTIFY_COMPLETION:
-			printf("\rnotifying a completion of recovery...");
-			break;
-		case RW_RECOVER_OBJ:
-			show_progress(rstate.nr_finished, rstate.nr_total,
-				      true);
-			break;
-		default:
-			panic("unknown state of recovery: %d", rstate.state);
-			break;
-		}
-
-		sleep(1);
-	} while (true);
-
-	return result < 0 ? EXIT_SYSFAIL : EXIT_SUCCESS;
-}
-
-static int node_recovery(int argc, char **argv)
-{
-	int i, ret;
-
-	if (node_cmd_data.recovery_progress)
-		return node_recovery_progress();
-
-	if (!raw_output) {
-		printf("Nodes In Recovery:\n");
-		printf("  Id   Host:Port         V-Nodes       Zone"
-		       "       Progress\n");
-	}
-
-	for (i = 0; i < sd_nodes_nr; i++) {
-		struct sd_req req;
-		struct recovery_state state;
-
-		memset(&state, 0, sizeof(state));
-
-		sd_init_req(&req, SD_OP_STAT_RECOVERY);
-		req.data_length = sizeof(state);
-
-		ret = collie_exec_req(sd_nodes[i].nid.addr,
-				      sd_nodes[i].nid.port, &req, &state);
-		if (ret < 0)
-			return EXIT_SYSFAIL;
-
-		if (state.in_recovery) {
-			const char *host = addr_to_str(sd_nodes[i].nid.addr,
-						       sd_nodes[i].nid.port);
-			if (raw_output)
-				printf("%d %s %d %d %"PRIu64" %"PRIu64"\n", i,
-				       host, sd_nodes[i].nr_vnodes,
-				       sd_nodes[i].zone, state.nr_finished,
-				       state.nr_total);
-			else
-				printf("%4d   %-20s%5d%11d%11.1f%%\n", i, host,
-				       sd_nodes[i].nr_vnodes, sd_nodes[i].zone,
-				       100 * (float)state.nr_finished
-				       / state.nr_total);
-		}
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int node_kill(int argc, char **argv)
-{
-	int node_id, ret;
-	struct sd_req req;
-	const char *p = argv[optind++];
-
-	if (!is_numeric(p)) {
-		sd_err("Invalid node id '%s', please specify a numeric value",
-		       p);
-		exit(EXIT_USAGE);
-	}
-
-	node_id = strtol(p, NULL, 10);
-	if (node_id < 0 || node_id >= sd_nodes_nr) {
-		sd_err("Invalid node id '%d'", node_id);
-		exit(EXIT_USAGE);
-	}
-
-	sd_init_req(&req, SD_OP_KILL_NODE);
-
-	ret = send_light_req(&req, sd_nodes[node_id].nid.addr,
-			     sd_nodes[node_id].nid.port);
-	if (ret) {
-		sd_err("Failed to execute request");
-		exit(EXIT_FAILURE);
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int node_md_info(struct node_id *nid)
-{
-	struct sd_md_info info = {};
-	char size_str[UINT64_DECIMAL_SIZE], used_str[UINT64_DECIMAL_SIZE],
-	     avail_str[UINT64_DECIMAL_SIZE];
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	int ret, i;
-
-	sd_init_req(&hdr, SD_OP_MD_INFO);
-	hdr.data_length = sizeof(info);
-
-	ret = collie_exec_req(nid->addr, nid->port, &hdr, &info);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("failed to get multi-disk infomation: %s",
-		       sd_strerror(rsp->result));
-		return EXIT_FAILURE;
-	}
-
-	for (i = 0; i < info.nr; i++) {
-		uint64_t size = info.disk[i].free + info.disk[i].used;
-		int ratio = (int)(((double)info.disk[i].used / size) * 100);
-
-		size_to_str(size, size_str, sizeof(size_str));
-		size_to_str(info.disk[i].used, used_str, sizeof(used_str));
-		size_to_str(info.disk[i].free, avail_str, sizeof(avail_str));
-		fprintf(stdout, "%2d\t%s\t%s\t%s\t%3d%%\t%s\n",
-			info.disk[i].idx, size_str, used_str, avail_str, ratio,
-			info.disk[i].path);
-	}
-	return EXIT_SUCCESS;
-}
-
-static int md_info(int argc, char **argv)
-{
-	int i, ret;
-
-	fprintf(stdout, "Id\tSize\tUsed\tAvail\tUse%%\tPath\n");
-
-	if (!node_cmd_data.all_nodes) {
-		struct node_id nid = {.port = sdport};
-
-		memcpy(nid.addr, sdhost, sizeof(nid.addr));
-
-		return node_md_info(&nid);
-	}
-
-	for (i = 0; i < sd_nodes_nr; i++) {
-		fprintf(stdout, "Node %d:\n", i);
-		ret = node_md_info(&sd_nodes[i].nid);
-		if (ret != EXIT_SUCCESS)
-			return EXIT_FAILURE;
-	}
-	return EXIT_SUCCESS;
-}
-
-static int do_plug_unplug(char *disks, bool plug)
-{
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	int ret;
-
-	if (!strlen(disks)) {
-		sd_err("Empty path isn't allowed");
-		return EXIT_FAILURE;
-	}
-
-	if (plug)
-		sd_init_req(&hdr, SD_OP_MD_PLUG);
-	else
-		sd_init_req(&hdr, SD_OP_MD_UNPLUG);
-	hdr.flags = SD_FLAG_CMD_WRITE;
-	hdr.data_length = strlen(disks) + 1;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, disks);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Failed to execute request, look for sheep.log"
-		       " for more information");
-		return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int md_plug(int argc, char **argv)
-{
-	return do_plug_unplug(argv[optind], true);
-}
-
-static int md_unplug(int argc, char **argv)
-{
-	return do_plug_unplug(argv[optind], false);
-}
-
-static struct subcommand node_md_cmd[] = {
-	{"info", NULL, NULL, "show multi-disk information",
-	 NULL, CMD_NEED_NODELIST, md_info},
-	{"plug", NULL, NULL, "plug more disk(s) into node",
-	 NULL, CMD_NEED_ARG, md_plug},
-	{"unplug", NULL, NULL, "unplug disk(s) from node",
-	 NULL, CMD_NEED_ARG, md_unplug},
-	{NULL},
-};
-
-static int node_md(int argc, char **argv)
-{
-	return do_generic_subcommand(node_md_cmd, argc, argv);
-}
-
-
-static int node_parser(int ch, char *opt)
-{
-	switch (ch) {
-	case 'A':
-		node_cmd_data.all_nodes = true;
-		break;
-	case 'P':
-		node_cmd_data.recovery_progress = true;
-		break;
-	}
-
-	return 0;
-}
-
-static struct sd_option node_options[] = {
-	{'A', "all", false, "show md information of all the nodes"},
-	{'P', "progress", false, "show progress of recovery in the node"},
-
-	{ 0, NULL, false, NULL },
-};
-
-static struct subcommand node_cmd[] = {
-	{"kill", "<node id>", "aprh", "kill node", NULL,
-	 CMD_NEED_ARG | CMD_NEED_NODELIST, node_kill},
-	{"list", NULL, "aprh", "list nodes", NULL,
-	 CMD_NEED_NODELIST, node_list},
-	{"info", NULL, "aprh", "show information about each node", NULL,
-	 CMD_NEED_NODELIST, node_info},
-	{"recovery", NULL, "aphPr", "show recovery information of nodes", NULL,
-	 CMD_NEED_NODELIST, node_recovery, node_options},
-	{"md", "[disks]", "apAh", "See 'collie node md' for more information",
-	 node_md_cmd, CMD_NEED_ARG, node_md, node_options},
-	{NULL,},
-};
-
-struct command node_command = {
-	"node",
-	node_cmd,
-	node_parser
-};
diff --git a/collie/trace.c b/collie/trace.c
deleted file mode 100644
index 1271e7c..0000000
--- a/collie/trace.c
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * Copyright (C) 2011 Taobao Inc.
- *
- * Liu Yuan <namei.unix at gmail.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <ctype.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <sys/mman.h>
-
-#include "collie.h"
-#include "rbtree.h"
-#include "list.h"
-
-static inline void print_thread_name(struct trace_graph_item *item)
-{
-	printf("%-*s|", TRACE_THREAD_LEN, item->tname);
-}
-
-static inline void print_time(struct trace_graph_item *item)
-{
-	if (item->type == TRACE_GRAPH_RETURN) {
-		unsigned duration = item->return_time - item->entry_time;
-		unsigned quot = duration / 1000, rem = duration % 1000;
-
-		printf("%8u.%-3u|", quot, rem);
-	} else if (item->type == TRACE_GRAPH_ENTRY) {
-		printf("            |");
-	}
-}
-
-static inline void print_finale(struct trace_graph_item *item)
-{
-	int i;
-
-	for (i = 0; i < item->depth; i++)
-		printf("   ");
-	if (item->type == TRACE_GRAPH_ENTRY)
-		printf("%s() {\n", item->fname);
-	else
-		printf("}\n");
-}
-
-static void print_trace_item(struct trace_graph_item *item)
-{
-	print_thread_name(item);
-	print_time(item);
-	print_finale(item);
-}
-
-static void cat_trace_file(void *buf, size_t size)
-{
-	struct trace_graph_item *item = (struct trace_graph_item *)buf;
-	size_t sz = size / sizeof(struct trace_graph_item), i;
-
-	printf("   Thread Name      |  Time(us)  |  Function Graph\n");
-	printf("--------------------------------------------------\n");
-	for (i = 0; i < sz; i++)
-		print_trace_item(item++);
-	return;
-}
-
-static const char *tracefile = "/tmp/tracefile";
-
-static int trace_read_buffer(void)
-{
-	int ret, tfd;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-#define TRACE_BUF_LEN      (1024 * 1024 * 20)
-	char *buf = xzalloc(TRACE_BUF_LEN);
-
-	tfd = open(tracefile, O_CREAT | O_RDWR | O_APPEND | O_TRUNC, 0644);
-	if (tfd < 0) {
-		sd_err("can't create tracefile");
-		return EXIT_SYSFAIL;
-	}
-
-read_buffer:
-	sd_init_req(&hdr, SD_OP_TRACE_READ_BUF);
-	hdr.data_length = TRACE_BUF_LEN;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, buf);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result == SD_RES_AGAIN)
-		goto read_buffer;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Trace failed: %s", sd_strerror(rsp->result));
-		return EXIT_FAILURE;
-	}
-
-	xwrite(tfd, buf, rsp->data_length);
-	if (rsp->data_length == TRACE_BUF_LEN) {
-		memset(buf, 0, TRACE_BUF_LEN);
-		goto read_buffer;
-	}
-
-	free(buf);
-	return EXIT_SUCCESS;
-}
-
-static int trace_enable(int argc, char **argv)
-{
-	const char *tracer = argv[optind];
-	int ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-
-	sd_init_req(&hdr, SD_OP_TRACE_ENABLE);
-	hdr.flags = SD_FLAG_CMD_WRITE;
-	hdr.data_length = strlen(tracer) + 1;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, (void *)tracer);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	switch (rsp->result) {
-	case SD_RES_SUCCESS:
-		break;
-	case SD_RES_NO_SUPPORT:
-		sd_err("no such tracer %s", tracer);
-		return EXIT_FAILURE;
-	case SD_RES_INVALID_PARMS:
-		sd_err("tracer %s is already enabled", tracer);
-		return EXIT_FAILURE;
-	default:
-		sd_err("unknown error (%s)", sd_strerror(rsp->result));
-		return EXIT_SYSFAIL;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int trace_disable(int argc, char **argv)
-{
-	const char *tracer = argv[optind];
-	int ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-
-	sd_init_req(&hdr, SD_OP_TRACE_DISABLE);
-	hdr.flags = SD_FLAG_CMD_WRITE;
-	hdr.data_length = strlen(tracer) + 1;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, (void *)tracer);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	switch (rsp->result) {
-	case SD_RES_SUCCESS:
-		break;
-	case SD_RES_NO_SUPPORT:
-		sd_err("no such tracer %s", tracer);
-		return EXIT_FAILURE;
-	case SD_RES_INVALID_PARMS:
-		sd_err("tracer %s is not enabled", tracer);
-		return EXIT_FAILURE;
-	default:
-		sd_err("unknown error (%s)", sd_strerror(rsp->result));
-		return EXIT_SYSFAIL;
-	}
-
-	return trace_read_buffer();
-}
-
-static int trace_status(int argc, char **argv)
-{
-	char buf[4096]; /* must have enough space to store tracer list */
-	int ret;
-	struct sd_req hdr;
-
-	sd_init_req(&hdr, SD_OP_TRACE_STATUS);
-	hdr.data_length = sizeof(buf);
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, buf);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	printf("%s", buf);
-
-	return EXIT_SUCCESS;
-}
-
-static void *map_trace_file(struct stat *st)
-{
-	int fd = open(tracefile, O_RDONLY);
-	void *map;
-
-	if (fd < 0) {
-		sd_err("%m");
-		return NULL;
-	}
-
-	if (fstat(fd, st) < 0) {
-		sd_err("%m");
-		close(fd);
-		return NULL;
-	}
-
-	if (st->st_size == 0) {
-		sd_err("trace file is empty");
-		return NULL;
-	}
-
-	map = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE, fd, 0);
-	close(fd);
-	if (map == MAP_FAILED) {
-		sd_err("%m");
-		return NULL;
-	}
-
-	return map;
-}
-
-static int graph_cat(int argc, char **argv)
-{
-	struct stat st;
-	void *map = map_trace_file(&st);
-
-	if (!map)
-		return EXIT_FAILURE;
-
-	cat_trace_file(map, st.st_size);
-	munmap(map, st.st_size);
-
-	return EXIT_SUCCESS;
-}
-
-struct graph_stat_entry {
-	struct rb_node rb;
-	struct list_head list;
-	char fname[TRACE_FNAME_LEN];
-	uint64_t duration;
-	uint16_t nr_calls;
-};
-
-static struct rb_root stat_tree_root;
-
-static LIST_HEAD(stat_list);
-
-static struct graph_stat_entry *
-stat_tree_insert(struct graph_stat_entry *new)
-{
-	struct rb_node **p = &stat_tree_root.rb_node;
-	struct rb_node *parent = NULL;
-	struct graph_stat_entry *entry;
-
-	while (*p) {
-		int cmp;
-
-		parent = *p;
-		entry = rb_entry(parent, struct graph_stat_entry, rb);
-		cmp = strcmp(new->fname, entry->fname);
-
-		if (cmp < 0)
-			p = &(*p)->rb_left;
-		else if (cmp > 0)
-			p = &(*p)->rb_right;
-		else {
-			entry->duration += new->duration;
-			entry->nr_calls++;
-			return entry;
-		}
-	}
-	rb_link_node(&new->rb, parent, p);
-	rb_insert_color(&new->rb, &stat_tree_root);
-
-	return NULL; /* insert successfully */
-}
-
-static void prepare_stat_tree(struct trace_graph_item *item)
-{
-	struct graph_stat_entry *new;
-
-	if (item->type != TRACE_GRAPH_RETURN)
-		return;
-	new = xmalloc(sizeof(*new));
-	pstrcpy(new->fname, sizeof(new->fname), item->fname);
-	new->duration = item->return_time - item->entry_time;
-	new->nr_calls = 1;
-	INIT_LIST_HEAD(&new->list);
-	if (stat_tree_insert(new)) {
-		free(new);
-		return;
-	}
-	list_add(&new->list, &stat_list);
-}
-
-static void stat_list_print(void)
-{
-	struct graph_stat_entry *entry;
-
-	list_for_each_entry(entry, &stat_list, list) {
-		float total = (float)entry->duration / 1000000000;
-		float per = (float)entry->duration / entry->nr_calls / 1000000;
-
-		printf("%10.3f   %10.3f        %5"PRIu16"   %-*s\n", total, per,
-		       entry->nr_calls, TRACE_FNAME_LEN, entry->fname);
-	}
-}
-
-static int stat_list_cmp(void *priv, struct list_head *a, struct list_head *b)
-{
-	struct graph_stat_entry *ga = container_of(a, struct graph_stat_entry,
-						   list);
-	struct graph_stat_entry *gb = container_of(b, struct graph_stat_entry,
-						   list);
-	/* '-' is for reverse sort, largest first */
-	return -intcmp(ga->duration, gb->duration);
-}
-
-static void stat_trace_file(void *buf, size_t size)
-{
-	struct trace_graph_item *item = (struct trace_graph_item *)buf;
-	size_t sz = size / sizeof(struct trace_graph_item), i;
-
-	printf("   Total (s)   Per Call (ms)   Calls   Name\n");
-	for (i = 0; i < sz; i++)
-		prepare_stat_tree(item++);
-	list_sort(NULL, &stat_list, stat_list_cmp);
-	stat_list_print();
-}
-
-static int graph_stat(int argc, char **argv)
-{
-	struct stat st;
-	void *map = map_trace_file(&st);
-
-	if (!map)
-		return EXIT_FAILURE;
-
-	stat_trace_file(map, st.st_size);
-	munmap(map, st.st_size);
-	return EXIT_SUCCESS;
-}
-
-static int trace_parser(int ch, char *opt)
-{
-	return 0;
-}
-
-static struct subcommand graph_cmd[] = {
-	{"cat", NULL, NULL, "cat the output of graph tracer",
-	 NULL, 0, graph_cat},
-	{"stat", NULL, NULL, "get the stat of the graph calls",
-	 NULL, 0, graph_stat},
-	{NULL,},
-};
-
-static int trace_graph(int argc, char **argv)
-{
-	return do_generic_subcommand(graph_cmd, argc, argv);
-}
-
-/* Subcommand list of trace */
-static struct subcommand trace_cmd[] = {
-	{"enable", "<tracer>", "aph", "enable tracer", NULL,
-	 CMD_NEED_ARG, trace_enable},
-	{"disable", "<tracer>", "aph", "disable tracer", NULL,
-	 CMD_NEED_ARG, trace_disable},
-	{"status", NULL, "aph", "show tracer statuses", NULL,
-	 0, trace_status},
-	{"graph", NULL, "aph", "run collie trace graph for more information",
-	 graph_cmd, CMD_NEED_ARG, trace_graph},
-	{NULL},
-};
-
-struct command trace_command = {
-	"trace",
-	trace_cmd,
-	trace_parser
-};
diff --git a/collie/treeview.c b/collie/treeview.c
deleted file mode 100644
index baf0f00..0000000
--- a/collie/treeview.c
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2009-2011 Nippon Telegraph and Telephone Corporation.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-
-#include "util.h"
-#include "treeview.h"
-
-#ifndef MAX_DEPTH
-#define MAX_DEPTH    100
-#endif
-
-struct vdi_tree {
-	char name[1024];
-	char label[256];
-	uint32_t vid;
-	uint32_t pvid;
-	bool highlight;
-	struct list_head children;
-	struct list_head siblings;
-};
-
-static int *width, *more;
-static struct vdi_tree *root;
-
-static struct vdi_tree *find_vdi(struct vdi_tree *parent, uint32_t vid,
-				 const char *name)
-{
-	struct vdi_tree *vdi, *ret;
-
-	list_for_each_entry(vdi, &parent->children, siblings) {
-		if (vdi->vid == vid && !strcmp(vdi->name, name))
-			return vdi;
-
-		ret = find_vdi(vdi, vid, name);
-		if (ret)
-			return ret;
-	}
-	return NULL;
-}
-
-static struct vdi_tree *new_vdi(const char *name, const char *label,
-				uint64_t vid, uint64_t pvid, bool highlight)
-{
-	struct vdi_tree *vdi;
-
-	vdi = xmalloc(sizeof(struct vdi_tree));
-	pstrcpy(vdi->name, sizeof(vdi->name), name);
-	pstrcpy(vdi->label, sizeof(vdi->label), label);
-	vdi->vid = vid;
-	vdi->pvid = pvid;
-	vdi->highlight = highlight;
-	INIT_LIST_HEAD(&vdi->children);
-	return vdi;
-}
-
-void init_tree(void)
-{
-	root = new_vdi("", "", 0, 0, 0);
-}
-
-void add_vdi_tree(const char *name, const char *label, uint32_t vid,
-		  uint32_t pvid, bool highlight)
-{
-	struct vdi_tree *vdi, *parent;
-
-	vdi = new_vdi(name, label, vid, pvid, highlight);
-	if (!vdi)
-		return;
-
-	parent = find_vdi(root, pvid, name);
-	if (!parent)
-		parent = root;
-
-	list_add_tail(&vdi->siblings, &parent->children);
-}
-
-static void compaction(struct vdi_tree *parent)
-{
-	struct vdi_tree *vdi, *e, *new_parent;
-
-	list_for_each_entry_safe(vdi, e, &parent->children, siblings) {
-		new_parent = find_vdi(root, vdi->pvid, vdi->name);
-		if (new_parent && parent != new_parent)
-			list_move_tail(&vdi->siblings, &new_parent->children);
-
-		compaction(vdi);
-	}
-}
-
-static int get_depth(struct vdi_tree *parent)
-{
-	struct vdi_tree *vdi;
-	int max_depth = 0, depth;
-
-	list_for_each_entry(vdi, &parent->children, siblings) {
-		depth = get_depth(vdi);
-		if (max_depth < depth)
-			max_depth = depth;
-	}
-	return max_depth + 1;
-}
-
-static void spaces(int n)
-{
-	while (n--)
-		putchar(' ');
-}
-
-static void indent(int level, bool first, bool last)
-{
-	int lvl;
-
-	if (first)
-		printf(last ? "---" : "-+-");
-	else {
-		for (lvl = 0; lvl < level - 1; lvl++) {
-			spaces(width[lvl] + 1);
-			printf(more[lvl + 1] ? "| " : "  ");
-		}
-
-		spaces(width[level - 1] + 1);
-		printf(last ? "`-" : "|-");
-	}
-}
-
-static void _dump_tree(struct vdi_tree *current, int level, bool first, bool last)
-{
-	struct vdi_tree *vdi;
-
-	indent(level, first, last);
-
-	if (current->highlight)
-		printf(TEXT_BOLD);
-
-	printf("%s", current->label);
-
-	if (current->highlight)
-		printf(TEXT_NORMAL);
-
-	if (list_empty(&current->children)) {
-		putchar('\n');
-		return;
-	}
-
-	more[level] = !last;
-	width[level] = strlen(current->label);
-
-	list_for_each_entry(vdi, &current->children, siblings) {
-		_dump_tree(vdi, level + 1,
-			   &vdi->siblings == current->children.next,
-			   vdi->siblings.next == &current->children);
-	}
-}
-
-void dump_tree(void)
-{
-	struct vdi_tree *vdi;
-	int depth;
-
-	compaction(root);
-
-	depth = get_depth(root);
-
-	width = malloc(sizeof(int) * depth);
-	more = malloc(sizeof(int) * depth);
-	if (!width || !more) {
-		sd_err("Failed to allocate memory");
-		return;
-	}
-
-	list_for_each_entry(vdi, &root->children, siblings) {
-		printf("%s", vdi->name);
-		more[0] = 0;
-		width[0] = strlen(vdi->name);
-		_dump_tree(vdi, 1, true, true);
-	}
-}
diff --git a/collie/treeview.h b/collie/treeview.h
deleted file mode 100644
index 9787fbb..0000000
--- a/collie/treeview.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2009-2011 Nippon Telegraph and Telephone Corporation.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef __TREEVIEW__
-#define __TREEVIEW__
-
-#include <stdbool.h>
-
-void init_tree(void);
-void add_vdi_tree(const char *label, const char *tag, uint32_t vid,
-		  uint32_t pvid, bool highlight);
-void dump_tree(void);
-
-#endif
diff --git a/collie/vdi.c b/collie/vdi.c
deleted file mode 100644
index 1b48079..0000000
--- a/collie/vdi.c
+++ /dev/null
@@ -1,2160 +0,0 @@
-/*
- * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <ctype.h>
-#include <time.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include "collie.h"
-#include "treeview.h"
-#include "sha1.h"
-
-static struct sd_option vdi_options[] = {
-	{'P', "prealloc", false, "preallocate all the data objects"},
-	{'i', "index", true, "specify the index of data objects"},
-	{'s', "snapshot", true, "specify a snapshot id or tag name"},
-	{'x', "exclusive", false, "write in an exclusive mode"},
-	{'d', "delete", false, "delete a key"},
-	{'w', "writeback", false, "use writeback mode"},
-	{'c', "copies", true, "specify the data redundancy (number of copies)"},
-	{'F', "from", true, "create a differential backup from the snapshot"},
-	{'f', "force", false, "do operation forcibly"},
-	{ 0, NULL, false, NULL },
-};
-
-static struct vdi_cmd_data {
-	unsigned int index;
-	int snapshot_id;
-	char snapshot_tag[SD_MAX_VDI_TAG_LEN];
-	bool exclusive;
-	bool delete;
-	bool prealloc;
-	int nr_copies;
-	bool writeback;
-	int from_snapshot_id;
-	char from_snapshot_tag[SD_MAX_VDI_TAG_LEN];
-	bool force;
-} vdi_cmd_data = { ~0, };
-
-struct get_vdi_info {
-	const char *name;
-	const char *tag;
-	uint32_t vid;
-	uint32_t snapid;
-	uint8_t nr_copies;
-};
-
-static int parse_option_size(const char *value, uint64_t *ret)
-{
-	char *postfix;
-	double sizef;
-
-	sizef = strtod(value, &postfix);
-	switch (*postfix) {
-	case 'T':
-		sizef *= 1024;
-	case 'G':
-		sizef *= 1024;
-	case 'M':
-		sizef *= 1024;
-	case 'K':
-	case 'k':
-		sizef *= 1024;
-	case 'b':
-	case '\0':
-		*ret = (uint64_t) sizef;
-		break;
-	default:
-		sd_err("Invalid size '%s'", value);
-		sd_err("You may use k, M, G or T suffixes for "
-		       "kilobytes, megabytes, gigabytes and terabytes.");
-		return -1;
-	}
-
-	return 0;
-}
-
-static void vdi_show_progress(uint64_t done, uint64_t total)
-{
-	return show_progress(done, total, false);
-}
-
-static void print_vdi_list(uint32_t vid, const char *name, const char *tag,
-			   uint32_t snapid, uint32_t flags,
-			   const struct sd_inode *i, void *data)
-{
-	int idx;
-	bool is_clone = false;
-	uint64_t my_objs, cow_objs;
-	char vdi_size_str[16], my_objs_str[16], cow_objs_str[16];
-	time_t ti;
-	struct tm tm;
-	char dbuf[128];
-	struct get_vdi_info *info = data;
-
-	if (info && strcmp(name, info->name) != 0)
-		return;
-
-	ti = i->create_time >> 32;
-	if (raw_output) {
-		snprintf(dbuf, sizeof(dbuf), "%" PRIu64, (uint64_t) ti);
-	} else {
-		localtime_r(&ti, &tm);
-		strftime(dbuf, sizeof(dbuf),
-			 "%Y-%m-%d %H:%M", &tm);
-	}
-
-	my_objs = 0;
-	cow_objs = 0;
-	for (idx = 0; idx < MAX_DATA_OBJS; idx++) {
-		if (!i->data_vdi_id[idx])
-			continue;
-		if (is_data_obj_writeable(i, idx))
-			my_objs++;
-		else
-			cow_objs++;
-	}
-
-	size_to_str(i->vdi_size, vdi_size_str, sizeof(vdi_size_str));
-	size_to_str(my_objs * SD_DATA_OBJ_SIZE, my_objs_str, sizeof(my_objs_str));
-	size_to_str(cow_objs * SD_DATA_OBJ_SIZE, cow_objs_str, sizeof(cow_objs_str));
-
-	if (i->snap_id == 1 && i->parent_vdi_id != 0)
-		is_clone = true;
-
-	if (raw_output) {
-		printf("%c ", vdi_is_snapshot(i) ? 's' : (is_clone ? 'c' : '='));
-		while (*name) {
-			if (isspace(*name) || *name == '\\')
-				putchar('\\');
-			putchar(*name++);
-		}
-		printf(" %d %s %s %s %s %" PRIx32 " %d %s\n", snapid,
-				vdi_size_str, my_objs_str, cow_objs_str, dbuf, vid,
-				i->nr_copies, i->tag);
-	} else {
-		printf("%c %-8s %5d %7s %7s %7s %s  %7" PRIx32 " %5d %13s\n",
-				vdi_is_snapshot(i) ? 's' : (is_clone ? 'c' : ' '),
-				name, snapid, vdi_size_str, my_objs_str, cow_objs_str,
-				dbuf, vid, i->nr_copies, i->tag);
-	}
-}
-
-static void print_vdi_tree(uint32_t vid, const char *name, const char *tag,
-			   uint32_t snapid, uint32_t flags,
-			   const struct sd_inode *i, void *data)
-{
-	time_t ti;
-	struct tm tm;
-	char buf[128];
-
-	if (vdi_is_snapshot(i)) {
-		ti = i->create_time >> 32;
-		localtime_r(&ti, &tm);
-
-		strftime(buf, sizeof(buf),
-			 "[%Y-%m-%d %H:%M]", &tm);
-	} else
-		pstrcpy(buf, sizeof(buf), "(you are here)");
-
-	add_vdi_tree(name, buf, vid, i->parent_vdi_id,
-		     highlight && !vdi_is_snapshot(i));
-}
-
-static void print_vdi_graph(uint32_t vid, const char *name, const char *tag,
-			    uint32_t snapid, uint32_t flags,
-			    const struct sd_inode *i, void *data)
-{
-	time_t ti;
-	struct tm tm;
-	char dbuf[128], tbuf[128], size_str[128];
-
-	ti = i->create_time >> 32;
-	localtime_r(&ti, &tm);
-
-	strftime(dbuf, sizeof(dbuf), "%Y-%m-%d", &tm);
-	strftime(tbuf, sizeof(tbuf), "%H:%M:%S", &tm);
-	size_to_str(i->vdi_size, size_str, sizeof(size_str));
-
-	printf("  \"%x\" -> \"%x\";\n", i->parent_vdi_id, vid);
-	printf("  \"%x\" [\n"
-	       "    group = \"%s\",\n"
-	       "    label = \"",
-	       vid, name);
-	printf("Name: %10s\\n"
-	       "Tag:  %10x\\n"
-	       "Size: %10s\\n"
-	       "Date: %10s\\n"
-	       "Time: %10s",
-	       name, snapid, size_str, dbuf, tbuf);
-
-	if (vdi_is_snapshot(i))
-		printf("\"\n  ];\n\n");
-	else
-		printf("\",\n    color=\"red\"\n  ];\n\n");
-
-}
-
-static void get_oid(uint32_t vid, const char *name, const char *tag,
-		    uint32_t snapid, uint32_t flags,
-		    const struct sd_inode *i, void *data)
-{
-	struct get_vdi_info *info = data;
-
-	if (info->name) {
-		if (info->tag && info->tag[0]) {
-			if (!strcmp(name, info->name) &&
-			    !strcmp(tag, info->tag)) {
-				info->vid = vid;
-			info->nr_copies = i->nr_copies;
-			}
-		} else if (info->snapid) {
-			if (!strcmp(name, info->name) &&
-			    snapid == info->snapid) {
-				info->vid = vid;
-				info->nr_copies = i->nr_copies;
-			}
-		} else {
-			if (!strcmp(name, info->name)) {
-				info->vid = vid;
-				info->nr_copies = i->nr_copies;
-			}
-		}
-	}
-}
-
-typedef int (*obj_parser_func_t)(const char *sheep, uint64_t oid,
-				 struct sd_rsp *rsp, char *buf, void *data);
-
-static int do_print_obj(const char *sheep, uint64_t oid, struct sd_rsp *rsp,
-			char *buf, void *data)
-{
-	switch (rsp->result) {
-	case SD_RES_SUCCESS:
-		printf("%s has the object (should be %d copies)\n",
-		       sheep, rsp->obj.copies);
-		break;
-	case SD_RES_NO_OBJ:
-		printf("%s doesn't have the object\n", sheep);
-		break;
-	case SD_RES_OLD_NODE_VER:
-	case SD_RES_NEW_NODE_VER:
-		sd_err("The node list has changed: please try again");
-		break;
-	default:
-		sd_err("%s: hit an unexpected error (%s)", sheep,
-		       sd_strerror(rsp->result));
-		break;
-	}
-
-	return 0;
-}
-
-struct get_data_oid_info {
-	bool success;
-	uint64_t data_oid;
-	unsigned idx;
-};
-
-static int get_data_oid(const char *sheep, uint64_t oid, struct sd_rsp *rsp,
-			char *buf, void *data)
-{
-	struct get_data_oid_info *info = data;
-	struct sd_inode *inode = (struct sd_inode *)buf;
-
-	switch (rsp->result) {
-	case SD_RES_SUCCESS:
-		if (info->success)
-			break;
-		info->success = true;
-		if (inode->data_vdi_id[info->idx]) {
-			info->data_oid = vid_to_data_oid(inode->data_vdi_id[info->idx], info->idx);
-			return 1;
-		}
-		break;
-	case SD_RES_NO_OBJ:
-		break;
-	case SD_RES_OLD_NODE_VER:
-	case SD_RES_NEW_NODE_VER:
-		sd_err("The node list has changed: please try again");
-		break;
-	default:
-		sd_err("%s: hit an unexpected error (%s)", sheep,
-		       sd_strerror(rsp->result));
-		break;
-	}
-
-	return 0;
-}
-
-static void parse_objs(uint64_t oid, obj_parser_func_t func, void *data, unsigned size)
-{
-	int i, ret, cb_ret;
-	char *buf;
-
-	buf = xzalloc(size);
-	for (i = 0; i < sd_nodes_nr; i++) {
-		struct sd_req hdr;
-		struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-
-		sd_init_req(&hdr, SD_OP_READ_PEER);
-		hdr.data_length = size;
-		hdr.flags = 0;
-		hdr.epoch = sd_epoch;
-
-		hdr.obj.oid = oid;
-
-		ret = collie_exec_req(sd_nodes[i].nid.addr,
-				      sd_nodes[i].nid.port, &hdr, buf);
-		if (ret < 0)
-			continue;
-
-		untrim_zero_blocks(buf, rsp->obj.offset, rsp->data_length,
-				   size);
-
-		cb_ret = func(addr_to_str(sd_nodes[i].nid.addr,
-					  sd_nodes[i].nid.port),
-			      oid, rsp, buf, data);
-		if (cb_ret)
-			break;
-	}
-
-	free(buf);
-}
-
-
-static int vdi_list(int argc, char **argv)
-{
-	const char *vdiname = argv[optind];
-
-	if (!raw_output)
-		printf("  Name        Id    Size    Used  Shared    Creation time   VDI id  Copies  Tag\n");
-
-	if (vdiname) {
-		struct get_vdi_info info;
-		memset(&info, 0, sizeof(info));
-		info.name = vdiname;
-		if (parse_vdi(print_vdi_list, SD_INODE_SIZE, &info) < 0)
-			return EXIT_SYSFAIL;
-		return EXIT_SUCCESS;
-	} else {
-		if (parse_vdi(print_vdi_list, SD_INODE_SIZE, NULL) < 0)
-			return EXIT_SYSFAIL;
-		return EXIT_SUCCESS;
-	}
-}
-
-static int vdi_tree(int argc, char **argv)
-{
-	init_tree();
-	if (parse_vdi(print_vdi_tree, SD_INODE_HEADER_SIZE, NULL) < 0)
-		return EXIT_SYSFAIL;
-	dump_tree();
-
-	return EXIT_SUCCESS;
-}
-
-static int vdi_graph(int argc, char **argv)
-{
-	/* print a header */
-	printf("digraph G {\n");
-	printf("  node [shape = \"box\", fontname = \"Courier\"];\n\n");
-	printf("  \"0\" [shape = \"ellipse\", label = \"root\"];\n\n");
-
-	if (parse_vdi(print_vdi_graph, SD_INODE_HEADER_SIZE, NULL) < 0)
-		return EXIT_SYSFAIL;
-
-	/* print a footer */
-	printf("}\n");
-
-	return EXIT_SUCCESS;
-}
-
-static int find_vdi_name(const char *vdiname, uint32_t snapid, const char *tag,
-			 uint32_t *vid, int for_snapshot)
-{
-	int ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	char buf[SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN];
-
-	memset(buf, 0, sizeof(buf));
-	pstrcpy(buf, SD_MAX_VDI_LEN, vdiname);
-	if (tag)
-		pstrcpy(buf + SD_MAX_VDI_LEN, SD_MAX_VDI_TAG_LEN, tag);
-
-	if (for_snapshot)
-		sd_init_req(&hdr, SD_OP_GET_VDI_INFO);
-	else
-		sd_init_req(&hdr, SD_OP_LOCK_VDI);
-	hdr.data_length = SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN;
-	hdr.flags = SD_FLAG_CMD_WRITE;
-	hdr.vdi.snapid = snapid;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, buf);
-	if (ret < 0)
-		return -1;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Cannot get VDI info for %s %d %s: %s", vdiname, snapid,
-		       tag, sd_strerror(rsp->result));
-		return -1;
-	}
-	*vid = rsp->vdi.vdi_id;
-
-	return 0;
-}
-
-static int read_vdi_obj(const char *vdiname, int snapid, const char *tag,
-			uint32_t *pvid, struct sd_inode *inode,
-			size_t size)
-{
-	int ret;
-	uint32_t vid;
-
-	ret = find_vdi_name(vdiname, snapid, tag, &vid, 0);
-	if (ret < 0) {
-		sd_err("Failed to open VDI %s", vdiname);
-		return EXIT_FAILURE;
-	}
-
-	ret = sd_read_object(vid_to_vdi_oid(vid), inode, size, 0, true);
-	if (ret != SD_RES_SUCCESS) {
-		if (snapid) {
-			sd_err("Failed to read a snapshot %s:%d", vdiname,
-			       snapid);
-		} else if (tag && tag[0]) {
-			sd_err("Failed to read a snapshot %s:%s", vdiname, tag);
-		} else {
-			sd_err("Failed to read a vdi %s", vdiname);
-		}
-		return EXIT_FAILURE;
-	}
-
-	if (pvid)
-		*pvid = vid;
-
-	return EXIT_SUCCESS;
-}
-
-int do_vdi_create(const char *vdiname, int64_t vdi_size,
-			 uint32_t base_vid, uint32_t *vdi_id, bool snapshot,
-			 int nr_copies)
-{
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	int ret;
-	char buf[SD_MAX_VDI_LEN];
-
-	memset(buf, 0, sizeof(buf));
-	pstrcpy(buf, SD_MAX_VDI_LEN, vdiname);
-
-	sd_init_req(&hdr, SD_OP_NEW_VDI);
-	hdr.flags = SD_FLAG_CMD_WRITE;
-	hdr.data_length = SD_MAX_VDI_LEN;
-
-	hdr.vdi.base_vdi_id = base_vid;
-	hdr.vdi.snapid = snapshot ? 1 : 0;
-	hdr.vdi.vdi_size = vdi_size;
-	hdr.vdi.copies = nr_copies;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, buf);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Failed to create VDI %s: %s", vdiname,
-		       sd_strerror(rsp->result));
-		return EXIT_FAILURE;
-	}
-
-	if (vdi_id)
-		*vdi_id = rsp->vdi.vdi_id;
-
-	return EXIT_SUCCESS;
-}
-
-static int vdi_create(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	uint64_t size;
-	uint32_t vid;
-	uint64_t oid;
-	int idx, max_idx, ret, nr_copies = vdi_cmd_data.nr_copies;
-	struct sd_inode *inode = NULL;
-
-	if (!argv[optind]) {
-		sd_err("Please specify the VDI size");
-		return EXIT_USAGE;
-	}
-	ret = parse_option_size(argv[optind], &size);
-	if (ret < 0)
-		return EXIT_USAGE;
-	if (size > SD_MAX_VDI_SIZE) {
-		sd_err("VDI size is too large");
-		return EXIT_USAGE;
-	}
-
-	if (nr_copies > sd_nodes_nr) {
-		sd_err("There are not enough nodes(%d) to hold the copies(%d)",
-		       sd_nodes_nr, nr_copies);
-		return EXIT_USAGE;
-	}
-
-	ret = do_vdi_create(vdiname, size, 0, &vid, false,
-			    vdi_cmd_data.nr_copies);
-	if (ret != EXIT_SUCCESS || !vdi_cmd_data.prealloc)
-		goto out;
-
-	inode = xmalloc(sizeof(*inode));
-
-	ret = sd_read_object(vid_to_vdi_oid(vid), inode, sizeof(*inode), 0, true);
-	if (ret != SD_RES_SUCCESS) {
-		sd_err("Failed to read a newly created VDI object");
-		ret = EXIT_FAILURE;
-		goto out;
-	}
-	max_idx = DIV_ROUND_UP(size, SD_DATA_OBJ_SIZE);
-
-	for (idx = 0; idx < max_idx; idx++) {
-		vdi_show_progress(idx * SD_DATA_OBJ_SIZE, inode->vdi_size);
-		oid = vid_to_data_oid(vid, idx);
-
-		ret = sd_write_object(oid, 0, NULL, 0, 0, 0, inode->nr_copies,
-				      true, true);
-		if (ret != SD_RES_SUCCESS) {
-			ret = EXIT_FAILURE;
-			goto out;
-		}
-
-		inode->data_vdi_id[idx] = vid;
-		ret = sd_write_object(vid_to_vdi_oid(vid), 0, &vid, sizeof(vid),
-				      SD_INODE_HEADER_SIZE + sizeof(vid) * idx, 0,
-				      inode->nr_copies, false, true);
-		if (ret) {
-			ret = EXIT_FAILURE;
-			goto out;
-		}
-	}
-	vdi_show_progress(idx * SD_DATA_OBJ_SIZE, inode->vdi_size);
-	ret = EXIT_SUCCESS;
-
-	if (verbose) {
-		if (raw_output)
-			printf("%x\n", vid);
-		else
-			printf("VDI ID of newly created VDI: %x\n", vid);
-	}
-
-out:
-	free(inode);
-	return ret;
-}
-
-static int vdi_snapshot(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	uint32_t vid;
-	int ret;
-	char buf[SD_INODE_HEADER_SIZE];
-	struct sd_inode *inode = (struct sd_inode *)buf;
-
-	if (vdi_cmd_data.snapshot_id != 0) {
-		sd_err("Please specify a non-integer value for "
-		       "a snapshot tag name");
-		return EXIT_USAGE;
-	}
-
-	ret = read_vdi_obj(vdiname, 0, "", &vid, inode, SD_INODE_HEADER_SIZE);
-	if (ret != EXIT_SUCCESS)
-		return ret;
-
-	ret = sd_write_object(vid_to_vdi_oid(vid), 0, vdi_cmd_data.snapshot_tag,
-			      SD_MAX_VDI_TAG_LEN,
-			      offsetof(struct sd_inode, tag),
-			      0, inode->nr_copies, false, false);
-	if (ret != SD_RES_SUCCESS)
-		return EXIT_FAILURE;
-
-	ret = do_vdi_create(vdiname, inode->vdi_size, vid, NULL, true,
-			    inode->nr_copies);
-
-	if (ret == EXIT_SUCCESS && verbose) {
-		if (raw_output)
-			printf("%x\n", vid);
-		else
-			printf("VDI ID of newly created snapshot: %x\n", vid);
-	}
-
-	return ret;
-}
-
-static int vdi_clone(int argc, char **argv)
-{
-	const char *src_vdi = argv[optind++], *dst_vdi;
-	uint32_t base_vid, new_vid;
-	uint64_t oid;
-	int idx, max_idx, ret;
-	struct sd_inode *inode = NULL;
-	char *buf = NULL;
-
-	dst_vdi = argv[optind];
-	if (!dst_vdi) {
-		sd_err("Destination VDI name must be specified");
-		ret = EXIT_USAGE;
-		goto out;
-	}
-
-	if (!vdi_cmd_data.snapshot_id && !vdi_cmd_data.snapshot_tag[0]) {
-		sd_err("Only snapshot VDIs can be cloned");
-		sd_err("Please specify the '-s' option");
-		ret = EXIT_USAGE;
-		goto out;
-	}
-
-	inode = xmalloc(sizeof(*inode));
-
-	ret = read_vdi_obj(src_vdi, vdi_cmd_data.snapshot_id,
-			   vdi_cmd_data.snapshot_tag, &base_vid, inode,
-			   SD_INODE_SIZE);
-	if (ret != EXIT_SUCCESS)
-		goto out;
-
-	ret = do_vdi_create(dst_vdi, inode->vdi_size, base_vid, &new_vid, false,
-			    vdi_cmd_data.nr_copies);
-	if (ret != EXIT_SUCCESS || !vdi_cmd_data.prealloc)
-		goto out;
-
-	buf = xzalloc(SD_DATA_OBJ_SIZE);
-	max_idx = DIV_ROUND_UP(inode->vdi_size, SD_DATA_OBJ_SIZE);
-
-	for (idx = 0; idx < max_idx; idx++) {
-		vdi_show_progress(idx * SD_DATA_OBJ_SIZE, inode->vdi_size);
-		if (inode->data_vdi_id[idx]) {
-			oid = vid_to_data_oid(inode->data_vdi_id[idx], idx);
-			ret = sd_read_object(oid, buf, SD_DATA_OBJ_SIZE, 0, true);
-			if (ret) {
-				ret = EXIT_FAILURE;
-				goto out;
-			}
-		} else
-			memset(buf, 0, SD_DATA_OBJ_SIZE);
-
-		oid = vid_to_data_oid(new_vid, idx);
-		ret = sd_write_object(oid, 0, buf, SD_DATA_OBJ_SIZE, 0, 0,
-				      inode->nr_copies, true, true);
-		if (ret != SD_RES_SUCCESS) {
-			ret = EXIT_FAILURE;
-			goto out;
-		}
-
-		ret = sd_write_object(vid_to_vdi_oid(new_vid), 0, &new_vid, sizeof(new_vid),
-				      SD_INODE_HEADER_SIZE + sizeof(new_vid) * idx, 0,
-				      inode->nr_copies, false, true);
-		if (ret) {
-			ret = EXIT_FAILURE;
-			goto out;
-		}
-	}
-	vdi_show_progress(idx * SD_DATA_OBJ_SIZE, inode->vdi_size);
-	ret = EXIT_SUCCESS;
-
-	if (verbose) {
-		if (raw_output)
-			printf("%x\n", new_vid);
-		else
-			printf("VDI ID of newly created clone: %x\n", new_vid);
-	}
-out:
-	free(inode);
-	free(buf);
-	return ret;
-}
-
-static int vdi_resize(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	uint64_t new_size;
-	uint32_t vid;
-	int ret;
-	char buf[SD_INODE_HEADER_SIZE];
-	struct sd_inode *inode = (struct sd_inode *)buf;
-
-	if (!argv[optind]) {
-		sd_err("Please specify the new size for the VDI");
-		return EXIT_USAGE;
-	}
-	ret = parse_option_size(argv[optind], &new_size);
-	if (ret < 0)
-		return EXIT_USAGE;
-	if (new_size > SD_MAX_VDI_SIZE) {
-		sd_err("New VDI size is too large");
-		return EXIT_USAGE;
-	}
-
-	ret = read_vdi_obj(vdiname, 0, "", &vid, inode, SD_INODE_HEADER_SIZE);
-	if (ret != EXIT_SUCCESS)
-		return ret;
-
-	if (new_size < inode->vdi_size) {
-		sd_err("Shrinking VDIs is not implemented");
-		return EXIT_USAGE;
-	}
-	inode->vdi_size = new_size;
-
-	ret = sd_write_object(vid_to_vdi_oid(vid), 0, inode, SD_INODE_HEADER_SIZE, 0,
-			      0, inode->nr_copies, false, true);
-	if (ret != SD_RES_SUCCESS) {
-		sd_err("Failed to update an inode header");
-		return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int do_vdi_delete(const char *vdiname, int snap_id, const char *snap_tag)
-{
-	int ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	char data[SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN];
-	uint32_t vid;
-
-	ret = find_vdi_name(vdiname, snap_id, snap_tag, &vid, 0);
-	if (ret < 0) {
-		sd_err("Failed to open VDI %s", vdiname);
-		return EXIT_FAILURE;
-	}
-
-	sd_init_req(&hdr, SD_OP_DELETE_CACHE);
-	hdr.obj.oid = vid_to_vdi_oid(vid);
-
-	ret = send_light_req(&hdr, sdhost, sdport);
-	if (ret) {
-		sd_err("failed to execute request");
-		return EXIT_FAILURE;
-	}
-
-	sd_init_req(&hdr, SD_OP_DEL_VDI);
-	hdr.flags = SD_FLAG_CMD_WRITE;
-	hdr.data_length = sizeof(data);
-	hdr.vdi.snapid = snap_id;
-	memset(data, 0, sizeof(data));
-	pstrcpy(data, SD_MAX_VDI_LEN, vdiname);
-	if (snap_tag)
-		pstrcpy(data + SD_MAX_VDI_LEN, SD_MAX_VDI_TAG_LEN, snap_tag);
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, data);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("Failed to delete %s: %s", vdiname,
-		       sd_strerror(rsp->result));
-		if (rsp->result == SD_RES_NO_VDI)
-			return EXIT_MISSING;
-		else
-			return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int vdi_delete(int argc, char **argv)
-{
-	char *vdiname = argv[optind];
-
-	return do_vdi_delete(vdiname, vdi_cmd_data.snapshot_id,
-			     vdi_cmd_data.snapshot_tag);
-}
-
-static int vdi_rollback(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	uint32_t base_vid, new_vid;
-	int ret;
-	char buf[SD_INODE_HEADER_SIZE];
-	struct sd_inode *inode = (struct sd_inode *)buf;
-
-	if (!vdi_cmd_data.snapshot_id && !vdi_cmd_data.snapshot_tag[0]) {
-		sd_err("Please specify the '-s' option");
-		return EXIT_USAGE;
-	}
-
-	ret = read_vdi_obj(vdiname, vdi_cmd_data.snapshot_id,
-			   vdi_cmd_data.snapshot_tag, &base_vid, inode,
-			   SD_INODE_HEADER_SIZE);
-	if (ret != EXIT_SUCCESS)
-		return ret;
-
-	if (!vdi_cmd_data.force)
-		confirm("This operation dicards any changes made since the"
-			" previous\nsnapshot was taken.  Continue? [yes/no]: ");
-
-	ret = do_vdi_delete(vdiname, 0, NULL);
-	if (ret != SD_RES_SUCCESS) {
-		sd_err("Failed to delete the current state");
-		return EXIT_FAILURE;
-	}
-
-	ret = do_vdi_create(vdiname, inode->vdi_size, base_vid, &new_vid,
-			     false, vdi_cmd_data.nr_copies);
-
-	if (ret == EXIT_SUCCESS && verbose) {
-		if (raw_output)
-			printf("%x\n", new_vid);
-		else
-			printf("New VDI ID of rollbacked VDI: %x\n", new_vid);
-	}
-
-	return ret;
-}
-
-static int vdi_object(int argc, char **argv)
-{
-	const char *vdiname = argv[optind];
-	unsigned idx = vdi_cmd_data.index;
-	struct get_vdi_info info;
-	uint32_t vid;
-
-	memset(&info, 0, sizeof(info));
-	info.name = vdiname;
-	info.tag = vdi_cmd_data.snapshot_tag;
-	info.vid = 0;
-	info.snapid = vdi_cmd_data.snapshot_id;
-
-	if (parse_vdi(get_oid, SD_INODE_HEADER_SIZE, &info) < 0)
-		return EXIT_SYSFAIL;
-
-	vid = info.vid;
-	if (vid == 0) {
-		sd_err("VDI not found");
-		return EXIT_MISSING;
-	}
-
-	if (idx == ~0) {
-		printf("Looking for the inode object 0x%" PRIx32 " with %d nodes\n\n",
-		       vid, sd_nodes_nr);
-		parse_objs(vid_to_vdi_oid(vid), do_print_obj, NULL, SD_INODE_SIZE);
-	} else {
-		struct get_data_oid_info oid_info = {0};
-
-		oid_info.success = false;
-		oid_info.idx = idx;
-
-		if (idx >= MAX_DATA_OBJS) {
-			printf("The offset is too large!\n");
-			exit(EXIT_FAILURE);
-		}
-
-		parse_objs(vid_to_vdi_oid(vid), get_data_oid, &oid_info, SD_DATA_OBJ_SIZE);
-
-		if (oid_info.success) {
-			if (oid_info.data_oid) {
-				printf("Looking for the object 0x%" PRIx64
-				       " (the inode vid 0x%" PRIx32 " idx %u) with %d nodes\n\n",
-				       oid_info.data_oid, vid, idx, sd_nodes_nr);
-
-				parse_objs(oid_info.data_oid, do_print_obj, NULL, SD_DATA_OBJ_SIZE);
-			} else
-				printf("The inode object 0x%" PRIx32 " idx %u is not allocated\n",
-				       vid, idx);
-		} else
-			sd_err("Failed to read the inode object 0x%" PRIx32,
-			       vid);
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int do_track_object(uint64_t oid, uint8_t nr_copies)
-{
-	int i, j, ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	struct sd_vnode *vnodes;
-	const struct sd_vnode *vnode_buf[SD_MAX_COPIES];
-	struct epoch_log *logs;
-	int vnodes_nr, nr_logs, log_length;
-
-	log_length = sd_epoch * sizeof(struct epoch_log);
-	logs = xmalloc(log_length);
-	vnodes = xmalloc(sizeof(*vnodes) * SD_MAX_VNODES);
-
-	sd_init_req(&hdr, SD_OP_STAT_CLUSTER);
-	hdr.data_length = log_length;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, logs);
-	if (ret < 0)
-		goto error;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		printf("%s\n", sd_strerror(rsp->result));
-		goto error;
-	}
-
-	nr_logs = rsp->data_length / sizeof(struct epoch_log);
-	for (i = nr_logs - 1; i >= 0; i--) {
-		printf("\nobj %"PRIx64" locations at epoch %d, copies = %d\n",
-		       oid, logs[i].epoch, nr_copies);
-		printf("---------------------------------------------------\n");
-
-		/*
-		 * When # of nodes is less than nr_copies, we only print
-		 * remaining nodes that holds all the remaining copies.
-		 */
-		if (logs[i].nr_nodes < nr_copies) {
-			for (j = 0; j < logs[i].nr_nodes; j++) {
-				const struct node_id *n = &logs[i].nodes[j].nid;
-
-				printf("%s\n", addr_to_str(n->addr, n->port));
-			}
-			continue;
-		}
-		vnodes_nr = nodes_to_vnodes(logs[i].nodes,
-					    logs[i].nr_nodes, vnodes);
-		oid_to_vnodes(vnodes, vnodes_nr, oid, nr_copies, vnode_buf);
-		for (j = 0; j < nr_copies; j++) {
-			const struct node_id *n = &vnode_buf[j]->nid;
-
-			printf("%s\n", addr_to_str(n->addr, n->port));
-		}
-	}
-
-	free(logs);
-	free(vnodes);
-	return EXIT_SUCCESS;
-error:
-	free(logs);
-	free(vnodes);
-	return EXIT_SYSFAIL;
-}
-
-static int vdi_track(int argc, char **argv)
-{
-	const char *vdiname = argv[optind];
-	unsigned idx = vdi_cmd_data.index;
-	struct get_vdi_info info;
-	struct get_data_oid_info oid_info = {0};
-	uint32_t vid;
-	uint8_t nr_copies;
-
-	memset(&info, 0, sizeof(info));
-	info.name = vdiname;
-	info.tag = vdi_cmd_data.snapshot_tag;
-	info.vid = 0;
-	info.snapid = vdi_cmd_data.snapshot_id;
-
-	if (parse_vdi(get_oid, SD_INODE_HEADER_SIZE, &info) < 0)
-		return EXIT_SYSFAIL;
-
-	vid = info.vid;
-	nr_copies = info.nr_copies;
-	if (vid == 0) {
-		sd_err("VDI not found");
-		return EXIT_MISSING;
-	}
-
-	if (idx == ~0) {
-		printf("Tracking the inode object 0x%" PRIx32 " with %d nodes\n",
-		       vid, sd_nodes_nr);
-		return do_track_object(vid_to_vdi_oid(vid), nr_copies);
-	}
-
-	oid_info.success = false;
-	oid_info.idx = idx;
-
-	if (idx >= MAX_DATA_OBJS) {
-		printf("The offset is too large!\n");
-		goto err;
-	}
-
-	parse_objs(vid_to_vdi_oid(vid), get_data_oid,
-		   &oid_info, SD_DATA_OBJ_SIZE);
-
-	if (!oid_info.success) {
-		sd_err("Failed to read the inode object 0x%" PRIx32, vid);
-		goto err;
-	}
-	if (!oid_info.data_oid) {
-		printf("The inode object 0x%"PRIx32" idx %u is not allocated\n",
-		       vid, idx);
-		goto err;
-	}
-	printf("Tracking the object 0x%" PRIx64
-	       " (the inode vid 0x%" PRIx32 " idx %u)"
-	       " with %d nodes\n",
-	       oid_info.data_oid, vid, idx, sd_nodes_nr);
-	return do_track_object(oid_info.data_oid, nr_copies);
-err:
-	return EXIT_FAILURE;
-}
-
-static int find_vdi_attr_oid(const char *vdiname, const char *tag, uint32_t snapid,
-			     const char *key, void *value, unsigned int value_len,
-			     uint32_t *vid, uint64_t *oid, unsigned int *nr_copies,
-			     bool create, bool excl, bool delete)
-{
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	int ret;
-	struct sheepdog_vdi_attr vattr;
-
-	memset(&vattr, 0, sizeof(vattr));
-	pstrcpy(vattr.name, SD_MAX_VDI_LEN, vdiname);
-	pstrcpy(vattr.tag, SD_MAX_VDI_TAG_LEN, vdi_cmd_data.snapshot_tag);
-	vattr.snap_id = vdi_cmd_data.snapshot_id;
-	pstrcpy(vattr.key, SD_MAX_VDI_ATTR_KEY_LEN, key);
-	if (value && value_len) {
-		vattr.value_len = value_len;
-		memcpy(vattr.value, value, value_len);
-	}
-
-	sd_init_req(&hdr, SD_OP_GET_VDI_ATTR);
-	hdr.flags = SD_FLAG_CMD_WRITE;
-	hdr.data_length = SD_ATTR_OBJ_SIZE;
-	hdr.vdi.snapid = vdi_cmd_data.snapshot_id;
-
-	if (create)
-		hdr.flags |= SD_FLAG_CMD_CREAT;
-	if (excl)
-		hdr.flags |= SD_FLAG_CMD_EXCL;
-	if (delete)
-		hdr.flags |= SD_FLAG_CMD_DEL;
-
-	ret = collie_exec_req(sdhost, sdport, &hdr, &vattr);
-	if (ret < 0)
-		return SD_RES_EIO;
-
-	if (rsp->result != SD_RES_SUCCESS)
-		return rsp->result;
-
-	*vid = rsp->vdi.vdi_id;
-	*oid = vid_to_attr_oid(rsp->vdi.vdi_id, rsp->vdi.attr_id);
-	*nr_copies = rsp->vdi.copies;
-
-	return SD_RES_SUCCESS;
-}
-
-static int vdi_setattr(int argc, char **argv)
-{
-	int ret, value_len = 0;
-	uint64_t attr_oid = 0;
-	uint32_t vid = 0, nr_copies = 0;
-	const char *vdiname = argv[optind++], *key;
-	char *value;
-	uint64_t offset;
-
-	key = argv[optind++];
-	if (!key) {
-		sd_err("Please specify the attribute key");
-		return EXIT_USAGE;
-	}
-
-	value = argv[optind++];
-	if (!value && !vdi_cmd_data.delete) {
-		value = xmalloc(SD_MAX_VDI_ATTR_VALUE_LEN);
-
-		offset = 0;
-reread:
-		ret = read(STDIN_FILENO, value + offset,
-			   SD_MAX_VDI_ATTR_VALUE_LEN - offset);
-		if (ret < 0) {
-			sd_err("Failed to read attribute value from stdin: %m");
-			return EXIT_SYSFAIL;
-		}
-		if (ret > 0) {
-			offset += ret;
-			goto reread;
-		}
-	}
-
-	if (value)
-		value_len = strlen(value);
-
-	ret = find_vdi_attr_oid(vdiname, vdi_cmd_data.snapshot_tag,
-				vdi_cmd_data.snapshot_id, key, value,
-				value_len, &vid, &attr_oid,
-				&nr_copies, !vdi_cmd_data.delete,
-				vdi_cmd_data.exclusive, vdi_cmd_data.delete);
-	if (ret) {
-		if (ret == SD_RES_VDI_EXIST) {
-			sd_err("The attribute '%s' already exists", key);
-			return EXIT_EXISTS;
-		} else if (ret == SD_RES_NO_OBJ) {
-			sd_err("Attribute '%s' not found", key);
-			return EXIT_MISSING;
-		} else if (ret == SD_RES_NO_VDI) {
-			sd_err("VDI not found");
-			return EXIT_MISSING;
-		} else
-			sd_err("Failed to set attribute: %s", sd_strerror(ret));
-		return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int vdi_getattr(int argc, char **argv)
-{
-	int ret;
-	uint64_t oid, attr_oid = 0;
-	uint32_t vid = 0, nr_copies = 0;
-	const char *vdiname = argv[optind++], *key;
-	struct sheepdog_vdi_attr vattr;
-
-	key = argv[optind++];
-	if (!key) {
-		sd_err("Please specify the attribute key");
-		return EXIT_USAGE;
-	}
-
-	ret = find_vdi_attr_oid(vdiname, vdi_cmd_data.snapshot_tag,
-				vdi_cmd_data.snapshot_id, key, NULL, 0, &vid,
-				&attr_oid, &nr_copies, false, false, false);
-	if (ret == SD_RES_NO_OBJ) {
-		sd_err("Attribute '%s' not found", key);
-		return EXIT_MISSING;
-	} else if (ret == SD_RES_NO_VDI) {
-		sd_err("VDI not found");
-		return EXIT_MISSING;
-	} else if (ret) {
-		sd_err("Failed to find attribute oid: %s", sd_strerror(ret));
-		return EXIT_MISSING;
-	}
-
-	oid = attr_oid;
-
-	ret = sd_read_object(oid, &vattr, SD_ATTR_OBJ_SIZE, 0, true);
-	if (ret != SD_RES_SUCCESS) {
-		sd_err("Failed to read attribute oid: %s", sd_strerror(ret));
-		return EXIT_SYSFAIL;
-	}
-
-	xwrite(STDOUT_FILENO, vattr.value, vattr.value_len);
-	return EXIT_SUCCESS;
-}
-
-static int vdi_read(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	int ret, idx;
-	struct sd_inode *inode = NULL;
-	uint64_t offset = 0, oid, done = 0, total = (uint64_t) -1;
-	unsigned int len;
-	char *buf = NULL;
-
-	if (argv[optind]) {
-		ret = parse_option_size(argv[optind++], &offset);
-		if (ret < 0)
-			return EXIT_USAGE;
-		if (argv[optind]) {
-			ret = parse_option_size(argv[optind++], &total);
-			if (ret < 0)
-				return EXIT_USAGE;
-		}
-	}
-
-	inode = malloc(sizeof(*inode));
-	buf = xmalloc(SD_DATA_OBJ_SIZE);
-
-	ret = read_vdi_obj(vdiname, vdi_cmd_data.snapshot_id,
-			   vdi_cmd_data.snapshot_tag, NULL, inode,
-			   SD_INODE_SIZE);
-	if (ret != EXIT_SUCCESS)
-		goto out;
-
-	if (inode->vdi_size < offset) {
-		sd_err("Read offset is beyond the end of the VDI");
-		ret = EXIT_FAILURE;
-		goto out;
-	}
-
-	total = min(total, inode->vdi_size - offset);
-	idx = offset / SD_DATA_OBJ_SIZE;
-	offset %= SD_DATA_OBJ_SIZE;
-	while (done < total) {
-		len = min(total - done, SD_DATA_OBJ_SIZE - offset);
-
-		if (inode->data_vdi_id[idx]) {
-			oid = vid_to_data_oid(inode->data_vdi_id[idx], idx);
-			ret = sd_read_object(oid, buf, len, offset, false);
-			if (ret != SD_RES_SUCCESS) {
-				sd_err("Failed to read VDI");
-				ret = EXIT_FAILURE;
-				goto out;
-			}
-		} else
-			memset(buf, 0, len);
-
-		ret = xwrite(STDOUT_FILENO, buf, len);
-		if (ret < 0) {
-			sd_err("Failed to write to stdout: %m");
-			ret = EXIT_SYSFAIL;
-			goto out;
-		}
-
-		offset = 0;
-		idx++;
-		done += len;
-	}
-	fsync(STDOUT_FILENO);
-	ret = EXIT_SUCCESS;
-out:
-	free(inode);
-	free(buf);
-
-	return ret;
-}
-
-static int vdi_write(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	uint32_t vid, flags;
-	int ret, idx;
-	struct sd_inode *inode = NULL;
-	uint64_t offset = 0, oid, old_oid, done = 0, total = (uint64_t) -1;
-	unsigned int len;
-	char *buf = NULL;
-	bool create;
-
-	if (argv[optind]) {
-		ret = parse_option_size(argv[optind++], &offset);
-		if (ret < 0)
-			return EXIT_USAGE;
-		if (argv[optind]) {
-			ret = parse_option_size(argv[optind++], &total);
-			if (ret < 0)
-				return EXIT_USAGE;
-		}
-	}
-
-	inode = xmalloc(sizeof(*inode));
-	buf = xmalloc(SD_DATA_OBJ_SIZE);
-
-	ret = read_vdi_obj(vdiname, 0, "", &vid, inode, SD_INODE_SIZE);
-	if (ret != EXIT_SUCCESS)
-		goto out;
-
-	if (inode->vdi_size < offset) {
-		sd_err("Write offset is beyond the end of the VDI");
-		ret = EXIT_FAILURE;
-		goto out;
-	}
-
-	total = min(total, inode->vdi_size - offset);
-	idx = offset / SD_DATA_OBJ_SIZE;
-	offset %= SD_DATA_OBJ_SIZE;
-	while (done < total) {
-		create = false;
-		old_oid = 0;
-		flags = 0;
-		len = min(total - done, SD_DATA_OBJ_SIZE - offset);
-
-		if (!inode->data_vdi_id[idx])
-			create = true;
-		else if (!is_data_obj_writeable(inode, idx)) {
-			create = true;
-			old_oid = vid_to_data_oid(inode->data_vdi_id[idx], idx);
-		}
-
-		if (vdi_cmd_data.writeback)
-			flags |= SD_FLAG_CMD_CACHE;
-
-		ret = xread(STDIN_FILENO, buf, len);
-		if (ret < 0) {
-			sd_err("Failed to read from stdin: %m");
-			ret = EXIT_SYSFAIL;
-			goto out;
-		} else if (ret < len) {
-			/* exit after this buffer is sent */
-			memset(buf + ret, 0, len - ret);
-			total = done + len;
-		}
-
-		inode->data_vdi_id[idx] = inode->vdi_id;
-		oid = vid_to_data_oid(inode->data_vdi_id[idx], idx);
-		ret = sd_write_object(oid, old_oid, buf, len, offset, flags,
-				      inode->nr_copies, create, false);
-		if (ret != SD_RES_SUCCESS) {
-			sd_err("Failed to write VDI");
-			ret = EXIT_FAILURE;
-			goto out;
-		}
-
-		if (create) {
-			ret = sd_write_object(vid_to_vdi_oid(vid), 0, &vid, sizeof(vid),
-					      SD_INODE_HEADER_SIZE + sizeof(vid) * idx,
-					      flags, inode->nr_copies, false, false);
-			if (ret) {
-				ret = EXIT_FAILURE;
-				goto out;
-			}
-		}
-
-		offset += len;
-		if (offset == SD_DATA_OBJ_SIZE) {
-			offset = 0;
-			idx++;
-		}
-		done += len;
-	}
-	ret = EXIT_SUCCESS;
-out:
-	free(inode);
-	free(buf);
-
-	return ret;
-}
-
-static void *read_object_from(const struct sd_vnode *vnode, uint64_t oid)
-{
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	int ret;
-	void *buf;
-	size_t size = get_objsize(oid);
-
-	buf = xmalloc(size);
-
-	sd_init_req(&hdr, SD_OP_READ_PEER);
-	hdr.epoch = sd_epoch;
-	hdr.flags = 0;
-	hdr.data_length = size;
-
-	hdr.obj.oid = oid;
-
-	ret = collie_exec_req(vnode->nid.addr, vnode->nid.port, &hdr, buf);
-
-	if (ret < 0)
-		exit(EXIT_SYSFAIL);
-
-	switch (rsp->result) {
-	case SD_RES_SUCCESS:
-		untrim_zero_blocks(buf, rsp->obj.offset, rsp->data_length,
-				   size);
-		break;
-	case SD_RES_NO_OBJ:
-		free(buf);
-		return NULL;
-	default:
-		sd_err("FATAL: failed to read %"PRIx64", %s", oid,
-		       sd_strerror(rsp->result));
-		exit(EXIT_FAILURE);
-	}
-	return buf;
-}
-
-static void write_object_to(const struct sd_vnode *vnode, uint64_t oid,
-			    void *buf, bool create)
-{
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	int ret;
-
-	if (create)
-		sd_init_req(&hdr, SD_OP_CREATE_AND_WRITE_PEER);
-	else
-		sd_init_req(&hdr, SD_OP_WRITE_PEER);
-	hdr.epoch = sd_epoch;
-	hdr.flags = SD_FLAG_CMD_WRITE;
-	hdr.data_length = get_objsize(oid);
-	hdr.obj.oid = oid;
-
-	ret = collie_exec_req(vnode->nid.addr, vnode->nid.port, &hdr, buf);
-
-	if (ret < 0)
-		exit(EXIT_SYSFAIL);
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("FATAL: failed to write %"PRIx64", %s", oid,
-		       sd_strerror(rsp->result));
-		exit(EXIT_FAILURE);
-	}
-}
-
-struct vdi_check_work {
-	struct vdi_check_info *info;
-	const struct sd_vnode *vnode;
-	uint8_t hash[SHA1_DIGEST_SIZE];
-	bool object_found;
-	struct work work;
-};
-
-struct vdi_check_info {
-	uint64_t oid;
-	int nr_copies;
-	uint64_t total;
-	uint64_t *done;
-	int refcnt;
-	struct work_queue *wq;
-	struct vdi_check_work *base;
-	struct vdi_check_work vcw[0];
-};
-
-static void free_vdi_check_info(struct vdi_check_info *info)
-{
-	if (info->done) {
-		*info->done += SD_DATA_OBJ_SIZE;
-		vdi_show_progress(*info->done, info->total);
-	}
-	free(info);
-}
-
-static void vdi_repair_work(struct work *work)
-{
-	struct vdi_check_work *vcw = container_of(work, struct vdi_check_work,
-						  work);
-	struct vdi_check_info *info = vcw->info;
-	void *buf;
-
-	buf = read_object_from(info->base->vnode, info->oid);
-	write_object_to(vcw->vnode, info->oid, buf, !vcw->object_found);
-	free(buf);
-}
-
-static void vdi_repair_main(struct work *work)
-{
-	struct vdi_check_work *vcw = container_of(work, struct vdi_check_work,
-						  work);
-	struct vdi_check_info *info = vcw->info;
-
-	if (vcw->object_found)
-		fprintf(stdout, "fixed replica %"PRIx64"\n", info->oid);
-	else
-		fprintf(stdout, "fixed missing %"PRIx64"\n", info->oid);
-
-	info->refcnt--;
-	if (info->refcnt == 0)
-		free_vdi_check_info(info);
-}
-
-static void vdi_hash_check_work(struct work *work)
-{
-	struct vdi_check_work *vcw = container_of(work, struct vdi_check_work,
-						  work);
-	struct vdi_check_info *info = vcw->info;
-	int ret;
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-
-	sd_init_req(&hdr, SD_OP_GET_HASH);
-	hdr.obj.oid = info->oid;
-	hdr.obj.tgt_epoch = sd_epoch;
-
-	ret = collie_exec_req(vcw->vnode->nid.addr, vcw->vnode->nid.port, &hdr,
-			      NULL);
-	if (ret < 0)
-		exit(EXIT_SYSFAIL);
-
-	switch (ret) {
-	case SD_RES_SUCCESS:
-		vcw->object_found = true;
-		memcpy(vcw->hash, rsp->hash.digest, sizeof(vcw->hash));
-		uatomic_set(&info->base, vcw);
-		break;
-	case SD_RES_NO_OBJ:
-		vcw->object_found = false;
-		break;
-	default:
-		sd_err("failed to read %" PRIx64 " from %s, %s", info->oid,
-		       addr_to_str(vcw->vnode->nid.addr, vcw->vnode->nid.port),
-		       sd_strerror(ret));
-		exit(EXIT_FAILURE);
-	}
-}
-
-static void vdi_hash_check_main(struct work *work)
-{
-	struct vdi_check_work *vcw = container_of(work, struct vdi_check_work,
-						  work);
-	struct vdi_check_info *info = vcw->info;
-
-	info->refcnt--;
-	if (info->refcnt > 0)
-		return;
-
-	if (info->base  == NULL) {
-		sd_err("no node has %" PRIx64, info->oid);
-		exit(EXIT_FAILURE);
-	}
-
-	for (int i = 0; i < info->nr_copies; i++) {
-		if (&info->vcw[i] == info->base)
-			continue;
-		/* need repair when object not found or consistency broken */
-		if (!info->vcw[i].object_found ||
-		    memcmp(info->base->hash, info->vcw[i].hash,
-			   sizeof(info->base->hash)) != 0) {
-			info->vcw[i].work.fn = vdi_repair_work;
-			info->vcw[i].work.done = vdi_repair_main;
-			info->refcnt++;
-			queue_work(info->wq, &info->vcw[i].work);
-		}
-	}
-
-	if (info->refcnt == 0)
-		free_vdi_check_info(info);
-}
-
-static void queue_vdi_check_work(struct sd_inode *inode, uint64_t oid,
-				 uint64_t *done, struct work_queue *wq)
-{
-	struct vdi_check_info *info;
-	const struct sd_vnode *tgt_vnodes[SD_MAX_COPIES];
-	int nr_copies = inode->nr_copies;
-
-	info = xzalloc(sizeof(*info) + sizeof(info->vcw[0]) * nr_copies);
-	info->oid = oid;
-	info->nr_copies = nr_copies;
-	info->total = inode->vdi_size;
-	info->done = done;
-	info->wq = wq;
-
-	oid_to_vnodes(sd_vnodes, sd_vnodes_nr, oid, nr_copies, tgt_vnodes);
-	for (int i = 0; i < nr_copies; i++) {
-		info->vcw[i].info = info;
-		info->vcw[i].vnode = tgt_vnodes[i];
-		info->vcw[i].work.fn = vdi_hash_check_work;
-		info->vcw[i].work.done = vdi_hash_check_main;
-		info->refcnt++;
-		queue_work(info->wq, &info->vcw[i].work);
-	}
-}
-
-static int vdi_check(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	int ret, max_idx;
-	uint64_t done = 0, oid;
-	uint32_t vid;
-	struct sd_inode *inode = xmalloc(sizeof(*inode));
-	struct work_queue *wq;
-
-	ret = read_vdi_obj(vdiname, vdi_cmd_data.snapshot_id,
-			   vdi_cmd_data.snapshot_tag, &vid, inode,
-			   SD_INODE_SIZE);
-	if (ret != EXIT_SUCCESS) {
-		sd_err("FATAL: no inode objects");
-		goto out;
-	}
-	if (sd_nodes_nr < inode->nr_copies) {
-		sd_err("ABORT: Not enough active nodes for consistency-check");
-		return EXIT_FAILURE;
-	}
-
-	wq = create_work_queue("vdi check", WQ_DYNAMIC);
-
-	queue_vdi_check_work(inode, vid_to_vdi_oid(vid), NULL, wq);
-
-	max_idx = DIV_ROUND_UP(inode->vdi_size, SD_DATA_OBJ_SIZE);
-	vdi_show_progress(done, inode->vdi_size);
-	for (int idx = 0; idx < max_idx; idx++) {
-		vid = inode->data_vdi_id[idx];
-		if (vid) {
-			oid = vid_to_data_oid(vid, idx);
-			queue_vdi_check_work(inode, oid, &done, wq);
-		} else {
-			done += SD_DATA_OBJ_SIZE;
-			vdi_show_progress(done, inode->vdi_size);
-		}
-	}
-
-	work_queue_wait(wq);
-
-	fprintf(stdout, "finish check&repair %s\n", vdiname);
-	return EXIT_SUCCESS;
-out:
-	return ret;
-}
-
-/* vdi backup format */
-
-#define VDI_BACKUP_FORMAT_VERSION 1
-#define VDI_BACKUP_MAGIC 0x11921192
-
-struct backup_hdr {
-	uint32_t version;
-	uint32_t magic;
-};
-
-struct obj_backup {
-	uint32_t idx;
-	uint32_t offset;
-	uint32_t length;
-	uint32_t reserved;
-	uint8_t data[SD_DATA_OBJ_SIZE];
-};
-
-/* discards redundant area from backup data */
-static void compact_obj_backup(struct obj_backup *backup, uint8_t *from_data)
-{
-	uint8_t *p1, *p2;
-
-	p1 = backup->data;
-	p2 = from_data;
-	while (backup->length > 0 && memcmp(p1, p2, SECTOR_SIZE) == 0) {
-		p1 += SECTOR_SIZE;
-		p2 += SECTOR_SIZE;
-		backup->offset += SECTOR_SIZE;
-		backup->length -= SECTOR_SIZE;
-	}
-
-	p1 = backup->data + SD_DATA_OBJ_SIZE - SECTOR_SIZE;
-	p2 = from_data + SD_DATA_OBJ_SIZE - SECTOR_SIZE;
-	while (backup->length > 0 && memcmp(p1, p2, SECTOR_SIZE) == 0) {
-		p1 -= SECTOR_SIZE;
-		p2 -= SECTOR_SIZE;
-		backup->length -= SECTOR_SIZE;
-	}
-}
-
-static int get_obj_backup(int idx, uint32_t from_vid, uint32_t to_vid,
-			  struct obj_backup *backup)
-{
-	int ret;
-	uint8_t *from_data = xzalloc(SD_DATA_OBJ_SIZE);
-
-	backup->idx = idx;
-	backup->offset = 0;
-	backup->length = SD_DATA_OBJ_SIZE;
-
-	if (to_vid) {
-		ret = sd_read_object(vid_to_data_oid(to_vid, idx), backup->data,
-				     SD_DATA_OBJ_SIZE, 0, true);
-		if (ret != SD_RES_SUCCESS) {
-			sd_err("Failed to read object %" PRIx32 ", %d", to_vid,
-			       idx);
-			return EXIT_FAILURE;
-		}
-	} else
-		memset(backup->data, 0, SD_DATA_OBJ_SIZE);
-
-	if (from_vid) {
-		ret = sd_read_object(vid_to_data_oid(from_vid, idx), from_data,
-				     SD_DATA_OBJ_SIZE, 0, true);
-		if (ret != SD_RES_SUCCESS) {
-			sd_err("Failed to read object %" PRIx32 ", %d",
-			       from_vid, idx);
-			return EXIT_FAILURE;
-		}
-	}
-
-	compact_obj_backup(backup, from_data);
-
-	free(from_data);
-
-	return EXIT_SUCCESS;
-}
-
-static int vdi_backup(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	int ret = EXIT_SUCCESS, idx, nr_objs;
-	struct sd_inode *from_inode = xzalloc(sizeof(*from_inode));
-	struct sd_inode *to_inode = xzalloc(sizeof(*to_inode));
-	struct backup_hdr hdr = {
-		.version = VDI_BACKUP_FORMAT_VERSION,
-		.magic = VDI_BACKUP_MAGIC,
-	};
-	struct obj_backup *backup = xzalloc(sizeof(*backup));
-
-	if ((!vdi_cmd_data.snapshot_id && !vdi_cmd_data.snapshot_tag[0]) ||
-	    (!vdi_cmd_data.from_snapshot_id &&
-	     !vdi_cmd_data.from_snapshot_tag[0])) {
-		sd_err("Please specify snapshots with '-F' and '-s' options");
-		ret = EXIT_USAGE;
-		goto out;
-	}
-
-	ret = read_vdi_obj(vdiname, vdi_cmd_data.from_snapshot_id,
-			   vdi_cmd_data.from_snapshot_tag, NULL,
-			   from_inode, SD_INODE_SIZE);
-	if (ret != EXIT_SUCCESS)
-		goto out;
-
-	ret = read_vdi_obj(vdiname, vdi_cmd_data.snapshot_id,
-			   vdi_cmd_data.snapshot_tag, NULL, to_inode,
-			   SD_INODE_SIZE);
-	if (ret != EXIT_SUCCESS)
-		goto out;
-
-	nr_objs = DIV_ROUND_UP(to_inode->vdi_size, SD_DATA_OBJ_SIZE);
-
-	ret = xwrite(STDOUT_FILENO, &hdr, sizeof(hdr));
-	if (ret < 0) {
-		sd_err("failed to write backup header, %m");
-		ret = EXIT_SYSFAIL;
-		goto out;
-	}
-
-	for (idx = 0; idx < nr_objs; idx++) {
-		uint32_t from_vid = from_inode->data_vdi_id[idx];
-		uint32_t to_vid = to_inode->data_vdi_id[idx];
-
-		if (to_vid == 0 && from_vid == 0)
-			continue;
-
-		ret = get_obj_backup(idx, from_vid, to_vid, backup);
-		if (ret != EXIT_SUCCESS)
-			goto out;
-
-		if (backup->length == 0)
-			continue;
-
-		ret = xwrite(STDOUT_FILENO, backup,
-			     sizeof(*backup) - sizeof(backup->data));
-		if (ret < 0) {
-			sd_err("failed to write backup data, %m");
-			ret = EXIT_SYSFAIL;
-			goto out;
-		}
-		ret = xwrite(STDOUT_FILENO, backup->data + backup->offset,
-			     backup->length);
-		if (ret < 0) {
-			sd_err("failed to write backup data, %m");
-			ret = EXIT_SYSFAIL;
-			goto out;
-		}
-	}
-
-	/* write end marker */
-	memset(backup, 0, sizeof(*backup) - sizeof(backup->data));
-	backup->idx = UINT32_MAX;
-	ret = xwrite(STDOUT_FILENO, backup,
-		     sizeof(*backup) - sizeof(backup->data));
-	if (ret < 0) {
-		sd_err("failed to write end marker, %m");
-		ret = EXIT_SYSFAIL;
-		goto out;
-	}
-
-	fsync(STDOUT_FILENO);
-out:
-	free(from_inode);
-	free(to_inode);
-	free(backup);
-	return ret;
-}
-
-/* restore backup data to vdi */
-static int restore_obj(struct obj_backup *backup, uint32_t vid,
-		       struct sd_inode *parent_inode)
-{
-	int ret;
-	uint32_t parent_vid = parent_inode->data_vdi_id[backup->idx];
-	uint64_t parent_oid = 0;
-
-	if (parent_vid)
-		parent_oid = vid_to_data_oid(parent_vid, backup->idx);
-
-	/* send a copy-on-write request */
-	ret = sd_write_object(vid_to_data_oid(vid, backup->idx), parent_oid,
-			      backup->data, backup->length, backup->offset,
-			      0, parent_inode->nr_copies, true, true);
-	if (ret != SD_RES_SUCCESS)
-		return ret;
-
-	return sd_write_object(vid_to_vdi_oid(vid), 0, &vid, sizeof(vid),
-			       SD_INODE_HEADER_SIZE + sizeof(vid) * backup->idx,
-			       0, parent_inode->nr_copies, false, true);
-}
-
-static uint32_t do_restore(const char *vdiname, int snapid, const char *tag)
-{
-	int ret;
-	uint32_t vid;
-	struct backup_hdr hdr;
-	struct obj_backup *backup = xzalloc(sizeof(*backup));
-	struct sd_inode *inode = xzalloc(sizeof(*inode));
-
-	ret = xread(STDIN_FILENO, &hdr, sizeof(hdr));
-	if (ret != sizeof(hdr))
-		sd_err("failed to read backup header, %m");
-
-	if (hdr.version != VDI_BACKUP_FORMAT_VERSION ||
-	    hdr.magic != VDI_BACKUP_MAGIC) {
-		sd_err("The backup file is corrupted");
-		ret = EXIT_SYSFAIL;
-		goto out;
-	}
-
-	ret = read_vdi_obj(vdiname, snapid, tag, NULL, inode, SD_INODE_SIZE);
-	if (ret != EXIT_SUCCESS)
-		goto out;
-
-	ret = do_vdi_create(vdiname, inode->vdi_size, inode->vdi_id, &vid,
-			    false, inode->nr_copies);
-	if (ret != EXIT_SUCCESS) {
-		sd_err("Failed to read VDI");
-		goto out;
-	}
-
-	while (true) {
-		ret = xread(STDIN_FILENO, backup,
-			    sizeof(*backup) - sizeof(backup->data));
-		if (ret != sizeof(*backup) - sizeof(backup->data)) {
-			sd_err("failed to read backup data");
-			ret = EXIT_SYSFAIL;
-			break;
-		}
-
-		if (backup->idx == UINT32_MAX) {
-			ret = EXIT_SUCCESS;
-			break;
-		}
-
-		ret = xread(STDIN_FILENO, backup->data, backup->length);
-		if (ret != backup->length) {
-			sd_err("failed to read backup data");
-			ret = EXIT_SYSFAIL;
-			break;
-		}
-
-		ret = restore_obj(backup, vid, inode);
-		if (ret != SD_RES_SUCCESS) {
-			sd_err("failed to restore backup");
-			do_vdi_delete(vdiname, 0, NULL);
-			ret = EXIT_FAILURE;
-			break;
-		}
-	}
-out:
-	free(backup);
-	free(inode);
-
-	return ret;
-}
-
-static int vdi_restore(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	int ret;
-	char buf[SD_INODE_HEADER_SIZE] = {0};
-	struct sd_inode *current_inode = xzalloc(sizeof(*current_inode));
-	struct sd_inode *parent_inode = (struct sd_inode *)buf;
-	bool need_current_recovery = false;
-
-	if (!vdi_cmd_data.snapshot_id && !vdi_cmd_data.snapshot_tag[0]) {
-		sd_err("We can restore a backup file only to snapshots");
-		sd_err("Please specify the '-s' option");
-		ret = EXIT_USAGE;
-		goto out;
-	}
-
-	/*
-	 * delete the current vdi temporarily first to avoid making
-	 * the current state become snapshot
-	 */
-	ret = read_vdi_obj(vdiname, 0, "", NULL, current_inode,
-			   SD_INODE_HEADER_SIZE);
-	if (ret != EXIT_SUCCESS)
-		goto out;
-
-	ret = sd_read_object(vid_to_vdi_oid(current_inode->parent_vdi_id),
-			     parent_inode, SD_INODE_HEADER_SIZE, 0, true);
-	if (ret != SD_RES_SUCCESS) {
-		printf("error\n");
-		goto out;
-	}
-
-	if (is_stdin_console()) {
-		sd_err("stdin must be pipe");
-		ret = EXIT_USAGE;
-		goto out;
-	}
-
-	ret = do_vdi_delete(vdiname, 0, NULL);
-	if (ret != EXIT_SUCCESS) {
-		sd_err("Failed to delete the current state");
-		goto out;
-	}
-	need_current_recovery = true;
-
-	/* restore backup data */
-	ret = do_restore(vdiname, vdi_cmd_data.snapshot_id,
-			 vdi_cmd_data.snapshot_tag);
-out:
-	if (need_current_recovery) {
-		int recovery_ret;
-		/* recreate the current vdi object */
-		recovery_ret = do_vdi_create(vdiname, current_inode->vdi_size,
-					     current_inode->parent_vdi_id, NULL,
-					     true, current_inode->nr_copies);
-		if (recovery_ret != EXIT_SUCCESS) {
-			sd_err("failed to resume the current vdi");
-			ret = recovery_ret;
-		}
-	}
-	free(current_inode);
-	return ret;
-}
-
-static int vdi_cache_flush(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	struct sd_req hdr;
-	uint32_t vid;
-	int ret = EXIT_SUCCESS;
-
-	ret = find_vdi_name(vdiname, vdi_cmd_data.snapshot_id,
-			    vdi_cmd_data.snapshot_tag, &vid, 0);
-	if (ret < 0) {
-		sd_err("Failed to open VDI %s", vdiname);
-		ret = EXIT_FAILURE;
-		goto out;
-	}
-
-	sd_init_req(&hdr, SD_OP_FLUSH_VDI);
-	hdr.obj.oid = vid_to_vdi_oid(vid);
-
-	ret = send_light_req(&hdr, sdhost, sdport);
-	if (ret) {
-		sd_err("failed to execute request");
-		return EXIT_FAILURE;
-	}
-out:
-	return ret;
-}
-
-static int vdi_cache_delete(int argc, char **argv)
-{
-	const char *vdiname = argv[optind++];
-	struct sd_req hdr;
-	uint32_t vid;
-	int ret = EXIT_SUCCESS;
-
-	ret = find_vdi_name(vdiname, vdi_cmd_data.snapshot_id,
-			    vdi_cmd_data.snapshot_tag, &vid, 0);
-	if (ret < 0) {
-		sd_err("Failed to open VDI %s", vdiname);
-		ret = EXIT_FAILURE;
-		goto out;
-	}
-
-	sd_init_req(&hdr, SD_OP_DELETE_CACHE);
-	hdr.obj.oid = vid_to_vdi_oid(vid);
-
-	ret = send_light_req(&hdr, sdhost, sdport);
-	if (ret) {
-		sd_err("failed to execute request");
-		return EXIT_FAILURE;
-	}
-out:
-	return ret;
-}
-
-static int vid_to_name_tag(uint32_t vid, char *name, char *tag)
-{
-	struct sd_inode inode;
-	int ret;
-
-	ret = sd_read_object(vid_to_vdi_oid(vid), &inode, SD_INODE_HEADER_SIZE,
-			     0, true);
-	if (ret != SD_RES_SUCCESS)
-		return ret;
-
-	pstrcpy(name, SD_MAX_VDI_LEN, inode.name);
-	pstrcpy(tag, SD_MAX_VDI_TAG_LEN, inode.tag);
-
-	return SD_RES_SUCCESS;
-}
-
-static int vdi_cache_info(int argc, char **argv)
-{
-	struct object_cache_info info = {};
-	struct sd_req hdr;
-	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
-	char size_str[UINT64_DECIMAL_SIZE], used_str[UINT64_DECIMAL_SIZE];
-	int ret, i;
-
-	sd_init_req(&hdr, SD_OP_GET_CACHE_INFO);
-	hdr.data_length = sizeof(info);
-	ret = collie_exec_req(sdhost, sdport, &hdr, &info);
-	if (ret < 0)
-		return EXIT_SYSFAIL;
-
-	if (rsp->result != SD_RES_SUCCESS) {
-		sd_err("failed to get cache infomation: %s",
-		       sd_strerror(rsp->result));
-		return EXIT_FAILURE;
-	}
-
-	fprintf(stdout, "Name\tTag\tTotal\tDirty\tClean\n");
-	for (i = 0; i < info.count; i++) {
-		char total_str[UINT64_DECIMAL_SIZE],
-		     dirty_str[UINT64_DECIMAL_SIZE],
-		     clean_str[UINT64_DECIMAL_SIZE];
-		uint64_t total = info.caches[i].total * SD_DATA_OBJ_SIZE,
-			 dirty = info.caches[i].dirty * SD_DATA_OBJ_SIZE,
-			 clean = total - dirty;
-		char name[SD_MAX_VDI_LEN], tag[SD_MAX_VDI_TAG_LEN];
-
-		size_to_str(total, total_str, sizeof(total_str));
-		size_to_str(dirty, dirty_str, sizeof(dirty_str));
-		size_to_str(clean, clean_str, sizeof(clean_str));
-		ret = vid_to_name_tag(info.caches[i].vid, name, tag);
-		if (ret != SD_RES_SUCCESS)
-			return EXIT_FAILURE;
-		fprintf(stdout, "%s\t%s\t%s\t%s\t%s\n",
-			name, tag, total_str, dirty_str, clean_str);
-	}
-
-	size_to_str(info.size, size_str, sizeof(size_str));
-	size_to_str(info.used, used_str, sizeof(used_str));
-	fprintf(stdout, "\nCache size %s, used %s\n", size_str, used_str);
-
-	return EXIT_SUCCESS;
-}
-
-static struct subcommand vdi_cache_cmd[] = {
-	{"flush", NULL, NULL, "flush the cache of the vdi specified.",
-	 NULL, CMD_NEED_ARG, vdi_cache_flush},
-	{"delete", NULL, NULL, "delete the cache of the vdi specified in all nodes.",
-	 NULL, CMD_NEED_ARG, vdi_cache_delete},
-	{"info", NULL, NULL, "show usage of the cache",
-	 NULL, 0, vdi_cache_info},
-	{NULL,},
-};
-
-static int vdi_cache(int argc, char **argv)
-{
-	return do_generic_subcommand(vdi_cache_cmd, argc, argv);
-}
-
-static struct subcommand vdi_cmd[] = {
-	{"check", "<vdiname>", "saph", "check and repair image's consistency",
-	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
-	 vdi_check, vdi_options},
-	{"create", "<vdiname> <size>", "Pcaphrv", "create an image",
-	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
-	 vdi_create, vdi_options},
-	{"snapshot", "<vdiname>", "saphrv", "create a snapshot",
-	 NULL, CMD_NEED_ARG,
-	 vdi_snapshot, vdi_options},
-	{"clone", "<src vdi> <dst vdi>", "sPcaphrv", "clone an image",
-	 NULL, CMD_NEED_ARG,
-	 vdi_clone, vdi_options},
-	{"delete", "<vdiname>", "saph", "delete an image",
-	 NULL, CMD_NEED_ARG,
-	 vdi_delete, vdi_options},
-	{"rollback", "<vdiname>", "saphfrv", "rollback to a snapshot",
-	 NULL, CMD_NEED_ARG,
-	 vdi_rollback, vdi_options},
-	{"list", "[vdiname]", "aprh", "list images",
-	 NULL, 0, vdi_list, vdi_options},
-	{"tree", NULL, "aph", "show images in tree view format",
-	 NULL, 0, vdi_tree, vdi_options},
-	{"graph", NULL, "aph", "show images in Graphviz dot format",
-	 NULL, 0, vdi_graph, vdi_options},
-	{"object", "<vdiname>", "isaph", "show object information in the image",
-	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
-	 vdi_object, vdi_options},
-	{"track", "<vdiname>", "isaph", "show the object epoch trace in the image",
-	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
-	 vdi_track, vdi_options},
-	{"setattr", "<vdiname> <key> [value]", "dxaph", "set a VDI attribute",
-	 NULL, CMD_NEED_ARG,
-	 vdi_setattr, vdi_options},
-	{"getattr", "<vdiname> <key>", "aph", "get a VDI attribute",
-	 NULL, CMD_NEED_ARG,
-	 vdi_getattr, vdi_options},
-	{"resize", "<vdiname> <new size>", "aph", "resize an image",
-	 NULL, CMD_NEED_ARG,
-	 vdi_resize, vdi_options},
-	{"read", "<vdiname> [<offset> [<len>]]", "saph", "read data from an image",
-	 NULL, CMD_NEED_ARG,
-	 vdi_read, vdi_options},
-	{"write", "<vdiname> [<offset> [<len>]]", "apwh", "write data to an image",
-	 NULL, CMD_NEED_ARG,
-	 vdi_write, vdi_options},
-	{"backup", "<vdiname> <backup>", "sFaph", "create an incremental backup between two snapshots",
-	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
-	 vdi_backup, vdi_options},
-	{"restore", "<vdiname> <backup>", "saph", "restore snapshot images from a backup",
-	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
-	 vdi_restore, vdi_options},
-	{"cache", "<vdiname>", "saph", "Run 'collie vdi cache' for more information",
-	 vdi_cache_cmd, CMD_NEED_ARG,
-	 vdi_cache, vdi_options},
-	{NULL,},
-};
-
-static int vdi_parser(int ch, char *opt)
-{
-	char *p;
-	int nr_copies;
-
-	switch (ch) {
-	case 'P':
-		vdi_cmd_data.prealloc = true;
-		break;
-	case 'i':
-		vdi_cmd_data.index = strtol(opt, &p, 10);
-		if (opt == p) {
-			sd_err("The index must be an integer");
-			exit(EXIT_FAILURE);
-		}
-		break;
-	case 's':
-		vdi_cmd_data.snapshot_id = strtol(opt, &p, 10);
-		if (opt == p) {
-			vdi_cmd_data.snapshot_id = 0;
-			pstrcpy(vdi_cmd_data.snapshot_tag,
-				sizeof(vdi_cmd_data.snapshot_tag), opt);
-		} else if (vdi_cmd_data.snapshot_id == 0) {
-			fprintf(stderr,
-				"The snapshot id must be larger than zero\n");
-			exit(EXIT_FAILURE);
-		}
-		break;
-	case 'x':
-		vdi_cmd_data.exclusive = true;
-		break;
-	case 'd':
-		vdi_cmd_data.delete = true;
-		break;
-	case 'w':
-		vdi_cmd_data.writeback = true;
-		break;
-	case 'c':
-		nr_copies = strtol(opt, &p, 10);
-		if (opt == p || nr_copies < 0 || nr_copies > SD_MAX_COPIES) {
-			sd_err("Invalid copies number, must be "
-			       "an integer between 0 and %d", SD_MAX_COPIES);
-			exit(EXIT_FAILURE);
-		}
-		vdi_cmd_data.nr_copies = nr_copies;
-		break;
-	case 'F':
-		vdi_cmd_data.from_snapshot_id = strtol(opt, &p, 10);
-		if (opt == p) {
-			vdi_cmd_data.from_snapshot_id = 0;
-			pstrcpy(vdi_cmd_data.from_snapshot_tag,
-				sizeof(vdi_cmd_data.from_snapshot_tag), opt);
-		}
-		break;
-	case 'f':
-		vdi_cmd_data.force = true;
-		break;
-	}
-
-	return 0;
-}
-
-struct command vdi_command = {
-	"vdi",
-	vdi_cmd,
-	vdi_parser
-};
diff --git a/configure.ac b/configure.ac
index 9831379..991a869 100644
--- a/configure.ac
+++ b/configure.ac
@@ -28,7 +28,7 @@ AC_INIT([sheepdog], m4_default(git_version, sheepdog_version),
 	[sheepdog at lists.wpkg.org])
 AM_INIT_AUTOMAKE([-Wno-portability])
 
-AC_CONFIG_SRCDIR([collie/collie.c])
+AC_CONFIG_SRCDIR([dog/dog.c])
 AC_CONFIG_HEADER([include/config.h])
 
 AC_CANONICAL_HOST
@@ -127,7 +127,7 @@ AC_CHECK_FUNCS([alarm alphasort atexit bzero dup2 endgrent endpwent fcntl \
 		strerror strrchr strspn strstr])
 
 AC_CONFIG_FILES([Makefile
-		collie/Makefile
+		dog/Makefile
 		sheep/Makefile
 		sheepfs/Makefile
 		include/Makefile
@@ -137,7 +137,7 @@ AC_CONFIG_FILES([Makefile
 		shepherd/Makefile
 		tests/unit/Makefile
 		tests/unit/mock/Makefile
-		tests/unit/collie/Makefile
+		tests/unit/dog/Makefile
 		tests/unit/sheep/Makefile
 		tools/Makefile])
 
diff --git a/debian/sheepdog.bash-completion b/debian/sheepdog.bash-completion
index 3406bc5..ba7fc2b 100644
--- a/debian/sheepdog.bash-completion
+++ b/debian/sheepdog.bash-completion
@@ -1 +1 @@
-script/bash_completion_collie collie
+script/bash_completion_dog dog
diff --git a/dog/Makefile.am b/dog/Makefile.am
new file mode 100644
index 0000000..fc826d9
--- /dev/null
+++ b/dog/Makefile.am
@@ -0,0 +1,56 @@
+#
+# Copyright 2010 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License 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; see the file COPYING.  If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+AM_CFLAGS		=
+
+INCLUDES		= -I$(top_builddir)/include -I$(top_srcdir)/include
+
+sbin_PROGRAMS		= dog
+
+dog_SOURCES		= farm/object_tree.c farm/sha1_file.c farm/snap.c \
+			  farm/trunk.c farm/farm.c farm/slice.c \
+			  dog.c common.c treeview.c vdi.c node.c cluster.c
+
+if BUILD_TRACE
+dog_SOURCES		+= trace.c
+override CFLAGS         := $(subst -pg,,$(CFLAGS))
+endif
+
+dog_LDADD		= ../lib/libsheepdog.a -lpthread
+dog_DEPENDENCIES	= ../lib/libsheepdog.a
+
+noinst_HEADERS		= treeview.h dog.h farm/farm.h
+
+EXTRA_DIST		=
+
+all-local:
+	@echo Built dog
+
+clean-local:
+	rm -f dog *.o gmon.out *.da *.bb *.bbg
+
+# support for GNU Flymake
+check-syntax:
+	$(COMPILE) -fsyntax-only $(CHK_SOURCES)
+
+check-style:
+	@$(CHECK_STYLE) $(dog_SOURCES) $(noinst_HEADERS)
+
+coverage:
+	@lcov -d . -c -o dog.info
diff --git a/dog/cluster.c b/dog/cluster.c
new file mode 100644
index 0000000..4c7654d
--- /dev/null
+++ b/dog/cluster.c
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/time.h>
+
+#include "dog.h"
+#include "farm/farm.h"
+
+static struct sd_option cluster_options[] = {
+	{'b', "store", true, "specify backend store"},
+	{'c', "copies", true, "specify the default data redundancy (number of copies)"},
+	{'f', "force", false, "do not prompt for confirmation"},
+
+	{ 0, NULL, false, NULL },
+};
+
+static struct cluster_cmd_data {
+	int copies;
+	bool force;
+	char name[STORE_LEN];
+} cluster_cmd_data;
+
+#define DEFAULT_STORE	"plain"
+
+static int list_store(void)
+{
+	int ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	char buf[512] = { 0 };
+
+	sd_init_req(&hdr, SD_OP_GET_STORE_LIST);
+	hdr.data_length = 512;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, buf);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Restore failed: %s", sd_strerror(rsp->result));
+		return EXIT_FAILURE;
+	}
+
+	printf("Available stores:\n");
+	printf("---------------------------------------\n");
+	printf("%s\n", buf);
+	return EXIT_SYSFAIL;
+}
+
+static bool no_vdi(const unsigned long *vdis)
+{
+	return find_next_bit(vdis, SD_NR_VDIS, 0) == SD_NR_VDIS;
+}
+
+#define FORMAT_PRINT				\
+	"    __\n"				\
+	"   ()'`;\n"				\
+	"   /\\|`\n"				\
+	"  /  |   Caution! The cluster is not empty.\n" \
+	"(/_)_|_  Are you sure you want to continue? [yes/no]: "
+
+static int cluster_format(int argc, char **argv)
+{
+	int ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	struct timeval tv;
+	char store_name[STORE_LEN];
+	static DECLARE_BITMAP(vdi_inuse, SD_NR_VDIS);
+
+	sd_init_req(&hdr, SD_OP_READ_VDIS);
+	hdr.data_length = sizeof(vdi_inuse);
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, &vdi_inuse);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (!no_vdi(vdi_inuse))
+		confirm(FORMAT_PRINT);
+
+	gettimeofday(&tv, NULL);
+
+	sd_init_req(&hdr, SD_OP_MAKE_FS);
+	hdr.cluster.copies = cluster_cmd_data.copies;
+	hdr.cluster.ctime = (uint64_t) tv.tv_sec << 32 | tv.tv_usec * 1000;
+
+	if (strlen(cluster_cmd_data.name))
+		pstrcpy(store_name, STORE_LEN, cluster_cmd_data.name);
+	else
+		pstrcpy(store_name, STORE_LEN, DEFAULT_STORE);
+	hdr.data_length = strlen(store_name) + 1;
+	hdr.flags |= SD_FLAG_CMD_WRITE;
+
+	printf("using backend %s store\n", store_name);
+	ret = dog_exec_req(sdhost, sdport, &hdr, store_name);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Format failed: %s", sd_strerror(rsp->result));
+		if (rsp->result == SD_RES_NO_STORE)
+			return list_store();
+		else
+			return EXIT_SYSFAIL;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int cluster_info(int argc, char **argv)
+{
+	int i, ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	struct epoch_log *logs;
+	int nr_logs, log_length;
+	time_t ti, ct;
+	struct tm tm;
+	char time_str[128];
+
+	log_length = sd_epoch * sizeof(struct epoch_log);
+	logs = xmalloc(log_length);
+
+	sd_init_req(&hdr, SD_OP_STAT_CLUSTER);
+	hdr.data_length = log_length;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, logs);
+	if (ret < 0)
+		goto error;
+
+	if (!raw_output)
+		printf("Cluster status: ");
+	if (rsp->result == SD_RES_SUCCESS)
+		printf("running, auto-recovery %s\n", logs->disable_recovery ?
+		       "disabled" : "enabled");
+	else
+		printf("%s\n", sd_strerror(rsp->result));
+
+	if (!raw_output && rsp->data_length > 0) {
+		ct = logs[0].ctime >> 32;
+		printf("\nCluster created at %s\n", ctime(&ct));
+		printf("Epoch Time           Version\n");
+	}
+
+	nr_logs = rsp->data_length / sizeof(struct epoch_log);
+	for (i = 0; i < nr_logs; i++) {
+		int j;
+		const struct sd_node *entry;
+
+		ti = logs[i].time;
+		if (raw_output) {
+			snprintf(time_str, sizeof(time_str), "%" PRIu64, (uint64_t) ti);
+		} else {
+			localtime_r(&ti, &tm);
+			strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &tm);
+		}
+
+		printf(raw_output ? "%s %d" : "%s %6d", time_str, logs[i].epoch);
+		printf(" [");
+		for (j = 0; j < logs[i].nr_nodes; j++) {
+			entry = logs[i].nodes + j;
+			printf("%s%s",
+			       (j == 0) ? "" : ", ",
+			       addr_to_str(entry->nid.addr, entry->nid.port));
+		}
+		printf("]\n");
+	}
+
+	free(logs);
+	return EXIT_SUCCESS;
+error:
+	free(logs);
+	return EXIT_SYSFAIL;
+}
+
+static int cluster_shutdown(int argc, char **argv)
+{
+	int ret;
+	struct sd_req hdr;
+
+	sd_init_req(&hdr, SD_OP_SHUTDOWN);
+
+	ret = send_light_req(&hdr, sdhost, sdport);
+	if (ret) {
+		sd_err("failed to execute request");
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static void print_list(void *buf, unsigned len)
+{
+	struct snap_log *log_buf = (struct snap_log *)buf;
+	unsigned nr = len / sizeof(struct snap_log);
+
+	printf("Index\t\tTag\t\tSnapshot Time\n");
+	for (unsigned i = 0; i < nr; i++, log_buf++) {
+		time_t *t = (time_t *)&log_buf->time;
+		printf("%d\t\t", log_buf->idx);
+		printf("%s\t\t", log_buf->tag);
+		printf("%s", ctime(t));
+	}
+}
+
+static int list_snapshot(int argc, char **argv)
+{
+	char *path = argv[optind++];
+	void *buf = NULL;
+	int log_nr;
+	int ret = EXIT_SYSFAIL;
+
+	if (farm_init(path) != SD_RES_SUCCESS)
+		goto out;
+
+	buf = snap_log_read(&log_nr);
+	if (!buf)
+		goto out;
+
+	print_list(buf, log_nr * sizeof(struct snap_log));
+	ret = EXIT_SUCCESS;
+out:
+	if (ret)
+		sd_err("Fail to list snapshot.");
+	free(buf);
+	return ret;
+}
+
+static void fill_object_tree(uint32_t vid, const char *name, const char *tag,
+			  uint32_t snapid, uint32_t flags,
+			  const struct sd_inode *i, void *data)
+{
+	uint64_t vdi_oid = vid_to_vdi_oid(vid), vmstate_oid;
+	int nr_vmstate_object;
+
+	/* ignore active vdi */
+	if (!vdi_is_snapshot(i))
+		return;
+
+	/* fill vdi object id */
+	object_tree_insert(vdi_oid, i->nr_copies);
+
+	/* fill data object id */
+	for (uint64_t idx = 0; idx < MAX_DATA_OBJS; idx++) {
+		if (i->data_vdi_id[idx]) {
+			uint64_t oid = vid_to_data_oid(i->data_vdi_id[idx],
+						       idx);
+			object_tree_insert(oid, i->nr_copies);
+		}
+	}
+
+	/* fill vmstate object id */
+	nr_vmstate_object = DIV_ROUND_UP(i->vm_state_size, SD_DATA_OBJ_SIZE);
+	for (int idx = 0; idx < nr_vmstate_object; idx++) {
+		vmstate_oid = vid_to_vmstate_oid(vid, idx);
+		object_tree_insert(vmstate_oid, i->nr_copies);
+	}
+}
+
+static int save_snapshot(int argc, char **argv)
+{
+	char *tag = argv[optind++];
+	char *path, *p;
+	int ret = EXIT_SYSFAIL, uninitialized_var(unused);
+
+	unused = strtol(tag, &p, 10);
+	if (tag != p) {
+		sd_err("Tag should not start with number.");
+		return EXIT_USAGE;
+	}
+
+	if (!argv[optind]) {
+		sd_err("Please specify the path to save snapshot.");
+		return EXIT_USAGE;
+	}
+	path = argv[optind];
+
+	if (farm_init(path) != SD_RES_SUCCESS)
+		goto out;
+
+	if (farm_contain_snapshot(0, tag)) {
+		sd_err("Snapshot tag has already been used for another"
+		       " snapshot, please, use another one.");
+		goto out;
+	}
+
+	if (parse_vdi(fill_object_tree, SD_INODE_SIZE, NULL) != SD_RES_SUCCESS)
+		goto out;
+
+	if (farm_save_snapshot(tag) != SD_RES_SUCCESS)
+		goto out;
+
+	ret = EXIT_SUCCESS;
+out:
+	if (ret)
+		sd_err("Fail to save snapshot to path: %s.", path);
+	object_tree_free();
+	return ret;
+}
+
+static int load_snapshot(int argc, char **argv)
+{
+	char *tag = argv[optind++];
+	char *path, *p;
+	uint32_t idx;
+	int ret = EXIT_SYSFAIL;
+
+	idx = strtol(tag, &p, 10);
+	if (tag == p)
+		idx = 0;
+
+	if (!argv[optind]) {
+		sd_err("Please specify the path to save snapshot.");
+		return EXIT_USAGE;
+	}
+	path = argv[optind];
+
+	if (farm_init(path) != SD_RES_SUCCESS)
+		goto out;
+
+	if (!farm_contain_snapshot(idx, tag)) {
+		sd_err("Snapshot index or tag does not exist.");
+		goto out;
+	}
+
+	if (cluster_format(0, NULL) != SD_RES_SUCCESS)
+		goto out;
+
+	if (farm_load_snapshot(idx, tag) != SD_RES_SUCCESS)
+		goto out;
+
+	ret = EXIT_SUCCESS;
+out:
+	if (ret)
+		sd_err("Fail to load snapshot");
+	return ret;
+}
+
+#define RECOVER_PRINT \
+	"Caution! Please try starting all the cluster nodes normally before\n" \
+	"running this command.\n\n" \
+	"The cluster may need to be force recovered if:\n" \
+	"  - the master node fails to start because of epoch mismatch; or\n" \
+	"  - some nodes fail to start after a cluster shutdown.\n\n" \
+	"Are you sure you want to continue? [yes/no]: "
+
+static int cluster_force_recover(int argc, char **argv)
+{
+	int ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	char str[123] = {'\0'};
+	struct sd_node nodes[SD_MAX_NODES];
+
+	if (!cluster_cmd_data.force) {
+		int i, l;
+		printf(RECOVER_PRINT);
+		ret = scanf("%s", str);
+		if (ret < 0)
+			return EXIT_SYSFAIL;
+		l = strlen(str);
+		for (i = 0; i < l; i++)
+			str[i] = tolower(str[i]);
+		if (strncmp(str, "yes", 3) != 0)
+			return EXIT_SUCCESS;
+	}
+
+	sd_init_req(&hdr, SD_OP_FORCE_RECOVER);
+	hdr.data_length = sizeof(nodes);
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, nodes);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("failed to execute request, %s",
+		       sd_strerror(rsp->result));
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int cluster_disable_recover(int argc, char **argv)
+{
+	int ret;
+	struct sd_req hdr;
+
+	sd_init_req(&hdr, SD_OP_DISABLE_RECOVER);
+
+	ret = send_light_req(&hdr, sdhost, sdport);
+	if (ret)
+		return EXIT_FAILURE;
+
+	printf("Cluster recovery: disable\n");
+	return EXIT_SUCCESS;
+}
+
+static int cluster_enable_recover(int argc, char **argv)
+{
+	int ret;
+	struct sd_req hdr;
+
+	sd_init_req(&hdr, SD_OP_ENABLE_RECOVER);
+
+	ret = send_light_req(&hdr, sdhost, sdport);
+	if (ret)
+		return EXIT_FAILURE;
+
+	printf("Cluster recovery: enable\n");
+	return EXIT_SUCCESS;
+}
+
+/* Subcommand list of recover */
+static struct subcommand cluster_recover_cmd[] = {
+	{"force", NULL, NULL, "force recover cluster immediately",
+	 NULL, 0, cluster_force_recover},
+	{"enable", NULL, NULL, "enable automatic recovery and "
+				"run once recover if necessary",
+	 NULL, 0, cluster_enable_recover},
+	{"disable", NULL, NULL, "disable automatic recovery",
+	 NULL, 0, cluster_disable_recover},
+	{NULL},
+};
+
+static int cluster_recover(int argc, char **argv)
+{
+	return do_generic_subcommand(cluster_recover_cmd, argc, argv);
+}
+
+/* Subcommand list of snapshot */
+static struct subcommand cluster_snapshot_cmd[] = {
+	{"save", NULL, "h", "save snapshot to localpath",
+	 NULL, CMD_NEED_ARG|CMD_NEED_NODELIST,
+	 save_snapshot, NULL},
+	{"list", NULL, "h", "list snapshot of localpath",
+	 NULL, CMD_NEED_ARG, list_snapshot, NULL},
+	{"load", NULL, "h", "load snapshot from localpath",
+	 NULL, CMD_NEED_ARG, load_snapshot, NULL},
+	{NULL},
+};
+
+static int cluster_snapshot(int argc, char **argv)
+{
+	return do_generic_subcommand(cluster_snapshot_cmd, argc, argv);
+}
+
+static int cluster_reweight(int argc, char **argv)
+{
+	int ret;
+	struct sd_req hdr;
+
+	sd_init_req(&hdr, SD_OP_REWEIGHT);
+	ret = send_light_req(&hdr, sdhost, sdport);
+	if (ret)
+		return EXIT_FAILURE;
+	return EXIT_SUCCESS;
+}
+
+static struct subcommand cluster_cmd[] = {
+	{"info", NULL, "aprh", "show cluster information",
+	 NULL, CMD_NEED_NODELIST, cluster_info, cluster_options},
+	{"format", NULL, "bcaph", "create a Sheepdog store",
+	 NULL, 0, cluster_format, cluster_options},
+	{"shutdown", NULL, "aph", "stop Sheepdog",
+	 NULL, 0, cluster_shutdown, cluster_options},
+	{"snapshot", "<tag|idx> <path>", "aph", "snapshot/restore the cluster",
+	 cluster_snapshot_cmd, CMD_NEED_ARG,
+	 cluster_snapshot, cluster_options},
+	{"recover", NULL, "afph",
+	 "See 'dog cluster recover' for more information",
+	 cluster_recover_cmd, CMD_NEED_ARG,
+	 cluster_recover, cluster_options},
+	{"reweight", NULL, "aph", "reweight the cluster", NULL, 0,
+	 cluster_reweight, cluster_options},
+	{NULL,},
+};
+
+static int cluster_parser(int ch, char *opt)
+{
+	int copies;
+	char *p;
+
+	switch (ch) {
+	case 'b':
+		pstrcpy(cluster_cmd_data.name, sizeof(cluster_cmd_data.name),
+			opt);
+		break;
+	case 'c':
+		copies = strtol(opt, &p, 10);
+		if (opt == p || copies < 1) {
+			sd_err("There must be at least one copy of data");
+			exit(EXIT_FAILURE);
+		} else if (copies > SD_MAX_COPIES) {
+			sd_err("Redundancy may not exceed %d copies",
+			       SD_MAX_COPIES);
+			exit(EXIT_FAILURE);
+		}
+		cluster_cmd_data.copies = copies;
+		break;
+	case 'f':
+		cluster_cmd_data.force = true;
+		break;
+	}
+
+	return 0;
+}
+
+struct command cluster_command = {
+	"cluster",
+	cluster_cmd,
+	cluster_parser
+};
diff --git a/dog/common.c b/dog/common.c
new file mode 100644
index 0000000..14d3b1d
--- /dev/null
+++ b/dog/common.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dog.h"
+#include "sha1.h"
+#include "sockfd_cache.h"
+
+char *size_to_str(uint64_t _size, char *str, int str_size)
+{
+	const char *units[] = {"MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
+	int i = 0;
+	double size;
+
+	if (raw_output) {
+		snprintf(str, str_size, "%" PRIu64, _size);
+		return str;
+	}
+
+	size = (double)_size;
+	size /= 1024 * 1024;
+	while (i < ARRAY_SIZE(units) - 1 && size >= 1024) {
+		i++;
+		size /= 1024;
+	}
+
+	if (size >= 10)
+		snprintf(str, str_size, "%.0lf %s", size, units[i]);
+	else
+		snprintf(str, str_size, "%.1lf %s", size, units[i]);
+
+	return str;
+}
+
+int sd_read_object(uint64_t oid, void *data, unsigned int datalen,
+		   uint64_t offset, bool direct)
+{
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	int ret;
+
+	sd_init_req(&hdr, SD_OP_READ_OBJ);
+	hdr.data_length = datalen;
+
+	hdr.obj.oid = oid;
+	hdr.obj.offset = offset;
+	if (direct)
+		hdr.flags |= SD_FLAG_CMD_DIRECT;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, data);
+	if (ret < 0) {
+		sd_err("Failed to read object %" PRIx64, oid);
+		return SD_RES_EIO;
+	}
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Failed to read object %" PRIx64 " %s", oid,
+		       sd_strerror(rsp->result));
+		return rsp->result;
+	}
+
+	untrim_zero_blocks(data, rsp->obj.offset, rsp->data_length, datalen);
+
+	return SD_RES_SUCCESS;
+}
+
+int sd_write_object(uint64_t oid, uint64_t cow_oid, void *data,
+		    unsigned int datalen, uint64_t offset, uint32_t flags,
+		    int copies, bool create, bool direct)
+{
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	int ret;
+
+	if (create)
+		sd_init_req(&hdr, SD_OP_CREATE_AND_WRITE_OBJ);
+	else
+		sd_init_req(&hdr, SD_OP_WRITE_OBJ);
+
+	hdr.data_length = datalen;
+	hdr.flags = flags | SD_FLAG_CMD_WRITE;
+	if (cow_oid)
+		hdr.flags |= SD_FLAG_CMD_COW;
+	if (direct)
+		hdr.flags |= SD_FLAG_CMD_DIRECT;
+
+	hdr.obj.copies = copies;
+	hdr.obj.oid = oid;
+	hdr.obj.cow_oid = cow_oid;
+	hdr.obj.offset = offset;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, data);
+	if (ret < 0) {
+		sd_err("Failed to write object %" PRIx64, oid);
+		return SD_RES_EIO;
+	}
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Failed to write object %" PRIx64 ": %s", oid,
+		       sd_strerror(rsp->result));
+		return rsp->result;
+	}
+
+	return SD_RES_SUCCESS;
+}
+
+#define FOR_EACH_VDI(nr, vdis) FOR_EACH_BIT(nr, vdis, SD_NR_VDIS)
+
+int parse_vdi(vdi_parser_func_t func, size_t size, void *data)
+{
+	int ret;
+	unsigned long nr;
+	static struct sd_inode i;
+	struct sd_req req;
+	static DECLARE_BITMAP(vdi_inuse, SD_NR_VDIS);
+	unsigned int rlen = sizeof(vdi_inuse);
+
+	sd_init_req(&req, SD_OP_READ_VDIS);
+	req.data_length = sizeof(vdi_inuse);
+
+	ret = dog_exec_req(sdhost, sdport, &req, &vdi_inuse);
+	if (ret < 0)
+		goto out;
+
+	FOR_EACH_VDI(nr, vdi_inuse) {
+		uint64_t oid;
+		uint32_t snapid;
+
+		oid = vid_to_vdi_oid(nr);
+
+		memset(&i, 0, sizeof(i));
+		ret = sd_read_object(oid, &i, SD_INODE_HEADER_SIZE, 0, true);
+		if (ret != SD_RES_SUCCESS) {
+			sd_err("Failed to read inode header");
+			continue;
+		}
+
+		if (i.name[0] == '\0') /* this VDI has been deleted */
+			continue;
+
+		if (size > SD_INODE_HEADER_SIZE) {
+			rlen = DIV_ROUND_UP(i.vdi_size, SD_DATA_OBJ_SIZE) *
+				sizeof(i.data_vdi_id[0]);
+			if (rlen > size - SD_INODE_HEADER_SIZE)
+				rlen = size - SD_INODE_HEADER_SIZE;
+
+			ret = sd_read_object(oid, ((char *)&i) + SD_INODE_HEADER_SIZE,
+					     rlen, SD_INODE_HEADER_SIZE, true);
+
+			if (ret != SD_RES_SUCCESS) {
+				sd_err("Failed to read inode");
+				continue;
+			}
+		}
+
+		snapid = vdi_is_snapshot(&i) ? i.snap_id : 0;
+		func(i.vdi_id, i.name, i.tag, snapid, 0, &i, data);
+	}
+
+out:
+	return ret;
+}
+
+int dog_exec_req(const uint8_t *addr, int port, struct sd_req *hdr,
+		    void *buf)
+{
+	struct node_id nid = {};
+	struct sockfd *sfd;
+	int ret;
+
+	memcpy(nid.addr, addr, sizeof(nid.addr));
+	nid.port = port;
+
+	sfd = sockfd_cache_get(&nid);
+	if (!sfd)
+		return -1;
+
+	/*
+	 * Retry forever for dog because
+	 * 1. We can't get the newest epoch
+	 * 2. Some operations might take unexpected long time
+	 */
+	ret = exec_req(sfd->fd, hdr, buf, NULL, 0, UINT32_MAX);
+
+	sockfd_cache_put(&nid, sfd);
+
+	return ret ? -1 : 0;
+}
+
+/* Light request only contains header, without body content. */
+int send_light_req(struct sd_req *hdr, const uint8_t *addr, int port)
+{
+	int ret = dog_exec_req(addr, port, hdr, NULL);
+
+	if (ret == -1)
+		return -1;
+
+	if (ret != SD_RES_SUCCESS) {
+		sd_err("Response's result: %s", sd_strerror(ret));
+		return -1;
+	}
+
+	return 0;
+}
+
+int do_generic_subcommand(struct subcommand *sub, int argc, char **argv)
+{
+	int i, ret;
+
+	for (i = 0; sub[i].name; i++) {
+		if (!strcmp(sub[i].name, argv[optind])) {
+			unsigned long flags = sub[i].flags;
+
+			if (flags & CMD_NEED_NODELIST) {
+				ret = update_node_list(SD_MAX_NODES);
+				if (ret < 0) {
+					sd_err("Failed to get node list");
+					exit(EXIT_SYSFAIL);
+				}
+			}
+
+			if (flags & CMD_NEED_ARG && argc < 5)
+				subcommand_usage(argv[1], argv[2], EXIT_USAGE);
+			optind++;
+			ret = sub[i].fn(argc, argv);
+			if (ret == EXIT_USAGE)
+				subcommand_usage(argv[1], argv[2], EXIT_USAGE);
+			return ret;
+		}
+	}
+
+	subcommand_usage(argv[1], argv[2], EXIT_FAILURE);
+	return EXIT_FAILURE;
+}
+
+void confirm(const char *message)
+{
+	char input[8] = "";
+	char *ret;
+
+	printf("%s", message);
+	ret = fgets(input, sizeof(input), stdin);
+	if (ret == NULL || strncasecmp(input, "yes", 3) != 0)
+		exit(EXIT_SUCCESS);
+}
+
+void work_queue_wait(struct work_queue *q)
+{
+	while (!work_queue_empty(q))
+		event_loop(-1);
+}
+
+#define DEFAULT_SCREEN_WIDTH 80
+
+static int get_screen_width(void)
+{
+	struct winsize wsz;
+
+	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz) < 0)
+		return DEFAULT_SCREEN_WIDTH;
+
+	return wsz.ws_col;
+}
+
+/*
+ * Show prograss bar as follows.
+ *
+ *  45.0 % [===============>                  ] 180 MB / 400 MB
+ */
+void show_progress(uint64_t done, uint64_t total, bool raw)
+{
+	char done_str[256], total_str[256];
+	int screen_width = get_screen_width();
+	int bar_length = screen_width - 30;
+	char *buf;
+
+	if (!is_stdout_console())
+		return;
+	if (screen_width <= 0)
+		return;
+
+	printf("\r"); /* move to the beginning of the line */
+
+	if (raw) {
+		snprintf(done_str, sizeof(done_str), "%"PRIu64, done);
+		snprintf(total_str, sizeof(total_str), "%"PRIu64, total);
+	} else {
+		size_to_str(done, done_str, sizeof(done_str));
+		size_to_str(total, total_str, sizeof(total_str));
+	}
+
+	buf = xmalloc(screen_width + 1);
+	snprintf(buf, screen_width, "%5.1lf %% [", (double)done / total * 100);
+
+	for (int i = 0; i < bar_length; i++) {
+		if (total * (i + 1) / bar_length <= done)
+			strcat(buf, "=");
+		else if (total * i / bar_length <= done &&
+			 done < total * (i + 1) / bar_length)
+			strcat(buf, ">");
+		else
+			strcat(buf, " ");
+	}
+	snprintf(buf + strlen(buf), screen_width - strlen(buf),
+		 "] %s / %s", done_str, total_str);
+
+	/* fill the rest of buffer with blank characters */
+	memset(buf + strlen(buf), ' ', screen_width - strlen(buf));
+	buf[screen_width] = '\0';
+	printf("%s", buf);
+
+	if (done == total)
+		printf("\n");
+
+	fflush(stdout);
+
+	free(buf);
+}
diff --git a/dog/dog.c b/dog/dog.c
new file mode 100644
index 0000000..cca8e66
--- /dev/null
+++ b/dog/dog.c
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2009-2011 Nippon Telegraph and Telephone Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include "sheepdog_proto.h"
+#include "sheep.h"
+#include "dog.h"
+#include "util.h"
+#include "sockfd_cache.h"
+
+#define EPOLL_SIZE 4096
+
+static const char program_name[] = "dog";
+/* default sdhost is "127.0.0.1" */
+uint8_t sdhost[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1 };
+int sdport = SD_LISTEN_PORT;
+bool highlight = true;
+bool raw_output;
+bool verbose;
+
+static const struct sd_option dog_options[] = {
+
+	/* common options for all dog commands */
+	{'a', "address", true, "specify the daemon address (default: localhost)"},
+	{'p', "port", true, "specify the daemon port"},
+	{'r', "raw", false, "raw output mode: omit headers, separate fields with\n"
+	 "                          single spaces and print all sizes in decimal bytes"},
+	{'v', "verbose", false, "print more information than default"},
+	{'h', "help", false, "display this help and exit"},
+
+	{ 0, NULL, false, NULL },
+};
+
+static void usage(const struct command *commands, int status);
+
+uint32_t sd_epoch;
+
+struct sd_node sd_nodes[SD_MAX_NODES];
+struct sd_vnode sd_vnodes[SD_MAX_VNODES];
+int sd_nodes_nr, sd_vnodes_nr;
+
+int update_node_list(int max_nodes)
+{
+	int ret;
+	unsigned int size;
+	char *buf = NULL;
+	struct sd_node *ent;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+
+	size = sizeof(*ent) * max_nodes;
+	buf = xzalloc(size);
+	sd_init_req(&hdr, SD_OP_GET_NODE_LIST);
+
+	hdr.data_length = size;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, buf);
+	if (ret < 0)
+		goto out;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Failed to update node list: %s",
+		       sd_strerror(rsp->result));
+		ret = -1;
+		goto out;
+	}
+
+	size = rsp->data_length;
+	sd_nodes_nr = size / sizeof(*ent);
+	if (sd_nodes_nr == 0) {
+		sd_err("There are no active sheep daemons");
+		exit(EXIT_FAILURE);
+	}
+
+	/* FIXME */
+	if (sd_nodes_nr > max_nodes) {
+		ret = -1;
+		goto out;
+	}
+
+	memcpy(sd_nodes, buf, size);
+	sd_vnodes_nr = nodes_to_vnodes(sd_nodes, sd_nodes_nr, sd_vnodes);
+	sd_epoch = hdr.epoch;
+out:
+	if (buf)
+		free(buf);
+
+	return ret;
+}
+
+static int (*command_parser)(int, char *);
+static int (*command_fn)(int, char **);
+static const char *command_opts;
+static const char *command_arg;
+static const char *command_desc;
+static struct sd_option *command_options;
+
+static const struct sd_option *find_opt(int ch)
+{
+	const struct sd_option *opt;
+
+	/* search for common options */
+	sd_for_each_option(opt, dog_options) {
+		if (opt->ch == ch)
+			return opt;
+	}
+
+	/* search for self options */
+	if (command_options) {
+		sd_for_each_option(opt, command_options) {
+			if (opt->ch == ch)
+				return opt;
+		}
+	}
+
+	sd_err("Internal error");
+	exit(EXIT_SYSFAIL);
+}
+
+static void init_commands(const struct command **commands)
+{
+	static struct command *cmds;
+	struct command command_list[] = {
+		vdi_command,
+		node_command,
+		cluster_command,
+		trace_command,
+		{NULL,}
+	};
+
+	if (!cmds) {
+		cmds = (struct command *)xmalloc(sizeof(command_list));
+		memcpy(cmds, command_list, sizeof(command_list));
+	}
+
+	*commands = cmds;
+	return;
+}
+
+static const struct subcommand *find_subcmd(const char *cmd, const char *subcmd)
+{
+	int i, j;
+	const struct command *commands;
+	const struct subcommand *sub;
+
+	init_commands(&commands);
+
+	for (i = 0; commands[i].name; i++) {
+		if (!strcmp(commands[i].name, cmd)) {
+			sub = commands[i].sub;
+			for (j = 0; sub[j].name; j++) {
+				if (!strcmp(sub[j].name, subcmd))
+					return &sub[j];
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static unsigned long setup_commands(const struct command *commands,
+				    char *cmd, char *subcmd)
+{
+	int i;
+	bool found = false;
+	struct subcommand *s;
+	unsigned long flags = 0;
+
+	for (i = 0; commands[i].name; i++) {
+		if (!strcmp(commands[i].name, cmd)) {
+			found = true;
+			if (commands[i].parser)
+				command_parser = commands[i].parser;
+			break;
+		}
+	}
+
+	if (!found) {
+		if (cmd && strcmp(cmd, "help") && strcmp(cmd, "--help") &&
+		    strcmp(cmd, "-h")) {
+			sd_err("Invalid command '%s'", cmd);
+			usage(commands, EXIT_USAGE);
+		}
+		usage(commands, 0);
+	}
+
+	for (s = commands[i].sub; subcmd && s->name; s++) {
+		if (!strcmp(s->name, subcmd)) {
+			command_fn = s->fn;
+			command_opts = s->opts;
+			command_arg = s->arg;
+			command_desc = s->desc;
+			command_options = s->options;
+			flags = s->flags;
+			break;
+		}
+	}
+
+	if (!command_fn) {
+		if (subcmd && strcmp(subcmd, "help") &&
+		    strcmp(subcmd, "--help") && strcmp(subcmd, "-h"))
+			sd_err("Invalid command '%s %s'", cmd, subcmd);
+		sd_err("Available %s commands:", cmd);
+		for (s = commands[i].sub; s->name; s++)
+			sd_err("  %s %s", cmd, s->name);
+		exit(EXIT_USAGE);
+	}
+
+	return flags;
+}
+
+static void usage(const struct command *commands, int status)
+{
+	int i;
+	struct subcommand *s;
+	char name[64];
+
+	if (status)
+		sd_err("Try '%s --help' for more information.", program_name);
+	else {
+		printf("Sheepdog administrator utility\n");
+		printf("Usage: %s <command> <subcommand> [options]\n", program_name);
+		printf("\nAvailable commands:\n");
+		for (i = 0; commands[i].name; i++) {
+			for (s = commands[i].sub; s->name; s++) {
+				snprintf(name, sizeof(name), "%s %s",
+					 commands[i].name, s->name);
+				printf("  %-24s%s\n", name, s->desc);
+			}
+		}
+		printf("\n");
+		printf("For more information, run "
+		       "'%s <command> <subcommand> --help'.\n", program_name);
+	}
+	exit(status);
+}
+
+void subcommand_usage(char *cmd, char *subcmd, int status)
+{
+	int i, n, len = strlen(command_opts);
+	const struct sd_option *sd_opt;
+	const struct subcommand *sub, *subsub;
+	char name[64];
+
+	printf("Usage: %s %s %s", program_name, cmd, subcmd);
+
+	/* Show subcmd's subcommands if necessary */
+	sub = find_subcmd(cmd, subcmd);
+	subsub = sub->sub;
+	if (subsub) {
+		n = 0;
+		while (subsub[n].name)
+			n++;
+		if (n == 1)
+			printf(" %s", subsub[0].name);
+		else if (n > 1) {
+			printf(" {%s", subsub[0].name);
+			for (i = 1; i < n; i++)
+				printf("|%s", subsub[i].name);
+			printf("}");
+		}
+	}
+
+	for (i = 0; i < len; i++) {
+		sd_opt = find_opt(command_opts[i]);
+		if (sd_opt->has_arg)
+			printf(" [-%c %s]", sd_opt->ch, sd_opt->name);
+		else
+			printf(" [-%c]", sd_opt->ch);
+	}
+	if (command_arg)
+		printf(" %s", command_arg);
+
+	printf("\n");
+	if (subsub) {
+		printf("Available subcommands:\n");
+		for (i = 0; subsub[i].name; i++)
+			printf("  %-24s%s\n", subsub[i].name, subsub[i].desc);
+
+	}
+
+	printf("Options:\n");
+	for (i = 0; i < len; i++) {
+		sd_opt = find_opt(command_opts[i]);
+		snprintf(name, sizeof(name), "-%c, --%s",
+			 sd_opt->ch, sd_opt->name);
+		printf("  %-24s%s\n", name, sd_opt->desc);
+	}
+
+	exit(status);
+}
+
+static const struct sd_option *build_sd_options(const char *opts)
+{
+	static struct sd_option sd_opts[256], *p;
+	int i, len = strlen(opts);
+
+	p = sd_opts;
+	for (i = 0; i < len; i++)
+		*p++ = *find_opt(opts[i]);
+	memset(p, 0, sizeof(struct sd_option));
+
+	return sd_opts;
+}
+
+static void crash_handler(int signo)
+{
+	sd_err("dog exits unexpectedly (%s).", strsignal(signo));
+
+	sd_backtrace();
+
+	/*
+	 * OOM raises SIGABRT in xmalloc but the administrator expects
+	 * that dog exits with EXIT_SYSFAIL.  We have to give up
+	 * dumping a core file in this case.
+	 */
+	if (signo == SIGABRT)
+		exit(EXIT_SYSFAIL);
+
+	reraise_crash_signal(signo, EXIT_SYSFAIL);
+}
+
+static size_t get_nr_nodes(void)
+{
+	return sd_nodes_nr;
+}
+
+int main(int argc, char **argv)
+{
+	int ch, longindex, ret;
+	unsigned long flags;
+	struct option *long_options;
+	const struct command *commands;
+	const char *short_options;
+	char *p;
+	const struct sd_option *sd_opts;
+
+	install_crash_handler(crash_handler);
+
+	init_commands(&commands);
+
+	if (argc < 2)
+		usage(commands, 0);
+
+	flags = setup_commands(commands, argv[1], argv[2]);
+
+	optind = 3;
+
+	sd_opts = build_sd_options(command_opts);
+	long_options = build_long_options(sd_opts);
+	short_options = build_short_options(sd_opts);
+
+	while ((ch = getopt_long(argc, argv, short_options, long_options,
+				&longindex)) >= 0) {
+
+		switch (ch) {
+		case 'a':
+			if (!str_to_addr(optarg, sdhost)) {
+				sd_err("Invalid ip address %s", optarg);
+				return EXIT_FAILURE;
+			}
+			break;
+		case 'p':
+			sdport = strtol(optarg, &p, 10);
+			if (optarg == p || sdport < 1 || sdport > UINT16_MAX) {
+				sd_err("Invalid port number '%s'", optarg);
+				exit(EXIT_USAGE);
+			}
+			break;
+		case 'r':
+			raw_output = true;
+			break;
+		case 'v':
+			verbose = true;
+			break;
+		case 'h':
+			subcommand_usage(argv[1], argv[2], EXIT_SUCCESS);
+			break;
+		case '?':
+			usage(commands, EXIT_USAGE);
+			break;
+		default:
+			if (command_parser)
+				command_parser(ch, optarg);
+			else
+				usage(commands, EXIT_USAGE);
+			break;
+		}
+	}
+
+	if (!is_stdout_console() || raw_output)
+		highlight = false;
+
+	if (flags & CMD_NEED_NODELIST) {
+		ret = update_node_list(SD_MAX_NODES);
+		if (ret < 0) {
+			sd_err("Failed to get node list");
+			exit(EXIT_SYSFAIL);
+		}
+	}
+
+	if (flags & CMD_NEED_ARG && argc == optind)
+		subcommand_usage(argv[1], argv[2], EXIT_USAGE);
+
+	if (init_event(EPOLL_SIZE) < 0)
+		exit(EXIT_SYSFAIL);
+
+	if (init_work_queue(get_nr_nodes) != 0) {
+		sd_err("Failed to init work queue");
+		exit(EXIT_SYSFAIL);
+	}
+
+	if (sockfd_init()) {
+		sd_err("sockfd_init() failed");
+		exit(EXIT_SYSFAIL);
+	}
+
+	ret = command_fn(argc, argv);
+	if (ret == EXIT_USAGE)
+		subcommand_usage(argv[1], argv[2], EXIT_USAGE);
+	return ret;
+}
diff --git a/dog/dog.h b/dog/dog.h
new file mode 100644
index 0000000..92dd4d2
--- /dev/null
+++ b/dog/dog.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __DOG_H__
+#define __DOG_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include "sheepdog_proto.h"
+#include "sheep.h"
+#include "exits.h"
+#include "option.h"
+#include "work.h"
+#include "event.h"
+#include "config.h"
+
+#define CMD_NEED_NODELIST (1 << 0)
+#define CMD_NEED_ARG (1 << 1)
+
+#define UINT64_DECIMAL_SIZE 21
+
+struct command {
+	const char *name;
+	struct subcommand *sub;
+	int (*parser)(int, char *);
+};
+
+struct subcommand {
+	const char *name;
+	const char *arg;
+	const char *opts;
+	const char *desc;
+	struct subcommand *sub;
+	unsigned long flags;
+	int (*fn)(int, char **);
+	struct sd_option *options;
+};
+void subcommand_usage(char *cmd, char *subcmd, int status);
+
+extern uint8_t sdhost[16];
+extern int sdport;
+extern bool highlight;
+extern bool raw_output;
+extern bool verbose;
+
+extern uint32_t sd_epoch;
+extern struct sd_node sd_nodes[SD_MAX_NODES];
+extern struct sd_vnode sd_vnodes[SD_MAX_VNODES];
+extern int sd_nodes_nr, sd_vnodes_nr;
+
+bool is_current(const struct sd_inode *i);
+char *size_to_str(uint64_t _size, char *str, int str_size);
+typedef void (*vdi_parser_func_t)(uint32_t vid, const char *name,
+				  const char *tag, uint32_t snapid,
+				  uint32_t flags,
+				  const struct sd_inode *i, void *data);
+int parse_vdi(vdi_parser_func_t func, size_t size, void *data);
+int sd_read_object(uint64_t oid, void *data, unsigned int datalen,
+		   uint64_t offset, bool direct);
+int sd_write_object(uint64_t oid, uint64_t cow_oid, void *data,
+		    unsigned int datalen, uint64_t offset, uint32_t flags,
+		    int copies, bool create, bool direct);
+int dog_exec_req(const uint8_t *addr, int port, struct sd_req *hdr,
+		    void *data);
+int send_light_req(struct sd_req *hdr, const uint8_t *addr, int port);
+int do_generic_subcommand(struct subcommand *sub, int argc, char **argv);
+int update_node_list(int max_nodes);
+void confirm(const char *message);
+void work_queue_wait(struct work_queue *q);
+int do_vdi_create(const char *vdiname, int64_t vdi_size,
+		  uint32_t base_vid, uint32_t *vdi_id, bool snapshot,
+		  int nr_copies);
+void show_progress(uint64_t done, uint64_t total, bool raw);
+
+extern struct command vdi_command;
+extern struct command node_command;
+extern struct command cluster_command;
+
+#ifdef HAVE_TRACE
+  extern struct command trace_command;
+#else
+  #define trace_command {}
+#endif /* HAVE_TRACE */
+
+#endif
diff --git a/dog/farm/farm.c b/dog/farm/farm.c
new file mode 100644
index 0000000..1969927
--- /dev/null
+++ b/dog/farm/farm.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2011 Taobao Inc.
+ * Copyright (C) 2013 Zelin.io
+ *
+ * Liu Yuan <namei.unix at gmail.com>
+ * Kai Zhang <kyle at zelin.io>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pthread.h>
+
+#include "farm.h"
+#include "list.h"
+
+static char farm_object_dir[PATH_MAX];
+static char farm_dir[PATH_MAX];
+
+static struct sd_lock vdi_list_lock = SD_LOCK_INITIALIZER;
+struct vdi_entry {
+	char name[SD_MAX_VDI_LEN];
+	uint64_t vdi_size;
+	uint32_t vdi_id;
+	uint32_t snap_id;
+	uint8_t  nr_copies;
+	struct list_head list;
+};
+static LIST_HEAD(last_vdi_list);
+
+struct snapshot_work {
+	struct trunk_entry entry;
+	struct strbuf *trunk_buf;
+	struct work work;
+};
+static struct work_queue *wq;
+static uatomic_bool work_error;
+
+static struct vdi_entry *find_vdi(const char *name)
+{
+	struct vdi_entry *vdi;
+
+	list_for_each_entry(vdi, &last_vdi_list, list) {
+		if (!strcmp(vdi->name, name))
+			return vdi;
+	}
+	return NULL;
+}
+
+static struct vdi_entry *new_vdi(const char *name, uint64_t vdi_size,
+				 uint32_t vdi_id, uint32_t snap_id,
+				 uint8_t nr_copies)
+{
+	struct vdi_entry *vdi;
+	vdi = xmalloc(sizeof(struct vdi_entry));
+	pstrcpy(vdi->name, sizeof(vdi->name), name);
+	vdi->vdi_size = vdi_size;
+	vdi->vdi_id = vdi_id;
+	vdi->snap_id = snap_id;
+	vdi->nr_copies = nr_copies;
+	INIT_LIST_HEAD(&vdi->list);
+	return vdi;
+}
+
+static void insert_vdi(struct sd_inode *new)
+{
+	struct vdi_entry *vdi;
+	vdi = find_vdi(new->name);
+	if (!vdi) {
+		vdi = new_vdi(new->name,
+			      new->vdi_size,
+			      new->vdi_id,
+			      new->snap_id,
+			      new->nr_copies);
+		list_add(&vdi->list, &last_vdi_list);
+	} else if (vdi->snap_id < new->snap_id) {
+		vdi->vdi_size = new->vdi_size;
+		vdi->vdi_id = new->vdi_id;
+		vdi->snap_id = new->snap_id;
+		vdi->nr_copies = new->nr_copies;
+	}
+}
+
+static int create_active_vdis(void)
+{
+	struct vdi_entry *vdi;
+	uint32_t new_vid;
+	list_for_each_entry(vdi, &last_vdi_list, list) {
+		if (do_vdi_create(vdi->name,
+				  vdi->vdi_size,
+				  vdi->vdi_id, &new_vid,
+				  false, vdi->nr_copies) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+static void free_vdi_list(void)
+{
+	struct vdi_entry *vdi, *next;
+	list_for_each_entry_safe(vdi, next, &last_vdi_list, list)
+		free(vdi);
+}
+
+char *get_object_directory(void)
+{
+	return farm_object_dir;
+}
+
+static int create_directory(const char *p)
+{
+	int ret = -1;
+	struct strbuf buf = STRBUF_INIT;
+
+	strbuf_addstr(&buf, p);
+	if (xmkdir(buf.buf, 0755) < 0) {
+		if (errno == EEXIST)
+			sd_err("Path is not a directory: %s", p);
+		goto out;
+	}
+
+	if (!strlen(farm_dir))
+		strbuf_copyout(&buf, farm_dir, sizeof(farm_dir));
+
+	strbuf_addstr(&buf, "/objects");
+	if (xmkdir(buf.buf, 0755) < 0)
+		goto out;
+
+	for (int i = 0; i < 256; i++) {
+		strbuf_addf(&buf, "/%02x", i);
+		if (xmkdir(buf.buf, 0755) < 0)
+			goto out;
+
+		strbuf_remove(&buf, buf.len - 3, 3);
+	}
+
+	if (!strlen(farm_object_dir))
+		strbuf_copyout(&buf, farm_object_dir, sizeof(farm_object_dir));
+
+	ret = 0;
+out:
+	if (ret)
+		sd_err("Fail to create directory: %m");
+	strbuf_release(&buf);
+	return ret;
+}
+
+static int get_trunk_sha1(uint32_t idx, const char *tag, unsigned char *outsha1)
+{
+	int nr_logs = -1, ret = -1;
+	struct snap_log *log_buf, *log_free = NULL;
+	struct snap_file *snap_buf = NULL;
+
+	log_free = log_buf = snap_log_read(&nr_logs);
+	if (nr_logs < 0)
+		goto out;
+
+	for (int i = 0; i < nr_logs; i++, log_buf++) {
+		if (log_buf->idx != idx && strcmp(log_buf->tag, tag))
+			continue;
+		snap_buf = snap_file_read(log_buf->sha1);
+		if (!snap_buf)
+			goto out;
+		memcpy(outsha1, snap_buf->trunk_sha1, SHA1_DIGEST_SIZE);
+		ret = 0;
+		goto out;
+	}
+out:
+	free(log_free);
+	free(snap_buf);
+	return ret;
+}
+
+static int notify_vdi_add(uint32_t vdi_id, uint32_t nr_copies)
+{
+	int ret = -1;
+	struct sd_req hdr;
+	char *buf = NULL;
+
+	sd_init_req(&hdr, SD_OP_NOTIFY_VDI_ADD);
+	hdr.vdi_state.new_vid = vdi_id;
+	hdr.vdi_state.copies = nr_copies;
+	hdr.vdi_state.set_bitmap = true;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, buf);
+
+	if (ret)
+		sd_err("Fail to notify vdi add event(%"PRIx32", %d)", vdi_id,
+		       nr_copies);
+
+	free(buf);
+	return ret;
+}
+
+int farm_init(const char *path)
+{
+	int ret = -1;
+
+	if (create_directory(path) < 0)
+		goto out;
+	if (snap_init(farm_dir) < 0)
+		goto out;
+	return 0;
+out:
+	if (ret)
+		sd_err("Fail to init farm.");
+	return ret;
+}
+
+bool farm_contain_snapshot(uint32_t idx, const char *tag)
+{
+	unsigned char trunk_sha1[SHA1_DIGEST_SIZE];
+	return (get_trunk_sha1(idx, tag, trunk_sha1) == 0);
+}
+
+static void do_save_object(struct work *work)
+{
+	void *buf;
+	size_t size;
+	struct snapshot_work *sw;
+
+	if (uatomic_is_true(&work_error))
+		return;
+
+	sw = container_of(work, struct snapshot_work, work);
+
+	size = get_objsize(sw->entry.oid);
+	buf = xmalloc(size);
+
+	if (sd_read_object(sw->entry.oid, buf, size, 0, true) < 0)
+		goto error;
+
+	if (slice_write(buf, size, sw->entry.sha1) < 0)
+		goto error;
+
+	free(buf);
+	return;
+error:
+	free(buf);
+	sd_err("Fail to save object, oid %"PRIx64, sw->entry.oid);
+	uatomic_set_true(&work_error);
+}
+
+static void farm_show_progress(uint64_t done, uint64_t total)
+{
+	return show_progress(done, total, true);
+}
+
+static void save_object_done(struct work *work)
+{
+	struct snapshot_work *sw = container_of(work, struct snapshot_work,
+						work);
+	static unsigned long saved;
+
+	if (uatomic_is_true(&work_error))
+		goto out;
+
+	strbuf_add(sw->trunk_buf, &sw->entry, sizeof(struct trunk_entry));
+	farm_show_progress(uatomic_add_return(&saved, 1), object_tree_size());
+out:
+	free(sw);
+}
+
+static int queue_save_snapshot_work(uint64_t oid, int nr_copies, void *data)
+{
+	struct snapshot_work *sw = xzalloc(sizeof(struct snapshot_work));
+	struct strbuf *trunk_buf = data;
+
+	sw->entry.oid = oid;
+	sw->entry.nr_copies = nr_copies;
+	sw->trunk_buf = trunk_buf;
+	sw->work.fn = do_save_object;
+	sw->work.done = save_object_done;
+	queue_work(wq, &sw->work);
+
+	return 0;
+}
+
+int farm_save_snapshot(const char *tag)
+{
+	unsigned char snap_sha1[SHA1_DIGEST_SIZE];
+	unsigned char trunk_sha1[SHA1_DIGEST_SIZE];
+	struct strbuf trunk_buf;
+	void *snap_log = NULL;
+	int log_nr, idx, ret = -1;
+	uint64_t nr_objects = object_tree_size();
+
+	snap_log = snap_log_read(&log_nr);
+	if (!snap_log)
+		goto out;
+
+	idx = log_nr + 1;
+
+	strbuf_init(&trunk_buf, sizeof(struct trunk_entry) * nr_objects);
+
+	wq = create_work_queue("save snapshot", WQ_ORDERED);
+	if (for_each_object_in_tree(queue_save_snapshot_work,
+				    &trunk_buf) < 0)
+		goto out;
+
+	work_queue_wait(wq);
+	if (uatomic_is_true(&work_error))
+		goto out;
+
+	if (trunk_file_write(nr_objects, (struct trunk_entry *)trunk_buf.buf,
+			     trunk_sha1) < 0)
+		goto out;
+
+	if (snap_file_write(idx, trunk_sha1, snap_sha1) < 0)
+		goto out;
+
+	if (snap_log_write(idx, tag, snap_sha1) < 0)
+		goto out;
+
+	ret = 0;
+out:
+	strbuf_release(&trunk_buf);
+	free(snap_log);
+	return ret;
+}
+
+static void do_load_object(struct work *work)
+{
+	void *buffer = NULL;
+	size_t size;
+	struct snapshot_work *sw;
+	static unsigned long loaded;
+
+	if (uatomic_is_true(&work_error))
+		return;
+
+	sw = container_of(work, struct snapshot_work, work);
+
+	buffer = slice_read(sw->entry.sha1, &size);
+
+	if (!buffer)
+		goto error;
+
+	if (sd_write_object(sw->entry.oid, 0, buffer, size, 0, 0,
+			    sw->entry.nr_copies, true, true) != 0)
+		goto error;
+
+	if (is_vdi_obj(sw->entry.oid)) {
+		if (notify_vdi_add(oid_to_vid(sw->entry.oid),
+				   sw->entry.nr_copies) < 0)
+			goto error;
+
+		sd_write_lock(&vdi_list_lock);
+		insert_vdi(buffer);
+		sd_unlock(&vdi_list_lock);
+	}
+
+	farm_show_progress(uatomic_add_return(&loaded, 1), trunk_get_count());
+	free(buffer);
+	return;
+error:
+	free(buffer);
+	sd_err("Fail to load object, oid %"PRIx64, sw->entry.oid);
+	uatomic_set_true(&work_error);
+}
+
+static void load_object_done(struct work *work)
+{
+	struct snapshot_work *sw = container_of(work, struct snapshot_work,
+						work);
+
+	free(sw);
+}
+
+static int queue_load_snapshot_work(struct trunk_entry *entry, void *data)
+{
+	struct snapshot_work *sw = xzalloc(sizeof(struct snapshot_work));
+
+	memcpy(&sw->entry, entry, sizeof(struct trunk_entry));
+	sw->work.fn = do_load_object;
+	sw->work.done = load_object_done;
+	queue_work(wq, &sw->work);
+
+	return 0;
+}
+
+int farm_load_snapshot(uint32_t idx, const char *tag)
+{
+	int ret = -1;
+	unsigned char trunk_sha1[SHA1_DIGEST_SIZE];
+
+	if (get_trunk_sha1(idx, tag, trunk_sha1) < 0)
+		goto out;
+
+	wq = create_work_queue("load snapshot", WQ_DYNAMIC);
+	if (for_each_entry_in_trunk(trunk_sha1, queue_load_snapshot_work,
+				    NULL) < 0)
+		goto out;
+
+	work_queue_wait(wq);
+	if (uatomic_is_true(&work_error))
+		goto out;
+
+	if (create_active_vdis() < 0)
+		goto out;
+
+	ret = 0;
+out:
+	free_vdi_list();
+	return ret;
+}
diff --git a/dog/farm/farm.h b/dog/farm/farm.h
new file mode 100644
index 0000000..0f457e4
--- /dev/null
+++ b/dog/farm/farm.h
@@ -0,0 +1,83 @@
+#ifndef FARM_H
+#define FARM_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <memory.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <linux/limits.h>
+
+#include "dog.h"
+#include "sheep.h"
+#include "strbuf.h"
+#include "sha1.h"
+
+struct trunk_entry {
+	uint64_t oid;
+	int nr_copies;
+	unsigned char sha1[SHA1_DIGEST_SIZE];
+};
+
+struct trunk_file {
+	uint64_t nr_entries;
+	struct trunk_entry *entries;
+};
+
+struct snap_file {
+	int idx;
+	unsigned char trunk_sha1[SHA1_DIGEST_SIZE];
+};
+
+struct snap_log {
+	uint32_t idx;
+	char tag[SD_MAX_SNAPSHOT_TAG_LEN];
+	uint64_t time;
+	unsigned char sha1[SHA1_DIGEST_SIZE];
+};
+
+/* farm.c */
+int farm_init(const char *path);
+bool farm_contain_snapshot(uint32_t idx, const char *tag);
+int farm_save_snapshot(const char *tag);
+int farm_load_snapshot(uint32_t idx, const char *tag);
+char *get_object_directory(void);
+
+/* trunk.c */
+int trunk_init(void);
+int trunk_file_write(uint64_t nr_entries, struct trunk_entry *entries,
+		     unsigned char *trunk_sha1);
+int for_each_entry_in_trunk(unsigned char *trunk_sha1,
+			    int (*func)(struct trunk_entry *entry, void *data),
+			    void *data);
+uint64_t trunk_get_count(void);
+
+/* snap.c */
+int snap_init(const char *path);
+struct snap_file *snap_file_read(unsigned char *sha1);
+int snap_file_write(uint32_t idx, unsigned char *trunk_sha1,
+		    unsigned char *outsha1);
+void *snap_log_read(int *out_nr);
+int snap_log_write(uint32_t idx, const char *tag, unsigned char *sha1);
+
+/* sha1_file.c */
+int sha1_file_write(void *buf, size_t len, unsigned char *sha1);
+void *sha1_file_read(const unsigned char *sha1, size_t *size);
+
+/* object_tree.c */
+int object_tree_size(void);
+void object_tree_insert(uint64_t oid, int nr_copies);
+void object_tree_free(void);
+void object_tree_print(void);
+int for_each_object_in_tree(int (*func)(uint64_t oid, int nr_copies,
+					void *data), void *data);
+/* slice.c */
+int slice_write(void *buf, size_t len, unsigned char *outsha1);
+void *slice_read(const unsigned char *sha1, size_t *outsize);
+
+#endif
diff --git a/dog/farm/object_tree.c b/dog/farm/object_tree.c
new file mode 100644
index 0000000..00cad2d
--- /dev/null
+++ b/dog/farm/object_tree.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2013 Zelin.io
+ *
+ * Kai Zhang <kyle at zelin.io>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "farm.h"
+#include "rbtree.h"
+
+struct object_tree_entry {
+	uint64_t oid;
+	int nr_copies;
+	struct rb_node node;
+	struct list_head list;
+};
+
+struct object_tree {
+	int nr_objs;
+	struct rb_root root;
+	struct list_head list;
+};
+
+static struct object_tree tree = {
+	.nr_objs = 0,
+	.root = RB_ROOT,
+	.list = LIST_HEAD_INIT(tree.list)
+};
+static struct object_tree_entry *cached_entry;
+
+static struct object_tree_entry *do_insert(struct rb_root *root,
+				      struct object_tree_entry *new)
+{
+	struct rb_node **p = &root->rb_node;
+	struct rb_node *parent = NULL;
+	struct object_tree_entry *entry;
+
+	while (*p) {
+		parent = *p;
+		entry = rb_entry(parent, struct object_tree_entry, node);
+
+		if (new->oid < entry->oid)
+			p = &(*p)->rb_left;
+		else if (new->oid > entry->oid)
+			p = &(*p)->rb_right;
+		else
+			return entry; /* already has this entry */
+	}
+	rb_link_node(&new->node, parent, p);
+	rb_insert_color(&new->node, root);
+
+	return NULL; /* insert sucessfully */
+}
+
+void object_tree_insert(uint64_t oid, int nr_copies)
+{
+	struct rb_root *root = &tree.root;
+	struct object_tree_entry *p = NULL;
+
+	if (!cached_entry)
+		cached_entry = xzalloc(sizeof(*cached_entry));
+	cached_entry->oid = oid;
+	cached_entry->nr_copies = nr_copies;
+	rb_init_node(&cached_entry->node);
+	p = do_insert(root, cached_entry);
+	if (!p) {
+		list_add(&cached_entry->list, &tree.list);
+		tree.nr_objs++;
+		cached_entry = NULL;
+	}
+}
+
+void object_tree_print(void)
+{
+	struct rb_node *p = rb_first(&tree.root);
+	struct object_tree_entry *entry;
+	printf("nr_objs: %d\n", tree.nr_objs);
+
+	while (p) {
+		entry = rb_entry(p, struct object_tree_entry, node);
+		printf("Obj id: %"PRIx64"\n", entry->oid);
+		p = rb_next(p);
+	}
+}
+
+void object_tree_free(void)
+{
+	struct object_tree_entry *entry, *next;
+	list_for_each_entry_safe(entry, next, &tree.list, list)
+		free(entry);
+
+	free(cached_entry);
+}
+
+int object_tree_size(void)
+{
+	return tree.nr_objs;
+}
+
+int for_each_object_in_tree(int (*func)(uint64_t oid, int nr_copies,
+					void *data), void *data)
+{
+	struct rb_node *p = rb_first(&tree.root);
+	struct object_tree_entry *entry;
+	int ret = -1;
+
+	while (p) {
+		entry = rb_entry(p, struct object_tree_entry, node);
+
+		if (func(entry->oid, entry->nr_copies, data) < 0)
+			goto out;
+
+		p = rb_next(p);
+	}
+	ret = 0;
+out:
+	return ret;
+}
diff --git a/dog/farm/sha1_file.c b/dog/farm/sha1_file.c
new file mode 100644
index 0000000..3ba2519
--- /dev/null
+++ b/dog/farm/sha1_file.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 Taobao Inc.
+ *
+ * Liu Yuan <namei.unix at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ *   sha1_file provide us some useful features:
+ *
+ *   - Regardless of object type, all objects are all in deflated with zlib,
+ *     and have a header that not only specifies their tag, but also size
+ *     information about the data in the object.
+ *
+ *   - the general consistency of an object can always be tested independently
+ *     of the contents or the type of the object: all objects can be validated
+ *     by verifying that their hashes match the content of the file.
+ */
+#include <sys/types.h>
+
+#include "farm.h"
+#include "util.h"
+
+static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
+{
+	int i;
+	for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+		static const char hex[] = "0123456789abcdef";
+		unsigned int val = sha1[i];
+		char *pos = pathbuf + i*2 + (i > 0);
+		*pos++ = hex[val >> 4];
+		*pos = hex[val & 0xf];
+	}
+}
+
+static char *sha1_to_path(const unsigned char *sha1)
+{
+	static __thread char buf[PATH_MAX];
+	const char *objdir;
+	int len;
+
+	objdir = get_object_directory();
+	len = strlen(objdir);
+
+	/* '/' + sha1(2) + '/' + sha1(38) + '\0' */
+	memcpy(buf, objdir, len);
+	buf[len] = '/';
+	buf[len+3] = '/';
+	buf[len+42] = '\0';
+	fill_sha1_path(buf + len + 1, sha1);
+	return buf;
+}
+
+static int sha1_buffer_write(const unsigned char *sha1,
+			     void *buf, unsigned int size)
+{
+	char *filename = sha1_to_path(sha1);
+	int fd, ret = 0, len;
+
+	fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+	if (fd < 0) {
+		if (errno != EEXIST) {
+			sd_err("failed to open file %s with error: %m",
+			       filename);
+			ret = -1;
+		}
+		goto err_open;
+	}
+	len = xwrite(fd, buf, size);
+	if (len != size) {
+		sd_err("%m");
+		close(fd);
+		return -1;
+	}
+
+	close(fd);
+err_open:
+	return ret;
+}
+
+int sha1_file_write(void *buf, size_t len, unsigned char *outsha1)
+{
+	unsigned char sha1[SHA1_DIGEST_SIZE];
+
+	sha1_from_buffer(buf, len, sha1);
+	if (sha1_buffer_write(sha1, buf, len) < 0)
+		return -1;
+	if (outsha1)
+		memcpy(outsha1, sha1, SHA1_DIGEST_SIZE);
+	return 0;
+}
+
+static int verify_sha1_file(const unsigned char *sha1,
+			    void *buf, unsigned long len)
+{
+	unsigned char tmp[SHA1_DIGEST_SIZE];
+
+	sha1_from_buffer(buf, len, tmp);
+	if (memcmp((char *)tmp, (char *)sha1, SHA1_DIGEST_SIZE) != 0) {
+		sd_err("failed, %s != %s", sha1_to_hex(sha1), sha1_to_hex(tmp));
+		return -1;
+	}
+	return 0;
+}
+
+void *sha1_file_read(const unsigned char *sha1, size_t *size)
+{
+	char *filename = sha1_to_path(sha1);
+	int fd = open(filename, O_RDONLY);
+	struct stat st;
+	void *buf = NULL;
+
+	if (fd < 0) {
+		perror(filename);
+		return NULL;
+	}
+	if (fstat(fd, &st) < 0) {
+		sd_err("%m");
+		goto out;
+	}
+
+	buf = xmalloc(st.st_size);
+	if (!buf)
+		goto out;
+
+	if (xread(fd, buf, st.st_size) != st.st_size) {
+		free(buf);
+		buf = NULL;
+		goto out;
+	}
+
+	if (verify_sha1_file(sha1, buf, st.st_size) < 0) {
+		free(buf);
+		buf = NULL;
+		goto out;
+	}
+
+	*size = st.st_size;
+out:
+	close(fd);
+	return buf;
+}
diff --git a/dog/farm/slice.c b/dog/farm/slice.c
new file mode 100644
index 0000000..53941c1
--- /dev/null
+++ b/dog/farm/slice.c
@@ -0,0 +1,109 @@
+/*
+ * copyright (c) 2013 taobao inc.
+ *
+ * liu yuan <namei.unix at gmail.com>
+ *
+ * this program is free software; you can redistribute it and/or
+ * modify it under the terms of the gnu general public license version
+ * 2 as published by the free software foundation.
+ *
+ * you should have received a copy of the gnu general public license
+ * along with this program. if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Slice is a fixed chunk of one object to be stored in farm. We slice
+ * the object into smaller chunks to get better deduplication.
+ */
+
+#include <pthread.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "farm.h"
+#include "strbuf.h"
+#include "util.h"
+#include "sheepdog_proto.h"
+
+struct slice {
+	unsigned char sha1[SHA1_DIGEST_SIZE];
+};
+
+struct slice_file {
+	uint32_t nr_slices;
+	struct slice *slices;
+};
+
+/* 128k, best empirical value from some tests, but no rationale */
+#define SLICE_SIZE (1024*128)
+
+int slice_write(void *buf, size_t len, unsigned char *outsha1)
+{
+	int count = DIV_ROUND_UP(len, SLICE_SIZE);
+	size_t slen = count * SHA1_DIGEST_SIZE;
+	char *sbuf = xmalloc(slen);
+	char *p = buf;
+
+	for (int i = 0; i < count; i++, p += SLICE_SIZE) {
+		unsigned char sha1[SHA1_DIGEST_SIZE];
+		size_t wlen = (ssize_t)len - SLICE_SIZE > 0 ? SLICE_SIZE : len;
+		len -= SLICE_SIZE;
+
+		if (sha1_file_write(p, wlen, sha1) < 0)
+			goto err;
+		memcpy(sbuf + i * SHA1_DIGEST_SIZE, sha1, SHA1_DIGEST_SIZE);
+	}
+
+	if (sha1_file_write(sbuf, slen, outsha1) < 0)
+		goto err;
+	free(sbuf);
+	return 0;
+err:
+	free(sbuf);
+	return -1;
+}
+
+static struct slice_file *slice_file_read(const unsigned char *sha1)
+{
+	size_t size;
+	struct slice_file *slice_file = NULL;
+	void *buf = sha1_file_read(sha1, &size);
+
+	if (!buf)
+		return NULL;
+	slice_file = xmalloc(sizeof(struct slice_file));
+	slice_file->nr_slices = size / SHA1_DIGEST_SIZE;
+	slice_file->slices = buf;
+
+	return slice_file;
+}
+
+void *slice_read(const unsigned char *sha1, size_t *outsize)
+{
+	struct slice_file *file = slice_file_read(sha1);
+	struct strbuf buf = STRBUF_INIT;
+	void *object;
+
+	if (!file)
+		goto err;
+
+	*outsize = 0;
+	for (uint32_t i = 0; i < file->nr_slices; i++) {
+		size_t size;
+		void *sbuf;
+
+		sbuf = sha1_file_read(file->slices[i].sha1, &size);
+		if (!sbuf)
+			goto err;
+		strbuf_add(&buf, sbuf, size);
+		*outsize += size;
+	}
+
+	object = xmalloc(*outsize);
+	strbuf_copyout(&buf, object, *outsize);
+	strbuf_release(&buf);
+	return object;
+err:
+	strbuf_release(&buf);
+	return NULL;
+}
diff --git a/dog/farm/snap.c b/dog/farm/snap.c
new file mode 100644
index 0000000..8c46484
--- /dev/null
+++ b/dog/farm/snap.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2011 Taobao Inc.
+ * Copyright (C) 2013 Zelin.io
+ *
+ * Liu Yuan <namei.unix at gmail.com>
+ * Kai Zhang <kyle at zelin.io>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Snap object is the meta data that describes the cluster snapshot. */
+
+#include <time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "farm.h"
+
+static char snap_log_path[PATH_MAX];
+
+int snap_init(const char *farm_dir)
+{
+	int fd, ret = -1;
+	struct strbuf buf = STRBUF_INIT;
+
+	strbuf_addstr(&buf, farm_dir);
+	strbuf_addf(&buf, "/%s", "user_snap");
+
+	if (!strlen(snap_log_path))
+		strbuf_copyout(&buf, snap_log_path, sizeof(snap_log_path));
+
+	fd = open(snap_log_path, O_CREAT | O_EXCL, 0666);
+	if (fd < 0) {
+		if (errno != EEXIST) {
+			sd_err("%m");
+			goto out;
+		}
+	}
+
+	ret = 0;
+	close(fd);
+out:
+	strbuf_release(&buf);
+	return ret;
+}
+
+int snap_log_write(uint32_t idx, const char *tag, unsigned char *sha1)
+{
+	int fd, ret = -1;
+	struct strbuf buf = STRBUF_INIT;
+	struct snap_log log = { .idx = idx,
+				.time = time(NULL) };
+	pstrcpy(log.tag, SD_MAX_SNAPSHOT_TAG_LEN, tag);
+	memcpy(log.sha1, sha1, SHA1_DIGEST_SIZE);
+
+	fd = open(snap_log_path, O_WRONLY | O_APPEND);
+	if (fd < 0) {
+		sd_err("%m");
+		goto out;
+	}
+
+	strbuf_reset(&buf);
+	strbuf_add(&buf, &log, sizeof(log));
+	ret = xwrite(fd, buf.buf, buf.len);
+	if (ret != buf.len)
+		goto out_close;
+
+	ret = 0;
+out_close:
+	close(fd);
+out:
+	strbuf_release(&buf);
+	return ret;
+}
+
+void *snap_log_read(int *out_nr)
+{
+	struct stat st;
+	void *buffer = NULL;
+	int len, fd;
+
+	fd = open(snap_log_path, O_RDONLY);
+	if (fd < 0) {
+		sd_err("%m");
+		goto out;
+	}
+	if (fstat(fd, &st) < 0) {
+		sd_err("%m");
+		goto out_close;
+	}
+
+	len = st.st_size;
+	buffer = xmalloc(len);
+	len = xread(fd, buffer, len);
+	if (len != st.st_size) {
+		free(buffer);
+		buffer = NULL;
+		goto out_close;
+	}
+	*out_nr = len / sizeof(struct snap_log);
+out_close:
+	close(fd);
+out:
+	return buffer;
+}
+
+struct snap_file *snap_file_read(unsigned char *sha1)
+{
+	size_t size;
+	return sha1_file_read(sha1, &size);
+}
+
+int snap_file_write(uint32_t idx, unsigned char *trunk_sha1,
+		    unsigned char *outsha1)
+{
+	struct snap_file snap;
+	snap.idx = idx;
+	memcpy(snap.trunk_sha1, trunk_sha1, SHA1_DIGEST_SIZE);
+
+	return sha1_file_write(&snap, sizeof(struct snap_file),
+			       outsha1);
+}
diff --git a/dog/farm/trunk.c b/dog/farm/trunk.c
new file mode 100644
index 0000000..91f293e
--- /dev/null
+++ b/dog/farm/trunk.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 Taobao Inc.
+ * Copyright (C) 2013 Zelin.io
+ *
+ * Liu Yuan <namei.unix at gmail.com>
+ * Kai Zhang <kyle at zelin.io>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Trunk object is meta data that describes the structure of the data objects
+ * at the time of snapshot being taken. It ties data objects together into a
+ * flat directory structure.
+ */
+#include <pthread.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "farm.h"
+#include "strbuf.h"
+#include "list.h"
+#include "util.h"
+#include "sheepdog_proto.h"
+
+static uint64_t total_count;
+
+int trunk_file_write(uint64_t nr_entries, struct trunk_entry *entries,
+		     unsigned char *trunk_sha1)
+{
+	size_t size = sizeof(struct trunk_entry) * nr_entries;
+	return sha1_file_write(entries, size, trunk_sha1);
+}
+
+static struct trunk_file *trunk_file_read(unsigned char *sha1)
+{
+	size_t size;
+	struct trunk_file *trunk = NULL;
+	void *buf = sha1_file_read(sha1, &size);
+
+	if (!buf)
+		return NULL;
+	trunk = xmalloc(sizeof(struct trunk_file));
+	trunk->nr_entries = size / sizeof(struct trunk_entry);
+	trunk->entries = buf;
+
+	return trunk;
+}
+
+int for_each_entry_in_trunk(unsigned char *trunk_sha1,
+			    int (*func)(struct trunk_entry *entry, void *data),
+			    void *data)
+{
+	struct trunk_file *trunk;
+	struct trunk_entry *entry;
+	int ret = -1;
+
+	trunk = trunk_file_read(trunk_sha1);
+	if (!trunk)
+		goto out;
+
+	total_count = trunk->nr_entries;
+	entry = trunk->entries;
+	for (uint64_t i = 0; i < trunk->nr_entries; i++, entry++) {
+		if (func(entry, data) < 0)
+			goto out;
+	}
+
+	ret = 0;
+out:
+	free(trunk->entries);
+	free(trunk);
+	return ret;
+}
+
+uint64_t trunk_get_count(void)
+{
+	return total_count;
+}
diff --git a/dog/node.c b/dog/node.c
new file mode 100644
index 0000000..e00058e
--- /dev/null
+++ b/dog/node.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dog.h"
+
+static struct node_cmd_data {
+	bool all_nodes;
+	bool recovery_progress;
+} node_cmd_data;
+
+static void cal_total_vdi_size(uint32_t vid, const char *name, const char *tag,
+			       uint32_t snapid, uint32_t flags,
+			       const struct sd_inode *i, void *data)
+{
+	uint64_t *size = data;
+
+	if (!vdi_is_snapshot(i))
+		*size += i->vdi_size;
+}
+
+static int node_list(int argc, char **argv)
+{
+	int i;
+
+	if (!raw_output)
+		printf("  Id   Host:Port         V-Nodes       Zone\n");
+	for (i = 0; i < sd_nodes_nr; i++) {
+		const char *host = addr_to_str(sd_nodes[i].nid.addr,
+					       sd_nodes[i].nid.port);
+
+		printf(raw_output ? "%d %s %d %u\n" : "%4d   %-20s\t%2d%11u\n",
+		       i, host, sd_nodes[i].nr_vnodes, sd_nodes[i].zone);
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int node_info(int argc, char **argv)
+{
+	int i, ret, success = 0;
+	uint64_t total_size = 0, total_avail = 0, total_vdi_size = 0;
+	char total_str[UINT64_DECIMAL_SIZE], use_str[UINT64_DECIMAL_SIZE],
+	     avail_str[UINT64_DECIMAL_SIZE], vdi_size_str[UINT64_DECIMAL_SIZE];
+
+	if (!raw_output)
+		printf("Id\tSize\tUsed\tAvail\tUse%%\n");
+
+	for (i = 0; i < sd_nodes_nr; i++) {
+		struct sd_req req;
+		struct sd_rsp *rsp = (struct sd_rsp *)&req;
+		char store_str[UINT64_DECIMAL_SIZE],
+		     used_str[UINT64_DECIMAL_SIZE],
+		     free_str[UINT64_DECIMAL_SIZE];
+
+		sd_init_req(&req, SD_OP_STAT_SHEEP);
+
+		ret = send_light_req(&req, sd_nodes[i].nid.addr,
+				     sd_nodes[i].nid.port);
+
+		size_to_str(rsp->node.store_size, store_str, sizeof(store_str));
+		size_to_str(rsp->node.store_free, free_str, sizeof(free_str));
+		size_to_str(rsp->node.store_size - rsp->node.store_free,
+			    used_str, sizeof(used_str));
+		if (!ret) {
+			int ratio = (int)(((double)(rsp->node.store_size -
+						    rsp->node.store_free) /
+					   rsp->node.store_size) * 100);
+			printf(raw_output ? "%d %s %s %s %d%%\n" :
+					"%2d\t%s\t%s\t%s\t%3d%%\n",
+			       i, store_str, used_str, free_str,
+			       rsp->node.store_size == 0 ? 0 : ratio);
+			success++;
+		}
+
+		total_size += rsp->node.store_size;
+		total_avail += rsp->node.store_free;
+	}
+
+	if (success == 0) {
+		sd_err("Cannot get information from any nodes");
+		return EXIT_SYSFAIL;
+	}
+
+	if (parse_vdi(cal_total_vdi_size, SD_INODE_HEADER_SIZE,
+			&total_vdi_size) < 0)
+		return EXIT_SYSFAIL;
+
+	size_to_str(total_size, total_str, sizeof(total_str));
+	size_to_str(total_avail, avail_str, sizeof(avail_str));
+	size_to_str(total_size - total_avail, use_str, sizeof(use_str));
+	size_to_str(total_vdi_size, vdi_size_str, sizeof(vdi_size_str));
+	printf(raw_output ? "Total %s %s %s %d%% %s\n"
+			  : "Total\t%s\t%s\t%s\t%3d%%\n\n"
+			  "Total virtual image size\t%s\n",
+	       total_str, use_str, avail_str,
+	       (int)(((double)(total_size - total_avail) / total_size) * 100),
+	       vdi_size_str);
+
+	return EXIT_SUCCESS;
+}
+
+static int get_recovery_state(struct recovery_state *state)
+{
+	int ret;
+	struct sd_req req;
+
+	sd_init_req(&req, SD_OP_STAT_RECOVERY);
+	req.data_length = sizeof(*state);
+
+	ret = dog_exec_req(sdhost, sdport, &req, state);
+	if (ret < 0) {
+		sd_err("Failed to execute request");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int node_recovery_progress(void)
+{
+	int result;
+	unsigned int prev_nr_total;
+	struct recovery_state rstate;
+
+	/*
+	 * ToDos
+	 *
+	 * 1. Calculate size of actually copied objects.
+	 *    For doing this, not so trivial changes for recovery process are
+	 *    required.
+	 *
+	 * 2. Print remaining physical time.
+	 *    Even if it is not so acculate, the information is helpful for
+	 *    administrators.
+	 */
+
+	result = get_recovery_state(&rstate);
+	if (result < 0)
+		return EXIT_SYSFAIL;
+
+	if (!rstate.in_recovery)
+		return EXIT_SUCCESS;
+
+	do {
+		prev_nr_total = rstate.nr_total;
+
+		result = get_recovery_state(&rstate);
+		if (result < 0)
+			break;
+
+		if (!rstate.in_recovery) {
+			show_progress(prev_nr_total, prev_nr_total, true);
+			break;
+		}
+
+		switch (rstate.state) {
+		case RW_PREPARE_LIST:
+			printf("\rpreparing a checked object list...");
+			break;
+		case RW_NOTIFY_COMPLETION:
+			printf("\rnotifying a completion of recovery...");
+			break;
+		case RW_RECOVER_OBJ:
+			show_progress(rstate.nr_finished, rstate.nr_total,
+				      true);
+			break;
+		default:
+			panic("unknown state of recovery: %d", rstate.state);
+			break;
+		}
+
+		sleep(1);
+	} while (true);
+
+	return result < 0 ? EXIT_SYSFAIL : EXIT_SUCCESS;
+}
+
+static int node_recovery(int argc, char **argv)
+{
+	int i, ret;
+
+	if (node_cmd_data.recovery_progress)
+		return node_recovery_progress();
+
+	if (!raw_output) {
+		printf("Nodes In Recovery:\n");
+		printf("  Id   Host:Port         V-Nodes       Zone"
+		       "       Progress\n");
+	}
+
+	for (i = 0; i < sd_nodes_nr; i++) {
+		struct sd_req req;
+		struct recovery_state state;
+
+		memset(&state, 0, sizeof(state));
+
+		sd_init_req(&req, SD_OP_STAT_RECOVERY);
+		req.data_length = sizeof(state);
+
+		ret = dog_exec_req(sd_nodes[i].nid.addr,
+				      sd_nodes[i].nid.port, &req, &state);
+		if (ret < 0)
+			return EXIT_SYSFAIL;
+
+		if (state.in_recovery) {
+			const char *host = addr_to_str(sd_nodes[i].nid.addr,
+						       sd_nodes[i].nid.port);
+			if (raw_output)
+				printf("%d %s %d %d %"PRIu64" %"PRIu64"\n", i,
+				       host, sd_nodes[i].nr_vnodes,
+				       sd_nodes[i].zone, state.nr_finished,
+				       state.nr_total);
+			else
+				printf("%4d   %-20s%5d%11d%11.1f%%\n", i, host,
+				       sd_nodes[i].nr_vnodes, sd_nodes[i].zone,
+				       100 * (float)state.nr_finished
+				       / state.nr_total);
+		}
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int node_kill(int argc, char **argv)
+{
+	int node_id, ret;
+	struct sd_req req;
+	const char *p = argv[optind++];
+
+	if (!is_numeric(p)) {
+		sd_err("Invalid node id '%s', please specify a numeric value",
+		       p);
+		exit(EXIT_USAGE);
+	}
+
+	node_id = strtol(p, NULL, 10);
+	if (node_id < 0 || node_id >= sd_nodes_nr) {
+		sd_err("Invalid node id '%d'", node_id);
+		exit(EXIT_USAGE);
+	}
+
+	sd_init_req(&req, SD_OP_KILL_NODE);
+
+	ret = send_light_req(&req, sd_nodes[node_id].nid.addr,
+			     sd_nodes[node_id].nid.port);
+	if (ret) {
+		sd_err("Failed to execute request");
+		exit(EXIT_FAILURE);
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int node_md_info(struct node_id *nid)
+{
+	struct sd_md_info info = {};
+	char size_str[UINT64_DECIMAL_SIZE], used_str[UINT64_DECIMAL_SIZE],
+	     avail_str[UINT64_DECIMAL_SIZE];
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	int ret, i;
+
+	sd_init_req(&hdr, SD_OP_MD_INFO);
+	hdr.data_length = sizeof(info);
+
+	ret = dog_exec_req(nid->addr, nid->port, &hdr, &info);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("failed to get multi-disk infomation: %s",
+		       sd_strerror(rsp->result));
+		return EXIT_FAILURE;
+	}
+
+	for (i = 0; i < info.nr; i++) {
+		uint64_t size = info.disk[i].free + info.disk[i].used;
+		int ratio = (int)(((double)info.disk[i].used / size) * 100);
+
+		size_to_str(size, size_str, sizeof(size_str));
+		size_to_str(info.disk[i].used, used_str, sizeof(used_str));
+		size_to_str(info.disk[i].free, avail_str, sizeof(avail_str));
+		fprintf(stdout, "%2d\t%s\t%s\t%s\t%3d%%\t%s\n",
+			info.disk[i].idx, size_str, used_str, avail_str, ratio,
+			info.disk[i].path);
+	}
+	return EXIT_SUCCESS;
+}
+
+static int md_info(int argc, char **argv)
+{
+	int i, ret;
+
+	fprintf(stdout, "Id\tSize\tUsed\tAvail\tUse%%\tPath\n");
+
+	if (!node_cmd_data.all_nodes) {
+		struct node_id nid = {.port = sdport};
+
+		memcpy(nid.addr, sdhost, sizeof(nid.addr));
+
+		return node_md_info(&nid);
+	}
+
+	for (i = 0; i < sd_nodes_nr; i++) {
+		fprintf(stdout, "Node %d:\n", i);
+		ret = node_md_info(&sd_nodes[i].nid);
+		if (ret != EXIT_SUCCESS)
+			return EXIT_FAILURE;
+	}
+	return EXIT_SUCCESS;
+}
+
+static int do_plug_unplug(char *disks, bool plug)
+{
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	int ret;
+
+	if (!strlen(disks)) {
+		sd_err("Empty path isn't allowed");
+		return EXIT_FAILURE;
+	}
+
+	if (plug)
+		sd_init_req(&hdr, SD_OP_MD_PLUG);
+	else
+		sd_init_req(&hdr, SD_OP_MD_UNPLUG);
+	hdr.flags = SD_FLAG_CMD_WRITE;
+	hdr.data_length = strlen(disks) + 1;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, disks);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Failed to execute request, look for sheep.log"
+		       " for more information");
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int md_plug(int argc, char **argv)
+{
+	return do_plug_unplug(argv[optind], true);
+}
+
+static int md_unplug(int argc, char **argv)
+{
+	return do_plug_unplug(argv[optind], false);
+}
+
+static struct subcommand node_md_cmd[] = {
+	{"info", NULL, NULL, "show multi-disk information",
+	 NULL, CMD_NEED_NODELIST, md_info},
+	{"plug", NULL, NULL, "plug more disk(s) into node",
+	 NULL, CMD_NEED_ARG, md_plug},
+	{"unplug", NULL, NULL, "unplug disk(s) from node",
+	 NULL, CMD_NEED_ARG, md_unplug},
+	{NULL},
+};
+
+static int node_md(int argc, char **argv)
+{
+	return do_generic_subcommand(node_md_cmd, argc, argv);
+}
+
+
+static int node_parser(int ch, char *opt)
+{
+	switch (ch) {
+	case 'A':
+		node_cmd_data.all_nodes = true;
+		break;
+	case 'P':
+		node_cmd_data.recovery_progress = true;
+		break;
+	}
+
+	return 0;
+}
+
+static struct sd_option node_options[] = {
+	{'A', "all", false, "show md information of all the nodes"},
+	{'P', "progress", false, "show progress of recovery in the node"},
+
+	{ 0, NULL, false, NULL },
+};
+
+static struct subcommand node_cmd[] = {
+	{"kill", "<node id>", "aprh", "kill node", NULL,
+	 CMD_NEED_ARG | CMD_NEED_NODELIST, node_kill},
+	{"list", NULL, "aprh", "list nodes", NULL,
+	 CMD_NEED_NODELIST, node_list},
+	{"info", NULL, "aprh", "show information about each node", NULL,
+	 CMD_NEED_NODELIST, node_info},
+	{"recovery", NULL, "aphPr", "show recovery information of nodes", NULL,
+	 CMD_NEED_NODELIST, node_recovery, node_options},
+	{"md", "[disks]", "apAh", "See 'dog node md' for more information",
+	 node_md_cmd, CMD_NEED_ARG, node_md, node_options},
+	{NULL,},
+};
+
+struct command node_command = {
+	"node",
+	node_cmd,
+	node_parser
+};
diff --git a/dog/trace.c b/dog/trace.c
new file mode 100644
index 0000000..162a935
--- /dev/null
+++ b/dog/trace.c
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2011 Taobao Inc.
+ *
+ * Liu Yuan <namei.unix at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include "dog.h"
+#include "rbtree.h"
+#include "list.h"
+
+static inline void print_thread_name(struct trace_graph_item *item)
+{
+	printf("%-*s|", TRACE_THREAD_LEN, item->tname);
+}
+
+static inline void print_time(struct trace_graph_item *item)
+{
+	if (item->type == TRACE_GRAPH_RETURN) {
+		unsigned duration = item->return_time - item->entry_time;
+		unsigned quot = duration / 1000, rem = duration % 1000;
+
+		printf("%8u.%-3u|", quot, rem);
+	} else if (item->type == TRACE_GRAPH_ENTRY) {
+		printf("            |");
+	}
+}
+
+static inline void print_finale(struct trace_graph_item *item)
+{
+	int i;
+
+	for (i = 0; i < item->depth; i++)
+		printf("   ");
+	if (item->type == TRACE_GRAPH_ENTRY)
+		printf("%s() {\n", item->fname);
+	else
+		printf("}\n");
+}
+
+static void print_trace_item(struct trace_graph_item *item)
+{
+	print_thread_name(item);
+	print_time(item);
+	print_finale(item);
+}
+
+static void cat_trace_file(void *buf, size_t size)
+{
+	struct trace_graph_item *item = (struct trace_graph_item *)buf;
+	size_t sz = size / sizeof(struct trace_graph_item), i;
+
+	printf("   Thread Name      |  Time(us)  |  Function Graph\n");
+	printf("--------------------------------------------------\n");
+	for (i = 0; i < sz; i++)
+		print_trace_item(item++);
+	return;
+}
+
+static const char *tracefile = "/tmp/tracefile";
+
+static int trace_read_buffer(void)
+{
+	int ret, tfd;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+#define TRACE_BUF_LEN      (1024 * 1024 * 20)
+	char *buf = xzalloc(TRACE_BUF_LEN);
+
+	tfd = open(tracefile, O_CREAT | O_RDWR | O_APPEND | O_TRUNC, 0644);
+	if (tfd < 0) {
+		sd_err("can't create tracefile");
+		return EXIT_SYSFAIL;
+	}
+
+read_buffer:
+	sd_init_req(&hdr, SD_OP_TRACE_READ_BUF);
+	hdr.data_length = TRACE_BUF_LEN;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, buf);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result == SD_RES_AGAIN)
+		goto read_buffer;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Trace failed: %s", sd_strerror(rsp->result));
+		return EXIT_FAILURE;
+	}
+
+	xwrite(tfd, buf, rsp->data_length);
+	if (rsp->data_length == TRACE_BUF_LEN) {
+		memset(buf, 0, TRACE_BUF_LEN);
+		goto read_buffer;
+	}
+
+	free(buf);
+	return EXIT_SUCCESS;
+}
+
+static int trace_enable(int argc, char **argv)
+{
+	const char *tracer = argv[optind];
+	int ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+
+	sd_init_req(&hdr, SD_OP_TRACE_ENABLE);
+	hdr.flags = SD_FLAG_CMD_WRITE;
+	hdr.data_length = strlen(tracer) + 1;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, (void *)tracer);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	switch (rsp->result) {
+	case SD_RES_SUCCESS:
+		break;
+	case SD_RES_NO_SUPPORT:
+		sd_err("no such tracer %s", tracer);
+		return EXIT_FAILURE;
+	case SD_RES_INVALID_PARMS:
+		sd_err("tracer %s is already enabled", tracer);
+		return EXIT_FAILURE;
+	default:
+		sd_err("unknown error (%s)", sd_strerror(rsp->result));
+		return EXIT_SYSFAIL;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int trace_disable(int argc, char **argv)
+{
+	const char *tracer = argv[optind];
+	int ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+
+	sd_init_req(&hdr, SD_OP_TRACE_DISABLE);
+	hdr.flags = SD_FLAG_CMD_WRITE;
+	hdr.data_length = strlen(tracer) + 1;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, (void *)tracer);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	switch (rsp->result) {
+	case SD_RES_SUCCESS:
+		break;
+	case SD_RES_NO_SUPPORT:
+		sd_err("no such tracer %s", tracer);
+		return EXIT_FAILURE;
+	case SD_RES_INVALID_PARMS:
+		sd_err("tracer %s is not enabled", tracer);
+		return EXIT_FAILURE;
+	default:
+		sd_err("unknown error (%s)", sd_strerror(rsp->result));
+		return EXIT_SYSFAIL;
+	}
+
+	return trace_read_buffer();
+}
+
+static int trace_status(int argc, char **argv)
+{
+	char buf[4096]; /* must have enough space to store tracer list */
+	int ret;
+	struct sd_req hdr;
+
+	sd_init_req(&hdr, SD_OP_TRACE_STATUS);
+	hdr.data_length = sizeof(buf);
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, buf);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	printf("%s", buf);
+
+	return EXIT_SUCCESS;
+}
+
+static void *map_trace_file(struct stat *st)
+{
+	int fd = open(tracefile, O_RDONLY);
+	void *map;
+
+	if (fd < 0) {
+		sd_err("%m");
+		return NULL;
+	}
+
+	if (fstat(fd, st) < 0) {
+		sd_err("%m");
+		close(fd);
+		return NULL;
+	}
+
+	if (st->st_size == 0) {
+		sd_err("trace file is empty");
+		return NULL;
+	}
+
+	map = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+	close(fd);
+	if (map == MAP_FAILED) {
+		sd_err("%m");
+		return NULL;
+	}
+
+	return map;
+}
+
+static int graph_cat(int argc, char **argv)
+{
+	struct stat st;
+	void *map = map_trace_file(&st);
+
+	if (!map)
+		return EXIT_FAILURE;
+
+	cat_trace_file(map, st.st_size);
+	munmap(map, st.st_size);
+
+	return EXIT_SUCCESS;
+}
+
+struct graph_stat_entry {
+	struct rb_node rb;
+	struct list_head list;
+	char fname[TRACE_FNAME_LEN];
+	uint64_t duration;
+	uint16_t nr_calls;
+};
+
+static struct rb_root stat_tree_root;
+
+static LIST_HEAD(stat_list);
+
+static struct graph_stat_entry *
+stat_tree_insert(struct graph_stat_entry *new)
+{
+	struct rb_node **p = &stat_tree_root.rb_node;
+	struct rb_node *parent = NULL;
+	struct graph_stat_entry *entry;
+
+	while (*p) {
+		int cmp;
+
+		parent = *p;
+		entry = rb_entry(parent, struct graph_stat_entry, rb);
+		cmp = strcmp(new->fname, entry->fname);
+
+		if (cmp < 0)
+			p = &(*p)->rb_left;
+		else if (cmp > 0)
+			p = &(*p)->rb_right;
+		else {
+			entry->duration += new->duration;
+			entry->nr_calls++;
+			return entry;
+		}
+	}
+	rb_link_node(&new->rb, parent, p);
+	rb_insert_color(&new->rb, &stat_tree_root);
+
+	return NULL; /* insert successfully */
+}
+
+static void prepare_stat_tree(struct trace_graph_item *item)
+{
+	struct graph_stat_entry *new;
+
+	if (item->type != TRACE_GRAPH_RETURN)
+		return;
+	new = xmalloc(sizeof(*new));
+	pstrcpy(new->fname, sizeof(new->fname), item->fname);
+	new->duration = item->return_time - item->entry_time;
+	new->nr_calls = 1;
+	INIT_LIST_HEAD(&new->list);
+	if (stat_tree_insert(new)) {
+		free(new);
+		return;
+	}
+	list_add(&new->list, &stat_list);
+}
+
+static void stat_list_print(void)
+{
+	struct graph_stat_entry *entry;
+
+	list_for_each_entry(entry, &stat_list, list) {
+		float total = (float)entry->duration / 1000000000;
+		float per = (float)entry->duration / entry->nr_calls / 1000000;
+
+		printf("%10.3f   %10.3f        %5"PRIu16"   %-*s\n", total, per,
+		       entry->nr_calls, TRACE_FNAME_LEN, entry->fname);
+	}
+}
+
+static int stat_list_cmp(void *priv, struct list_head *a, struct list_head *b)
+{
+	struct graph_stat_entry *ga = container_of(a, struct graph_stat_entry,
+						   list);
+	struct graph_stat_entry *gb = container_of(b, struct graph_stat_entry,
+						   list);
+	/* '-' is for reverse sort, largest first */
+	return -intcmp(ga->duration, gb->duration);
+}
+
+static void stat_trace_file(void *buf, size_t size)
+{
+	struct trace_graph_item *item = (struct trace_graph_item *)buf;
+	size_t sz = size / sizeof(struct trace_graph_item), i;
+
+	printf("   Total (s)   Per Call (ms)   Calls   Name\n");
+	for (i = 0; i < sz; i++)
+		prepare_stat_tree(item++);
+	list_sort(NULL, &stat_list, stat_list_cmp);
+	stat_list_print();
+}
+
+static int graph_stat(int argc, char **argv)
+{
+	struct stat st;
+	void *map = map_trace_file(&st);
+
+	if (!map)
+		return EXIT_FAILURE;
+
+	stat_trace_file(map, st.st_size);
+	munmap(map, st.st_size);
+	return EXIT_SUCCESS;
+}
+
+static int trace_parser(int ch, char *opt)
+{
+	return 0;
+}
+
+static struct subcommand graph_cmd[] = {
+	{"cat", NULL, NULL, "cat the output of graph tracer",
+	 NULL, 0, graph_cat},
+	{"stat", NULL, NULL, "get the stat of the graph calls",
+	 NULL, 0, graph_stat},
+	{NULL,},
+};
+
+static int trace_graph(int argc, char **argv)
+{
+	return do_generic_subcommand(graph_cmd, argc, argv);
+}
+
+/* Subcommand list of trace */
+static struct subcommand trace_cmd[] = {
+	{"enable", "<tracer>", "aph", "enable tracer", NULL,
+	 CMD_NEED_ARG, trace_enable},
+	{"disable", "<tracer>", "aph", "disable tracer", NULL,
+	 CMD_NEED_ARG, trace_disable},
+	{"status", NULL, "aph", "show tracer statuses", NULL,
+	 0, trace_status},
+	{"graph", NULL, "aph", "run dog trace graph for more information",
+	 graph_cmd, CMD_NEED_ARG, trace_graph},
+	{NULL},
+};
+
+struct command trace_command = {
+	"trace",
+	trace_cmd,
+	trace_parser
+};
diff --git a/dog/treeview.c b/dog/treeview.c
new file mode 100644
index 0000000..baf0f00
--- /dev/null
+++ b/dog/treeview.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2009-2011 Nippon Telegraph and Telephone Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "util.h"
+#include "treeview.h"
+
+#ifndef MAX_DEPTH
+#define MAX_DEPTH    100
+#endif
+
+struct vdi_tree {
+	char name[1024];
+	char label[256];
+	uint32_t vid;
+	uint32_t pvid;
+	bool highlight;
+	struct list_head children;
+	struct list_head siblings;
+};
+
+static int *width, *more;
+static struct vdi_tree *root;
+
+static struct vdi_tree *find_vdi(struct vdi_tree *parent, uint32_t vid,
+				 const char *name)
+{
+	struct vdi_tree *vdi, *ret;
+
+	list_for_each_entry(vdi, &parent->children, siblings) {
+		if (vdi->vid == vid && !strcmp(vdi->name, name))
+			return vdi;
+
+		ret = find_vdi(vdi, vid, name);
+		if (ret)
+			return ret;
+	}
+	return NULL;
+}
+
+static struct vdi_tree *new_vdi(const char *name, const char *label,
+				uint64_t vid, uint64_t pvid, bool highlight)
+{
+	struct vdi_tree *vdi;
+
+	vdi = xmalloc(sizeof(struct vdi_tree));
+	pstrcpy(vdi->name, sizeof(vdi->name), name);
+	pstrcpy(vdi->label, sizeof(vdi->label), label);
+	vdi->vid = vid;
+	vdi->pvid = pvid;
+	vdi->highlight = highlight;
+	INIT_LIST_HEAD(&vdi->children);
+	return vdi;
+}
+
+void init_tree(void)
+{
+	root = new_vdi("", "", 0, 0, 0);
+}
+
+void add_vdi_tree(const char *name, const char *label, uint32_t vid,
+		  uint32_t pvid, bool highlight)
+{
+	struct vdi_tree *vdi, *parent;
+
+	vdi = new_vdi(name, label, vid, pvid, highlight);
+	if (!vdi)
+		return;
+
+	parent = find_vdi(root, pvid, name);
+	if (!parent)
+		parent = root;
+
+	list_add_tail(&vdi->siblings, &parent->children);
+}
+
+static void compaction(struct vdi_tree *parent)
+{
+	struct vdi_tree *vdi, *e, *new_parent;
+
+	list_for_each_entry_safe(vdi, e, &parent->children, siblings) {
+		new_parent = find_vdi(root, vdi->pvid, vdi->name);
+		if (new_parent && parent != new_parent)
+			list_move_tail(&vdi->siblings, &new_parent->children);
+
+		compaction(vdi);
+	}
+}
+
+static int get_depth(struct vdi_tree *parent)
+{
+	struct vdi_tree *vdi;
+	int max_depth = 0, depth;
+
+	list_for_each_entry(vdi, &parent->children, siblings) {
+		depth = get_depth(vdi);
+		if (max_depth < depth)
+			max_depth = depth;
+	}
+	return max_depth + 1;
+}
+
+static void spaces(int n)
+{
+	while (n--)
+		putchar(' ');
+}
+
+static void indent(int level, bool first, bool last)
+{
+	int lvl;
+
+	if (first)
+		printf(last ? "---" : "-+-");
+	else {
+		for (lvl = 0; lvl < level - 1; lvl++) {
+			spaces(width[lvl] + 1);
+			printf(more[lvl + 1] ? "| " : "  ");
+		}
+
+		spaces(width[level - 1] + 1);
+		printf(last ? "`-" : "|-");
+	}
+}
+
+static void _dump_tree(struct vdi_tree *current, int level, bool first, bool last)
+{
+	struct vdi_tree *vdi;
+
+	indent(level, first, last);
+
+	if (current->highlight)
+		printf(TEXT_BOLD);
+
+	printf("%s", current->label);
+
+	if (current->highlight)
+		printf(TEXT_NORMAL);
+
+	if (list_empty(&current->children)) {
+		putchar('\n');
+		return;
+	}
+
+	more[level] = !last;
+	width[level] = strlen(current->label);
+
+	list_for_each_entry(vdi, &current->children, siblings) {
+		_dump_tree(vdi, level + 1,
+			   &vdi->siblings == current->children.next,
+			   vdi->siblings.next == &current->children);
+	}
+}
+
+void dump_tree(void)
+{
+	struct vdi_tree *vdi;
+	int depth;
+
+	compaction(root);
+
+	depth = get_depth(root);
+
+	width = malloc(sizeof(int) * depth);
+	more = malloc(sizeof(int) * depth);
+	if (!width || !more) {
+		sd_err("Failed to allocate memory");
+		return;
+	}
+
+	list_for_each_entry(vdi, &root->children, siblings) {
+		printf("%s", vdi->name);
+		more[0] = 0;
+		width[0] = strlen(vdi->name);
+		_dump_tree(vdi, 1, true, true);
+	}
+}
diff --git a/dog/treeview.h b/dog/treeview.h
new file mode 100644
index 0000000..9787fbb
--- /dev/null
+++ b/dog/treeview.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2009-2011 Nippon Telegraph and Telephone Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __TREEVIEW__
+#define __TREEVIEW__
+
+#include <stdbool.h>
+
+void init_tree(void);
+void add_vdi_tree(const char *label, const char *tag, uint32_t vid,
+		  uint32_t pvid, bool highlight);
+void dump_tree(void);
+
+#endif
diff --git a/dog/vdi.c b/dog/vdi.c
new file mode 100644
index 0000000..370ad7b
--- /dev/null
+++ b/dog/vdi.c
@@ -0,0 +1,2160 @@
+/*
+ * Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "dog.h"
+#include "treeview.h"
+#include "sha1.h"
+
+static struct sd_option vdi_options[] = {
+	{'P', "prealloc", false, "preallocate all the data objects"},
+	{'i', "index", true, "specify the index of data objects"},
+	{'s', "snapshot", true, "specify a snapshot id or tag name"},
+	{'x', "exclusive", false, "write in an exclusive mode"},
+	{'d', "delete", false, "delete a key"},
+	{'w', "writeback", false, "use writeback mode"},
+	{'c', "copies", true, "specify the data redundancy (number of copies)"},
+	{'F', "from", true, "create a differential backup from the snapshot"},
+	{'f', "force", false, "do operation forcibly"},
+	{ 0, NULL, false, NULL },
+};
+
+static struct vdi_cmd_data {
+	unsigned int index;
+	int snapshot_id;
+	char snapshot_tag[SD_MAX_VDI_TAG_LEN];
+	bool exclusive;
+	bool delete;
+	bool prealloc;
+	int nr_copies;
+	bool writeback;
+	int from_snapshot_id;
+	char from_snapshot_tag[SD_MAX_VDI_TAG_LEN];
+	bool force;
+} vdi_cmd_data = { ~0, };
+
+struct get_vdi_info {
+	const char *name;
+	const char *tag;
+	uint32_t vid;
+	uint32_t snapid;
+	uint8_t nr_copies;
+};
+
+static int parse_option_size(const char *value, uint64_t *ret)
+{
+	char *postfix;
+	double sizef;
+
+	sizef = strtod(value, &postfix);
+	switch (*postfix) {
+	case 'T':
+		sizef *= 1024;
+	case 'G':
+		sizef *= 1024;
+	case 'M':
+		sizef *= 1024;
+	case 'K':
+	case 'k':
+		sizef *= 1024;
+	case 'b':
+	case '\0':
+		*ret = (uint64_t) sizef;
+		break;
+	default:
+		sd_err("Invalid size '%s'", value);
+		sd_err("You may use k, M, G or T suffixes for "
+		       "kilobytes, megabytes, gigabytes and terabytes.");
+		return -1;
+	}
+
+	return 0;
+}
+
+static void vdi_show_progress(uint64_t done, uint64_t total)
+{
+	return show_progress(done, total, false);
+}
+
+static void print_vdi_list(uint32_t vid, const char *name, const char *tag,
+			   uint32_t snapid, uint32_t flags,
+			   const struct sd_inode *i, void *data)
+{
+	int idx;
+	bool is_clone = false;
+	uint64_t my_objs, cow_objs;
+	char vdi_size_str[16], my_objs_str[16], cow_objs_str[16];
+	time_t ti;
+	struct tm tm;
+	char dbuf[128];
+	struct get_vdi_info *info = data;
+
+	if (info && strcmp(name, info->name) != 0)
+		return;
+
+	ti = i->create_time >> 32;
+	if (raw_output) {
+		snprintf(dbuf, sizeof(dbuf), "%" PRIu64, (uint64_t) ti);
+	} else {
+		localtime_r(&ti, &tm);
+		strftime(dbuf, sizeof(dbuf),
+			 "%Y-%m-%d %H:%M", &tm);
+	}
+
+	my_objs = 0;
+	cow_objs = 0;
+	for (idx = 0; idx < MAX_DATA_OBJS; idx++) {
+		if (!i->data_vdi_id[idx])
+			continue;
+		if (is_data_obj_writeable(i, idx))
+			my_objs++;
+		else
+			cow_objs++;
+	}
+
+	size_to_str(i->vdi_size, vdi_size_str, sizeof(vdi_size_str));
+	size_to_str(my_objs * SD_DATA_OBJ_SIZE, my_objs_str, sizeof(my_objs_str));
+	size_to_str(cow_objs * SD_DATA_OBJ_SIZE, cow_objs_str, sizeof(cow_objs_str));
+
+	if (i->snap_id == 1 && i->parent_vdi_id != 0)
+		is_clone = true;
+
+	if (raw_output) {
+		printf("%c ", vdi_is_snapshot(i) ? 's' : (is_clone ? 'c' : '='));
+		while (*name) {
+			if (isspace(*name) || *name == '\\')
+				putchar('\\');
+			putchar(*name++);
+		}
+		printf(" %d %s %s %s %s %" PRIx32 " %d %s\n", snapid,
+				vdi_size_str, my_objs_str, cow_objs_str, dbuf, vid,
+				i->nr_copies, i->tag);
+	} else {
+		printf("%c %-8s %5d %7s %7s %7s %s  %7" PRIx32 " %5d %13s\n",
+				vdi_is_snapshot(i) ? 's' : (is_clone ? 'c' : ' '),
+				name, snapid, vdi_size_str, my_objs_str, cow_objs_str,
+				dbuf, vid, i->nr_copies, i->tag);
+	}
+}
+
+static void print_vdi_tree(uint32_t vid, const char *name, const char *tag,
+			   uint32_t snapid, uint32_t flags,
+			   const struct sd_inode *i, void *data)
+{
+	time_t ti;
+	struct tm tm;
+	char buf[128];
+
+	if (vdi_is_snapshot(i)) {
+		ti = i->create_time >> 32;
+		localtime_r(&ti, &tm);
+
+		strftime(buf, sizeof(buf),
+			 "[%Y-%m-%d %H:%M]", &tm);
+	} else
+		pstrcpy(buf, sizeof(buf), "(you are here)");
+
+	add_vdi_tree(name, buf, vid, i->parent_vdi_id,
+		     highlight && !vdi_is_snapshot(i));
+}
+
+static void print_vdi_graph(uint32_t vid, const char *name, const char *tag,
+			    uint32_t snapid, uint32_t flags,
+			    const struct sd_inode *i, void *data)
+{
+	time_t ti;
+	struct tm tm;
+	char dbuf[128], tbuf[128], size_str[128];
+
+	ti = i->create_time >> 32;
+	localtime_r(&ti, &tm);
+
+	strftime(dbuf, sizeof(dbuf), "%Y-%m-%d", &tm);
+	strftime(tbuf, sizeof(tbuf), "%H:%M:%S", &tm);
+	size_to_str(i->vdi_size, size_str, sizeof(size_str));
+
+	printf("  \"%x\" -> \"%x\";\n", i->parent_vdi_id, vid);
+	printf("  \"%x\" [\n"
+	       "    group = \"%s\",\n"
+	       "    label = \"",
+	       vid, name);
+	printf("Name: %10s\\n"
+	       "Tag:  %10x\\n"
+	       "Size: %10s\\n"
+	       "Date: %10s\\n"
+	       "Time: %10s",
+	       name, snapid, size_str, dbuf, tbuf);
+
+	if (vdi_is_snapshot(i))
+		printf("\"\n  ];\n\n");
+	else
+		printf("\",\n    color=\"red\"\n  ];\n\n");
+
+}
+
+static void get_oid(uint32_t vid, const char *name, const char *tag,
+		    uint32_t snapid, uint32_t flags,
+		    const struct sd_inode *i, void *data)
+{
+	struct get_vdi_info *info = data;
+
+	if (info->name) {
+		if (info->tag && info->tag[0]) {
+			if (!strcmp(name, info->name) &&
+			    !strcmp(tag, info->tag)) {
+				info->vid = vid;
+			info->nr_copies = i->nr_copies;
+			}
+		} else if (info->snapid) {
+			if (!strcmp(name, info->name) &&
+			    snapid == info->snapid) {
+				info->vid = vid;
+				info->nr_copies = i->nr_copies;
+			}
+		} else {
+			if (!strcmp(name, info->name)) {
+				info->vid = vid;
+				info->nr_copies = i->nr_copies;
+			}
+		}
+	}
+}
+
+typedef int (*obj_parser_func_t)(const char *sheep, uint64_t oid,
+				 struct sd_rsp *rsp, char *buf, void *data);
+
+static int do_print_obj(const char *sheep, uint64_t oid, struct sd_rsp *rsp,
+			char *buf, void *data)
+{
+	switch (rsp->result) {
+	case SD_RES_SUCCESS:
+		printf("%s has the object (should be %d copies)\n",
+		       sheep, rsp->obj.copies);
+		break;
+	case SD_RES_NO_OBJ:
+		printf("%s doesn't have the object\n", sheep);
+		break;
+	case SD_RES_OLD_NODE_VER:
+	case SD_RES_NEW_NODE_VER:
+		sd_err("The node list has changed: please try again");
+		break;
+	default:
+		sd_err("%s: hit an unexpected error (%s)", sheep,
+		       sd_strerror(rsp->result));
+		break;
+	}
+
+	return 0;
+}
+
+struct get_data_oid_info {
+	bool success;
+	uint64_t data_oid;
+	unsigned idx;
+};
+
+static int get_data_oid(const char *sheep, uint64_t oid, struct sd_rsp *rsp,
+			char *buf, void *data)
+{
+	struct get_data_oid_info *info = data;
+	struct sd_inode *inode = (struct sd_inode *)buf;
+
+	switch (rsp->result) {
+	case SD_RES_SUCCESS:
+		if (info->success)
+			break;
+		info->success = true;
+		if (inode->data_vdi_id[info->idx]) {
+			info->data_oid = vid_to_data_oid(inode->data_vdi_id[info->idx], info->idx);
+			return 1;
+		}
+		break;
+	case SD_RES_NO_OBJ:
+		break;
+	case SD_RES_OLD_NODE_VER:
+	case SD_RES_NEW_NODE_VER:
+		sd_err("The node list has changed: please try again");
+		break;
+	default:
+		sd_err("%s: hit an unexpected error (%s)", sheep,
+		       sd_strerror(rsp->result));
+		break;
+	}
+
+	return 0;
+}
+
+static void parse_objs(uint64_t oid, obj_parser_func_t func, void *data, unsigned size)
+{
+	int i, ret, cb_ret;
+	char *buf;
+
+	buf = xzalloc(size);
+	for (i = 0; i < sd_nodes_nr; i++) {
+		struct sd_req hdr;
+		struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+
+		sd_init_req(&hdr, SD_OP_READ_PEER);
+		hdr.data_length = size;
+		hdr.flags = 0;
+		hdr.epoch = sd_epoch;
+
+		hdr.obj.oid = oid;
+
+		ret = dog_exec_req(sd_nodes[i].nid.addr,
+				      sd_nodes[i].nid.port, &hdr, buf);
+		if (ret < 0)
+			continue;
+
+		untrim_zero_blocks(buf, rsp->obj.offset, rsp->data_length,
+				   size);
+
+		cb_ret = func(addr_to_str(sd_nodes[i].nid.addr,
+					  sd_nodes[i].nid.port),
+			      oid, rsp, buf, data);
+		if (cb_ret)
+			break;
+	}
+
+	free(buf);
+}
+
+
+static int vdi_list(int argc, char **argv)
+{
+	const char *vdiname = argv[optind];
+
+	if (!raw_output)
+		printf("  Name        Id    Size    Used  Shared    Creation time   VDI id  Copies  Tag\n");
+
+	if (vdiname) {
+		struct get_vdi_info info;
+		memset(&info, 0, sizeof(info));
+		info.name = vdiname;
+		if (parse_vdi(print_vdi_list, SD_INODE_SIZE, &info) < 0)
+			return EXIT_SYSFAIL;
+		return EXIT_SUCCESS;
+	} else {
+		if (parse_vdi(print_vdi_list, SD_INODE_SIZE, NULL) < 0)
+			return EXIT_SYSFAIL;
+		return EXIT_SUCCESS;
+	}
+}
+
+static int vdi_tree(int argc, char **argv)
+{
+	init_tree();
+	if (parse_vdi(print_vdi_tree, SD_INODE_HEADER_SIZE, NULL) < 0)
+		return EXIT_SYSFAIL;
+	dump_tree();
+
+	return EXIT_SUCCESS;
+}
+
+static int vdi_graph(int argc, char **argv)
+{
+	/* print a header */
+	printf("digraph G {\n");
+	printf("  node [shape = \"box\", fontname = \"Courier\"];\n\n");
+	printf("  \"0\" [shape = \"ellipse\", label = \"root\"];\n\n");
+
+	if (parse_vdi(print_vdi_graph, SD_INODE_HEADER_SIZE, NULL) < 0)
+		return EXIT_SYSFAIL;
+
+	/* print a footer */
+	printf("}\n");
+
+	return EXIT_SUCCESS;
+}
+
+static int find_vdi_name(const char *vdiname, uint32_t snapid, const char *tag,
+			 uint32_t *vid, int for_snapshot)
+{
+	int ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	char buf[SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN];
+
+	memset(buf, 0, sizeof(buf));
+	pstrcpy(buf, SD_MAX_VDI_LEN, vdiname);
+	if (tag)
+		pstrcpy(buf + SD_MAX_VDI_LEN, SD_MAX_VDI_TAG_LEN, tag);
+
+	if (for_snapshot)
+		sd_init_req(&hdr, SD_OP_GET_VDI_INFO);
+	else
+		sd_init_req(&hdr, SD_OP_LOCK_VDI);
+	hdr.data_length = SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN;
+	hdr.flags = SD_FLAG_CMD_WRITE;
+	hdr.vdi.snapid = snapid;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, buf);
+	if (ret < 0)
+		return -1;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Cannot get VDI info for %s %d %s: %s", vdiname, snapid,
+		       tag, sd_strerror(rsp->result));
+		return -1;
+	}
+	*vid = rsp->vdi.vdi_id;
+
+	return 0;
+}
+
+static int read_vdi_obj(const char *vdiname, int snapid, const char *tag,
+			uint32_t *pvid, struct sd_inode *inode,
+			size_t size)
+{
+	int ret;
+	uint32_t vid;
+
+	ret = find_vdi_name(vdiname, snapid, tag, &vid, 0);
+	if (ret < 0) {
+		sd_err("Failed to open VDI %s", vdiname);
+		return EXIT_FAILURE;
+	}
+
+	ret = sd_read_object(vid_to_vdi_oid(vid), inode, size, 0, true);
+	if (ret != SD_RES_SUCCESS) {
+		if (snapid) {
+			sd_err("Failed to read a snapshot %s:%d", vdiname,
+			       snapid);
+		} else if (tag && tag[0]) {
+			sd_err("Failed to read a snapshot %s:%s", vdiname, tag);
+		} else {
+			sd_err("Failed to read a vdi %s", vdiname);
+		}
+		return EXIT_FAILURE;
+	}
+
+	if (pvid)
+		*pvid = vid;
+
+	return EXIT_SUCCESS;
+}
+
+int do_vdi_create(const char *vdiname, int64_t vdi_size,
+			 uint32_t base_vid, uint32_t *vdi_id, bool snapshot,
+			 int nr_copies)
+{
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	int ret;
+	char buf[SD_MAX_VDI_LEN];
+
+	memset(buf, 0, sizeof(buf));
+	pstrcpy(buf, SD_MAX_VDI_LEN, vdiname);
+
+	sd_init_req(&hdr, SD_OP_NEW_VDI);
+	hdr.flags = SD_FLAG_CMD_WRITE;
+	hdr.data_length = SD_MAX_VDI_LEN;
+
+	hdr.vdi.base_vdi_id = base_vid;
+	hdr.vdi.snapid = snapshot ? 1 : 0;
+	hdr.vdi.vdi_size = vdi_size;
+	hdr.vdi.copies = nr_copies;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, buf);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Failed to create VDI %s: %s", vdiname,
+		       sd_strerror(rsp->result));
+		return EXIT_FAILURE;
+	}
+
+	if (vdi_id)
+		*vdi_id = rsp->vdi.vdi_id;
+
+	return EXIT_SUCCESS;
+}
+
+static int vdi_create(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	uint64_t size;
+	uint32_t vid;
+	uint64_t oid;
+	int idx, max_idx, ret, nr_copies = vdi_cmd_data.nr_copies;
+	struct sd_inode *inode = NULL;
+
+	if (!argv[optind]) {
+		sd_err("Please specify the VDI size");
+		return EXIT_USAGE;
+	}
+	ret = parse_option_size(argv[optind], &size);
+	if (ret < 0)
+		return EXIT_USAGE;
+	if (size > SD_MAX_VDI_SIZE) {
+		sd_err("VDI size is too large");
+		return EXIT_USAGE;
+	}
+
+	if (nr_copies > sd_nodes_nr) {
+		sd_err("There are not enough nodes(%d) to hold the copies(%d)",
+		       sd_nodes_nr, nr_copies);
+		return EXIT_USAGE;
+	}
+
+	ret = do_vdi_create(vdiname, size, 0, &vid, false,
+			    vdi_cmd_data.nr_copies);
+	if (ret != EXIT_SUCCESS || !vdi_cmd_data.prealloc)
+		goto out;
+
+	inode = xmalloc(sizeof(*inode));
+
+	ret = sd_read_object(vid_to_vdi_oid(vid), inode, sizeof(*inode), 0, true);
+	if (ret != SD_RES_SUCCESS) {
+		sd_err("Failed to read a newly created VDI object");
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+	max_idx = DIV_ROUND_UP(size, SD_DATA_OBJ_SIZE);
+
+	for (idx = 0; idx < max_idx; idx++) {
+		vdi_show_progress(idx * SD_DATA_OBJ_SIZE, inode->vdi_size);
+		oid = vid_to_data_oid(vid, idx);
+
+		ret = sd_write_object(oid, 0, NULL, 0, 0, 0, inode->nr_copies,
+				      true, true);
+		if (ret != SD_RES_SUCCESS) {
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+
+		inode->data_vdi_id[idx] = vid;
+		ret = sd_write_object(vid_to_vdi_oid(vid), 0, &vid, sizeof(vid),
+				      SD_INODE_HEADER_SIZE + sizeof(vid) * idx, 0,
+				      inode->nr_copies, false, true);
+		if (ret) {
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+	}
+	vdi_show_progress(idx * SD_DATA_OBJ_SIZE, inode->vdi_size);
+	ret = EXIT_SUCCESS;
+
+	if (verbose) {
+		if (raw_output)
+			printf("%x\n", vid);
+		else
+			printf("VDI ID of newly created VDI: %x\n", vid);
+	}
+
+out:
+	free(inode);
+	return ret;
+}
+
+static int vdi_snapshot(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	uint32_t vid;
+	int ret;
+	char buf[SD_INODE_HEADER_SIZE];
+	struct sd_inode *inode = (struct sd_inode *)buf;
+
+	if (vdi_cmd_data.snapshot_id != 0) {
+		sd_err("Please specify a non-integer value for "
+		       "a snapshot tag name");
+		return EXIT_USAGE;
+	}
+
+	ret = read_vdi_obj(vdiname, 0, "", &vid, inode, SD_INODE_HEADER_SIZE);
+	if (ret != EXIT_SUCCESS)
+		return ret;
+
+	ret = sd_write_object(vid_to_vdi_oid(vid), 0, vdi_cmd_data.snapshot_tag,
+			      SD_MAX_VDI_TAG_LEN,
+			      offsetof(struct sd_inode, tag),
+			      0, inode->nr_copies, false, false);
+	if (ret != SD_RES_SUCCESS)
+		return EXIT_FAILURE;
+
+	ret = do_vdi_create(vdiname, inode->vdi_size, vid, NULL, true,
+			    inode->nr_copies);
+
+	if (ret == EXIT_SUCCESS && verbose) {
+		if (raw_output)
+			printf("%x\n", vid);
+		else
+			printf("VDI ID of newly created snapshot: %x\n", vid);
+	}
+
+	return ret;
+}
+
+static int vdi_clone(int argc, char **argv)
+{
+	const char *src_vdi = argv[optind++], *dst_vdi;
+	uint32_t base_vid, new_vid;
+	uint64_t oid;
+	int idx, max_idx, ret;
+	struct sd_inode *inode = NULL;
+	char *buf = NULL;
+
+	dst_vdi = argv[optind];
+	if (!dst_vdi) {
+		sd_err("Destination VDI name must be specified");
+		ret = EXIT_USAGE;
+		goto out;
+	}
+
+	if (!vdi_cmd_data.snapshot_id && !vdi_cmd_data.snapshot_tag[0]) {
+		sd_err("Only snapshot VDIs can be cloned");
+		sd_err("Please specify the '-s' option");
+		ret = EXIT_USAGE;
+		goto out;
+	}
+
+	inode = xmalloc(sizeof(*inode));
+
+	ret = read_vdi_obj(src_vdi, vdi_cmd_data.snapshot_id,
+			   vdi_cmd_data.snapshot_tag, &base_vid, inode,
+			   SD_INODE_SIZE);
+	if (ret != EXIT_SUCCESS)
+		goto out;
+
+	ret = do_vdi_create(dst_vdi, inode->vdi_size, base_vid, &new_vid, false,
+			    vdi_cmd_data.nr_copies);
+	if (ret != EXIT_SUCCESS || !vdi_cmd_data.prealloc)
+		goto out;
+
+	buf = xzalloc(SD_DATA_OBJ_SIZE);
+	max_idx = DIV_ROUND_UP(inode->vdi_size, SD_DATA_OBJ_SIZE);
+
+	for (idx = 0; idx < max_idx; idx++) {
+		vdi_show_progress(idx * SD_DATA_OBJ_SIZE, inode->vdi_size);
+		if (inode->data_vdi_id[idx]) {
+			oid = vid_to_data_oid(inode->data_vdi_id[idx], idx);
+			ret = sd_read_object(oid, buf, SD_DATA_OBJ_SIZE, 0, true);
+			if (ret) {
+				ret = EXIT_FAILURE;
+				goto out;
+			}
+		} else
+			memset(buf, 0, SD_DATA_OBJ_SIZE);
+
+		oid = vid_to_data_oid(new_vid, idx);
+		ret = sd_write_object(oid, 0, buf, SD_DATA_OBJ_SIZE, 0, 0,
+				      inode->nr_copies, true, true);
+		if (ret != SD_RES_SUCCESS) {
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+
+		ret = sd_write_object(vid_to_vdi_oid(new_vid), 0, &new_vid, sizeof(new_vid),
+				      SD_INODE_HEADER_SIZE + sizeof(new_vid) * idx, 0,
+				      inode->nr_copies, false, true);
+		if (ret) {
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+	}
+	vdi_show_progress(idx * SD_DATA_OBJ_SIZE, inode->vdi_size);
+	ret = EXIT_SUCCESS;
+
+	if (verbose) {
+		if (raw_output)
+			printf("%x\n", new_vid);
+		else
+			printf("VDI ID of newly created clone: %x\n", new_vid);
+	}
+out:
+	free(inode);
+	free(buf);
+	return ret;
+}
+
+static int vdi_resize(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	uint64_t new_size;
+	uint32_t vid;
+	int ret;
+	char buf[SD_INODE_HEADER_SIZE];
+	struct sd_inode *inode = (struct sd_inode *)buf;
+
+	if (!argv[optind]) {
+		sd_err("Please specify the new size for the VDI");
+		return EXIT_USAGE;
+	}
+	ret = parse_option_size(argv[optind], &new_size);
+	if (ret < 0)
+		return EXIT_USAGE;
+	if (new_size > SD_MAX_VDI_SIZE) {
+		sd_err("New VDI size is too large");
+		return EXIT_USAGE;
+	}
+
+	ret = read_vdi_obj(vdiname, 0, "", &vid, inode, SD_INODE_HEADER_SIZE);
+	if (ret != EXIT_SUCCESS)
+		return ret;
+
+	if (new_size < inode->vdi_size) {
+		sd_err("Shrinking VDIs is not implemented");
+		return EXIT_USAGE;
+	}
+	inode->vdi_size = new_size;
+
+	ret = sd_write_object(vid_to_vdi_oid(vid), 0, inode, SD_INODE_HEADER_SIZE, 0,
+			      0, inode->nr_copies, false, true);
+	if (ret != SD_RES_SUCCESS) {
+		sd_err("Failed to update an inode header");
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int do_vdi_delete(const char *vdiname, int snap_id, const char *snap_tag)
+{
+	int ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	char data[SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN];
+	uint32_t vid;
+
+	ret = find_vdi_name(vdiname, snap_id, snap_tag, &vid, 0);
+	if (ret < 0) {
+		sd_err("Failed to open VDI %s", vdiname);
+		return EXIT_FAILURE;
+	}
+
+	sd_init_req(&hdr, SD_OP_DELETE_CACHE);
+	hdr.obj.oid = vid_to_vdi_oid(vid);
+
+	ret = send_light_req(&hdr, sdhost, sdport);
+	if (ret) {
+		sd_err("failed to execute request");
+		return EXIT_FAILURE;
+	}
+
+	sd_init_req(&hdr, SD_OP_DEL_VDI);
+	hdr.flags = SD_FLAG_CMD_WRITE;
+	hdr.data_length = sizeof(data);
+	hdr.vdi.snapid = snap_id;
+	memset(data, 0, sizeof(data));
+	pstrcpy(data, SD_MAX_VDI_LEN, vdiname);
+	if (snap_tag)
+		pstrcpy(data + SD_MAX_VDI_LEN, SD_MAX_VDI_TAG_LEN, snap_tag);
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, data);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("Failed to delete %s: %s", vdiname,
+		       sd_strerror(rsp->result));
+		if (rsp->result == SD_RES_NO_VDI)
+			return EXIT_MISSING;
+		else
+			return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int vdi_delete(int argc, char **argv)
+{
+	char *vdiname = argv[optind];
+
+	return do_vdi_delete(vdiname, vdi_cmd_data.snapshot_id,
+			     vdi_cmd_data.snapshot_tag);
+}
+
+static int vdi_rollback(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	uint32_t base_vid, new_vid;
+	int ret;
+	char buf[SD_INODE_HEADER_SIZE];
+	struct sd_inode *inode = (struct sd_inode *)buf;
+
+	if (!vdi_cmd_data.snapshot_id && !vdi_cmd_data.snapshot_tag[0]) {
+		sd_err("Please specify the '-s' option");
+		return EXIT_USAGE;
+	}
+
+	ret = read_vdi_obj(vdiname, vdi_cmd_data.snapshot_id,
+			   vdi_cmd_data.snapshot_tag, &base_vid, inode,
+			   SD_INODE_HEADER_SIZE);
+	if (ret != EXIT_SUCCESS)
+		return ret;
+
+	if (!vdi_cmd_data.force)
+		confirm("This operation dicards any changes made since the"
+			" previous\nsnapshot was taken.  Continue? [yes/no]: ");
+
+	ret = do_vdi_delete(vdiname, 0, NULL);
+	if (ret != SD_RES_SUCCESS) {
+		sd_err("Failed to delete the current state");
+		return EXIT_FAILURE;
+	}
+
+	ret = do_vdi_create(vdiname, inode->vdi_size, base_vid, &new_vid,
+			     false, vdi_cmd_data.nr_copies);
+
+	if (ret == EXIT_SUCCESS && verbose) {
+		if (raw_output)
+			printf("%x\n", new_vid);
+		else
+			printf("New VDI ID of rollbacked VDI: %x\n", new_vid);
+	}
+
+	return ret;
+}
+
+static int vdi_object(int argc, char **argv)
+{
+	const char *vdiname = argv[optind];
+	unsigned idx = vdi_cmd_data.index;
+	struct get_vdi_info info;
+	uint32_t vid;
+
+	memset(&info, 0, sizeof(info));
+	info.name = vdiname;
+	info.tag = vdi_cmd_data.snapshot_tag;
+	info.vid = 0;
+	info.snapid = vdi_cmd_data.snapshot_id;
+
+	if (parse_vdi(get_oid, SD_INODE_HEADER_SIZE, &info) < 0)
+		return EXIT_SYSFAIL;
+
+	vid = info.vid;
+	if (vid == 0) {
+		sd_err("VDI not found");
+		return EXIT_MISSING;
+	}
+
+	if (idx == ~0) {
+		printf("Looking for the inode object 0x%" PRIx32 " with %d nodes\n\n",
+		       vid, sd_nodes_nr);
+		parse_objs(vid_to_vdi_oid(vid), do_print_obj, NULL, SD_INODE_SIZE);
+	} else {
+		struct get_data_oid_info oid_info = {0};
+
+		oid_info.success = false;
+		oid_info.idx = idx;
+
+		if (idx >= MAX_DATA_OBJS) {
+			printf("The offset is too large!\n");
+			exit(EXIT_FAILURE);
+		}
+
+		parse_objs(vid_to_vdi_oid(vid), get_data_oid, &oid_info, SD_DATA_OBJ_SIZE);
+
+		if (oid_info.success) {
+			if (oid_info.data_oid) {
+				printf("Looking for the object 0x%" PRIx64
+				       " (the inode vid 0x%" PRIx32 " idx %u) with %d nodes\n\n",
+				       oid_info.data_oid, vid, idx, sd_nodes_nr);
+
+				parse_objs(oid_info.data_oid, do_print_obj, NULL, SD_DATA_OBJ_SIZE);
+			} else
+				printf("The inode object 0x%" PRIx32 " idx %u is not allocated\n",
+				       vid, idx);
+		} else
+			sd_err("Failed to read the inode object 0x%" PRIx32,
+			       vid);
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int do_track_object(uint64_t oid, uint8_t nr_copies)
+{
+	int i, j, ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	struct sd_vnode *vnodes;
+	const struct sd_vnode *vnode_buf[SD_MAX_COPIES];
+	struct epoch_log *logs;
+	int vnodes_nr, nr_logs, log_length;
+
+	log_length = sd_epoch * sizeof(struct epoch_log);
+	logs = xmalloc(log_length);
+	vnodes = xmalloc(sizeof(*vnodes) * SD_MAX_VNODES);
+
+	sd_init_req(&hdr, SD_OP_STAT_CLUSTER);
+	hdr.data_length = log_length;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, logs);
+	if (ret < 0)
+		goto error;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		printf("%s\n", sd_strerror(rsp->result));
+		goto error;
+	}
+
+	nr_logs = rsp->data_length / sizeof(struct epoch_log);
+	for (i = nr_logs - 1; i >= 0; i--) {
+		printf("\nobj %"PRIx64" locations at epoch %d, copies = %d\n",
+		       oid, logs[i].epoch, nr_copies);
+		printf("---------------------------------------------------\n");
+
+		/*
+		 * When # of nodes is less than nr_copies, we only print
+		 * remaining nodes that holds all the remaining copies.
+		 */
+		if (logs[i].nr_nodes < nr_copies) {
+			for (j = 0; j < logs[i].nr_nodes; j++) {
+				const struct node_id *n = &logs[i].nodes[j].nid;
+
+				printf("%s\n", addr_to_str(n->addr, n->port));
+			}
+			continue;
+		}
+		vnodes_nr = nodes_to_vnodes(logs[i].nodes,
+					    logs[i].nr_nodes, vnodes);
+		oid_to_vnodes(vnodes, vnodes_nr, oid, nr_copies, vnode_buf);
+		for (j = 0; j < nr_copies; j++) {
+			const struct node_id *n = &vnode_buf[j]->nid;
+
+			printf("%s\n", addr_to_str(n->addr, n->port));
+		}
+	}
+
+	free(logs);
+	free(vnodes);
+	return EXIT_SUCCESS;
+error:
+	free(logs);
+	free(vnodes);
+	return EXIT_SYSFAIL;
+}
+
+static int vdi_track(int argc, char **argv)
+{
+	const char *vdiname = argv[optind];
+	unsigned idx = vdi_cmd_data.index;
+	struct get_vdi_info info;
+	struct get_data_oid_info oid_info = {0};
+	uint32_t vid;
+	uint8_t nr_copies;
+
+	memset(&info, 0, sizeof(info));
+	info.name = vdiname;
+	info.tag = vdi_cmd_data.snapshot_tag;
+	info.vid = 0;
+	info.snapid = vdi_cmd_data.snapshot_id;
+
+	if (parse_vdi(get_oid, SD_INODE_HEADER_SIZE, &info) < 0)
+		return EXIT_SYSFAIL;
+
+	vid = info.vid;
+	nr_copies = info.nr_copies;
+	if (vid == 0) {
+		sd_err("VDI not found");
+		return EXIT_MISSING;
+	}
+
+	if (idx == ~0) {
+		printf("Tracking the inode object 0x%" PRIx32 " with %d nodes\n",
+		       vid, sd_nodes_nr);
+		return do_track_object(vid_to_vdi_oid(vid), nr_copies);
+	}
+
+	oid_info.success = false;
+	oid_info.idx = idx;
+
+	if (idx >= MAX_DATA_OBJS) {
+		printf("The offset is too large!\n");
+		goto err;
+	}
+
+	parse_objs(vid_to_vdi_oid(vid), get_data_oid,
+		   &oid_info, SD_DATA_OBJ_SIZE);
+
+	if (!oid_info.success) {
+		sd_err("Failed to read the inode object 0x%" PRIx32, vid);
+		goto err;
+	}
+	if (!oid_info.data_oid) {
+		printf("The inode object 0x%"PRIx32" idx %u is not allocated\n",
+		       vid, idx);
+		goto err;
+	}
+	printf("Tracking the object 0x%" PRIx64
+	       " (the inode vid 0x%" PRIx32 " idx %u)"
+	       " with %d nodes\n",
+	       oid_info.data_oid, vid, idx, sd_nodes_nr);
+	return do_track_object(oid_info.data_oid, nr_copies);
+err:
+	return EXIT_FAILURE;
+}
+
+static int find_vdi_attr_oid(const char *vdiname, const char *tag, uint32_t snapid,
+			     const char *key, void *value, unsigned int value_len,
+			     uint32_t *vid, uint64_t *oid, unsigned int *nr_copies,
+			     bool create, bool excl, bool delete)
+{
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	int ret;
+	struct sheepdog_vdi_attr vattr;
+
+	memset(&vattr, 0, sizeof(vattr));
+	pstrcpy(vattr.name, SD_MAX_VDI_LEN, vdiname);
+	pstrcpy(vattr.tag, SD_MAX_VDI_TAG_LEN, vdi_cmd_data.snapshot_tag);
+	vattr.snap_id = vdi_cmd_data.snapshot_id;
+	pstrcpy(vattr.key, SD_MAX_VDI_ATTR_KEY_LEN, key);
+	if (value && value_len) {
+		vattr.value_len = value_len;
+		memcpy(vattr.value, value, value_len);
+	}
+
+	sd_init_req(&hdr, SD_OP_GET_VDI_ATTR);
+	hdr.flags = SD_FLAG_CMD_WRITE;
+	hdr.data_length = SD_ATTR_OBJ_SIZE;
+	hdr.vdi.snapid = vdi_cmd_data.snapshot_id;
+
+	if (create)
+		hdr.flags |= SD_FLAG_CMD_CREAT;
+	if (excl)
+		hdr.flags |= SD_FLAG_CMD_EXCL;
+	if (delete)
+		hdr.flags |= SD_FLAG_CMD_DEL;
+
+	ret = dog_exec_req(sdhost, sdport, &hdr, &vattr);
+	if (ret < 0)
+		return SD_RES_EIO;
+
+	if (rsp->result != SD_RES_SUCCESS)
+		return rsp->result;
+
+	*vid = rsp->vdi.vdi_id;
+	*oid = vid_to_attr_oid(rsp->vdi.vdi_id, rsp->vdi.attr_id);
+	*nr_copies = rsp->vdi.copies;
+
+	return SD_RES_SUCCESS;
+}
+
+static int vdi_setattr(int argc, char **argv)
+{
+	int ret, value_len = 0;
+	uint64_t attr_oid = 0;
+	uint32_t vid = 0, nr_copies = 0;
+	const char *vdiname = argv[optind++], *key;
+	char *value;
+	uint64_t offset;
+
+	key = argv[optind++];
+	if (!key) {
+		sd_err("Please specify the attribute key");
+		return EXIT_USAGE;
+	}
+
+	value = argv[optind++];
+	if (!value && !vdi_cmd_data.delete) {
+		value = xmalloc(SD_MAX_VDI_ATTR_VALUE_LEN);
+
+		offset = 0;
+reread:
+		ret = read(STDIN_FILENO, value + offset,
+			   SD_MAX_VDI_ATTR_VALUE_LEN - offset);
+		if (ret < 0) {
+			sd_err("Failed to read attribute value from stdin: %m");
+			return EXIT_SYSFAIL;
+		}
+		if (ret > 0) {
+			offset += ret;
+			goto reread;
+		}
+	}
+
+	if (value)
+		value_len = strlen(value);
+
+	ret = find_vdi_attr_oid(vdiname, vdi_cmd_data.snapshot_tag,
+				vdi_cmd_data.snapshot_id, key, value,
+				value_len, &vid, &attr_oid,
+				&nr_copies, !vdi_cmd_data.delete,
+				vdi_cmd_data.exclusive, vdi_cmd_data.delete);
+	if (ret) {
+		if (ret == SD_RES_VDI_EXIST) {
+			sd_err("The attribute '%s' already exists", key);
+			return EXIT_EXISTS;
+		} else if (ret == SD_RES_NO_OBJ) {
+			sd_err("Attribute '%s' not found", key);
+			return EXIT_MISSING;
+		} else if (ret == SD_RES_NO_VDI) {
+			sd_err("VDI not found");
+			return EXIT_MISSING;
+		} else
+			sd_err("Failed to set attribute: %s", sd_strerror(ret));
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int vdi_getattr(int argc, char **argv)
+{
+	int ret;
+	uint64_t oid, attr_oid = 0;
+	uint32_t vid = 0, nr_copies = 0;
+	const char *vdiname = argv[optind++], *key;
+	struct sheepdog_vdi_attr vattr;
+
+	key = argv[optind++];
+	if (!key) {
+		sd_err("Please specify the attribute key");
+		return EXIT_USAGE;
+	}
+
+	ret = find_vdi_attr_oid(vdiname, vdi_cmd_data.snapshot_tag,
+				vdi_cmd_data.snapshot_id, key, NULL, 0, &vid,
+				&attr_oid, &nr_copies, false, false, false);
+	if (ret == SD_RES_NO_OBJ) {
+		sd_err("Attribute '%s' not found", key);
+		return EXIT_MISSING;
+	} else if (ret == SD_RES_NO_VDI) {
+		sd_err("VDI not found");
+		return EXIT_MISSING;
+	} else if (ret) {
+		sd_err("Failed to find attribute oid: %s", sd_strerror(ret));
+		return EXIT_MISSING;
+	}
+
+	oid = attr_oid;
+
+	ret = sd_read_object(oid, &vattr, SD_ATTR_OBJ_SIZE, 0, true);
+	if (ret != SD_RES_SUCCESS) {
+		sd_err("Failed to read attribute oid: %s", sd_strerror(ret));
+		return EXIT_SYSFAIL;
+	}
+
+	xwrite(STDOUT_FILENO, vattr.value, vattr.value_len);
+	return EXIT_SUCCESS;
+}
+
+static int vdi_read(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	int ret, idx;
+	struct sd_inode *inode = NULL;
+	uint64_t offset = 0, oid, done = 0, total = (uint64_t) -1;
+	unsigned int len;
+	char *buf = NULL;
+
+	if (argv[optind]) {
+		ret = parse_option_size(argv[optind++], &offset);
+		if (ret < 0)
+			return EXIT_USAGE;
+		if (argv[optind]) {
+			ret = parse_option_size(argv[optind++], &total);
+			if (ret < 0)
+				return EXIT_USAGE;
+		}
+	}
+
+	inode = malloc(sizeof(*inode));
+	buf = xmalloc(SD_DATA_OBJ_SIZE);
+
+	ret = read_vdi_obj(vdiname, vdi_cmd_data.snapshot_id,
+			   vdi_cmd_data.snapshot_tag, NULL, inode,
+			   SD_INODE_SIZE);
+	if (ret != EXIT_SUCCESS)
+		goto out;
+
+	if (inode->vdi_size < offset) {
+		sd_err("Read offset is beyond the end of the VDI");
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	total = min(total, inode->vdi_size - offset);
+	idx = offset / SD_DATA_OBJ_SIZE;
+	offset %= SD_DATA_OBJ_SIZE;
+	while (done < total) {
+		len = min(total - done, SD_DATA_OBJ_SIZE - offset);
+
+		if (inode->data_vdi_id[idx]) {
+			oid = vid_to_data_oid(inode->data_vdi_id[idx], idx);
+			ret = sd_read_object(oid, buf, len, offset, false);
+			if (ret != SD_RES_SUCCESS) {
+				sd_err("Failed to read VDI");
+				ret = EXIT_FAILURE;
+				goto out;
+			}
+		} else
+			memset(buf, 0, len);
+
+		ret = xwrite(STDOUT_FILENO, buf, len);
+		if (ret < 0) {
+			sd_err("Failed to write to stdout: %m");
+			ret = EXIT_SYSFAIL;
+			goto out;
+		}
+
+		offset = 0;
+		idx++;
+		done += len;
+	}
+	fsync(STDOUT_FILENO);
+	ret = EXIT_SUCCESS;
+out:
+	free(inode);
+	free(buf);
+
+	return ret;
+}
+
+static int vdi_write(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	uint32_t vid, flags;
+	int ret, idx;
+	struct sd_inode *inode = NULL;
+	uint64_t offset = 0, oid, old_oid, done = 0, total = (uint64_t) -1;
+	unsigned int len;
+	char *buf = NULL;
+	bool create;
+
+	if (argv[optind]) {
+		ret = parse_option_size(argv[optind++], &offset);
+		if (ret < 0)
+			return EXIT_USAGE;
+		if (argv[optind]) {
+			ret = parse_option_size(argv[optind++], &total);
+			if (ret < 0)
+				return EXIT_USAGE;
+		}
+	}
+
+	inode = xmalloc(sizeof(*inode));
+	buf = xmalloc(SD_DATA_OBJ_SIZE);
+
+	ret = read_vdi_obj(vdiname, 0, "", &vid, inode, SD_INODE_SIZE);
+	if (ret != EXIT_SUCCESS)
+		goto out;
+
+	if (inode->vdi_size < offset) {
+		sd_err("Write offset is beyond the end of the VDI");
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	total = min(total, inode->vdi_size - offset);
+	idx = offset / SD_DATA_OBJ_SIZE;
+	offset %= SD_DATA_OBJ_SIZE;
+	while (done < total) {
+		create = false;
+		old_oid = 0;
+		flags = 0;
+		len = min(total - done, SD_DATA_OBJ_SIZE - offset);
+
+		if (!inode->data_vdi_id[idx])
+			create = true;
+		else if (!is_data_obj_writeable(inode, idx)) {
+			create = true;
+			old_oid = vid_to_data_oid(inode->data_vdi_id[idx], idx);
+		}
+
+		if (vdi_cmd_data.writeback)
+			flags |= SD_FLAG_CMD_CACHE;
+
+		ret = xread(STDIN_FILENO, buf, len);
+		if (ret < 0) {
+			sd_err("Failed to read from stdin: %m");
+			ret = EXIT_SYSFAIL;
+			goto out;
+		} else if (ret < len) {
+			/* exit after this buffer is sent */
+			memset(buf + ret, 0, len - ret);
+			total = done + len;
+		}
+
+		inode->data_vdi_id[idx] = inode->vdi_id;
+		oid = vid_to_data_oid(inode->data_vdi_id[idx], idx);
+		ret = sd_write_object(oid, old_oid, buf, len, offset, flags,
+				      inode->nr_copies, create, false);
+		if (ret != SD_RES_SUCCESS) {
+			sd_err("Failed to write VDI");
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+
+		if (create) {
+			ret = sd_write_object(vid_to_vdi_oid(vid), 0, &vid, sizeof(vid),
+					      SD_INODE_HEADER_SIZE + sizeof(vid) * idx,
+					      flags, inode->nr_copies, false, false);
+			if (ret) {
+				ret = EXIT_FAILURE;
+				goto out;
+			}
+		}
+
+		offset += len;
+		if (offset == SD_DATA_OBJ_SIZE) {
+			offset = 0;
+			idx++;
+		}
+		done += len;
+	}
+	ret = EXIT_SUCCESS;
+out:
+	free(inode);
+	free(buf);
+
+	return ret;
+}
+
+static void *read_object_from(const struct sd_vnode *vnode, uint64_t oid)
+{
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	int ret;
+	void *buf;
+	size_t size = get_objsize(oid);
+
+	buf = xmalloc(size);
+
+	sd_init_req(&hdr, SD_OP_READ_PEER);
+	hdr.epoch = sd_epoch;
+	hdr.flags = 0;
+	hdr.data_length = size;
+
+	hdr.obj.oid = oid;
+
+	ret = dog_exec_req(vnode->nid.addr, vnode->nid.port, &hdr, buf);
+
+	if (ret < 0)
+		exit(EXIT_SYSFAIL);
+
+	switch (rsp->result) {
+	case SD_RES_SUCCESS:
+		untrim_zero_blocks(buf, rsp->obj.offset, rsp->data_length,
+				   size);
+		break;
+	case SD_RES_NO_OBJ:
+		free(buf);
+		return NULL;
+	default:
+		sd_err("FATAL: failed to read %"PRIx64", %s", oid,
+		       sd_strerror(rsp->result));
+		exit(EXIT_FAILURE);
+	}
+	return buf;
+}
+
+static void write_object_to(const struct sd_vnode *vnode, uint64_t oid,
+			    void *buf, bool create)
+{
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	int ret;
+
+	if (create)
+		sd_init_req(&hdr, SD_OP_CREATE_AND_WRITE_PEER);
+	else
+		sd_init_req(&hdr, SD_OP_WRITE_PEER);
+	hdr.epoch = sd_epoch;
+	hdr.flags = SD_FLAG_CMD_WRITE;
+	hdr.data_length = get_objsize(oid);
+	hdr.obj.oid = oid;
+
+	ret = dog_exec_req(vnode->nid.addr, vnode->nid.port, &hdr, buf);
+
+	if (ret < 0)
+		exit(EXIT_SYSFAIL);
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("FATAL: failed to write %"PRIx64", %s", oid,
+		       sd_strerror(rsp->result));
+		exit(EXIT_FAILURE);
+	}
+}
+
+struct vdi_check_work {
+	struct vdi_check_info *info;
+	const struct sd_vnode *vnode;
+	uint8_t hash[SHA1_DIGEST_SIZE];
+	bool object_found;
+	struct work work;
+};
+
+struct vdi_check_info {
+	uint64_t oid;
+	int nr_copies;
+	uint64_t total;
+	uint64_t *done;
+	int refcnt;
+	struct work_queue *wq;
+	struct vdi_check_work *base;
+	struct vdi_check_work vcw[0];
+};
+
+static void free_vdi_check_info(struct vdi_check_info *info)
+{
+	if (info->done) {
+		*info->done += SD_DATA_OBJ_SIZE;
+		vdi_show_progress(*info->done, info->total);
+	}
+	free(info);
+}
+
+static void vdi_repair_work(struct work *work)
+{
+	struct vdi_check_work *vcw = container_of(work, struct vdi_check_work,
+						  work);
+	struct vdi_check_info *info = vcw->info;
+	void *buf;
+
+	buf = read_object_from(info->base->vnode, info->oid);
+	write_object_to(vcw->vnode, info->oid, buf, !vcw->object_found);
+	free(buf);
+}
+
+static void vdi_repair_main(struct work *work)
+{
+	struct vdi_check_work *vcw = container_of(work, struct vdi_check_work,
+						  work);
+	struct vdi_check_info *info = vcw->info;
+
+	if (vcw->object_found)
+		fprintf(stdout, "fixed replica %"PRIx64"\n", info->oid);
+	else
+		fprintf(stdout, "fixed missing %"PRIx64"\n", info->oid);
+
+	info->refcnt--;
+	if (info->refcnt == 0)
+		free_vdi_check_info(info);
+}
+
+static void vdi_hash_check_work(struct work *work)
+{
+	struct vdi_check_work *vcw = container_of(work, struct vdi_check_work,
+						  work);
+	struct vdi_check_info *info = vcw->info;
+	int ret;
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+
+	sd_init_req(&hdr, SD_OP_GET_HASH);
+	hdr.obj.oid = info->oid;
+	hdr.obj.tgt_epoch = sd_epoch;
+
+	ret = dog_exec_req(vcw->vnode->nid.addr, vcw->vnode->nid.port, &hdr,
+			      NULL);
+	if (ret < 0)
+		exit(EXIT_SYSFAIL);
+
+	switch (ret) {
+	case SD_RES_SUCCESS:
+		vcw->object_found = true;
+		memcpy(vcw->hash, rsp->hash.digest, sizeof(vcw->hash));
+		uatomic_set(&info->base, vcw);
+		break;
+	case SD_RES_NO_OBJ:
+		vcw->object_found = false;
+		break;
+	default:
+		sd_err("failed to read %" PRIx64 " from %s, %s", info->oid,
+		       addr_to_str(vcw->vnode->nid.addr, vcw->vnode->nid.port),
+		       sd_strerror(ret));
+		exit(EXIT_FAILURE);
+	}
+}
+
+static void vdi_hash_check_main(struct work *work)
+{
+	struct vdi_check_work *vcw = container_of(work, struct vdi_check_work,
+						  work);
+	struct vdi_check_info *info = vcw->info;
+
+	info->refcnt--;
+	if (info->refcnt > 0)
+		return;
+
+	if (info->base  == NULL) {
+		sd_err("no node has %" PRIx64, info->oid);
+		exit(EXIT_FAILURE);
+	}
+
+	for (int i = 0; i < info->nr_copies; i++) {
+		if (&info->vcw[i] == info->base)
+			continue;
+		/* need repair when object not found or consistency broken */
+		if (!info->vcw[i].object_found ||
+		    memcmp(info->base->hash, info->vcw[i].hash,
+			   sizeof(info->base->hash)) != 0) {
+			info->vcw[i].work.fn = vdi_repair_work;
+			info->vcw[i].work.done = vdi_repair_main;
+			info->refcnt++;
+			queue_work(info->wq, &info->vcw[i].work);
+		}
+	}
+
+	if (info->refcnt == 0)
+		free_vdi_check_info(info);
+}
+
+static void queue_vdi_check_work(struct sd_inode *inode, uint64_t oid,
+				 uint64_t *done, struct work_queue *wq)
+{
+	struct vdi_check_info *info;
+	const struct sd_vnode *tgt_vnodes[SD_MAX_COPIES];
+	int nr_copies = inode->nr_copies;
+
+	info = xzalloc(sizeof(*info) + sizeof(info->vcw[0]) * nr_copies);
+	info->oid = oid;
+	info->nr_copies = nr_copies;
+	info->total = inode->vdi_size;
+	info->done = done;
+	info->wq = wq;
+
+	oid_to_vnodes(sd_vnodes, sd_vnodes_nr, oid, nr_copies, tgt_vnodes);
+	for (int i = 0; i < nr_copies; i++) {
+		info->vcw[i].info = info;
+		info->vcw[i].vnode = tgt_vnodes[i];
+		info->vcw[i].work.fn = vdi_hash_check_work;
+		info->vcw[i].work.done = vdi_hash_check_main;
+		info->refcnt++;
+		queue_work(info->wq, &info->vcw[i].work);
+	}
+}
+
+static int vdi_check(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	int ret, max_idx;
+	uint64_t done = 0, oid;
+	uint32_t vid;
+	struct sd_inode *inode = xmalloc(sizeof(*inode));
+	struct work_queue *wq;
+
+	ret = read_vdi_obj(vdiname, vdi_cmd_data.snapshot_id,
+			   vdi_cmd_data.snapshot_tag, &vid, inode,
+			   SD_INODE_SIZE);
+	if (ret != EXIT_SUCCESS) {
+		sd_err("FATAL: no inode objects");
+		goto out;
+	}
+	if (sd_nodes_nr < inode->nr_copies) {
+		sd_err("ABORT: Not enough active nodes for consistency-check");
+		return EXIT_FAILURE;
+	}
+
+	wq = create_work_queue("vdi check", WQ_DYNAMIC);
+
+	queue_vdi_check_work(inode, vid_to_vdi_oid(vid), NULL, wq);
+
+	max_idx = DIV_ROUND_UP(inode->vdi_size, SD_DATA_OBJ_SIZE);
+	vdi_show_progress(done, inode->vdi_size);
+	for (int idx = 0; idx < max_idx; idx++) {
+		vid = inode->data_vdi_id[idx];
+		if (vid) {
+			oid = vid_to_data_oid(vid, idx);
+			queue_vdi_check_work(inode, oid, &done, wq);
+		} else {
+			done += SD_DATA_OBJ_SIZE;
+			vdi_show_progress(done, inode->vdi_size);
+		}
+	}
+
+	work_queue_wait(wq);
+
+	fprintf(stdout, "finish check&repair %s\n", vdiname);
+	return EXIT_SUCCESS;
+out:
+	return ret;
+}
+
+/* vdi backup format */
+
+#define VDI_BACKUP_FORMAT_VERSION 1
+#define VDI_BACKUP_MAGIC 0x11921192
+
+struct backup_hdr {
+	uint32_t version;
+	uint32_t magic;
+};
+
+struct obj_backup {
+	uint32_t idx;
+	uint32_t offset;
+	uint32_t length;
+	uint32_t reserved;
+	uint8_t data[SD_DATA_OBJ_SIZE];
+};
+
+/* discards redundant area from backup data */
+static void compact_obj_backup(struct obj_backup *backup, uint8_t *from_data)
+{
+	uint8_t *p1, *p2;
+
+	p1 = backup->data;
+	p2 = from_data;
+	while (backup->length > 0 && memcmp(p1, p2, SECTOR_SIZE) == 0) {
+		p1 += SECTOR_SIZE;
+		p2 += SECTOR_SIZE;
+		backup->offset += SECTOR_SIZE;
+		backup->length -= SECTOR_SIZE;
+	}
+
+	p1 = backup->data + SD_DATA_OBJ_SIZE - SECTOR_SIZE;
+	p2 = from_data + SD_DATA_OBJ_SIZE - SECTOR_SIZE;
+	while (backup->length > 0 && memcmp(p1, p2, SECTOR_SIZE) == 0) {
+		p1 -= SECTOR_SIZE;
+		p2 -= SECTOR_SIZE;
+		backup->length -= SECTOR_SIZE;
+	}
+}
+
+static int get_obj_backup(int idx, uint32_t from_vid, uint32_t to_vid,
+			  struct obj_backup *backup)
+{
+	int ret;
+	uint8_t *from_data = xzalloc(SD_DATA_OBJ_SIZE);
+
+	backup->idx = idx;
+	backup->offset = 0;
+	backup->length = SD_DATA_OBJ_SIZE;
+
+	if (to_vid) {
+		ret = sd_read_object(vid_to_data_oid(to_vid, idx), backup->data,
+				     SD_DATA_OBJ_SIZE, 0, true);
+		if (ret != SD_RES_SUCCESS) {
+			sd_err("Failed to read object %" PRIx32 ", %d", to_vid,
+			       idx);
+			return EXIT_FAILURE;
+		}
+	} else
+		memset(backup->data, 0, SD_DATA_OBJ_SIZE);
+
+	if (from_vid) {
+		ret = sd_read_object(vid_to_data_oid(from_vid, idx), from_data,
+				     SD_DATA_OBJ_SIZE, 0, true);
+		if (ret != SD_RES_SUCCESS) {
+			sd_err("Failed to read object %" PRIx32 ", %d",
+			       from_vid, idx);
+			return EXIT_FAILURE;
+		}
+	}
+
+	compact_obj_backup(backup, from_data);
+
+	free(from_data);
+
+	return EXIT_SUCCESS;
+}
+
+static int vdi_backup(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	int ret = EXIT_SUCCESS, idx, nr_objs;
+	struct sd_inode *from_inode = xzalloc(sizeof(*from_inode));
+	struct sd_inode *to_inode = xzalloc(sizeof(*to_inode));
+	struct backup_hdr hdr = {
+		.version = VDI_BACKUP_FORMAT_VERSION,
+		.magic = VDI_BACKUP_MAGIC,
+	};
+	struct obj_backup *backup = xzalloc(sizeof(*backup));
+
+	if ((!vdi_cmd_data.snapshot_id && !vdi_cmd_data.snapshot_tag[0]) ||
+	    (!vdi_cmd_data.from_snapshot_id &&
+	     !vdi_cmd_data.from_snapshot_tag[0])) {
+		sd_err("Please specify snapshots with '-F' and '-s' options");
+		ret = EXIT_USAGE;
+		goto out;
+	}
+
+	ret = read_vdi_obj(vdiname, vdi_cmd_data.from_snapshot_id,
+			   vdi_cmd_data.from_snapshot_tag, NULL,
+			   from_inode, SD_INODE_SIZE);
+	if (ret != EXIT_SUCCESS)
+		goto out;
+
+	ret = read_vdi_obj(vdiname, vdi_cmd_data.snapshot_id,
+			   vdi_cmd_data.snapshot_tag, NULL, to_inode,
+			   SD_INODE_SIZE);
+	if (ret != EXIT_SUCCESS)
+		goto out;
+
+	nr_objs = DIV_ROUND_UP(to_inode->vdi_size, SD_DATA_OBJ_SIZE);
+
+	ret = xwrite(STDOUT_FILENO, &hdr, sizeof(hdr));
+	if (ret < 0) {
+		sd_err("failed to write backup header, %m");
+		ret = EXIT_SYSFAIL;
+		goto out;
+	}
+
+	for (idx = 0; idx < nr_objs; idx++) {
+		uint32_t from_vid = from_inode->data_vdi_id[idx];
+		uint32_t to_vid = to_inode->data_vdi_id[idx];
+
+		if (to_vid == 0 && from_vid == 0)
+			continue;
+
+		ret = get_obj_backup(idx, from_vid, to_vid, backup);
+		if (ret != EXIT_SUCCESS)
+			goto out;
+
+		if (backup->length == 0)
+			continue;
+
+		ret = xwrite(STDOUT_FILENO, backup,
+			     sizeof(*backup) - sizeof(backup->data));
+		if (ret < 0) {
+			sd_err("failed to write backup data, %m");
+			ret = EXIT_SYSFAIL;
+			goto out;
+		}
+		ret = xwrite(STDOUT_FILENO, backup->data + backup->offset,
+			     backup->length);
+		if (ret < 0) {
+			sd_err("failed to write backup data, %m");
+			ret = EXIT_SYSFAIL;
+			goto out;
+		}
+	}
+
+	/* write end marker */
+	memset(backup, 0, sizeof(*backup) - sizeof(backup->data));
+	backup->idx = UINT32_MAX;
+	ret = xwrite(STDOUT_FILENO, backup,
+		     sizeof(*backup) - sizeof(backup->data));
+	if (ret < 0) {
+		sd_err("failed to write end marker, %m");
+		ret = EXIT_SYSFAIL;
+		goto out;
+	}
+
+	fsync(STDOUT_FILENO);
+out:
+	free(from_inode);
+	free(to_inode);
+	free(backup);
+	return ret;
+}
+
+/* restore backup data to vdi */
+static int restore_obj(struct obj_backup *backup, uint32_t vid,
+		       struct sd_inode *parent_inode)
+{
+	int ret;
+	uint32_t parent_vid = parent_inode->data_vdi_id[backup->idx];
+	uint64_t parent_oid = 0;
+
+	if (parent_vid)
+		parent_oid = vid_to_data_oid(parent_vid, backup->idx);
+
+	/* send a copy-on-write request */
+	ret = sd_write_object(vid_to_data_oid(vid, backup->idx), parent_oid,
+			      backup->data, backup->length, backup->offset,
+			      0, parent_inode->nr_copies, true, true);
+	if (ret != SD_RES_SUCCESS)
+		return ret;
+
+	return sd_write_object(vid_to_vdi_oid(vid), 0, &vid, sizeof(vid),
+			       SD_INODE_HEADER_SIZE + sizeof(vid) * backup->idx,
+			       0, parent_inode->nr_copies, false, true);
+}
+
+static uint32_t do_restore(const char *vdiname, int snapid, const char *tag)
+{
+	int ret;
+	uint32_t vid;
+	struct backup_hdr hdr;
+	struct obj_backup *backup = xzalloc(sizeof(*backup));
+	struct sd_inode *inode = xzalloc(sizeof(*inode));
+
+	ret = xread(STDIN_FILENO, &hdr, sizeof(hdr));
+	if (ret != sizeof(hdr))
+		sd_err("failed to read backup header, %m");
+
+	if (hdr.version != VDI_BACKUP_FORMAT_VERSION ||
+	    hdr.magic != VDI_BACKUP_MAGIC) {
+		sd_err("The backup file is corrupted");
+		ret = EXIT_SYSFAIL;
+		goto out;
+	}
+
+	ret = read_vdi_obj(vdiname, snapid, tag, NULL, inode, SD_INODE_SIZE);
+	if (ret != EXIT_SUCCESS)
+		goto out;
+
+	ret = do_vdi_create(vdiname, inode->vdi_size, inode->vdi_id, &vid,
+			    false, inode->nr_copies);
+	if (ret != EXIT_SUCCESS) {
+		sd_err("Failed to read VDI");
+		goto out;
+	}
+
+	while (true) {
+		ret = xread(STDIN_FILENO, backup,
+			    sizeof(*backup) - sizeof(backup->data));
+		if (ret != sizeof(*backup) - sizeof(backup->data)) {
+			sd_err("failed to read backup data");
+			ret = EXIT_SYSFAIL;
+			break;
+		}
+
+		if (backup->idx == UINT32_MAX) {
+			ret = EXIT_SUCCESS;
+			break;
+		}
+
+		ret = xread(STDIN_FILENO, backup->data, backup->length);
+		if (ret != backup->length) {
+			sd_err("failed to read backup data");
+			ret = EXIT_SYSFAIL;
+			break;
+		}
+
+		ret = restore_obj(backup, vid, inode);
+		if (ret != SD_RES_SUCCESS) {
+			sd_err("failed to restore backup");
+			do_vdi_delete(vdiname, 0, NULL);
+			ret = EXIT_FAILURE;
+			break;
+		}
+	}
+out:
+	free(backup);
+	free(inode);
+
+	return ret;
+}
+
+static int vdi_restore(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	int ret;
+	char buf[SD_INODE_HEADER_SIZE] = {0};
+	struct sd_inode *current_inode = xzalloc(sizeof(*current_inode));
+	struct sd_inode *parent_inode = (struct sd_inode *)buf;
+	bool need_current_recovery = false;
+
+	if (!vdi_cmd_data.snapshot_id && !vdi_cmd_data.snapshot_tag[0]) {
+		sd_err("We can restore a backup file only to snapshots");
+		sd_err("Please specify the '-s' option");
+		ret = EXIT_USAGE;
+		goto out;
+	}
+
+	/*
+	 * delete the current vdi temporarily first to avoid making
+	 * the current state become snapshot
+	 */
+	ret = read_vdi_obj(vdiname, 0, "", NULL, current_inode,
+			   SD_INODE_HEADER_SIZE);
+	if (ret != EXIT_SUCCESS)
+		goto out;
+
+	ret = sd_read_object(vid_to_vdi_oid(current_inode->parent_vdi_id),
+			     parent_inode, SD_INODE_HEADER_SIZE, 0, true);
+	if (ret != SD_RES_SUCCESS) {
+		printf("error\n");
+		goto out;
+	}
+
+	if (is_stdin_console()) {
+		sd_err("stdin must be pipe");
+		ret = EXIT_USAGE;
+		goto out;
+	}
+
+	ret = do_vdi_delete(vdiname, 0, NULL);
+	if (ret != EXIT_SUCCESS) {
+		sd_err("Failed to delete the current state");
+		goto out;
+	}
+	need_current_recovery = true;
+
+	/* restore backup data */
+	ret = do_restore(vdiname, vdi_cmd_data.snapshot_id,
+			 vdi_cmd_data.snapshot_tag);
+out:
+	if (need_current_recovery) {
+		int recovery_ret;
+		/* recreate the current vdi object */
+		recovery_ret = do_vdi_create(vdiname, current_inode->vdi_size,
+					     current_inode->parent_vdi_id, NULL,
+					     true, current_inode->nr_copies);
+		if (recovery_ret != EXIT_SUCCESS) {
+			sd_err("failed to resume the current vdi");
+			ret = recovery_ret;
+		}
+	}
+	free(current_inode);
+	return ret;
+}
+
+static int vdi_cache_flush(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	struct sd_req hdr;
+	uint32_t vid;
+	int ret = EXIT_SUCCESS;
+
+	ret = find_vdi_name(vdiname, vdi_cmd_data.snapshot_id,
+			    vdi_cmd_data.snapshot_tag, &vid, 0);
+	if (ret < 0) {
+		sd_err("Failed to open VDI %s", vdiname);
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	sd_init_req(&hdr, SD_OP_FLUSH_VDI);
+	hdr.obj.oid = vid_to_vdi_oid(vid);
+
+	ret = send_light_req(&hdr, sdhost, sdport);
+	if (ret) {
+		sd_err("failed to execute request");
+		return EXIT_FAILURE;
+	}
+out:
+	return ret;
+}
+
+static int vdi_cache_delete(int argc, char **argv)
+{
+	const char *vdiname = argv[optind++];
+	struct sd_req hdr;
+	uint32_t vid;
+	int ret = EXIT_SUCCESS;
+
+	ret = find_vdi_name(vdiname, vdi_cmd_data.snapshot_id,
+			    vdi_cmd_data.snapshot_tag, &vid, 0);
+	if (ret < 0) {
+		sd_err("Failed to open VDI %s", vdiname);
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	sd_init_req(&hdr, SD_OP_DELETE_CACHE);
+	hdr.obj.oid = vid_to_vdi_oid(vid);
+
+	ret = send_light_req(&hdr, sdhost, sdport);
+	if (ret) {
+		sd_err("failed to execute request");
+		return EXIT_FAILURE;
+	}
+out:
+	return ret;
+}
+
+static int vid_to_name_tag(uint32_t vid, char *name, char *tag)
+{
+	struct sd_inode inode;
+	int ret;
+
+	ret = sd_read_object(vid_to_vdi_oid(vid), &inode, SD_INODE_HEADER_SIZE,
+			     0, true);
+	if (ret != SD_RES_SUCCESS)
+		return ret;
+
+	pstrcpy(name, SD_MAX_VDI_LEN, inode.name);
+	pstrcpy(tag, SD_MAX_VDI_TAG_LEN, inode.tag);
+
+	return SD_RES_SUCCESS;
+}
+
+static int vdi_cache_info(int argc, char **argv)
+{
+	struct object_cache_info info = {};
+	struct sd_req hdr;
+	struct sd_rsp *rsp = (struct sd_rsp *)&hdr;
+	char size_str[UINT64_DECIMAL_SIZE], used_str[UINT64_DECIMAL_SIZE];
+	int ret, i;
+
+	sd_init_req(&hdr, SD_OP_GET_CACHE_INFO);
+	hdr.data_length = sizeof(info);
+	ret = dog_exec_req(sdhost, sdport, &hdr, &info);
+	if (ret < 0)
+		return EXIT_SYSFAIL;
+
+	if (rsp->result != SD_RES_SUCCESS) {
+		sd_err("failed to get cache infomation: %s",
+		       sd_strerror(rsp->result));
+		return EXIT_FAILURE;
+	}
+
+	fprintf(stdout, "Name\tTag\tTotal\tDirty\tClean\n");
+	for (i = 0; i < info.count; i++) {
+		char total_str[UINT64_DECIMAL_SIZE],
+		     dirty_str[UINT64_DECIMAL_SIZE],
+		     clean_str[UINT64_DECIMAL_SIZE];
+		uint64_t total = info.caches[i].total * SD_DATA_OBJ_SIZE,
+			 dirty = info.caches[i].dirty * SD_DATA_OBJ_SIZE,
+			 clean = total - dirty;
+		char name[SD_MAX_VDI_LEN], tag[SD_MAX_VDI_TAG_LEN];
+
+		size_to_str(total, total_str, sizeof(total_str));
+		size_to_str(dirty, dirty_str, sizeof(dirty_str));
+		size_to_str(clean, clean_str, sizeof(clean_str));
+		ret = vid_to_name_tag(info.caches[i].vid, name, tag);
+		if (ret != SD_RES_SUCCESS)
+			return EXIT_FAILURE;
+		fprintf(stdout, "%s\t%s\t%s\t%s\t%s\n",
+			name, tag, total_str, dirty_str, clean_str);
+	}
+
+	size_to_str(info.size, size_str, sizeof(size_str));
+	size_to_str(info.used, used_str, sizeof(used_str));
+	fprintf(stdout, "\nCache size %s, used %s\n", size_str, used_str);
+
+	return EXIT_SUCCESS;
+}
+
+static struct subcommand vdi_cache_cmd[] = {
+	{"flush", NULL, NULL, "flush the cache of the vdi specified.",
+	 NULL, CMD_NEED_ARG, vdi_cache_flush},
+	{"delete", NULL, NULL, "delete the cache of the vdi specified in all nodes.",
+	 NULL, CMD_NEED_ARG, vdi_cache_delete},
+	{"info", NULL, NULL, "show usage of the cache",
+	 NULL, 0, vdi_cache_info},
+	{NULL,},
+};
+
+static int vdi_cache(int argc, char **argv)
+{
+	return do_generic_subcommand(vdi_cache_cmd, argc, argv);
+}
+
+static struct subcommand vdi_cmd[] = {
+	{"check", "<vdiname>", "saph", "check and repair image's consistency",
+	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
+	 vdi_check, vdi_options},
+	{"create", "<vdiname> <size>", "Pcaphrv", "create an image",
+	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
+	 vdi_create, vdi_options},
+	{"snapshot", "<vdiname>", "saphrv", "create a snapshot",
+	 NULL, CMD_NEED_ARG,
+	 vdi_snapshot, vdi_options},
+	{"clone", "<src vdi> <dst vdi>", "sPcaphrv", "clone an image",
+	 NULL, CMD_NEED_ARG,
+	 vdi_clone, vdi_options},
+	{"delete", "<vdiname>", "saph", "delete an image",
+	 NULL, CMD_NEED_ARG,
+	 vdi_delete, vdi_options},
+	{"rollback", "<vdiname>", "saphfrv", "rollback to a snapshot",
+	 NULL, CMD_NEED_ARG,
+	 vdi_rollback, vdi_options},
+	{"list", "[vdiname]", "aprh", "list images",
+	 NULL, 0, vdi_list, vdi_options},
+	{"tree", NULL, "aph", "show images in tree view format",
+	 NULL, 0, vdi_tree, vdi_options},
+	{"graph", NULL, "aph", "show images in Graphviz dot format",
+	 NULL, 0, vdi_graph, vdi_options},
+	{"object", "<vdiname>", "isaph", "show object information in the image",
+	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
+	 vdi_object, vdi_options},
+	{"track", "<vdiname>", "isaph", "show the object epoch trace in the image",
+	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
+	 vdi_track, vdi_options},
+	{"setattr", "<vdiname> <key> [value]", "dxaph", "set a VDI attribute",
+	 NULL, CMD_NEED_ARG,
+	 vdi_setattr, vdi_options},
+	{"getattr", "<vdiname> <key>", "aph", "get a VDI attribute",
+	 NULL, CMD_NEED_ARG,
+	 vdi_getattr, vdi_options},
+	{"resize", "<vdiname> <new size>", "aph", "resize an image",
+	 NULL, CMD_NEED_ARG,
+	 vdi_resize, vdi_options},
+	{"read", "<vdiname> [<offset> [<len>]]", "saph", "read data from an image",
+	 NULL, CMD_NEED_ARG,
+	 vdi_read, vdi_options},
+	{"write", "<vdiname> [<offset> [<len>]]", "apwh", "write data to an image",
+	 NULL, CMD_NEED_ARG,
+	 vdi_write, vdi_options},
+	{"backup", "<vdiname> <backup>", "sFaph", "create an incremental backup between two snapshots",
+	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
+	 vdi_backup, vdi_options},
+	{"restore", "<vdiname> <backup>", "saph", "restore snapshot images from a backup",
+	 NULL, CMD_NEED_NODELIST|CMD_NEED_ARG,
+	 vdi_restore, vdi_options},
+	{"cache", "<vdiname>", "saph", "Run 'dog vdi cache' for more information",
+	 vdi_cache_cmd, CMD_NEED_ARG,
+	 vdi_cache, vdi_options},
+	{NULL,},
+};
+
+static int vdi_parser(int ch, char *opt)
+{
+	char *p;
+	int nr_copies;
+
+	switch (ch) {
+	case 'P':
+		vdi_cmd_data.prealloc = true;
+		break;
+	case 'i':
+		vdi_cmd_data.index = strtol(opt, &p, 10);
+		if (opt == p) {
+			sd_err("The index must be an integer");
+			exit(EXIT_FAILURE);
+		}
+		break;
+	case 's':
+		vdi_cmd_data.snapshot_id = strtol(opt, &p, 10);
+		if (opt == p) {
+			vdi_cmd_data.snapshot_id = 0;
+			pstrcpy(vdi_cmd_data.snapshot_tag,
+				sizeof(vdi_cmd_data.snapshot_tag), opt);
+		} else if (vdi_cmd_data.snapshot_id == 0) {
+			fprintf(stderr,
+				"The snapshot id must be larger than zero\n");
+			exit(EXIT_FAILURE);
+		}
+		break;
+	case 'x':
+		vdi_cmd_data.exclusive = true;
+		break;
+	case 'd':
+		vdi_cmd_data.delete = true;
+		break;
+	case 'w':
+		vdi_cmd_data.writeback = true;
+		break;
+	case 'c':
+		nr_copies = strtol(opt, &p, 10);
+		if (opt == p || nr_copies < 0 || nr_copies > SD_MAX_COPIES) {
+			sd_err("Invalid copies number, must be "
+			       "an integer between 0 and %d", SD_MAX_COPIES);
+			exit(EXIT_FAILURE);
+		}
+		vdi_cmd_data.nr_copies = nr_copies;
+		break;
+	case 'F':
+		vdi_cmd_data.from_snapshot_id = strtol(opt, &p, 10);
+		if (opt == p) {
+			vdi_cmd_data.from_snapshot_id = 0;
+			pstrcpy(vdi_cmd_data.from_snapshot_tag,
+				sizeof(vdi_cmd_data.from_snapshot_tag), opt);
+		}
+		break;
+	case 'f':
+		vdi_cmd_data.force = true;
+		break;
+	}
+
+	return 0;
+}
+
+struct command vdi_command = {
+	"vdi",
+	vdi_cmd,
+	vdi_parser
+};
diff --git a/include/internal_proto.h b/include/internal_proto.h
index 7a15336..8802300 100644
--- a/include/internal_proto.h
+++ b/include/internal_proto.h
@@ -13,7 +13,7 @@
 
 /*
  * This file specified the sheepdog-internal protocol, which is spoken between
- * sheepdog daemons, as well as between collie and sheepdog daemon for internal
+ * sheepdog daemons, as well as between dog and sheepdog daemon for internal
  * operations.
  */
 
@@ -33,7 +33,7 @@
 
 /*
  * Operations with opcodes above 0x80 are considered part of the inter-sheep
- * include sheep-collie protocol and are versioned using SD_SHEEP_PROTO_VER
+ * include sheep-dog protocol and are versioned using SD_SHEEP_PROTO_VER
  * instead of SD_PROTO_VER.
  *
  * These same applies for the above 0x80 flags and error values below.
diff --git a/lib/logger.c b/lib/logger.c
index b7dbd2f..012cfa2 100644
--- a/lib/logger.c
+++ b/lib/logger.c
@@ -117,7 +117,7 @@ static int64_t max_logsize = 500 * 1024 * 1024;  /*500MB*/
 static pthread_mutex_t logsize_lock = PTHREAD_MUTEX_INITIALIZER;
 
 /*
- * We need to set default log formatter because collie doesn't want to call
+ * We need to set default log formatter because dog doesn't want to call
  * select_log_formatter().
  */
 static void __attribute__((constructor(65535)))
diff --git a/man/Makefile.am b/man/Makefile.am
index 6962bb8..6d34cdd 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -1,12 +1,12 @@
 MAINTAINERCLEANFILES    = Makefile.in
 
-dist_man_MANS		= sheep.8 collie.8
+dist_man_MANS		= sheep.8 dog.8
 
 if BUILD_SHEEPFS
 dist_man_MANS 		+= sheepfs.8
 endif
 
-EXTRA_DIST		= sheep.8.in collie.8.in sheepfs.8.in
+EXTRA_DIST		= sheep.8.in dog.8.in sheepfs.8.in
 
 sheep.8.options = $(shell ../sheep/sheep -h | \
 	perl -ne 'print ".TP\n.BI $$1 \"\\fR, \\fP\" $$2\n$$3\n" if /^  ([^,]+), (\S+)\s+(.+)/' | \
@@ -14,9 +14,9 @@ sheep.8.options = $(shell ../sheep/sheep -h | \
 	sed 's/"/\\"/g' | \
 	perl -pe 's/\n/\\n/g')
 
-collie.8.options = $(shell ../collie/collie -h | \
-	perl -ne 'if (/^  (.+?)  \s+(.+)/) {system "../collie/collie $$1 -h"; print "Description:\n  $$2\n"}'| \
-	sed 's/Usage: collie \(.*\)/.TP\n.BI "\1"/g' | \
+dog.8.options = $(shell ../dog/dog -h | \
+	perl -ne 'if (/^  (.+?)  \s+(.+)/) {system "../dog/dog $$1 -h"; print "Description:\n  $$2\n"}'| \
+	sed 's/Usage: dog \(.*\)/.TP\n.BI "\1"/g' | \
 	sed 's/\(^[a-zA-Z]*:\)/\n\1/g' | \
 	sed 's/\\/\\\\\\/g' | \
 	sed 's/"/\\"/g' | \
diff --git a/man/collie.8.in b/man/collie.8.in
deleted file mode 100644
index 7fdee4a..0000000
--- a/man/collie.8.in
+++ /dev/null
@@ -1,37 +0,0 @@
-.TH SHEEPDOG 8 @DATE@
-.SH NAME
-collie \- Command line utility for the sheep daemon
-.SH SYNOPSIS
-.B "collie <command> <subcommand> [options]"
-.SH DESCRIPTION
-.B collie
-- Sheepdog is a distributed storage system for QEMU. It provides
-highly available block level storage volumes to virtual machines.
-Sheepdog supports advanced volume management features such as snapshot,
-cloning, and thin provisioning. The architecture of Sheepdog is fully
-symmetric; there is no central node such as a meta-data server.
-
-The server daemon is called sheep(8).  A command line utility is available
-via collie(8).  QEMU virtual machines use the sheep daemon via a block
-driver available in qemu(1).
-
-For more information, run 'collie <command> <subcommand> --help'.
-.SH COMMAND & SUBCOMMAND
- at OPTIONS@
-
-.SH DEPENDENCIES
-\fBSheepdog\fP requires QEMU 0.13.z or later and Corosync 1.y.z or 2.y.z.
-
-.SH FILES
-none
-
-.SH SEE ALSO
-.BR sheep(8),
-.BR qemu(1),
-.BR sheepfs(8),
-.BR corosync_overview(8)
-
-.SH AUTHORS
-This software is developed by the Sheepdog community which may be reached
-via mailing list at <sheepdog at lists.wpkg.org>.
-.PP
diff --git a/man/dog.8.in b/man/dog.8.in
new file mode 100644
index 0000000..9834a56
--- /dev/null
+++ b/man/dog.8.in
@@ -0,0 +1,37 @@
+.TH SHEEPDOG 8 @DATE@
+.SH NAME
+dog \- Command line utility for the sheep daemon
+.SH SYNOPSIS
+.B "dog <command> <subcommand> [options]"
+.SH DESCRIPTION
+.B dog
+- Sheepdog is a distributed storage system for QEMU. It provides
+highly available block level storage volumes to virtual machines.
+Sheepdog supports advanced volume management features such as snapshot,
+cloning, and thin provisioning. The architecture of Sheepdog is fully
+symmetric; there is no central node such as a meta-data server.
+
+The server daemon is called sheep(8).  A command line utility is available
+via dog(8).  QEMU virtual machines use the sheep daemon via a block
+driver available in qemu(1).
+
+For more information, run 'dog <command> <subcommand> --help'.
+.SH COMMAND & SUBCOMMAND
+ at OPTIONS@
+
+.SH DEPENDENCIES
+\fBSheepdog\fP requires QEMU 0.13.z or later and Corosync 1.y.z or 2.y.z.
+
+.SH FILES
+none
+
+.SH SEE ALSO
+.BR sheep(8),
+.BR qemu(1),
+.BR sheepfs(8),
+.BR corosync_overview(8)
+
+.SH AUTHORS
+This software is developed by the Sheepdog community which may be reached
+via mailing list at <sheepdog at lists.wpkg.org>.
+.PP
diff --git a/man/sheep.8.in b/man/sheep.8.in
index 93295a8..6f855c2 100644
--- a/man/sheep.8.in
+++ b/man/sheep.8.in
@@ -12,7 +12,7 @@ cloning, and thin provisioning. The architecture of Sheepdog is fully
 symmetric; there is no central node such as a meta-data server.
 
 The server daemon is called sheep(8).  A command line utility is available
-via collie(8).  QEMU virtual machines use the sheep daemon via a block
+via dog(8).  QEMU virtual machines use the sheep daemon via a block
 driver available in qemu(1).
 .SH OPTIONS
 @OPTIONS@
@@ -32,7 +32,7 @@ mount \-o remount,user_xattr /var/lib/sheepdog
 .B /var/lib/sheepdog - Directory containing block storage information
 
 .SH SEE ALSO
-.BR collie(8),
+.BR dog(8),
 .BR qemu(1),
 .BR sheepfs(8),
 .BR corosync_overview(8)
diff --git a/man/sheepfs.8.in b/man/sheepfs.8.in
index 58d8624..0a3a3a1 100644
--- a/man/sheepfs.8.in
+++ b/man/sheepfs.8.in
@@ -12,7 +12,7 @@ cloning, and thin provisioning. The architecture of Sheepdog is fully
 symmetric; there is no central node such as a meta-data server.
 
 The server daemon is called sheep(8).  A command line utility is available
-via collie(8). A pseudo file system is available via sheepfs(8). QEMU
+via dog(8). A pseudo file system is available via sheepfs(8). QEMU
 virtual machines use the sheep daemon via a block driver available in qemu(1).
 
 Sheepfs is a FUSE-based pseudo file system in userland to access both
@@ -47,7 +47,7 @@ none
 
 .SH SEE ALSO
 .BR sheep(8),
-.BR collie(8),
+.BR dog(8),
 .BR qemu(1),
 .BR corosync_overview(8)
 
diff --git a/script/Makefile.am b/script/Makefile.am
index 6ae958d..66ee428 100644
--- a/script/Makefile.am
+++ b/script/Makefile.am
@@ -2,7 +2,7 @@ MAINTAINERCLEANFILES    = Makefile.in
 
 EXTRA_DIST		= generic.in
 
-noinst_HEADERS		= bash_completion_collie checkarch.sh vditest
+noinst_HEADERS		= bash_completion_dog checkarch.sh vditest
 
 target_INIT             = generic
 
diff --git a/script/bash_completion_collie b/script/bash_completion_collie
deleted file mode 100644
index a368184..0000000
--- a/script/bash_completion_collie
+++ /dev/null
@@ -1,303 +0,0 @@
-#!bash
-
-_collie_cluster_format()
-{
-    local cur
-    cur="${COMP_WORDS[COMP_CWORD]}"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-c --copies" \
-                -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_cluster_recover()
-{
-    local cur
-    cur="${COMP_WORDS[COMP_CWORD]}"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-f --force" \
-                -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_vdi_create()
-{
-    local cur
-    cur="${COMP_WORDS[COMP_CWORD]}"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-P --prealloc" \
-                -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_vdi_snapshot()
-{
-    local cur
-    cur="${COMP_WORDS[COMP_CWORD]}"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-s --snapshot" \
-                -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_vdi_clone()
-{
-    local cur
-    cur="${COMP_WORDS[COMP_CWORD]}"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-P --prealloc -s --snapshot" \
-                -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_vdi_read()
-{
-    local cur
-    cur="${COMP_WORDS[COMP_CWORD]}"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-s --snapshot" \
-                -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_vdi_delete()
-{
-    local cur collie vdilist
-    cur="${COMP_WORDS[COMP_CWORD]}"
-    collie="${COMP_WORDS[0]}"
-    vdilist="$(${collie} vdi list | tail -n+3 | grep '^  ' | awk '{print $1}')"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-s --snapshot" \
-                -- ${cur} ))
-            ;;
-        *)
-            COMPREPLY=($( compgen -W "${vdilist}" -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_vdi_object()
-{
-    local cur collie vdilist
-    cur="${COMP_WORDS[COMP_CWORD]}"
-    collie="${COMP_WORDS[0]}"
-    vdilist="$(${collie} vdi list | tail -n+3 | grep '^  ' | awk '{print $1}')"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-i --index -s --snapshot" \
-                -- ${cur} ))
-            ;;
-        *)
-            COMPREPLY=($( compgen -W "${vdilist}" -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_vdi_setattr()
-{
-    local cur
-    cur="${COMP_WORDS[COMP_CWORD]}"
-
-    case "$cur" in
-        -*)
-            COMPREPLY=(${COMPREPLY[@]} \
-                $( compgen \
-                -W "-d --delete -x --exclusive" \
-                -- ${cur} ))
-            ;;
-    esac
-}
-
-_collie_cluster()
-{
-    local opts
-    opts="info format shutdown recover snapshot"
-
-    case "$1" in
-        info)
-            ;;
-        format)
-            _collie_cluster_format
-            ;;
-        shutdown)
-            ;;
-        recover)
-            _collie_cluster_recover
-            ;;
-        snapshot)
-            ;;
-        "")
-            COMPREPLY=($( compgen \
-                -W "${opts}" \
-                -- "${COMP_WORDS[COMP_CWORD]}" ))
-            ;;
-        *)
-            COMPREPLY=()
-            ;;
-    esac
-}
-
-_collie_node()
-{
-    local opts
-    opts="info list recovery kill cache"
-
-    case "$1" in
-        info)
-            ;;
-        list)
-            ;;
-        recovery)
-            ;;
-        kill)
-            ;;
-        cache)
-            ;;
-        "")
-            COMPREPLY=($( compgen \
-                -W "${opts}" \
-                -- "${COMP_WORDS[COMP_CWORD]}" ))
-            ;;
-        *)
-            COMPREPLY=()
-            ;;
-    esac
-}
-
-_collie_vdi()
-{
-    local opts
-    opts="create snapshot clone resize read write \
-list tree graph delete object setattr getattr track check \
-rollback flush backup restore"
-
-    case "$1" in
-	create)
-	    _collie_vdi_create
-	    ;;
-	snapshot)
-	    _collie_vdi_snapshot
-	    ;;
-	clone)
-	    _collie_vdi_clone
-	    ;;
-	resize)
-	    ;;
-	read)
-	    _collie_vdi_read
-	    ;;
-	write)
-	    ;;
-        list)
-            ;;
-        tree)
-            ;;
-        graph)
-            ;;
-        delete)
-            _collie_vdi_delete
-            ;;
-        object)
-            _collie_vdi_object
-            ;;
-        setattr)
-            _collie_vdi_setattr
-            ;;
-        getattr)
-            ;;
-        track)
-            ;;
-        check)
-            ;;
-	rollback)
-	    ;;
-	flush)
-	    ;;
-	backup)
-	    ;;
-	restore)
-	    ;;
-        "")
-            COMPREPLY=($( compgen \
-                -W "${opts}" \
-                -- "${COMP_WORDS[COMP_CWORD]}" ))
-            ;;
-        *)
-            COMPREPLY=()
-            ;;
-    esac
-}
-
-_collie()
-{
-    local opts cur cmd subcmd i
-    opts="cluster node vdi"
-    cur="${COMP_WORDS[COMP_CWORD]}"
-
-    if [ $COMP_CWORD -gt 1 ]; then
-        cmd=${COMP_WORDS[1]}
-    fi
-
-    if [ $COMP_CWORD -gt 2 ]; then
-        subcmd=${COMP_WORDS[2]}
-    fi
-
-    COMPREPLY=($( compgen -W "-a --address -p --port -h --help" -- ${cur} ))
-
-    case "${cmd}" in
-        cluster)
-            _collie_cluster ${subcmd}
-            ;;
-        node)
-            _collie_node ${subcmd}
-            ;;
-        vdi)
-            _collie_vdi ${subcmd}
-            ;;
-        "")
-            COMPREPLY=($( compgen -W "${opts}" -- ${cur} ))
-            ;;
-        *)
-            COMPREPLY=()
-            ;;
-    esac
-}
-
-complete -F _collie collie
diff --git a/script/bash_completion_dog b/script/bash_completion_dog
new file mode 100644
index 0000000..89c9544
--- /dev/null
+++ b/script/bash_completion_dog
@@ -0,0 +1,303 @@
+#!bash
+
+_dog_cluster_format()
+{
+    local cur
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-c --copies" \
+                -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_cluster_recover()
+{
+    local cur
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-f --force" \
+                -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_vdi_create()
+{
+    local cur
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-P --prealloc" \
+                -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_vdi_snapshot()
+{
+    local cur
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-s --snapshot" \
+                -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_vdi_clone()
+{
+    local cur
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-P --prealloc -s --snapshot" \
+                -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_vdi_read()
+{
+    local cur
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-s --snapshot" \
+                -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_vdi_delete()
+{
+    local cur dog vdilist
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    dog="${COMP_WORDS[0]}"
+    vdilist="$(${dog} vdi list | tail -n+3 | grep '^  ' | awk '{print $1}')"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-s --snapshot" \
+                -- ${cur} ))
+            ;;
+        *)
+            COMPREPLY=($( compgen -W "${vdilist}" -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_vdi_object()
+{
+    local cur dog vdilist
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    dog="${COMP_WORDS[0]}"
+    vdilist="$(${dog} vdi list | tail -n+3 | grep '^  ' | awk '{print $1}')"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-i --index -s --snapshot" \
+                -- ${cur} ))
+            ;;
+        *)
+            COMPREPLY=($( compgen -W "${vdilist}" -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_vdi_setattr()
+{
+    local cur
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    case "$cur" in
+        -*)
+            COMPREPLY=(${COMPREPLY[@]} \
+                $( compgen \
+                -W "-d --delete -x --exclusive" \
+                -- ${cur} ))
+            ;;
+    esac
+}
+
+_dog_cluster()
+{
+    local opts
+    opts="info format shutdown recover snapshot"
+
+    case "$1" in
+        info)
+            ;;
+        format)
+            _dog_cluster_format
+            ;;
+        shutdown)
+            ;;
+        recover)
+            _dog_cluster_recover
+            ;;
+        snapshot)
+            ;;
+        "")
+            COMPREPLY=($( compgen \
+                -W "${opts}" \
+                -- "${COMP_WORDS[COMP_CWORD]}" ))
+            ;;
+        *)
+            COMPREPLY=()
+            ;;
+    esac
+}
+
+_dog_node()
+{
+    local opts
+    opts="info list recovery kill cache"
+
+    case "$1" in
+        info)
+            ;;
+        list)
+            ;;
+        recovery)
+            ;;
+        kill)
+            ;;
+        cache)
+            ;;
+        "")
+            COMPREPLY=($( compgen \
+                -W "${opts}" \
+                -- "${COMP_WORDS[COMP_CWORD]}" ))
+            ;;
+        *)
+            COMPREPLY=()
+            ;;
+    esac
+}
+
+_dog_vdi()
+{
+    local opts
+    opts="create snapshot clone resize read write \
+list tree graph delete object setattr getattr track check \
+rollback flush backup restore"
+
+    case "$1" in
+	create)
+	    _dog_vdi_create
+	    ;;
+	snapshot)
+	    _dog_vdi_snapshot
+	    ;;
+	clone)
+	    _dog_vdi_clone
+	    ;;
+	resize)
+	    ;;
+	read)
+	    _dog_vdi_read
+	    ;;
+	write)
+	    ;;
+        list)
+            ;;
+        tree)
+            ;;
+        graph)
+            ;;
+        delete)
+            _dog_vdi_delete
+            ;;
+        object)
+            _dog_vdi_object
+            ;;
+        setattr)
+            _dog_vdi_setattr
+            ;;
+        getattr)
+            ;;
+        track)
+            ;;
+        check)
+            ;;
+	rollback)
+	    ;;
+	flush)
+	    ;;
+	backup)
+	    ;;
+	restore)
+	    ;;
+        "")
+            COMPREPLY=($( compgen \
+                -W "${opts}" \
+                -- "${COMP_WORDS[COMP_CWORD]}" ))
+            ;;
+        *)
+            COMPREPLY=()
+            ;;
+    esac
+}
+
+_dog()
+{
+    local opts cur cmd subcmd i
+    opts="cluster node vdi"
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    if [ $COMP_CWORD -gt 1 ]; then
+        cmd=${COMP_WORDS[1]}
+    fi
+
+    if [ $COMP_CWORD -gt 2 ]; then
+        subcmd=${COMP_WORDS[2]}
+    fi
+
+    COMPREPLY=($( compgen -W "-a --address -p --port -h --help" -- ${cur} ))
+
+    case "${cmd}" in
+        cluster)
+            _dog_cluster ${subcmd}
+            ;;
+        node)
+            _dog_node ${subcmd}
+            ;;
+        vdi)
+            _dog_vdi ${subcmd}
+            ;;
+        "")
+            COMPREPLY=($( compgen -W "${opts}" -- ${cur} ))
+            ;;
+        *)
+            COMPREPLY=()
+            ;;
+    esac
+}
+
+complete -F _dog dog
diff --git a/sheep/sheep.c b/sheep/sheep.c
index 461a729..61d7f76 100644
--- a/sheep/sheep.c
+++ b/sheep/sheep.c
@@ -29,7 +29,7 @@ static const char bind_help[] =
 "This tries to teach sheep listen to NIC of 192.168.1.1.\n"
 "\nExample:\n\t$ sheep -b 0.0.0.0 ...\n"
 "This tries to teach sheep listen to all the NICs available. It can be useful\n"
-"when you want sheep to response collie without specified address and port.\n";
+"when you want sheep to response dog without specified address and port.\n";
 
 static const char ioaddr_help[] =
 "Example:\n\t$ sheep -i host=192.168.1.1,port=7002 ...\n"
diff --git a/sheep/store.c b/sheep/store.c
index f4343b3..3e898b8 100644
--- a/sheep/store.c
+++ b/sheep/store.c
@@ -25,7 +25,7 @@ int update_epoch_log(uint32_t epoch, struct sd_node *nodes, size_t nr_nodes)
 
 	sd_debug("update epoch: %d, %zu", epoch, nr_nodes);
 
-	/* Piggyback the epoch creation time for 'collie cluster info' */
+	/* Piggyback the epoch creation time for 'dog cluster info' */
 	time(&t);
 	nodes_len = nr_nodes * sizeof(struct sd_node);
 	len = nodes_len + sizeof(time_t);
diff --git a/sheepdog.spec.in b/sheepdog.spec.in
index 5e19366..b6ec4e6 100644
--- a/sheepdog.spec.in
+++ b/sheepdog.spec.in
@@ -63,12 +63,12 @@ fi
 %doc COPYING README INSTALL
 %{_sbindir}/sheep
 %{_sbindir}/sheepfs
-%{_sbindir}/collie
+%{_sbindir}/dog
 %attr(755,-,-)%config %{_initddir}/sheepdog
 %dir %{_localstatedir}/lib/sheepdog
 %{_mandir}/man8/sheep.8*
 %{_mandir}/man8/sheepfs.8*
-%{_mandir}/man8/collie.8*
+%{_mandir}/man8/dog.8*
 
 %changelog
 * @date@ Autotools generated version <nobody at nowhere.org> - @version at -1.@alphatag@
diff --git a/sheepfs/cluster.c b/sheepfs/cluster.c
index 6665bce..3eb1d0b 100644
--- a/sheepfs/cluster.c
+++ b/sheepfs/cluster.c
@@ -50,7 +50,7 @@ size_t cluster_info_get_size(const char *path)
 	size_t len;
 	char cmd[COMMAND_LEN];
 
-	snprintf(cmd, sizeof(cmd), "collie cluster info -a %s -p %d",
+	snprintf(cmd, sizeof(cmd), "dog cluster info -a %s -p %d",
 		 sdhost, sdport);
 	buf = sheepfs_run_cmd(cmd);
 	if (!buf)
diff --git a/sheepfs/node.c b/sheepfs/node.c
index a3558d5..2a7df5b 100644
--- a/sheepfs/node.c
+++ b/sheepfs/node.c
@@ -57,7 +57,7 @@ size_t node_info_get_size(const char *path)
 	size_t len;
 	char cmd[COMMAND_LEN];
 
-	snprintf(cmd, sizeof(cmd), "collie node info -a %s -p %d",
+	snprintf(cmd, sizeof(cmd), "dog node info -a %s -p %d",
 		 sdhost, sdport);
 	buf = sheepfs_run_cmd(cmd);
 	if (!buf)
@@ -80,7 +80,7 @@ size_t node_list_get_size(const char *path)
 	size_t len;
 	char cmd[COMMAND_LEN];
 
-	snprintf(cmd, sizeof(cmd), "collie node list -a %s -p %d",
+	snprintf(cmd, sizeof(cmd), "dog node list -a %s -p %d",
 		 sdhost, sdport);
 	buf = sheepfs_run_cmd(cmd);
 	if (!buf)
diff --git a/sheepfs/vdi.c b/sheepfs/vdi.c
index 814a40b..f6d0639 100644
--- a/sheepfs/vdi.c
+++ b/sheepfs/vdi.c
@@ -62,7 +62,7 @@ size_t vdi_list_get_size(const char *path)
 	size_t len;
 	char cmd[COMMAND_LEN];
 
-	snprintf(cmd, sizeof(cmd), "collie vdi list -a %s -p %d",
+	snprintf(cmd, sizeof(cmd), "dog vdi list -a %s -p %d",
 		sdhost, sdport);
 	buf = sheepfs_run_cmd(cmd);
 	if (!buf)
diff --git a/sheepfs/volume.c b/sheepfs/volume.c
index 73e7419..0558639 100644
--- a/sheepfs/volume.c
+++ b/sheepfs/volume.c
@@ -390,7 +390,7 @@ static int init_vdi_info(const char *entry, uint32_t *vid, size_t *size)
 	struct vdi_inode *inode = NULL, *dummy;
 	char command[COMMAND_LEN];
 
-	snprintf(command, sizeof(command), "collie vdi list -r %s -a %s -p %d",
+	snprintf(command, sizeof(command), "dog vdi list -r %s -a %s -p %d",
 		 entry, sdhost, sdport);
 	buf = sheepfs_run_cmd(command);
 	if (!buf)
diff --git a/tests/dynamorio/journaling/01.sh b/tests/dynamorio/journaling/01.sh
index 820793e..4d23929 100755
--- a/tests/dynamorio/journaling/01.sh
+++ b/tests/dynamorio/journaling/01.sh
@@ -15,7 +15,7 @@ sudo ~/dynamorio/build/bin64/drrun -c libjournaling.so 1 -- \
 
 sleep 3
 
-collie cluster format -c 1
-collie vdi create test 100M
+dog cluster format -c 1
+dog vdi create test 100M
 
 sudo sheep -d -c shepherd:127.0.0.1 -p 7000 -j size=64 /tmp/sheepdog/dynamorio/0
diff --git a/tests/functional/001 b/tests/functional/001
index 0897bb4..2c812e3 100755
--- a/tests/functional/001
+++ b/tests/functional/001
@@ -37,5 +37,5 @@ done
 _wait_for_sheep 3
 echo check whether all nodes have the same cluster info
 for i in 0 1 2; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/002 b/tests/functional/002
index 698f545..1812985 100755
--- a/tests/functional/002
+++ b/tests/functional/002
@@ -32,5 +32,5 @@ _wait_for_sheep 3
 
 echo check whether all nodes have the same cluster info
 for i in 0 1 2; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/003 b/tests/functional/003
index c799164..d949bc1 100755
--- a/tests/functional/003
+++ b/tests/functional/003
@@ -30,5 +30,5 @@ done
 _wait_for_sheep 3
 echo check whether all nodes have the same cluster info
 for i in 0 1 2; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/004 b/tests/functional/004
index 83eb955..2a912db 100755
--- a/tests/functional/004
+++ b/tests/functional/004
@@ -38,5 +38,5 @@ done
 _wait_for_sheep 5
 echo check whether all nodes have the same cluster info
 for i in 0 1 2 3 4; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/005 b/tests/functional/005
index 5523b9e..09613fe 100755
--- a/tests/functional/005
+++ b/tests/functional/005
@@ -22,7 +22,7 @@ done
 
 _wait_for_sheep 2 4
 
-$COLLIE cluster shutdown -p 7004
+$DOG cluster shutdown -p 7004
 _wait_for_sheep_stop
 
 nr=0
@@ -35,5 +35,5 @@ done
 _wait_for_sheep 5
 echo check whether all nodes have the same cluster info
 for i in 0 1 2 3 4; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/006 b/tests/functional/006
index 55ebbf8..308da48 100755
--- a/tests/functional/006
+++ b/tests/functional/006
@@ -19,7 +19,7 @@ _wait_for_sheep 10
 
 echo check whether all nodes have the same cluster info
 for i in `seq 0 9`; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info > $STORE/cinfo.$i
+    $DOG cluster info -p 700$i | _filter_cluster_info > $STORE/cinfo.$i
 done
 for i in `seq 1 9`; do
     diff -u $STORE/cinfo.0 $STORE/cinfo.$i
diff --git a/tests/functional/007 b/tests/functional/007
index 0fda0eb..0221033 100755
--- a/tests/functional/007
+++ b/tests/functional/007
@@ -9,7 +9,7 @@
 _start_sheep 1
 _wait_for_sheep 1 1
 _cluster_format -p 7001 -c 1
-$COLLIE cluster shutdown -p 7001
+$DOG cluster shutdown -p 7001
 _wait_for_sheep_stop
 
 # start Sheepdog with one node
@@ -23,5 +23,5 @@ _wait_for_sheep 1
 _start_sheep 2  # should succeed
 _wait_for_sheep 2
 
-$COLLIE cluster info -p 7000 | _filter_cluster_info
-$COLLIE cluster info -p 7002 | _filter_cluster_info
+$DOG cluster info -p 7000 | _filter_cluster_info
+$DOG cluster info -p 7002 | _filter_cluster_info
diff --git a/tests/functional/008 b/tests/functional/008
index 665cb6b..9afe56b 100755
--- a/tests/functional/008
+++ b/tests/functional/008
@@ -13,11 +13,11 @@ _wait_for_sheep "8"
 _cluster_format -c 3
 
 for i in `seq 0 4`; do
-    $COLLIE vdi create test$i 100M
+    $DOG vdi create test$i 100M
 done
 
 for i in `seq 0 4`; do
-    _random | $COLLIE vdi write test$i -p 7000 &
+    _random | $DOG vdi write test$i -p 7000 &
 done
 
 sleep 3
@@ -43,7 +43,7 @@ wait
 
 for i in `seq 0 4`; do
     for port in `seq 0 7`; do
-        $COLLIE vdi read test$i -p 700$port | md5sum > $STORE/csum.$port &
+        $DOG vdi read test$i -p 700$port | md5sum > $STORE/csum.$port &
     done
     wait
     for port in `seq 1 7`; do
diff --git a/tests/functional/009 b/tests/functional/009
index acf9693..86a4806 100755
--- a/tests/functional/009
+++ b/tests/functional/009
@@ -14,13 +14,13 @@ _wait_for_sheep 3
 _cluster_format -c 2
 
 # create a pre-allocated vdi
-$COLLIE vdi create test 80M -P
+$DOG vdi create test 80M -P
 
 # stop the 3rd node
 _kill_sheep 2
 
 # write data to the vdi
-_random | $COLLIE vdi write test
+_random | $DOG vdi write test
 
 # restart the 3rd node
 _start_sheep 2
@@ -29,7 +29,7 @@ _wait_for_sheep_recovery 0
 
 # show md5sum of the vdi on each node
 for i in 0 1 2; do
-    $COLLIE vdi read test -p 700$i | md5sum > $STORE/csum.$i
+    $DOG vdi read test -p 700$i | md5sum > $STORE/csum.$i
 done
 
 diff -u $STORE/csum.0 $STORE/csum.1
diff --git a/tests/functional/010 b/tests/functional/010
index de7ddbb..d050941 100755
--- a/tests/functional/010
+++ b/tests/functional/010
@@ -11,13 +11,13 @@ done
 _wait_for_sheep 5
 
 _cluster_format
-$COLLIE cluster recover disable
+$DOG cluster recover disable
 
-$COLLIE vdi create test 4G
+$DOG vdi create test 4G
 
 # create 8 objects
 for i in `seq 0 7`; do
-    echo $i | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
 ls $STORE/*/obj/* | _filter_store | sort
@@ -26,43 +26,43 @@ _kill_sheep 3
 _kill_sheep 4
 
 _wait_for_sheep 3
-$COLLIE cluster info | head -6 | _filter_cluster_info
+$DOG cluster info | head -6 | _filter_cluster_info
 ls $STORE/*/obj/* | _filter_store | sort
 
 # overwrite the objects to invoke object recovery
 for i in `seq 4 7`; do
-    $COLLIE vdi read test $((i * 4 * 1024 * 1024)) 512 | md5sum
-    echo $(($i + 100)) | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    $DOG vdi read test $((i * 4 * 1024 * 1024)) 512 | md5sum
+    echo $(($i + 100)) | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
-$COLLIE cluster info | head -6 | _filter_cluster_info
+$DOG cluster info | head -6 | _filter_cluster_info
 ls $STORE/*/obj/* | _filter_store | sort
 
-$COLLIE cluster recover enable
+$DOG cluster recover enable
 _wait_for_sheep_recovery 0
-$COLLIE cluster info | head -6 | _filter_cluster_info
+$DOG cluster info | head -6 | _filter_cluster_info
 ls $STORE/*/obj/* | _filter_store | sort
 
-$COLLIE cluster recover disable
+$DOG cluster recover disable
 for i in `seq 3 7`; do
     _start_sheep $i
 done
 
 _wait_for_sheep 8
 
-$COLLIE cluster info | head -6 | _filter_cluster_info
+$DOG cluster info | head -6 | _filter_cluster_info
 ls $STORE/*/obj/* | _filter_store | sort
 
 # overwrite the objects to invoke object recovery
 for i in `seq 0 3`; do
-    $COLLIE vdi read test $((i * 4 * 1024 * 1024)) 512 -p 7007 | md5sum
-    echo $(($i + 200)) | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    $DOG vdi read test $((i * 4 * 1024 * 1024)) 512 -p 7007 | md5sum
+    echo $(($i + 200)) | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
-$COLLIE cluster info | head -6 | _filter_cluster_info
+$DOG cluster info | head -6 | _filter_cluster_info
 ls $STORE/*/obj/* | _filter_store | sort
 
-$COLLIE cluster recover enable
+$DOG cluster recover enable
 _wait_for_sheep_recovery 0
-$COLLIE cluster info | head -6 | _filter_cluster_info
+$DOG cluster info | head -6 | _filter_cluster_info
 ls $STORE/*/obj/* | _filter_store | sort
diff --git a/tests/functional/011 b/tests/functional/011
index c2dcffd..71548f6 100755
--- a/tests/functional/011
+++ b/tests/functional/011
@@ -18,7 +18,7 @@ _wait_for_sheep 3
 
 echo check the number of vnodes
 for i in 0 1 2; do
-    $COLLIE node list -p 700$i
+    $DOG node list -p 700$i
 done
 
 status=0
diff --git a/tests/functional/012 b/tests/functional/012
index 9b1657e..91ac244 100755
--- a/tests/functional/012
+++ b/tests/functional/012
@@ -16,4 +16,4 @@ _start_sheep 4 "-g"
 _wait_for_sheep 4
 
 echo check the number of vnodes
-$COLLIE node list
+$DOG node list
diff --git a/tests/functional/014 b/tests/functional/014
index b561ba4..764f3ee 100755
--- a/tests/functional/014
+++ b/tests/functional/014
@@ -11,19 +11,19 @@ done
 _wait_for_sheep 2
 
 _cluster_format -c 2
-$COLLIE vdi create test 4G
+$DOG vdi create test 4G
 
 echo -n value > $STORE/tmp.dat
 
 echo "key shouldn't be found"
-$COLLIE vdi getattr test key
+$DOG vdi getattr test key
 
-$COLLIE vdi setattr test key value
-$COLLIE vdi getattr test key | diff - $STORE/tmp.dat
+$DOG vdi setattr test key value
+$DOG vdi getattr test key | diff - $STORE/tmp.dat
 
-$COLLIE vdi setattr test key value -d
+$DOG vdi setattr test key value -d
 
 echo "key shouldn't be found"
-$COLLIE vdi getattr test key
+$DOG vdi getattr test key
 
 status=0
diff --git a/tests/functional/015 b/tests/functional/015
index 2492d3b..b985506 100755
--- a/tests/functional/015
+++ b/tests/functional/015
@@ -12,38 +12,38 @@ _wait_for_sheep 2
 
 _cluster_format -c 2
 
-$COLLIE vdi create test 539545600
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
+$DOG vdi create test 539545600
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
 wait
 echo "there should be 3 setattr errors"
 
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
 wait
 echo "there should be 8 setattr errors"
 
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
 wait
 echo "there should be 6 setattr errors"
 
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
-$COLLIE vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
+$DOG vdi setattr test lock 1 -x &
 wait
 echo "there should be 5 setattr errors"
diff --git a/tests/functional/016 b/tests/functional/016
index d52fd9c..92af8ef 100755
--- a/tests/functional/016
+++ b/tests/functional/016
@@ -11,23 +11,23 @@ done
 _wait_for_sheep 3
 
 _cluster_format -c 3
-$COLLIE vdi create base 100M -P
+$DOG vdi create base 100M -P
 
 $QEMU_IMG snapshot -c tag sheepdog:base
 
 sleep 1
 
-$COLLIE vdi clone -s 1 base test
+$DOG vdi clone -s 1 base test
 
 sleep 1
 
-$COLLIE vdi delete test
+$DOG vdi delete test
 
 sleep 1
-$COLLIE vdi delete base
+$DOG vdi delete base
 
 sleep 1
-$COLLIE vdi delete -s 1 base
+$DOG vdi delete -s 1 base
 
 sleep 3
 echo there should be no vdi
diff --git a/tests/functional/017 b/tests/functional/017
index 30552f4..93e3d56 100755
--- a/tests/functional/017
+++ b/tests/functional/017
@@ -23,4 +23,4 @@ $QEMU_IMG snapshot -c tag2 sheepdog:test2
 $QEMU_IO -c "write 0 512" sheepdog:test2:1 | _filter_qemu_io
 $QEMU_IMG snapshot -c tag3 sheepdog:test2
 
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
diff --git a/tests/functional/018 b/tests/functional/018
index acec3c0..c0ab9ee 100755
--- a/tests/functional/018
+++ b/tests/functional/018
@@ -12,14 +12,14 @@ _wait_for_sheep "3"
 
 _cluster_format -c 2
 
-$COLLIE vdi create test 4M
+$DOG vdi create test 4M
 
-_random | $COLLIE vdi write -w test
+_random | $DOG vdi write -w test
 
-$COLLIE vdi cache flush test
+$DOG vdi cache flush test
 
 for port in `seq 0 2`; do
-    $COLLIE vdi read test -p 700$port | md5sum > $STORE/csum.$port
+    $DOG vdi read test -p 700$port | md5sum > $STORE/csum.$port
 done
 
 for port in `seq 1 2`; do
diff --git a/tests/functional/019 b/tests/functional/019
index 7fc0921..efe8cb8 100755
--- a/tests/functional/019
+++ b/tests/functional/019
@@ -12,12 +12,12 @@ _wait_for_sheep "3"
 
 _cluster_format -c 2
 
-$COLLIE vdi create test 4M
+$DOG vdi create test 4M
 
-_random | $COLLIE vdi write test
+_random | $DOG vdi write test
 
 for port in `seq 0 2`; do
-    $COLLIE vdi read test -p 700$port | md5sum > $STORE/csum.$port
+    $DOG vdi read test -p 700$port | md5sum > $STORE/csum.$port
 done
 
 for port in `seq 1 2`; do
diff --git a/tests/functional/020 b/tests/functional/020
index 53f1d20..77066bf 100755
--- a/tests/functional/020
+++ b/tests/functional/020
@@ -12,9 +12,9 @@ _wait_for_sheep "3"
 
 _cluster_format -c 2
 
-$COLLIE vdi create test 40M
+$DOG vdi create test 40M
 
-_random | $COLLIE vdi write test
+_random | $DOG vdi write test
 
 # check cache size, should be 20 * 80%
 nr=`ls $STORE/0/cache/7c2b25 | wc -l`
diff --git a/tests/functional/022 b/tests/functional/022
index 6330dba..c2fe9f0 100755
--- a/tests/functional/022
+++ b/tests/functional/022
@@ -13,6 +13,6 @@ _wait_for_sheep "3"
 _cluster_format -c 3
 
 echo "creating a VDI should fail without data nodes available"
-$COLLIE vdi create test 100M
+$DOG vdi create test 100M
 
 status=0
diff --git a/tests/functional/023 b/tests/functional/023
index 52e8281..dc3747f 100755
--- a/tests/functional/023
+++ b/tests/functional/023
@@ -10,7 +10,7 @@ done
 
 _wait_for_sheep "1"
 
-$COLLIE cluster format -c 1
+$DOG cluster format -c 1
 
 for i in `seq 1 9`; do
     _start_sheep $i "-g"
@@ -21,7 +21,7 @@ _wait_for_sheep "10"
 
 echo "comparing cluster info for all sheep.  Should be the same"
 for i in `seq 0 10`; do
-    $COLLIE cluster info > $STORE/cinfo.$i
+    $DOG cluster info > $STORE/cinfo.$i
 done
 for i in `seq 1 10`; do
     diff -u $STORE/cinfo.0 $STORE/cinfo.$i
diff --git a/tests/functional/024 b/tests/functional/024
index 9ef591d..87421a8 100755
--- a/tests/functional/024
+++ b/tests/functional/024
@@ -19,7 +19,7 @@ echo "formatting cluster"
 _cluster_format -c 1
 
 echo "creating vdi ${NAME}"
-$COLLIE vdi create ${VDI_NAME} ${VDI_SIZE}
+$DOG vdi create ${VDI_NAME} ${VDI_SIZE}
 sleep 1
 
 echo "filling ${VDI_NAME} with data"
diff --git a/tests/functional/025 b/tests/functional/025
index 2669888..2eb2f6d 100755
--- a/tests/functional/025
+++ b/tests/functional/025
@@ -23,7 +23,7 @@ echo "formatting cluster"
 _cluster_format -c 2
 
 echo "creating vdi ${NAME}"
-$COLLIE vdi create ${VDI_NAME} ${VDI_SIZE}
+$DOG vdi create ${VDI_NAME} ${VDI_SIZE}
 
 echo "filling ${VDI_NAME} with data"
 $QEMU_IO -c "write 0 ${VDI_SIZE}" sheepdog:${VDI_NAME} | _filter_qemu_io
@@ -40,5 +40,5 @@ _wait_for_sheep 4
 
 echo "check that all sheep are alive"
 for i in `seq 0 3`; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/026 b/tests/functional/026
index 94af215..bf62a6d 100755
--- a/tests/functional/026
+++ b/tests/functional/026
@@ -19,7 +19,7 @@ _cluster_format
 # create new vdis
 (
 for i in `seq 0 40`;do
-    $COLLIE vdi create test$i 4M
+    $DOG vdi create test$i 4M
 done
 ) &
 
@@ -36,7 +36,7 @@ _wait_for_sheep_recovery 0
 for i in `seq 1 5`; do _start_sheep $i;done
 _wait_for_sheep 8
 
-# wait for collie to finish
+# wait for dog to finish
 wait
 
-echo `$COLLIE vdi list | wc -l`
+echo `$DOG vdi list | wc -l`
diff --git a/tests/functional/027 b/tests/functional/027
index 455e300..e851e69 100755
--- a/tests/functional/027
+++ b/tests/functional/027
@@ -12,8 +12,8 @@ _wait_for_sheep "4"
 
 _cluster_format -c 2
 
-$COLLIE vdi create test0 40M
-$COLLIE vdi create test1 40M
+$DOG vdi create test0 40M
+$DOG vdi create test1 40M
 
 _kill_sheep 3
 
diff --git a/tests/functional/028 b/tests/functional/028
index 4b34db3..ff813c5 100755
--- a/tests/functional/028
+++ b/tests/functional/028
@@ -10,13 +10,13 @@ _wait_for_sheep 2
 
 _cluster_format -c 2
 
-$COLLIE vdi create test 100M
+$DOG vdi create test 100M
 
 for i in `seq 0 24`; do
-    echo $i | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
-$COLLIE vdi read test | md5sum
+$DOG vdi read test | md5sum
 
 for i in 2 3; do
     _start_sheep $i
@@ -27,10 +27,10 @@ done
 
 # write different content
 for i in `seq 0 24`; do
-    echo $(($i+1)) | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512 -p 7002
+    echo $(($i+1)) | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512 -p 7002
 done
 
-$COLLIE vdi read test -p 7002 | md5sum
+$DOG vdi read test -p 7002 | md5sum
 
 for i in 2 3; do
     _start_sheep $(($i-2))
@@ -39,11 +39,11 @@ for i in 2 3; do
     _wait_for_sheep_recovery $(($i-2))
 done
 
-$COLLIE vdi read test | md5sum
-$COLLIE vdi read test -p 7001 | md5sum
+$DOG vdi read test | md5sum
+$DOG vdi read test -p 7001 | md5sum
 
-$COLLIE vdi object test
+$DOG vdi object test
 
 for i in `seq 0 24`; do
-    $COLLIE vdi object -i $i test
+    $DOG vdi object -i $i test
 done
diff --git a/tests/functional/029 b/tests/functional/029
index ab361f4..7101d5e 100755
--- a/tests/functional/029
+++ b/tests/functional/029
@@ -13,11 +13,11 @@ _wait_for_sheep 7
 _cluster_format -c 3
 
 for i in `seq 2 4`; do
-	$COLLIE vdi create test$i 20M -c $i -P
+	$DOG vdi create test$i 20M -c $i -P
 done
 
-$COLLIE vdi snapshot -s tag test2
-$COLLIE vdi clone -s 1 test2 clone -c 3
+$DOG vdi snapshot -s tag test2
+$DOG vdi clone -s 1 test2 clone -c 3
 
 
 for i in `seq 2 3`; do
@@ -30,12 +30,12 @@ done
 
 _wait_for_sheep_recovery 0
 
-$COLLIE vdi delete clone
-$COLLIE vdi delete -s 1 test2
+$DOG vdi delete clone
+$DOG vdi delete -s 1 test2
 
 _vdi_list
 
 for i in `seq 2 4`; do
-	$COLLIE vdi object test$i -i 1;
+	$DOG vdi object test$i -i 1;
 done
 
diff --git a/tests/functional/030 b/tests/functional/030
index 7e9daae..219f348 100755
--- a/tests/functional/030
+++ b/tests/functional/030
@@ -13,26 +13,26 @@ rm -rf $TMPDIR
 
 _cluster_format -c 3
 
-$COLLIE vdi create test1 10M
-$COLLIE vdi create test2 10M
-
-_random | $COLLIE vdi write test1
-_random | $COLLIE vdi write test2
-$COLLIE vdi read test1 | md5sum > $STORE/csum.11.org
-$COLLIE vdi read test2 | md5sum > $STORE/csum.21.org
-$COLLIE vdi snapshot test1
-$COLLIE vdi snapshot test2
-$COLLIE cluster snapshot save s1 $TMPDIR
-$COLLIE cluster snapshot list $TMPDIR | _filter_date
-
-_random | $COLLIE vdi write test1
-_random | $COLLIE vdi write test2
-$COLLIE vdi read test1 | md5sum > $STORE/csum.12.org
-$COLLIE vdi read test2 | md5sum > $STORE/csum.22.org
-$COLLIE vdi snapshot test1
-$COLLIE vdi snapshot test2
-$COLLIE cluster snapshot save s2 $TMPDIR
-$COLLIE cluster snapshot list $TMPDIR | _filter_date
+$DOG vdi create test1 10M
+$DOG vdi create test2 10M
+
+_random | $DOG vdi write test1
+_random | $DOG vdi write test2
+$DOG vdi read test1 | md5sum > $STORE/csum.11.org
+$DOG vdi read test2 | md5sum > $STORE/csum.21.org
+$DOG vdi snapshot test1
+$DOG vdi snapshot test2
+$DOG cluster snapshot save s1 $TMPDIR
+$DOG cluster snapshot list $TMPDIR | _filter_date
+
+_random | $DOG vdi write test1
+_random | $DOG vdi write test2
+$DOG vdi read test1 | md5sum > $STORE/csum.12.org
+$DOG vdi read test2 | md5sum > $STORE/csum.22.org
+$DOG vdi snapshot test1
+$DOG vdi snapshot test2
+$DOG cluster snapshot save s2 $TMPDIR
+$DOG cluster snapshot list $TMPDIR | _filter_date
 
 _vdi_list
 
@@ -43,11 +43,11 @@ done
 _wait_for_sheep 4
 
 _cluster_format -c 3
-$COLLIE cluster snapshot load s1 $TMPDIR
+$DOG cluster snapshot load s1 $TMPDIR
 _vdi_list
 
-$COLLIE vdi read test1 | md5sum > $STORE/csum.11.new
-$COLLIE vdi read test2 | md5sum > $STORE/csum.21.new
+$DOG vdi read test1 | md5sum > $STORE/csum.11.new
+$DOG vdi read test2 | md5sum > $STORE/csum.21.new
 diff -u $STORE/csum.11.org $STORE/csum.11.new
 diff -u $STORE/csum.21.org $STORE/csum.21.new
 
@@ -57,7 +57,7 @@ for i in `seq 0 3`; do
 done
 _wait_for_sheep 4
 
-$COLLIE cluster snapshot load s2 $TMPDIR
+$DOG cluster snapshot load s2 $TMPDIR
 _vdi_list
 
 _cleanup
@@ -66,20 +66,20 @@ for i in `seq 0 3`; do
 done
 _wait_for_sheep 4
 
-$COLLIE cluster snapshot load 2 $TMPDIR
+$DOG cluster snapshot load 2 $TMPDIR
 _vdi_list
 
-$COLLIE vdi read -s 2 test1 | md5sum > $STORE/csum.12.new
-$COLLIE vdi read -s 2 test2 | md5sum > $STORE/csum.22.new
+$DOG vdi read -s 2 test1 | md5sum > $STORE/csum.12.new
+$DOG vdi read -s 2 test2 | md5sum > $STORE/csum.22.new
 diff -u $STORE/csum.12.org $STORE/csum.12.new
 diff -u $STORE/csum.22.org $STORE/csum.22.new
 
-$COLLIE vdi read test1 | md5sum > $STORE/csum.12.new
-$COLLIE vdi read test2 | md5sum > $STORE/csum.22.new
+$DOG vdi read test1 | md5sum > $STORE/csum.12.new
+$DOG vdi read test2 | md5sum > $STORE/csum.22.new
 diff -u $STORE/csum.12.org $STORE/csum.12.new
 diff -u $STORE/csum.22.org $STORE/csum.22.new
 
-$COLLIE vdi read -s 1 test1 | md5sum > $STORE/csum.11.new
-$COLLIE vdi read -s 1 test2 | md5sum > $STORE/csum.21.new
+$DOG vdi read -s 1 test1 | md5sum > $STORE/csum.11.new
+$DOG vdi read -s 1 test2 | md5sum > $STORE/csum.21.new
 diff -u $STORE/csum.11.org $STORE/csum.11.new
 diff -u $STORE/csum.21.org $STORE/csum.21.new
diff --git a/tests/functional/031 b/tests/functional/031
index dcb6a42..93623ff 100755
--- a/tests/functional/031
+++ b/tests/functional/031
@@ -10,12 +10,12 @@ _wait_for_sheep 1
 _cluster_format -c 1
 
 for i in 1 2 3; do
-    $COLLIE vdi create test$i ${i}00MB
+    $DOG vdi create test$i ${i}00MB
 done
 
 _vdi_list
 
-$COLLIE cluster shutdown
+$DOG cluster shutdown
 _wait_for_sheep_stop
 
 _start_sheep 0
diff --git a/tests/functional/032 b/tests/functional/032
index 691d360..467a402 100755
--- a/tests/functional/032
+++ b/tests/functional/032
@@ -12,13 +12,13 @@ _wait_for_sheep 8
 
 _cluster_format
 
-$COLLIE vdi create test 100MB
+$DOG vdi create test 100MB
 # create 25 objects
 for i in `seq 0 24`; do
-    echo $i | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
-$COLLIE vdi read test | md5sum
+$DOG vdi read test | md5sum
 
 for i in `seq 7 -1 0`; do
     _wait_for_sheep_recovery 0
@@ -42,4 +42,4 @@ for i in `seq 0 24`; do
 done
 ls $STORE/*/obj/.stale | _filter_store | sort
 
-$COLLIE vdi read test | md5sum
+$DOG vdi read test | md5sum
diff --git a/tests/functional/033 b/tests/functional/033
index b5aa00e..d752c5d 100755
--- a/tests/functional/033
+++ b/tests/functional/033
@@ -12,13 +12,13 @@ _wait_for_sheep 8
 
 _cluster_format
 
-$COLLIE vdi create test 100MB
+$DOG vdi create test 100MB
 # create 25 objects
 for i in `seq 0 24`; do
-    echo $i | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
-$COLLIE vdi read test | md5sum
+$DOG vdi read test | md5sum
 
 # kill 7 nodes without waiting to finish recovery
 for i in 7 6 5; do
@@ -53,4 +53,4 @@ for i in `seq 0 24`; do
 done
 ls $STORE/*/obj/.stale | _filter_store | sort
 
-$COLLIE vdi read test | md5sum
+$DOG vdi read test | md5sum
diff --git a/tests/functional/034 b/tests/functional/034
index c0ccab7..07caf5c 100755
--- a/tests/functional/034
+++ b/tests/functional/034
@@ -17,10 +17,10 @@ _wait_for_sheep 8
 
 _cluster_format
 
-$COLLIE vdi create test 100MB
+$DOG vdi create test 100MB
 # create 25 objects
 for i in `seq 0 24`; do
-    echo $i | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
 # kill 5 nodes
@@ -35,10 +35,10 @@ _wait_for_sheep 3
 
 # update vdi
 for i in `seq 0 24`; do
-    echo $(($i + 100)) | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $(($i + 100)) | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
-$COLLIE vdi read test | md5sum
+$DOG vdi read test | md5sum
 
 # start 5 nodes who have old objects
 for i in `seq 3 7`; do
@@ -48,5 +48,5 @@ done
 _wait_for_sheep 8
 
 for i in `seq 0 7`; do
-    $COLLIE vdi read test -p 700$i | md5sum
+    $DOG vdi read test -p 700$i | md5sum
 done
diff --git a/tests/functional/035 b/tests/functional/035
index dda4dd4..c1559e0 100755
--- a/tests/functional/035
+++ b/tests/functional/035
@@ -22,8 +22,8 @@ _wait_for_sheep 6
 
 _cluster_format -c 3
 
-$COLLIE vdi create test 40M
-_random | $COLLIE vdi write test &
+$DOG vdi create test 40M
+_random | $DOG vdi write test &
 
 sleep 3
 # Test write timeout
@@ -31,14 +31,14 @@ for i in `seq 1 4`; do
 	_simulate_machine_down $i
 done
 
-# wait for collie to finish
+# wait for dog to finish
 wait
 _wait_for_sheep_recovery 0
 
-$COLLIE vdi read test | md5sum > $STORE/csum.1
+$DOG vdi read test | md5sum > $STORE/csum.1
 
 for i in `seq 0 9`; do
-	$COLLIE vdi object -i $i test
+	$DOG vdi object -i $i test
 done
 
 for i in 6 7 8; do
@@ -48,7 +48,7 @@ done
 _wait_for_sheep_recovery 0
 
 # Test read timeout
-$COLLIE vdi read test | md5sum > $STORE/csum.2 &
+$DOG vdi read test | md5sum > $STORE/csum.2 &
 for i in `seq 5 7`; do
 	_simulate_machine_down $i
 done
diff --git a/tests/functional/036 b/tests/functional/036
index 36db7c9..dd440dd 100755
--- a/tests/functional/036
+++ b/tests/functional/036
@@ -9,7 +9,7 @@ for i in `seq 0 7`; do
 done
 
 while true; do
-    $COLLIE cluster format 2> /dev/null
+    $DOG cluster format 2> /dev/null
     if [ $? == 0 ]; then
         break
     fi
@@ -30,6 +30,6 @@ kill $pid > /dev/null 2>&1
 _wait_for_sheep 8
 
 for i in `seq 0 7`; do
-    $COLLIE cluster info -p 700$i | head -1
-    $COLLIE node list -p 700$i
+    $DOG cluster info -p 700$i | head -1
+    $DOG node list -p 700$i
 done
diff --git a/tests/functional/037 b/tests/functional/037
index a4753c8..c203752 100755
--- a/tests/functional/037
+++ b/tests/functional/037
@@ -10,13 +10,13 @@ done
 
 _wait_for_sheep 8
 
-$COLLIE cluster format &
+$DOG cluster format &
 
 for i in `seq 5 7`; do
     _kill_sheep $i
 done
 
-# wait for collie to finish
+# wait for dog to finish
 wait
 
 for i in `seq 5 7`; do
@@ -26,6 +26,6 @@ done
 _wait_for_sheep "8"
 
 for i in `seq 0 7`; do
-    $COLLIE cluster info -p 700$i | head -1
-    $COLLIE node list -p 700$i
+    $DOG cluster info -p 700$i | head -1
+    $DOG node list -p 700$i
 done
diff --git a/tests/functional/038 b/tests/functional/038
index 4b07ee1..0c62988 100755
--- a/tests/functional/038
+++ b/tests/functional/038
@@ -9,7 +9,7 @@ for i in `seq 0 7`; do
 done
 
 while true; do
-    $COLLIE cluster format 2> /dev/null
+    $DOG cluster format 2> /dev/null
     if [ $? == 0 ]; then
         break
     fi
@@ -21,7 +21,7 @@ for i in `seq 4 7`; do
     _kill_sheep $i
 done
 
-# wait for collie to finish
+# wait for dog to finish
 for cnt in `seq 60`; do # wait at most 60 seconds
     jobs 1 > /dev/null 2>&1
     if [ $? != 0 ]; then
@@ -38,6 +38,6 @@ done
 _wait_for_sheep "8"
 
 for i in `seq 0 7`; do
-    $COLLIE cluster info -p 700$i | head -1
-    $COLLIE node list -p 700$i
+    $DOG cluster info -p 700$i | head -1
+    $DOG node list -p 700$i
 done
diff --git a/tests/functional/039 b/tests/functional/039
index 74bf2e1..b7e1f57 100755
--- a/tests/functional/039
+++ b/tests/functional/039
@@ -11,46 +11,46 @@ done
 _wait_for_sheep 5
 
 _cluster_format
-$COLLIE vdi create test 4G
+$DOG vdi create test 4G
 
 $QEMU_IO -c "write 0 512 -P 1" sheepdog:test | _filter_qemu_io
-$COLLIE vdi snapshot test -s snap1
+$DOG vdi snapshot test -s snap1
 $QEMU_IO -c "write 0 512 -P 2" sheepdog:test | _filter_qemu_io
 
-echo yes | $COLLIE vdi rollback test -s snap1
+echo yes | $DOG vdi rollback test -s snap1
 $QEMU_IO -c "read 0 512 -P 1" sheepdog:test | _filter_qemu_io
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 _vdi_list
 
 $QEMU_IO -c "write 0 512 -P 2" sheepdog:test | _filter_qemu_io
-$COLLIE vdi snapshot test -s snap2
+$DOG vdi snapshot test -s snap2
 $QEMU_IO -c "write 0 512 -P 3" sheepdog:test | _filter_qemu_io
 
-echo yes | $COLLIE vdi rollback test -s snap1
+echo yes | $DOG vdi rollback test -s snap1
 $QEMU_IO -c "read 0 512 -P 1" sheepdog:test | _filter_qemu_io
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 _vdi_list
 
-echo yes | $COLLIE vdi rollback test -s snap2
+echo yes | $DOG vdi rollback test -s snap2
 $QEMU_IO -c "read 0 512 -P 2" sheepdog:test | _filter_qemu_io
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 _vdi_list
 
-echo yes | $COLLIE vdi rollback test -s snap1
+echo yes | $DOG vdi rollback test -s snap1
 $QEMU_IO -c "read 0 512 -P 1" sheepdog:test | _filter_qemu_io
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 _vdi_list
 
 $QEMU_IO -c "write 0 512 -P 3" sheepdog:test | _filter_qemu_io
-$COLLIE vdi snapshot test -s snap3
+$DOG vdi snapshot test -s snap3
 $QEMU_IO -c "write 0 512 -P 4" sheepdog:test | _filter_qemu_io
-$COLLIE vdi snapshot test -s snap4
+$DOG vdi snapshot test -s snap4
 
 # these fail since the snap ids don't belong to snapshots
-echo yes | $COLLIE vdi rollback test -s 0
-echo yes | $COLLIE vdi rollback test -s 5
+echo yes | $DOG vdi rollback test -s 0
+echo yes | $DOG vdi rollback test -s 5
 
-echo yes | $COLLIE vdi rollback test -s snap3
+echo yes | $DOG vdi rollback test -s snap3
 $QEMU_IO -c "read 0 512 -P 3" sheepdog:test | _filter_qemu_io
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 _vdi_list
diff --git a/tests/functional/040 b/tests/functional/040
index 2935226..77bbb61 100755
--- a/tests/functional/040
+++ b/tests/functional/040
@@ -10,7 +10,7 @@ _wait_for_sheep 1
 _cluster_format -c 1
 
 # create vdi in background
-$COLLIE vdi create test 4G &
+$DOG vdi create test 4G &
 
 # the created vdi object will be move from sheep 0 to sheep 1
 _start_sheep 1
diff --git a/tests/functional/041 b/tests/functional/041
index f72708d..6b9d87b 100755
--- a/tests/functional/041
+++ b/tests/functional/041
@@ -11,51 +11,51 @@ done
 _wait_for_sheep 5
 
 _cluster_format
-$COLLIE vdi create test 12M
+$DOG vdi create test 12M
 
 # create the first object
-echo 0 | $COLLIE vdi write test 0 512
-$COLLIE vdi snapshot test -s snap1
+echo 0 | $DOG vdi write test 0 512
+$DOG vdi snapshot test -s snap1
 
 # create the second object
-echo 1 | $COLLIE vdi write test $((4 * 1024 * 1024)) 512
-$COLLIE vdi snapshot test -s snap2
+echo 1 | $DOG vdi write test $((4 * 1024 * 1024)) 512
+$DOG vdi snapshot test -s snap2
 
 # update the first object
-echo 2 | $COLLIE vdi write test 0 512
-$COLLIE vdi snapshot test -s snap3
+echo 2 | $DOG vdi write test 0 512
+$DOG vdi snapshot test -s snap3
 
 # check vdis
 _vdi_list
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 for i in `seq 1 3`; do
-    $COLLIE vdi read test -s snap$i | md5sum
+    $DOG vdi read test -s snap$i | md5sum
 done
 
 # create backup files between snapshots
-$COLLIE vdi backup test -F snap1 -s snap2 > $STORE/backup.1.2
-$COLLIE vdi backup test -F snap1 -s snap3 > $STORE/backup.1.3
-$COLLIE vdi backup test -F snap2 -s snap3 > $STORE/backup.2.3
+$DOG vdi backup test -F snap1 -s snap2 > $STORE/backup.1.2
+$DOG vdi backup test -F snap1 -s snap3 > $STORE/backup.1.3
+$DOG vdi backup test -F snap2 -s snap3 > $STORE/backup.2.3
 
 # restore backups
-$COLLIE vdi restore test -s snap1 < $STORE/backup.1.2
+$DOG vdi restore test -s snap1 < $STORE/backup.1.2
 _vdi_list
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 
-$COLLIE vdi restore test -s 4 < $STORE/backup.2.3
+$DOG vdi restore test -s 4 < $STORE/backup.2.3
 _vdi_list
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 
-$COLLIE vdi restore test -s snap1 < $STORE/backup.1.3
+$DOG vdi restore test -s snap1 < $STORE/backup.1.3
 _vdi_list
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 
-$COLLIE vdi restore test -s snap2 < $STORE/backup.2.3
+$DOG vdi restore test -s snap2 < $STORE/backup.2.3
 _vdi_list
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 
 # check vdi contents
-$COLLIE vdi read test | md5sum
-for i in `seq 1 $($COLLIE vdi list | grep "^s " | wc -l)`; do
-    $COLLIE vdi read test -s $i | md5sum
+$DOG vdi read test | md5sum
+for i in `seq 1 $($DOG vdi list | grep "^s " | wc -l)`; do
+    $DOG vdi read test -s $i | md5sum
 done
diff --git a/tests/functional/042 b/tests/functional/042
index df706a1..54e8a33 100755
--- a/tests/functional/042
+++ b/tests/functional/042
@@ -25,8 +25,8 @@ _wait_for_sheep 4
 _cluster_format
 
 # create two VDIs before there are enough spaces
-$COLLIE vdi create test0 100M
-$COLLIE vdi create test1 100M
+$DOG vdi create test0 100M
+$DOG vdi create test1 100M
 
 # make sheep 0 and 1 disk full
 dd if=/dev/zero of=$STORE/2/zero > /dev/null 2>&1
@@ -34,16 +34,16 @@ dd if=/dev/zero of=$STORE/3/zero > /dev/null 2>&1
 
 # test data write against disk-full cluster
 for i in `seq 0 10`; do
-    echo $i | $COLLIE vdi write test0 $((i * 4 * 1024 * 1024)) 512 -p 7000
-    echo $i | $COLLIE vdi write test1 $((i * 4 * 1024 * 1024)) 512 -p 7002
+    echo $i | $DOG vdi write test0 $((i * 4 * 1024 * 1024)) 512 -p 7000
+    echo $i | $DOG vdi write test1 $((i * 4 * 1024 * 1024)) 512 -p 7002
 done
 
 # test vdi creation against disk-full cluster
-$COLLIE vdi create test2 100M -p 7000
-$COLLIE vdi create test3 100M -p 7002
+$DOG vdi create test2 100M -p 7000
+$DOG vdi create test3 100M -p 7002
 
 for i in `seq 0 3`; do
-    $COLLIE node list -p 700$i
+    $DOG node list -p 700$i
 done
 _node_info
 ls $STORE/*/obj/* | _filter_store | sort
diff --git a/tests/functional/043 b/tests/functional/043
index 2a59a4e..d336cf5 100755
--- a/tests/functional/043
+++ b/tests/functional/043
@@ -12,43 +12,43 @@ _wait_for_sheep 5
 
 _cluster_format
 
-$COLLIE vdi create test 40M
+$DOG vdi create test 40M
 
 for i in `seq 0 10`; do
-    echo $i | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
 for i in 0 1 2 3 4; do
-    $COLLIE vdi read test -p 700$i | md5sum
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG vdi read test -p 700$i | md5sum
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
 
 # remove obj directory to occur EIO
 _safe_remove $STORE/4/obj
 
 for i in `seq 0 10`; do
-    echo $(($i + 100)) | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $(($i + 100)) | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
 for i in 0 1 2 3 4; do
-    $COLLIE vdi read test -p 700$i | md5sum
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG vdi read test -p 700$i | md5sum
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
 
 # check whether sheep 4 can receive confchg event
 _kill_sheep 3
 
 for i in `seq 0 10`; do
-    echo $(($i + 200)) | $COLLIE vdi write test $((i * 4 * 1024 * 1024)) 512
+    echo $(($i + 200)) | $DOG vdi write test $((i * 4 * 1024 * 1024)) 512
 done
 
 for i in 0 1 2 4; do
-    $COLLIE vdi read test -p 700$i | md5sum
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG vdi read test -p 700$i | md5sum
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
 
 # check whether sheep 4 can receive notify event
-$COLLIE vdi create test2 20M
+$DOG vdi create test2 20M
 
 for i in 0 1 2 4; do
     _vdi_list -p 700$i
@@ -56,4 +56,4 @@ done
 
 # check whether we can write to a gateway after EIO
 _safe_remove $STORE/0/obj
-echo hello | $COLLIE vdi write test 0 512
+echo hello | $DOG vdi write test 0 512
diff --git a/tests/functional/044 b/tests/functional/044
index 75a427c..fc5d8be 100755
--- a/tests/functional/044
+++ b/tests/functional/044
@@ -11,55 +11,55 @@ done
 _wait_for_sheep 3
 
 _cluster_format
-$COLLIE vdi create base1 20M -p 7000
-$COLLIE vdi create base2 20M -p 7001
-$COLLIE vdi create base3 20M -p 7002
+$DOG vdi create base1 20M -p 7000
+$DOG vdi create base2 20M -p 7001
+$DOG vdi create base3 20M -p 7002
 
 for i in `seq 0 4`; do
-    echo $i | $COLLIE vdi write base1 $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write base1 $((i * 4 * 1024 * 1024)) 512
 done &
 
 for i in `seq 0 4`; do
-    echo $i | $COLLIE vdi write base2 $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write base2 $((i * 4 * 1024 * 1024)) 512
 done &
 
 for i in `seq 0 4`; do
-    echo $i | $COLLIE vdi write base3 $((i * 4 * 1024 * 1024)) 512
+    echo $i | $DOG vdi write base3 $((i * 4 * 1024 * 1024)) 512
 done &
 
 wait
 
-$COLLIE vdi snapshot -s snap0 base1 -p 7000
-$COLLIE vdi snapshot -s snap0 base2 -p 7001
-$COLLIE vdi snapshot -s snap0 base3 -p 7002
+$DOG vdi snapshot -s snap0 base1 -p 7000
+$DOG vdi snapshot -s snap0 base2 -p 7001
+$DOG vdi snapshot -s snap0 base3 -p 7002
 sleep 1
 
 for i in `seq 1 10`; do
-	$COLLIE vdi snapshot -s snap$i base1 -p 7000
-	$COLLIE vdi delete -s snap$(($i - 1)) base1 -p 7000
+	$DOG vdi snapshot -s snap$i base1 -p 7000
+	$DOG vdi delete -s snap$(($i - 1)) base1 -p 7000
 	sleep 1
 done &
 
 for i in `seq 1 10`; do
-	$COLLIE vdi snapshot -s snap$i base2 -p 7001
-	$COLLIE vdi delete -s snap$(($i - 1)) base2 -p 7001
+	$DOG vdi snapshot -s snap$i base2 -p 7001
+	$DOG vdi delete -s snap$(($i - 1)) base2 -p 7001
 	sleep 1
 done &
 
 for i in `seq 1 10`; do
-	$COLLIE vdi snapshot -s snap$i base3 -p 7002
-	$COLLIE vdi delete -s snap$(($i - 1)) base3 -p 7002
+	$DOG vdi snapshot -s snap$i base3 -p 7002
+	$DOG vdi delete -s snap$(($i - 1)) base3 -p 7002
 	sleep 1
 done &
 
 wait
 
-$COLLIE vdi delete base1 -p 7000
-$COLLIE vdi delete base2 -p 7001
-$COLLIE vdi delete base3 -p 7002
-$COLLIE vdi delete -s snap10 base1 -p 7000
-$COLLIE vdi delete -s snap10 base2 -p 7001
-$COLLIE vdi delete -s snap10 base3 -p 7002
+$DOG vdi delete base1 -p 7000
+$DOG vdi delete base2 -p 7001
+$DOG vdi delete base3 -p 7002
+$DOG vdi delete -s snap10 base1 -p 7000
+$DOG vdi delete -s snap10 base2 -p 7001
+$DOG vdi delete -s snap10 base3 -p 7002
 
 echo there should be no vdi
 _vdi_list
diff --git a/tests/functional/045 b/tests/functional/045
index d8f6583..530af28 100755
--- a/tests/functional/045
+++ b/tests/functional/045
@@ -11,14 +11,14 @@ done
 _wait_for_sheep 3
 
 _cluster_format -c 2
-$COLLIE vdi create test 4M
+$DOG vdi create test 4M
 
 for i in `seq 0 7`; do
-    echo $i | $COLLIE vdi write test $(($i * 1536 + 512)) 512
+    echo $i | $DOG vdi write test $(($i * 1536 + 512)) 512
 done
 
 for i in `seq 0 7`; do
-    $COLLIE vdi read test $(($i * 1536 + 512)) 512 | md5sum
+    $DOG vdi read test $(($i * 1536 + 512)) 512 | md5sum
 done
 
-$COLLIE vdi read test | md5sum
+$DOG vdi read test | md5sum
diff --git a/tests/functional/046 b/tests/functional/046
index 84b2a21..9d58658 100755
--- a/tests/functional/046
+++ b/tests/functional/046
@@ -13,27 +13,27 @@ _wait_for_sheep 3
 _cluster_format -c 2
 
 echo "delete snapshot image before current one"
-$COLLIE vdi create test 4G
-$COLLIE vdi snapshot test -s snap
+$DOG vdi create test 4G
+$DOG vdi snapshot test -s snap
 _vdi_list
 
-$COLLIE vdi delete test -s snap
-$COLLIE vdi delete test -s snap # error
+$DOG vdi delete test -s snap
+$DOG vdi delete test -s snap # error
 _vdi_list
 
-$COLLIE vdi delete test
-$COLLIE vdi delete test # error
+$DOG vdi delete test
+$DOG vdi delete test # error
 _vdi_list
 
 echo "delete current image before snapshot one"
-$COLLIE vdi create test 4G
-$COLLIE vdi snapshot test -s snap
+$DOG vdi create test 4G
+$DOG vdi snapshot test -s snap
 _vdi_list
 
-$COLLIE vdi delete test
-$COLLIE vdi delete test # error
+$DOG vdi delete test
+$DOG vdi delete test # error
 _vdi_list
 
-$COLLIE vdi delete test -s snap
-$COLLIE vdi delete test -s snap # error
+$DOG vdi delete test -s snap
+$DOG vdi delete test -s snap # error
 _vdi_list
diff --git a/tests/functional/047 b/tests/functional/047
index feda20b..675f74e 100755
--- a/tests/functional/047
+++ b/tests/functional/047
@@ -10,11 +10,11 @@ _wait_for_sheep 1
 
 _cluster_format -c 1
 
-$COLLIE vdi create test 4G
+$DOG vdi create test 4G
 
 # write something to vdi 'test'
-echo "hello" | $COLLIE vdi write test 0 512
-echo "sheepdog" | $COLLIE vdi write test 4M 512
+echo "hello" | $DOG vdi write test 0 512
+echo "sheepdog" | $DOG vdi write test 4M 512
 # corrupt the vdi...
 _kill_sheep 0
 if $MD; then
@@ -32,7 +32,7 @@ _wait_for_sheep 1
 sleep 3
 
 _vdi_list
-$COLLIE vdi read test 0 512 | tr -d [:cntrl:]
+$DOG vdi read test 0 512 | tr -d [:cntrl:]
 echo -n ' '
-$COLLIE vdi read test 4M 512 | tr -d [:cntrl:]
+$DOG vdi read test 4M 512 | tr -d [:cntrl:]
 echo ''
diff --git a/tests/functional/048 b/tests/functional/048
index b44819e..c487704 100755
--- a/tests/functional/048
+++ b/tests/functional/048
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# Test some collie commands
+# Test some dog commands
 
 . ./common
 
@@ -12,14 +12,14 @@ _wait_for_sheep 3
 
 _cluster_format
 
-$COLLIE vdi create test 1G
+$DOG vdi create test 1G
 
-echo hello | $COLLIE vdi write test 4M 512
+echo hello | $DOG vdi write test 4M 512
 # kill the master gracefully
-$COLLIE node kill 0
+$DOG node kill 0
 sleep 2
 _wait_for_sheep_recovery 1
-$COLLIE node list -p 7001
+$DOG node list -p 7001
 
 nr=2
 for i in 3 4 0; do
@@ -29,12 +29,12 @@ for i in 3 4 0; do
 done
 _wait_for_sheep_recovery 0
 
-$COLLIE vdi track test
-$COLLIE vdi track -i 1 test
+$DOG vdi track test
+$DOG vdi track -i 1 test
 _vdi_list test
-$COLLIE vdi list -r test | awk '{$7="MASKED";print $0}'
+$DOG vdi list -r test | awk '{$7="MASKED";print $0}'
 
-$COLLIE vdi check test
+$DOG vdi check test
 # clear the 'first' data block
 dd if=/dev/zero of=$STORE/0/obj/007c2b2500000001 bs=1M count=4 > /dev/null 2>&1
-$COLLIE vdi check test | sort | uniq
+$DOG vdi check test | sort | uniq
diff --git a/tests/functional/049 b/tests/functional/049
index bcc1025..d466130 100755
--- a/tests/functional/049
+++ b/tests/functional/049
@@ -12,10 +12,10 @@ _wait_for_sheep 3
 
 _cluster_format -c 1
 
-$COLLIE vdi create test 20M
-_random | $COLLIE vdi write -w test
+$DOG vdi create test 20M
+_random | $DOG vdi write -w test
 
-$COLLIE cluster shutdown
+$DOG cluster shutdown
 _wait_for_sheep_stop
 
 #trigger an object reclaim at startup
diff --git a/tests/functional/050 b/tests/functional/050
index a44fb8a..8a35c59 100755
--- a/tests/functional/050
+++ b/tests/functional/050
@@ -22,14 +22,14 @@ _wait_for_sheep 3
 
 _cluster_format
 
-$COLLIE vdi create test 100M
-dd if=/dev/zero | $COLLIE vdi write test &
+$DOG vdi create test 100M
+dd if=/dev/zero | $DOG vdi write test &
 
 # simulate IO NIC down of sheep 1
 iptables -A INPUT -p tcp --sport 8001 -j DROP
 iptables -A INPUT -p tcp --dport 8001 -j DROP
 
-# wait for collie to finish
+# wait for dog to finish
 wait
 # Logger flush in 1 second internval. We need assure log is flushed.
 sleep 1
@@ -37,6 +37,6 @@ sleep 1
 if [ "`grep fallback $STORE/0/sheep.log`" ];then
 	echo fallback done
 fi
-$COLLIE vdi check test
+$DOG vdi check test
 
 status=0
diff --git a/tests/functional/051 b/tests/functional/051
index c2c96c6..b128660 100755
--- a/tests/functional/051
+++ b/tests/functional/051
@@ -9,12 +9,12 @@ for i in 0 1; do
 done
 _wait_for_sheep 2
 _cluster_format
-$COLLIE cluster info | _filter_cluster_info
+$DOG cluster info | _filter_cluster_info
 
 _start_sheep 2
 _wait_for_sheep 3
-$COLLIE cluster info | _filter_cluster_info
+$DOG cluster info | _filter_cluster_info
 
 _kill_sheep 2
 _wait_for_sheep 2
-$COLLIE cluster info | _filter_cluster_info
+$DOG cluster info | _filter_cluster_info
diff --git a/tests/functional/052 b/tests/functional/052
index 679a698..bff9d52 100755
--- a/tests/functional/052
+++ b/tests/functional/052
@@ -9,10 +9,10 @@ for i in 0 1 2 3; do
 done
 _wait_for_sheep 4
 _cluster_format
-$COLLIE vdi create test 20M -P
-$COLLIE cluster info | _filter_cluster_info
+$DOG vdi create test 20M -P
+$DOG cluster info | _filter_cluster_info
 
-$COLLIE cluster shutdown
+$DOG cluster shutdown
 _wait_for_sheep_stop
 
 for i in 0 1 2 4; do
@@ -20,24 +20,24 @@ for i in 0 1 2 4; do
 done
 _wait_for_sheep 4
 for i in 0 1 2 4; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
     _vdi_list
 done
 
-echo yes | $COLLIE cluster recover force
+echo yes | $DOG cluster recover force
 echo ""
 
 _wait_for_sheep_recovery 0
-$COLLIE vdi check test | sort
+$DOG vdi check test | sort
 
 for i in 0 1 2 4; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
     _vdi_list
 done
 
 _start_sheep 3
 _wait_for_sheep 5
 for i in 0 1 2 3 4; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
     _vdi_list
 done
diff --git a/tests/functional/053 b/tests/functional/053
index 3874a7e..be2dd36 100755
--- a/tests/functional/053
+++ b/tests/functional/053
@@ -14,9 +14,9 @@ _wait_for_sheep 4
 
 _cluster_format -c 2
 for i in 0 1 2 3; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
-$COLLIE cluster shutdown
+$DOG cluster shutdown
 _wait_for_sheep_stop
 
 _start_sheep 0 -g
@@ -27,7 +27,7 @@ _start_sheep 2
 _start_sheep 3
 _wait_for_sheep 4
 for i in 0 1 2 3; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
 
 _start_sheep 4
@@ -35,7 +35,7 @@ _wait_for_sheep 5
 _start_sheep 5
 _wait_for_sheep 6
 for i in 0 1 2 3 4 5; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
 
 _kill_sheep 3
@@ -43,5 +43,5 @@ _wait_for_sheep 5
 _kill_sheep 4
 _wait_for_sheep 4
 for i in 0 1 2 5; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/054 b/tests/functional/054
index 5ecbc41..3b28bae 100755
--- a/tests/functional/054
+++ b/tests/functional/054
@@ -17,15 +17,15 @@ done
 _wait_for_sheep 3
 _cluster_format
 
-$COLLIE vdi create test 100M -P
-$COLLIE cluster info | _filter_cluster_info
+$DOG vdi create test 100M -P
+$DOG cluster info | _filter_cluster_info
 
 #start recovery
 _start_sheep 3
 
 _wait_for_sheep 4
 _wait_for_sheep_recovery 0
-$COLLIE cluster info | _filter_cluster_info
+$DOG cluster info | _filter_cluster_info
 
 #test no object in .stale
 ls $STORE/*/*/.stale | grep ^- | wc -l
diff --git a/tests/functional/055 b/tests/functional/055
index 0e78e2a..ac54011 100755
--- a/tests/functional/055
+++ b/tests/functional/055
@@ -11,34 +11,34 @@ for i in 0 1 2; do
 done
 _wait_for_sheep 3
 _cluster_format -c 2
-$COLLIE vdi create test 200M -P
+$DOG vdi create test 200M -P
 
 # simulate one disk failure
 _safe_remove $STORE/0/d0
-_random | $COLLIE vdi write test
+_random | $DOG vdi write test
 _wait_for_sheep_recovery 0
-$COLLIE vdi check test
-$COLLIE cluster info | _filter_cluster_info
+$DOG vdi check test
+$DOG cluster info | _filter_cluster_info
 
 # simulate multiple disk failure
 _safe_remove $STORE/1/d0
-_random | $COLLIE vdi write test &
+_random | $DOG vdi write test &
 sleep 1
 _safe_remove $STORE/1/d1
 _wait_for_sheep_recovery 0
 wait
-$COLLIE vdi check test
-$COLLIE cluster info | _filter_cluster_info
+$DOG vdi check test
+$DOG cluster info | _filter_cluster_info
 
 # simulate all disks failure
 _safe_remove $STORE/1/d2
-_random | $COLLIE vdi write test
-$COLLIE vdi check test
-$COLLIE cluster info | _filter_cluster_info
+_random | $DOG vdi write test
+$DOG vdi check test
+$DOG cluster info | _filter_cluster_info
 
 # simulate simultaneous multiple disks failure
 _safe_remove $STORE/2/d0
 _safe_remove $STORE/2/d1
-dd if=/dev/zero | $COLLIE vdi write test
-$COLLIE vdi check test
-$COLLIE cluster info | _filter_cluster_info
+dd if=/dev/zero | $DOG vdi write test
+$DOG vdi check test
+$DOG cluster info | _filter_cluster_info
diff --git a/tests/functional/056 b/tests/functional/056
index d2b1ebc..9530a6d 100755
--- a/tests/functional/056
+++ b/tests/functional/056
@@ -11,28 +11,28 @@ for i in 0 1 2; do
 done
 _wait_for_sheep 3
 _cluster_format
-$COLLIE vdi create test 200M -P
+$DOG vdi create test 200M -P
 
 # node event after disk failure
 _safe_remove $STORE/0/d0
 
-_random | $COLLIE vdi write test &
+_random | $DOG vdi write test &
 sleep 1
 _start_sheep 3
 _wait_for_sheep 4
 _wait_for_sheep_recovery 0
-wait # collie
-$COLLIE vdi check test
-$COLLIE cluster info | _filter_cluster_info
+wait # dog
+$DOG vdi check test
+$DOG cluster info | _filter_cluster_info
 
 # disk failures after node event
 _safe_remove $STORE/1/d0
-dd if=/dev/zero | $COLLIE vdi write test &
+dd if=/dev/zero | $DOG vdi write test &
 sleep 1
 _kill_sheep 2
 _wait_for_sheep 3
 _safe_remove $STORE/1/d1
 _wait_for_sheep_recovery 0
-wait # collie
-$COLLIE vdi check test
-$COLLIE cluster info | _filter_cluster_info
+wait # dog
+$DOG vdi check test
+$DOG cluster info | _filter_cluster_info
diff --git a/tests/functional/057 b/tests/functional/057
index ab47ffc..d1c95f5 100755
--- a/tests/functional/057
+++ b/tests/functional/057
@@ -15,36 +15,36 @@ for i in 0 1 2; do
 done
 _wait_for_sheep 3
 _cluster_format
-$COLLIE vdi create test 100M -P
+$DOG vdi create test 100M -P
 
 _md_info
 
 # plug during node event
 _start_sheep 3
 _wait_for_sheep 4
-$COLLIE node md plug  $STORE/0/d3,$STORE/0/d4
+$DOG node md plug  $STORE/0/d3,$STORE/0/d4
 _wait_for_sheep_recovery 0
 _md_info
-$COLLIE vdi check test
-$COLLIE cluster info | _filter_cluster_info
+$DOG vdi check test
+$DOG cluster info | _filter_cluster_info
 
 # plug duplicate path
-$COLLIE node md plug  $STORE/0/d3
-$COLLIE node recovery
+$DOG node md plug  $STORE/0/d3
+$DOG node recovery
 _md_info
 
 # unplug
-$COLLIE node md unplug  $STORE/0/d0,$STORE/0/d1
+$DOG node md unplug  $STORE/0/d0,$STORE/0/d1
 _wait_for_sheep_recovery 0
 _md_info
-$COLLIE vdi check test
-$COLLIE cluster info | _filter_cluster_info
+$DOG vdi check test
+$DOG cluster info | _filter_cluster_info
 
 # unplug invalid path
-$COLLIE node md unplug  $STORE/0/d0
-$COLLIE node recovery
+$DOG node md unplug  $STORE/0/d0
+$DOG node recovery
 _md_info
-$COLLIE cluster info | _filter_cluster_info
+$DOG cluster info | _filter_cluster_info
 
 # check stale object purging
 find $STORE/*/d*/.stale/ -type f | _filter_store
diff --git a/tests/functional/058 b/tests/functional/058
index 53393c0..ea584de 100755
--- a/tests/functional/058
+++ b/tests/functional/058
@@ -8,11 +8,11 @@ for i in 0 1 2; do
 done
 _wait_for_sheep 3
 _cluster_format
-$COLLIE vdi create test 100M
-dd if=/dev/zero | $COLLIE vdi write -w test
+$DOG vdi create test 100M
+dd if=/dev/zero | $DOG vdi write -w test
 $QEMU_IO -c "discard 0 100m" sheepdog:test | _filter_qemu_io
-$COLLIE vdi check test
+$DOG vdi check test
 for i in `seq 0 24`; do
-	$COLLIE vdi object test -i $i;
+	$DOG vdi object test -i $i;
 done
 _node_info
diff --git a/tests/functional/059 b/tests/functional/059
index 3c133ec..b719a58 100755
--- a/tests/functional/059
+++ b/tests/functional/059
@@ -11,14 +11,14 @@ done
 _wait_for_sheep 3
 
 _cluster_format -c 3
-$COLLIE vdi create test 4M
+$DOG vdi create test 4M
 
 _input()
 {
     for i in `seq 1 10`; do
 	echo "multiwrite -P $i 0 1k ; 2k 1k ; 4k 1k ; 8k 1k"
 	sleep 1
-	$COLLIE vdi snapshot test -s snap$i
+	$DOG vdi snapshot test -s snap$i
     done
 
     echo quit
@@ -27,8 +27,8 @@ _input()
 _input | $QEMU_IO sheepdog:test > /dev/null
 
 _vdi_list
-$COLLIE vdi tree | _filter_short_date
+$DOG vdi tree | _filter_short_date
 
 for i in `seq 1 10`; do
-    $COLLIE vdi read test -s snap$i | md5sum
+    $DOG vdi read test -s snap$i | md5sum
 done
diff --git a/tests/functional/060 b/tests/functional/060
index b5c1384..fb6bc31 100755
--- a/tests/functional/060
+++ b/tests/functional/060
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# Test reboot sheepdog without collie shutdown
+# Test reboot sheepdog without dog shutdown
 . ./common
 
 for i in `seq 0 7`; do
@@ -12,19 +12,19 @@ _wait_for_sheep 8
 _cluster_format
 
 for i in `seq 0 3`; do
-    $COLLIE vdi create test$i 100M
+    $DOG vdi create test$i 100M
 
     for j in `seq 0 24`; do
-	echo "$i $j" | $COLLIE vdi write test$i $(($j * 4 * 1024 ** 2)) 512
+	echo "$i $j" | $DOG vdi write test$i $(($j * 4 * 1024 ** 2)) 512
     done &
 done
 wait
 
 for i in `seq 0 3`; do
-    $COLLIE vdi read test$i | md5sum
+    $DOG vdi read test$i | md5sum
 done
 
-_reboot_without_collie_shutdown()
+_reboot_without_dog_shutdown()
 {
     local i
     for i in `seq 0 7`; do
@@ -41,14 +41,14 @@ _reboot_without_collie_shutdown()
 }
 
 for i in 0 1 2; do
-    _reboot_without_collie_shutdown
+    _reboot_without_dog_shutdown
 done
 
 for i in `seq 0 7`; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info > $STORE/cinfo.$i
+    $DOG cluster info -p 700$i | _filter_cluster_info > $STORE/cinfo.$i
     _vdi_list -p 700$i
     for j in `seq 0 3`; do
-	$COLLIE vdi read test$j -p 700$i | md5sum > $STORE/csum.$i.$j &
+	$DOG vdi read test$j -p 700$i | md5sum > $STORE/csum.$i.$j &
     done
     wait
     for j in `seq 0 3`; do
diff --git a/tests/functional/061 b/tests/functional/061
index 26b8daf..5e03ba0 100755
--- a/tests/functional/061
+++ b/tests/functional/061
@@ -12,11 +12,11 @@ _wait_for_sheep 3
 
 _cluster_format -c 1
 
-$COLLIE vdi create test 40M -P
+$DOG vdi create test 40M -P
 
 _kill_sheep 2
 _wait_for_sheep_recovery 0
 _start_sheep 2
 _wait_for_sheep_recovery 0
 
-$COLLIE vdi check test
+$DOG vdi check test
diff --git a/tests/functional/062 b/tests/functional/062
index 6bd84ee..5133287 100755
--- a/tests/functional/062
+++ b/tests/functional/062
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# Test unaligned collie vdi read/write
+# Test unaligned dog vdi read/write
 
 . ./common
 
@@ -12,21 +12,21 @@ _wait_for_sheep 3
 
 _cluster_format
 
-$COLLIE vdi create test 1234
+$DOG vdi create test 1234
 sleep 1
-$COLLIE vdi list -r test | awk '{$7="MASKED";print $0}'
-
-echo hello | $COLLIE vdi write test 1 6
-$COLLIE vdi read test 1 6
-echo world | $COLLIE vdi write test 1 6
-$COLLIE vdi read test 1 6
-
-echo h*** | $COLLIE vdi write test 510 512
-echo h!!! | $COLLIE vdi write test 510 512
-$COLLIE vdi read test 510 5
-echo ^^ | $COLLIE vdi write test 511 2
-$COLLIE vdi read test 510 5
-echo xxx | $COLLIE vdi write test 1020 4
-$COLLIE vdi read test 1020 4
-$COLLIE vdi read test 510 5
-$COLLIE vdi read test 1 6
+$DOG vdi list -r test | awk '{$7="MASKED";print $0}'
+
+echo hello | $DOG vdi write test 1 6
+$DOG vdi read test 1 6
+echo world | $DOG vdi write test 1 6
+$DOG vdi read test 1 6
+
+echo h*** | $DOG vdi write test 510 512
+echo h!!! | $DOG vdi write test 510 512
+$DOG vdi read test 510 5
+echo ^^ | $DOG vdi write test 511 2
+$DOG vdi read test 510 5
+echo xxx | $DOG vdi write test 1020 4
+$DOG vdi read test 1020 4
+$DOG vdi read test 510 5
+$DOG vdi read test 1 6
diff --git a/tests/functional/063 b/tests/functional/063
index b43f374..8a648c6 100755
--- a/tests/functional/063
+++ b/tests/functional/063
@@ -20,17 +20,17 @@ _start_sheep 3 "-g"
 _wait_for_sheep 4
 _cluster_format -c 1
 
-$COLLIE vdi create test 200M -P
+$DOG vdi create test 200M -P
 _node_info
-$COLLIE node list
+$DOG node list
 
-$COLLIE node md plug $STORE/4
+$DOG node md plug $STORE/4
 _wait_for_sheep_recovery 0
 
-$COLLIE cluster reweight
+$DOG cluster reweight
 _start_sheep 5
 _wait_for_sheep 5
 _wait_for_sheep_recovery 0
 _node_info
-$COLLIE node list
-$COLLIE cluster info | _filter_cluster_info
+$DOG node list
+$DOG cluster info | _filter_cluster_info
diff --git a/tests/functional/064 b/tests/functional/064
index 77304fe..3a6f168 100755
--- a/tests/functional/064
+++ b/tests/functional/064
@@ -17,25 +17,25 @@ done
 _wait_for_sheep 3
 _cluster_format -c 2
 
-$COLLIE vdi create test 100M -P
+$DOG vdi create test 100M -P
 _node_info
-$COLLIE node list
+$DOG node list
 
-$COLLIE node md plug $STORE/3
+$DOG node md plug $STORE/3
 _wait_for_sheep_recovery 0
 _node_info
-$COLLIE node list
+$DOG node list
 
-$COLLIE cluster reweight
+$DOG cluster reweight
 
 # restart sheep1 while reweighting
 _kill_sheep 2
 _wait_for_sheep_recovery 0
 _node_info | grep -v ^2 # the content of sheep 2 is non-deterministic
-$COLLIE node list
+$DOG node list
 _start_sheep 2
 
 _wait_for_sheep_recovery 0
 _node_info
-$COLLIE node list
-$COLLIE cluster info | _filter_cluster_info
+$DOG node list
+$DOG cluster info | _filter_cluster_info
diff --git a/tests/functional/065 b/tests/functional/065
index 388d7e8..e0ee1ae 100755
--- a/tests/functional/065
+++ b/tests/functional/065
@@ -11,16 +11,16 @@ done
 _wait_for_sheep 2
 _cluster_format -c 2
 
-$COLLIE vdi create t 1G -P
+$DOG vdi create t 1G -P
 
 # move objects into stale directory
 _kill_sheep 0
 _start_sheep 0 "-w size=400"
 _wait_for_sheep 2
 
-dd if=/dev/zero | $COLLIE vdi write -w t &
+dd if=/dev/zero | $DOG vdi write -w t &
 
 _wait_for_sheep_recovery 1
 wait
 
-$COLLIE cluster info | _filter_cluster_info
+$DOG cluster info | _filter_cluster_info
diff --git a/tests/functional/066 b/tests/functional/066
index 1a36492..97f0b9f 100755
--- a/tests/functional/066
+++ b/tests/functional/066
@@ -11,13 +11,13 @@ done
 _wait_for_sheep 3
 _cluster_format -c 2
 
-$COLLIE vdi create t 300M -P
-$COLLIE vdi create t0 300M -P
-$COLLIE vdi create t1 300M -P
+$DOG vdi create t 300M -P
+$DOG vdi create t0 300M -P
+$DOG vdi create t1 300M -P
 
-dd if=/dev/zero | $COLLIE vdi write -w t &
+dd if=/dev/zero | $DOG vdi write -w t &
 for j in 0 1; do
-    dd if=/dev/zero | $COLLIE vdi write -w t$j -p 700$j &
+    dd if=/dev/zero | $DOG vdi write -w t$j -p 700$j &
 done
 
 for i in `seq 3 7`; do
@@ -30,5 +30,5 @@ done
 wait
 
 for i in 0 1 7; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/067 b/tests/functional/067
index c47d387..8b309ef 100755
--- a/tests/functional/067
+++ b/tests/functional/067
@@ -12,7 +12,7 @@ _wait_for_sheep 3
 
 _cluster_format
 
-$COLLIE node list
+$DOG node list
 
 # make sure sheep1 and sheep2 quit simutaniously
 _kill_sheep 1 &
@@ -33,5 +33,5 @@ sleep 1
 
 echo check whether all nodes have the same cluster info
 for i in 1 2; do
-    $COLLIE node list -p 700$i
+    $DOG node list -p 700$i
 done
diff --git a/tests/functional/068 b/tests/functional/068
index 28afe4e..7d53d63 100755
--- a/tests/functional/068
+++ b/tests/functional/068
@@ -10,7 +10,7 @@ done
 
 _wait_for_sheep 3
 
-$COLLIE cluster format
+$DOG cluster format
 sleep 1
 
 # kill a non-master sheep
@@ -26,10 +26,10 @@ _kill_zk_session 0
 sleep 5
 
 for i in 0 1 2; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
 
-$COLLIE vdi create test 10M
+$DOG vdi create test 10M
 for i in 0 1 2; do
     _vdi_list -p $((7000+$i))
 done
diff --git a/tests/functional/069 b/tests/functional/069
index cf19f50..0e974e2 100755
--- a/tests/functional/069
+++ b/tests/functional/069
@@ -16,7 +16,7 @@ _cluster_format
 # kill sheep 0 so that sheep 2 has larger epoch
 _kill_sheep 0
 
-$COLLIE cluster shutdown -p 7001
+$DOG cluster shutdown -p 7001
 _wait_for_sheep_stop
 
 # clean up sheep 0 and sheep 1
@@ -32,7 +32,7 @@ _wait_for_sheep 2
 # start Sheepdog with 2 nodes
 _cluster_format
 
-$COLLIE cluster shutdown
+$DOG cluster shutdown
 _wait_for_sheep_stop
 
 # sheep 2 should fail to join because it was formatted at the different time
@@ -45,5 +45,5 @@ _start_sheep 1
 _wait_for_sheep 2
 
 for i in 0 1; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/070 b/tests/functional/070
index 1f7660e..a78f271 100755
--- a/tests/functional/070
+++ b/tests/functional/070
@@ -13,7 +13,7 @@ _wait_for_sheep 3
 # start Sheepdog with 3 nodes
 _cluster_format
 
-$COLLIE cluster shutdown -p 7000
+$DOG cluster shutdown -p 7000
 _wait_for_sheep_stop
 
 for i in 3 4 5; do
@@ -29,5 +29,5 @@ done
 _wait_for_sheep 6
 
 for i in `seq 0 5`; do
-    $COLLIE cluster info -p 700$i | _filter_cluster_info
+    $DOG cluster info -p 700$i | _filter_cluster_info
 done
diff --git a/tests/functional/071 b/tests/functional/071
index acf8b9f..de935a4 100755
--- a/tests/functional/071
+++ b/tests/functional/071
@@ -12,7 +12,7 @@ _wait_for_sheep 3
 
 _cluster_format
 
-$COLLIE vdi create test 4M
+$DOG vdi create test 4M
 
 # Create a 28 KB file.  The first 12 KB (3 blocks) is filled with zero,
 # next 4 KB is filled with one, and the rest is filled with zero.
@@ -25,9 +25,9 @@ for skip in 0 1 2 3; do
     for offset in 0 512; do
 	for length in $((4 * 4096)) $((4 * 4096 - 512)); do
 	    dd if=$DATA_FILE bs=4k skip=$skip count=4 2> /dev/null \
-		| $COLLIE vdi write test $offset $length
+		| $DOG vdi write test $offset $length
 
-	    $COLLIE vdi read test $offset $length | hd
+	    $DOG vdi read test $offset $length | hd
 	done
     done
 done
diff --git a/tests/functional/072 b/tests/functional/072
index b10f204..4f7a7c3 100755
--- a/tests/functional/072
+++ b/tests/functional/072
@@ -16,8 +16,8 @@ _cluster_format -c 1
 _start_sheep 2
 _wait_for_sheep 3
 
-$COLLIE vdi create test 8M -P
-$COLLIE cluster shutdown
+$DOG vdi create test 8M -P
+$DOG cluster shutdown
 
 mv $STORE/0/obj/007c2b2500000001 $STORE/0/obj/.stale/007c2b2500000001.1
 mv $STORE/1/obj/807c2b2500000000 $STORE/1/obj/.stale/807c2b2500000000.1
@@ -33,4 +33,4 @@ for i in 0 1 2; do
     _vdi_list -p $((7000+$i))
 done
 
-$COLLIE vdi check test
+$DOG vdi check test
diff --git a/tests/functional/check b/tests/functional/check
index 5addc23..e7b7a17 100755
--- a/tests/functional/check
+++ b/tests/functional/check
@@ -314,7 +314,7 @@ fi
 
 if $valgrind; then
     export SHEEP=_valgrind_sheep
-    export COLLIE=_valgrind_collie
+    export DOG=_valgrind_dog
 fi
 
 # we need common.rc
diff --git a/tests/functional/common.config b/tests/functional/common.config
index db7d757..a946f2e 100644
--- a/tests/functional/common.config
+++ b/tests/functional/common.config
@@ -78,8 +78,8 @@ export STORE=$WD
 export SHEEP_PROG=${SHEEP_PROG:-../../sheep/sheep}
 export SHEEP=${SHEEP:-$SHEEP_PROG}
 export SHEEP_OPTIONS=${SHEEP_OPTIONS:-"-n -y 127.0.0.1 -d"}
-export COLLIE_PROG=${COLLIE_PROG:-../../collie/collie}
-export COLLIE=${COLLIE:-$COLLIE_PROG}
+export DOG_PROG=${DOG_PROG:-../../dog/dog}
+export DOG=${DOG:-$DOG_PROG}
 export VALGRIND_OPTIONS=${VALGRIND_OPTIONS:-"-q"}
 export MD=${MD:-false}
 export QEMU_IO=${QEMU_IO_PROG:-qemu-io}
diff --git a/tests/functional/common.filter b/tests/functional/common.filter
index f1fffc4..2c62ef2 100644
--- a/tests/functional/common.filter
+++ b/tests/functional/common.filter
@@ -145,7 +145,7 @@ _filter_spaces()
     sed -e 's/ *$//'
 }
 
-# normalize collie cluster info output
+# normalize dog cluster info output
 _filter_cluster_info()
 {
     _filter_date | _filter_iso_date
diff --git a/tests/functional/common.rc b/tests/functional/common.rc
index 83fb195..2a64b53 100644
--- a/tests/functional/common.rc
+++ b/tests/functional/common.rc
@@ -141,7 +141,7 @@ _die()
 _cleanup()
 {
     local i
-    _kill_all_collies
+    _kill_all_dogs
     _kill_all_sheeps
 
     _cleanup_devices
@@ -225,7 +225,7 @@ _wait_for_sheep()
             _die "should have $1, but have $(_count_sheep_processes) sheep"
         fi
 
-        node_list="$($COLLIE node list -p $PORT 2> /dev/null)"
+        node_list="$($DOG node list -p $PORT 2> /dev/null)"
 
         if [ $? != 0 ]; then
             # sheep is not ready yet
@@ -270,10 +270,10 @@ _valgrind_sheep()
     done
 }
 
-_valgrind_collie()
+_valgrind_dog()
 {
     local logfile=$(mktemp)
-    valgrind --log-file=$logfile --error-exitcode=99 ${VALGRIND_OPTIONS} $COLLIE_PROG $*
+    valgrind --log-file=$logfile --error-exitcode=99 ${VALGRIND_OPTIONS} $DOG_PROG $*
     local ret=$?
     if [ $ret == 99 ]; then
         cat $logfile 1>&2
@@ -311,12 +311,12 @@ _start_sheep()
     fi
 }
 
-_kill_all_collies()
+_kill_all_dogs()
 {
-    pkill -9 -f "$COLLIE_PROG (cluster|vdi|node|debug)"
+    pkill -9 -f "$DOG_PROG (cluster|vdi|node|debug)"
 
     while [ $? == 0 ]; do
-        pgrep -f "$COLLIE_PROG (cluster|vdi|node|debug)" > /dev/null
+        pgrep -f "$DOG_PROG (cluster|vdi|node|debug)" > /dev/null
     done
 }
 
@@ -342,7 +342,7 @@ _wait_for_sheep_recovery()
 {
     while true; do
         sleep 2
-        recovery_info="$($COLLIE node recovery -p $((7000+$1)))"
+        recovery_info="$($DOG node recovery -p $((7000+$1)))"
 
         if [ $? != 0 ]; then
             _die "failed to get recovery info"
@@ -459,18 +459,18 @@ _cluster_format()
     local args=$*
     local port_opt=$(echo $args | grep -oE '\-*p[^ ]* 7...')
 
-    $COLLIE cluster format $args
+    $DOG cluster format $args
     if [ $? != 0 ]; then
 	_die "failed to format cluster"
     fi
 
     # wait for all the sheeps to be running
-    local ports=$($COLLIE node list -r $port_opt | perl -ne 'print "$1\n" if /:(\d+)/')
+    local ports=$($DOG node list -r $port_opt | perl -ne 'print "$1\n" if /:(\d+)/')
     local port
     for port in $ports; do
 	local cnt
 	for cnt in `seq 10`; do # wait at most 10 seconds
-	    $COLLIE cluster info -p $port | grep -E running > /dev/null
+	    $DOG cluster info -p $port | grep -E running > /dev/null
 	    if [ $? == 0 ]; then
 		continue 2
 	    fi
@@ -507,7 +507,7 @@ _kill_zk_session()
 _vdi_list()
 {
 	local args=$*
-	$COLLIE vdi list $args | _filter_short_date
+	$DOG vdi list $args | _filter_short_date
 }
 
 # make sure this script returns success
diff --git a/tests/functional/group b/tests/functional/group
index dfa6558..1749e13 100644
--- a/tests/functional/group
+++ b/tests/functional/group
@@ -11,7 +11,7 @@
 # cluster:	cluster drivers tests: join/leave/etc
 # store:	basic data integrity
 # vdi:		qemu I/O, snapshots, volume creation and deletion
-# collie:	check collie commands
+# dog:	check dog commands
 # md:		multi-disk tests
 #
 001 auto quick cluster md
@@ -61,7 +61,7 @@
 045 auto quick store md
 046 auto quick vdi md
 047 auto quick vdi md
-048 auto quick collie
+048 auto quick dog
 049 auto quick cache md
 050 auto quick cluster md
 051 auto quick cluster md
diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am
index 3e6255a..11ddf52 100644
--- a/tests/unit/Makefile.am
+++ b/tests/unit/Makefile.am
@@ -1,3 +1,3 @@
 MAINTAINERCLEANFILES	= Makefile.in
 
-SUBDIRS			= mock collie sheep
+SUBDIRS			= mock dog sheep
diff --git a/tests/unit/collie/Makefile.am b/tests/unit/collie/Makefile.am
deleted file mode 100644
index 404bc66..0000000
--- a/tests/unit/collie/Makefile.am
+++ /dev/null
@@ -1,23 +0,0 @@
-MAINTAINERCLEANFILES	= Makefile.in
-
-TESTS			= test_common
-
-check_PROGRAMS		= ${TESTS}
-
-INCLUDES		= -I$(top_srcdir)/include			\
-			  -I$(top_srcdir)/collie			\
-			  -I$(top_srcdir)/collie/farm			\
-			  -I../mock					\
-			  @CHECK_CFLAGS@
-
-LIBS			= $(top_srcdir)/lib/libsheepdog.a -lpthread	\
-			  ../mock/libmock.a @CHECK_LIBS@
-
-test_common_SOURCES	= test_common.c mock_collie.c			\
-			  $(top_srcdir)/collie/common.c
-
-clean-local:
-	rm -f ${check_PROGRAMS} *.o
-
-coverage:
-	@lcov -d . -c -o collie.info
diff --git a/tests/unit/collie/mock_collie.c b/tests/unit/collie/mock_collie.c
deleted file mode 100644
index ff52ed1..0000000
--- a/tests/unit/collie/mock_collie.c
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2013 Zelin.io
- *
- * Kai Zhang <kyle at zelin.io>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "collie.h"
-#include "mock.h"
-
-/* collie mock */
-uint8_t sdhost[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1 };
-int sdport = 7000, sd_vnodes_nr = 100;
-bool highlight = true;
-bool raw_output;
-struct sd_vnode sd_vnodes[SD_MAX_VNODES];
-
-MOCK_METHOD(update_node_list, int, 0, int max_nodes)
-MOCK_VOID_METHOD(subcommand_usage, char *cmd, char *subcmd, int status)
diff --git a/tests/unit/collie/test_common.c b/tests/unit/collie/test_common.c
deleted file mode 100644
index ee3c8ff..0000000
--- a/tests/unit/collie/test_common.c
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2013 Zelin.io
- *
- * Kai Zhang <kyle at zelin.io>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <check.h>
-
-#include "collie.h"
-
-/* test */
-START_TEST(test_common)
-{
-	char str[100];
-
-	raw_output = true;
-	ck_assert_str_eq(size_to_str(10, str, 100), "10");
-	ck_assert_str_eq(size_to_str(10000, str, 100), "10000");
-	ck_assert_str_eq(size_to_str(100000000, str, 100), "100000000");
-
-	raw_output = false;
-	ck_assert_str_eq(size_to_str(10, str, 100), "0.0 MB");
-	ck_assert_str_eq(size_to_str(10000, str, 100), "0.0 MB");
-	ck_assert_str_eq(size_to_str(100000000, str, 100), "95 MB");
-}
-END_TEST
-
-static Suite *test_suite(void)
-{
-	Suite *s = suite_create("test common");
-
-	TCase *tc_common = tcase_create("common");
-	tcase_add_test(tc_common, test_common);
-
-	suite_add_tcase(s, tc_common);
-
-	return s;
-}
-
-int main(void)
-{
-	int number_failed;
-	Suite *s = test_suite();
-	SRunner *sr = srunner_create(s);
-	srunner_run_all(sr, CK_NORMAL);
-	number_failed = srunner_ntests_failed(sr);
-	srunner_free(sr);
-	return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
-}
diff --git a/tests/unit/dog/Makefile.am b/tests/unit/dog/Makefile.am
new file mode 100644
index 0000000..6d5978b
--- /dev/null
+++ b/tests/unit/dog/Makefile.am
@@ -0,0 +1,23 @@
+MAINTAINERCLEANFILES	= Makefile.in
+
+TESTS			= test_common
+
+check_PROGRAMS		= ${TESTS}
+
+INCLUDES		= -I$(top_srcdir)/include			\
+			  -I$(top_srcdir)/dog				\
+			  -I$(top_srcdir)/dog/farm			\
+			  -I../mock					\
+			  @CHECK_CFLAGS@
+
+LIBS			= $(top_srcdir)/lib/libsheepdog.a -lpthread	\
+			  ../mock/libmock.a @CHECK_LIBS@
+
+test_common_SOURCES	= test_common.c mock_dog.c			\
+			  $(top_srcdir)/dog/common.c
+
+clean-local:
+	rm -f ${check_PROGRAMS} *.o
+
+coverage:
+	@lcov -d . -c -o dog.info
diff --git a/tests/unit/dog/mock_dog.c b/tests/unit/dog/mock_dog.c
new file mode 100644
index 0000000..6059a0a
--- /dev/null
+++ b/tests/unit/dog/mock_dog.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 Zelin.io
+ *
+ * Kai Zhang <kyle at zelin.io>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dog.h"
+#include "mock.h"
+
+/* dog mock */
+uint8_t sdhost[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1 };
+int sdport = 7000, sd_vnodes_nr = 100;
+bool highlight = true;
+bool raw_output;
+struct sd_vnode sd_vnodes[SD_MAX_VNODES];
+
+MOCK_METHOD(update_node_list, int, 0, int max_nodes)
+MOCK_VOID_METHOD(subcommand_usage, char *cmd, char *subcmd, int status)
diff --git a/tests/unit/dog/test_common.c b/tests/unit/dog/test_common.c
new file mode 100644
index 0000000..6fec940
--- /dev/null
+++ b/tests/unit/dog/test_common.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 Zelin.io
+ *
+ * Kai Zhang <kyle at zelin.io>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <check.h>
+
+#include "dog.h"
+
+/* test */
+START_TEST(test_common)
+{
+	char str[100];
+
+	raw_output = true;
+	ck_assert_str_eq(size_to_str(10, str, 100), "10");
+	ck_assert_str_eq(size_to_str(10000, str, 100), "10000");
+	ck_assert_str_eq(size_to_str(100000000, str, 100), "100000000");
+
+	raw_output = false;
+	ck_assert_str_eq(size_to_str(10, str, 100), "0.0 MB");
+	ck_assert_str_eq(size_to_str(10000, str, 100), "0.0 MB");
+	ck_assert_str_eq(size_to_str(100000000, str, 100), "95 MB");
+}
+END_TEST
+
+static Suite *test_suite(void)
+{
+	Suite *s = suite_create("test common");
+
+	TCase *tc_common = tcase_create("common");
+	tcase_add_test(tc_common, test_common);
+
+	suite_add_tcase(s, tc_common);
+
+	return s;
+}
+
+int main(void)
+{
+	int number_failed;
+	Suite *s = test_suite();
+	SRunner *sr = srunner_create(s);
+	srunner_run_all(sr, CK_NORMAL);
+	number_failed = srunner_ntests_failed(sr);
+	srunner_free(sr);
+	return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
-- 
1.7.9.5




More information about the sheepdog mailing list