]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: reorg rbd tests
authorSage Weil <sage@inktank.com>
Sun, 4 Nov 2012 11:47:10 +0000 (03:47 -0800)
committerSage Weil <sage@inktank.com>
Sun, 4 Nov 2012 11:47:10 +0000 (03:47 -0800)
Signed-off-by: Sage Weil <sage@inktank.com>
src/Makefile.am
src/test/cls_rbd/test_cls_rbd.cc [new file with mode: 0644]
src/test/librbd/fsx.c [new file with mode: 0644]
src/test/librbd/test_librbd.cc [new file with mode: 0644]
src/test/rbd/fsx.c [deleted file]
src/test/rbd/test_cls_rbd.cc [deleted file]
src/test/test_librbd.cc [deleted file]

index 25e650b4ec69a331afbec16b5c92153356e99fda..904822cd4b7427060d22ed70b78c744473a050f2 100644 (file)
@@ -807,17 +807,17 @@ unittest_texttable_LDADD = librados.la ${UNITTEST_LDADD}
 unittest_texttable_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS}
 check_PROGRAMS += unittest_texttable
 
-test_librbd_SOURCES = test/test_librbd.cc test/rados-api/test.cc
+test_librbd_SOURCES = test/librbd/test_librbd.cc test/rados-api/test.cc
 test_librbd_LDADD =  librbd.la librados.la ${UNITTEST_STATIC_LDADD}
 test_librbd_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS}
 bin_DEBUGPROGRAMS += test_librbd
 
-test_librbd_fsx_SOURCES = test/rbd/fsx.c
+test_librbd_fsx_SOURCES = test/librbd/fsx.c
 test_librbd_fsx_LDADD =  librbd.la librados.la -lm
 test_librbd_fsx_CFLAGS = ${AM_CFLAGS} -Wno-format
 bin_DEBUGPROGRAMS += test_librbd_fsx
 
-test_cls_rbd_SOURCES = test/rbd/test_cls_rbd.cc \
+test_cls_rbd_SOURCES = test/cls_rbd/test_cls_rbd.cc \
        test/rados-api/test.cc \
        cls/rbd/cls_rbd_client.cc \
        cls/lock/cls_lock_client.cc \
diff --git a/src/test/cls_rbd/test_cls_rbd.cc b/src/test/cls_rbd/test_cls_rbd.cc
new file mode 100644 (file)
index 0000000..a0ded60
--- /dev/null
@@ -0,0 +1,911 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "common/snap_types.h"
+#include "include/encoding.h"
+#include "include/rados.h"
+#include "include/rados/librados.h"
+#include "include/types.h"
+#include "librbd/cls_rbd.h"
+#include "librbd/cls_rbd_client.h"
+
+#include "gtest/gtest.h"
+#include "test/rados-api/test.h"
+
+#include <errno.h>
+#include <string>
+#include <vector>
+
+using namespace std;
+using ::librbd::cls_client::create_image;
+using ::librbd::cls_client::get_features;
+using ::librbd::cls_client::get_size;
+using ::librbd::cls_client::get_object_prefix;
+using ::librbd::cls_client::set_size;
+using ::librbd::cls_client::get_parent;
+using ::librbd::cls_client::set_parent;
+using ::librbd::cls_client::remove_parent;
+using ::librbd::cls_client::snapshot_add;
+using ::librbd::cls_client::snapshot_remove;
+using ::librbd::cls_client::add_child;
+using ::librbd::cls_client::remove_child;
+using ::librbd::cls_client::get_children;
+using ::librbd::cls_client::get_snapcontext;
+using ::librbd::cls_client::snapshot_list;
+using ::librbd::cls_client::copyup;
+using ::librbd::cls_client::get_id;
+using ::librbd::cls_client::set_id;
+using ::librbd::cls_client::dir_get_id;
+using ::librbd::cls_client::dir_get_name;
+using ::librbd::cls_client::dir_list;
+using ::librbd::cls_client::dir_add_image;
+using ::librbd::cls_client::dir_remove_image;
+using ::librbd::cls_client::dir_rename_image;
+using ::librbd::parent_info;
+using ::librbd::parent_spec;
+using ::librbd::cls_client::get_protection_status;
+using ::librbd::cls_client::set_protection_status;
+using ::librbd::cls_client::get_stripe_unit_count;
+using ::librbd::cls_client::set_stripe_unit_count;
+using ::librbd::cls_client::old_snapshot_add;
+
+static char *random_buf(size_t len)
+{
+  char *b = new char[len];
+  for (size_t i = 0; i < len; i++)
+    b[i] = (rand() % (128 - 32)) + 32;
+  return b;
+}
+
+TEST(cls_rbd, copyup)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  string oid = "rbd_copyup_test";
+  bufferlist inbl, outbl;
+
+  // copyup of 0-len nonexistent object should create new 0-len object
+  ioctx.remove(oid);
+  ASSERT_EQ(0, copyup(&ioctx, oid, inbl));
+  uint64_t size;
+  ASSERT_EQ(0, ioctx.stat(oid, &size, NULL));
+  ASSERT_EQ(0U, size);
+
+  // create some random data to write
+  size_t l = 4 << 20;
+  char *b = random_buf(l);
+  inbl.append(b, l);
+  delete b;
+  ASSERT_EQ(l, inbl.length());
+
+  // copyup to nonexistent object should create new object
+  ioctx.remove(oid);
+  ASSERT_EQ(-ENOENT, ioctx.remove(oid));
+  ASSERT_EQ(0, copyup(&ioctx, oid, inbl));
+  // and its contents should match
+  ASSERT_EQ(l, (size_t)ioctx.read(oid, outbl, l, 0));
+  ASSERT_TRUE(outbl.contents_equal(inbl));
+
+  // now send different data, but with a preexisting object
+  bufferlist inbl2;
+  b = random_buf(l);
+  inbl2.append(b, l);
+  delete b;
+  ASSERT_EQ(l, inbl2.length());
+
+  // should still succeed
+  ASSERT_EQ(0, copyup(&ioctx, oid, inbl));
+  ASSERT_EQ(l, (size_t)ioctx.read(oid, outbl, l, 0));
+  // but contents should not have changed
+  ASSERT_FALSE(outbl.contents_equal(inbl2));
+  ASSERT_TRUE(outbl.contents_equal(inbl));
+
+  ASSERT_EQ(0, ioctx.remove(oid));
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, get_and_set_id)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  string oid = "rbd_id_test";
+  string id;
+  string valid_id = "0123abcxyzZYXCBA";
+  string invalid_id = ".abc";
+  string empty_id;
+
+  ASSERT_EQ(-ENOENT, get_id(&ioctx, oid, &id));
+  ASSERT_EQ(-ENOENT, set_id(&ioctx, oid, valid_id));
+
+  ASSERT_EQ(0, ioctx.create(oid, true));
+  ASSERT_EQ(-EINVAL, set_id(&ioctx, oid, invalid_id));
+  ASSERT_EQ(-EINVAL, set_id(&ioctx, oid, empty_id));
+  ASSERT_EQ(-ENOENT, get_id(&ioctx, oid, &id));
+
+  ASSERT_EQ(0, set_id(&ioctx, oid, valid_id));
+  ASSERT_EQ(-EEXIST, set_id(&ioctx, oid, valid_id));
+  ASSERT_EQ(-EEXIST, set_id(&ioctx, oid, valid_id + valid_id));
+  ASSERT_EQ(0, get_id(&ioctx, oid, &id));
+  ASSERT_EQ(id, valid_id);
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, add_remove_child)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  string oid = "rbd_children_test";
+  ASSERT_EQ(0, ioctx.create(oid, true));
+
+  string snapname = "parent_snap";
+  snapid_t snapid(10);
+  string parent_image = "parent_id";
+  set<string>children;
+  parent_spec pspec(ioctx.get_id(), parent_image, snapid);
+
+  // nonexistent children cannot be listed or removed
+  ASSERT_EQ(-ENOENT, get_children(&ioctx, oid, pspec, children));
+  ASSERT_EQ(-ENOENT, remove_child(&ioctx, oid, pspec, "child1"));
+
+  // create the parent and snapshot
+  ASSERT_EQ(0, create_image(&ioctx, parent_image, 2<<20, 0,
+                           RBD_FEATURE_LAYERING, parent_image));
+  ASSERT_EQ(0, snapshot_add(&ioctx, parent_image, snapid, snapname));
+
+  // add child to it, verify it showed up
+  ASSERT_EQ(0, add_child(&ioctx, oid, pspec, "child1"));
+  ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children));
+  ASSERT_TRUE(children.find("child1") != children.end());
+  // add another child to it, verify it showed up
+  ASSERT_EQ(0, add_child(&ioctx, oid, pspec, "child2"));
+  ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children));
+  ASSERT_TRUE(children.find("child2") != children.end());
+  // add child2 again, expect -EEXIST
+  ASSERT_EQ(-EEXIST, add_child(&ioctx, oid, pspec, "child2"));
+  // remove first, verify it's gone
+  ASSERT_EQ(0, remove_child(&ioctx, oid, pspec, "child1"));
+  ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children));
+  ASSERT_FALSE(children.find("child1") != children.end());
+  // remove second, verify list empty
+  ASSERT_EQ(0, remove_child(&ioctx, oid, pspec, "child2"));
+  ASSERT_EQ(-ENOENT, get_children(&ioctx, oid, pspec, children));
+  // try to remove again, validate -ENOENT to that as well
+  ASSERT_EQ(-ENOENT, remove_child(&ioctx, oid, pspec, "child2"));
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, directory_methods)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  string oid = "rbd_id_test";
+  string id, name;
+  string imgname = "bar";
+  string imgname2 = "foo";
+  string imgname3 = "baz";
+  string valid_id = "0123abcxyzZYXCBA";
+  string valid_id2 = "5";
+  string invalid_id = ".abc";
+  string empty;
+
+  ASSERT_EQ(-ENOENT, dir_get_id(&ioctx, oid, imgname, &id));
+  ASSERT_EQ(-ENOENT, dir_get_name(&ioctx, oid, valid_id, &name));
+  ASSERT_EQ(-ENOENT, dir_remove_image(&ioctx, oid, imgname, valid_id));
+
+  ASSERT_EQ(-EINVAL, dir_add_image(&ioctx, oid, imgname, invalid_id));
+  ASSERT_EQ(-EINVAL, dir_add_image(&ioctx, oid, imgname, empty));
+  ASSERT_EQ(-EINVAL, dir_add_image(&ioctx, oid, empty, valid_id));
+
+  map<string, string> images;
+  ASSERT_EQ(-ENOENT, dir_list(&ioctx, oid, "", 30, &images));
+
+  ASSERT_EQ(0, ioctx.create(oid, true));
+  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
+  ASSERT_EQ(0u, images.size());
+  ASSERT_EQ(0, ioctx.remove(oid));
+
+  ASSERT_EQ(0, dir_add_image(&ioctx, oid, imgname, valid_id));
+  ASSERT_EQ(-EEXIST, dir_add_image(&ioctx, oid, imgname, valid_id2));
+  ASSERT_EQ(-EBADF, dir_add_image(&ioctx, oid, imgname2, valid_id));
+  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
+  ASSERT_EQ(1u, images.size());
+  ASSERT_EQ(valid_id, images[imgname]);
+  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 0, &images));
+  ASSERT_EQ(0u, images.size());
+  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id, &name));
+  ASSERT_EQ(imgname, name);
+  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname, &id));
+  ASSERT_EQ(valid_id, id);
+
+  ASSERT_EQ(0, dir_add_image(&ioctx, oid, imgname2, valid_id2));
+  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
+  ASSERT_EQ(2u, images.size());
+  ASSERT_EQ(valid_id, images[imgname]);
+  ASSERT_EQ(valid_id2, images[imgname2]);
+  ASSERT_EQ(0, dir_list(&ioctx, oid, imgname, 0, &images));
+  ASSERT_EQ(0u, images.size());
+  ASSERT_EQ(0, dir_list(&ioctx, oid, imgname, 2, &images));
+  ASSERT_EQ(1u, images.size());
+  ASSERT_EQ(valid_id2, images[imgname2]);
+  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id2, &name));
+  ASSERT_EQ(imgname2, name);
+  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname2, &id));
+  ASSERT_EQ(valid_id2, id);
+
+  ASSERT_EQ(-ESTALE, dir_rename_image(&ioctx, oid, imgname, imgname2, valid_id2));
+  ASSERT_EQ(-ESTALE, dir_remove_image(&ioctx, oid, imgname, valid_id2));
+  ASSERT_EQ(-EEXIST, dir_rename_image(&ioctx, oid, imgname, imgname2, valid_id));
+  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname, &id));
+  ASSERT_EQ(valid_id, id);
+  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id2, &name));
+  ASSERT_EQ(imgname2, name);
+
+  ASSERT_EQ(0, dir_rename_image(&ioctx, oid, imgname, imgname3, valid_id));
+  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname3, &id));
+  ASSERT_EQ(valid_id, id);
+  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id, &name));
+  ASSERT_EQ(imgname3, name);
+  ASSERT_EQ(0, dir_rename_image(&ioctx, oid, imgname3, imgname, valid_id));
+
+  ASSERT_EQ(0, dir_remove_image(&ioctx, oid, imgname, valid_id));
+  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
+  ASSERT_EQ(1u, images.size());
+  ASSERT_EQ(valid_id2, images[imgname2]);
+  ASSERT_EQ(0, dir_list(&ioctx, oid, imgname2, 30, &images));
+  ASSERT_EQ(0u, images.size());
+  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id2, &name));
+  ASSERT_EQ(imgname2, name);
+  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname2, &id));
+  ASSERT_EQ(valid_id2, id);
+  ASSERT_EQ(-ENOENT, dir_get_name(&ioctx, oid, valid_id, &name));
+  ASSERT_EQ(-ENOENT, dir_get_id(&ioctx, oid, imgname, &id));
+
+  ASSERT_EQ(0, dir_add_image(&ioctx, oid, imgname, valid_id));
+  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
+  ASSERT_EQ(2u, images.size());
+  ASSERT_EQ(valid_id, images[imgname]);
+  ASSERT_EQ(valid_id2, images[imgname2]);
+  ASSERT_EQ(0, dir_remove_image(&ioctx, oid, imgname, valid_id));
+  ASSERT_EQ(-ENOENT, dir_remove_image(&ioctx, oid, imgname, valid_id));
+  ASSERT_EQ(0, dir_remove_image(&ioctx, oid, imgname2, valid_id2));
+  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
+  ASSERT_EQ(0u, images.size());
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, create)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  string oid = "testobj";
+  uint64_t size = 20 << 30;
+  uint64_t features = 0;
+  uint8_t order = 22;
+  string object_prefix = "foo";
+
+  ASSERT_EQ(0, create_image(&ioctx, oid, size, order,
+                           features, object_prefix));
+  ASSERT_EQ(-EEXIST, create_image(&ioctx, oid, size, order,
+                                 features, object_prefix));
+  ASSERT_EQ(0, ioctx.remove(oid));
+
+  ASSERT_EQ(-EINVAL, create_image(&ioctx, oid, size, order,
+                                 features, ""));
+  ASSERT_EQ(-ENOENT, ioctx.remove(oid));
+
+  ASSERT_EQ(0, create_image(&ioctx, oid, 0, order,
+                           features, object_prefix));
+  ASSERT_EQ(0, ioctx.remove(oid));
+
+  ASSERT_EQ(-ENOSYS, create_image(&ioctx, oid, size, order,
+                                 -1, object_prefix));
+  ASSERT_EQ(-ENOENT, ioctx.remove(oid));
+
+  bufferlist inbl, outbl;
+  ASSERT_EQ(-EINVAL, ioctx.exec(oid, "rbd", "create", inbl, outbl));
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, get_features)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  uint64_t features;
+  ASSERT_EQ(-ENOENT, get_features(&ioctx, "foo", CEPH_NOSNAP, &features));
+
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, 0, "foo"));
+  ASSERT_EQ(0, get_features(&ioctx, "foo", CEPH_NOSNAP, &features));
+  ASSERT_EQ(0u, features);
+
+  ASSERT_EQ(-ENOENT, get_features(&ioctx, "foo", 1, &features));
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, get_object_prefix)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  string object_prefix;
+  ASSERT_EQ(-ENOENT, get_object_prefix(&ioctx, "foo", &object_prefix));
+
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, 0, "foo"));
+  ASSERT_EQ(0, get_object_prefix(&ioctx, "foo", &object_prefix));
+  ASSERT_EQ("foo", object_prefix);
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, get_size)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  uint64_t size;
+  uint8_t order;
+  ASSERT_EQ(-ENOENT, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
+
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, 0, "foo"));
+  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
+  ASSERT_EQ(0u, size);
+  ASSERT_EQ(22, order);
+  ASSERT_EQ(0, ioctx.remove("foo"));
+
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 2 << 22, 0, 0, "foo"));
+  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
+  ASSERT_EQ(2u << 22, size);
+  ASSERT_EQ(0, order);
+
+  ASSERT_EQ(-ENOENT, get_size(&ioctx, "foo", 1, &size, &order));
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, set_size)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  ASSERT_EQ(-ENOENT, set_size(&ioctx, "foo", 5));
+
+  uint64_t size;
+  uint8_t order;
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, 0, "foo"));
+  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
+  ASSERT_EQ(0u, size);
+  ASSERT_EQ(22, order);
+
+  ASSERT_EQ(0, set_size(&ioctx, "foo", 0));
+  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
+  ASSERT_EQ(0u, size);
+  ASSERT_EQ(22, order);
+
+  ASSERT_EQ(0, set_size(&ioctx, "foo", 3 << 22));
+  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
+  ASSERT_EQ(3u << 22, size);
+  ASSERT_EQ(22, order);
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, protection_status)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  uint8_t status = RBD_PROTECTION_STATUS_UNPROTECTED;
+  ASSERT_EQ(-ENOENT, get_protection_status(&ioctx, "foo",
+                                          CEPH_NOSNAP, &status));
+  ASSERT_EQ(-ENOENT, set_protection_status(&ioctx, "foo",
+                                          CEPH_NOSNAP, status));
+
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, RBD_FEATURE_LAYERING, "foo"));
+  ASSERT_EQ(0, create_image(&ioctx, "bar", 0, 22, 0, "foo"));
+  ASSERT_EQ(-EINVAL, get_protection_status(&ioctx, "bar",
+                                          CEPH_NOSNAP, &status));
+  ASSERT_EQ(-ENOEXEC, set_protection_status(&ioctx, "bar",
+                                          CEPH_NOSNAP, status));
+  ASSERT_EQ(-EINVAL, get_protection_status(&ioctx, "foo",
+                                          CEPH_NOSNAP, &status));
+  ASSERT_EQ(-EINVAL, set_protection_status(&ioctx, "foo",
+                                          CEPH_NOSNAP, status));
+  ASSERT_EQ(-ENOENT, get_protection_status(&ioctx, "foo",
+                                          2, &status));
+  ASSERT_EQ(-ENOENT, set_protection_status(&ioctx, "foo",
+                                          2, status));
+
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 10, "snap1"));
+  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
+                                    10, &status));
+  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTED, status);
+
+  ASSERT_EQ(0, set_protection_status(&ioctx, "foo",
+                                    10, RBD_PROTECTION_STATUS_PROTECTED));
+  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
+                                    10, &status));
+  ASSERT_EQ(RBD_PROTECTION_STATUS_PROTECTED, status);
+  ASSERT_EQ(-EBUSY, snapshot_remove(&ioctx, "foo", 10));
+
+  ASSERT_EQ(0, set_protection_status(&ioctx, "foo",
+                                    10, RBD_PROTECTION_STATUS_UNPROTECTING));
+  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
+                                    10, &status));
+  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTING, status);
+  ASSERT_EQ(-EBUSY, snapshot_remove(&ioctx, "foo", 10));
+
+  ASSERT_EQ(-EINVAL, set_protection_status(&ioctx, "foo",
+                                          10, RBD_PROTECTION_STATUS_LAST));
+  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
+                                    10, &status));
+  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTING, status);
+
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 20, "snap2"));
+  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
+                                    20, &status));
+  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTED, status);
+  ASSERT_EQ(0, set_protection_status(&ioctx, "foo",
+                                    10, RBD_PROTECTION_STATUS_UNPROTECTED));
+  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
+                                    10, &status));
+  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTED, status);
+
+  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", 10));
+  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", 20));
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, parents)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  parent_spec pspec;
+  uint64_t size;
+
+  ASSERT_EQ(-ENOENT, get_parent(&ioctx, "doesnotexist", CEPH_NOSNAP, &pspec, &size));
+
+  // old image should fail
+  ASSERT_EQ(0, create_image(&ioctx, "old", 33<<20, 22, 0, "old_blk."));
+  // get nonexistent parent: succeed, return (-1, "", CEPH_NOSNAP), overlap 0
+  ASSERT_EQ(0, get_parent(&ioctx, "old", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, -1);
+  ASSERT_STREQ("", pspec.image_id.c_str());
+  ASSERT_EQ(pspec.snap_id, CEPH_NOSNAP);
+  ASSERT_EQ(size, 0ULL);
+  pspec = parent_spec(-1, "parent", 3);
+  ASSERT_EQ(-ENOEXEC, set_parent(&ioctx, "old", parent_spec(-1, "parent", 3), 10<<20));
+  ASSERT_EQ(-ENOEXEC, remove_parent(&ioctx, "old"));
+
+  // new image will work
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 33<<20, 22, RBD_FEATURE_LAYERING, "foo."));
+
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(-1, pspec.pool_id);
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 123, &pspec, &size));
+  ASSERT_EQ(-1, pspec.pool_id);
+
+  ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(-1, "parent", 3), 10<<20));
+  ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "", 3), 10<<20));
+  ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "parent", CEPH_NOSNAP), 10<<20));
+  ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 0));
+
+  pspec = parent_spec(1, "parent", 3);
+  ASSERT_EQ(0, set_parent(&ioctx, "foo", pspec, 10<<20));
+  ASSERT_EQ(-EEXIST, set_parent(&ioctx, "foo", pspec, 10<<20));
+  ASSERT_EQ(-EEXIST, set_parent(&ioctx, "foo", parent_spec(2, "parent", 34), 10<<20));
+
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+
+  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
+  ASSERT_EQ(-ENOENT, remove_parent(&ioctx, "foo"));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(-1, pspec.pool_id);
+
+  // snapshots
+  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 10<<20));
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 10, "snap1"));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 10ull<<20);
+
+  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
+  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(4, "parent2", 6), 5<<20));
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 11, "snap2"));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 10ull<<20);
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 11, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 4);
+  ASSERT_EQ(pspec.image_id, "parent2");
+  ASSERT_EQ(pspec.snap_id, snapid_t(6));
+  ASSERT_EQ(size, 5ull<<20);
+
+  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 12, "snap3"));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 10ull<<20);
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 11, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 4);
+  ASSERT_EQ(pspec.image_id, "parent2");
+  ASSERT_EQ(pspec.snap_id, snapid_t(6));
+  ASSERT_EQ(size, 5ull<<20);
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 12, &pspec, &size));
+  ASSERT_EQ(-1, pspec.pool_id);
+
+  // make sure set_parent takes min of our size and parent's size
+  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 1<<20));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 1ull<<20);
+  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
+
+  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 100<<20));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 33ull<<20);
+  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
+
+  // make sure resize adjust parent overlap
+  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 10<<20));
+
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 14, "snap4"));
+  ASSERT_EQ(0, set_size(&ioctx, "foo", 3 << 20));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 3ull<<20);
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 14, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 10ull<<20);
+
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 15, "snap5"));
+  ASSERT_EQ(0, set_size(&ioctx, "foo", 30 << 20));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 3ull<<20);
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 14, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 10ull<<20);
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 15, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 3ull<<20);
+
+  ASSERT_EQ(0, set_size(&ioctx, "foo", 2 << 20));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 2ull<<20);
+
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 16, "snap6"));
+  ASSERT_EQ(0, get_parent(&ioctx, "foo", 16, &pspec, &size));
+  ASSERT_EQ(pspec.pool_id, 1);
+  ASSERT_EQ(pspec.image_id, "parent");
+  ASSERT_EQ(pspec.snap_id, snapid_t(3));
+  ASSERT_EQ(size, 2ull<<20);
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, snapshots)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  ASSERT_EQ(-ENOENT, snapshot_add(&ioctx, "foo", 0, "snap1"));
+
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 10, 22, 0, "foo"));
+
+  vector<string> snap_names;
+  vector<uint64_t> snap_sizes;
+  vector<uint64_t> snap_features;
+  SnapContext snapc;
+  vector<parent_info> parents;
+  vector<uint8_t> protection_status;
+
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(0u, snapc.snaps.size());
+  ASSERT_EQ(0u, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(0u, snap_names.size());
+  ASSERT_EQ(0u, snap_sizes.size());
+  ASSERT_EQ(0u, snap_features.size());
+
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 0, "snap1"));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(1u, snapc.snaps.size());
+  ASSERT_EQ(0u, snapc.snaps[0]);
+  ASSERT_EQ(0u, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(1u, snap_names.size());
+  ASSERT_EQ("snap1", snap_names[0]);
+  ASSERT_EQ(10u, snap_sizes[0]);
+  ASSERT_EQ(0u, snap_features[0]);
+
+  // snap with same id and name
+  ASSERT_EQ(-EEXIST, snapshot_add(&ioctx, "foo", 0, "snap1"));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(1u, snapc.snaps.size());
+  ASSERT_EQ(0u, snapc.snaps[0]);
+  ASSERT_EQ(0u, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(1u, snap_names.size());
+  ASSERT_EQ("snap1", snap_names[0]);
+  ASSERT_EQ(10u, snap_sizes[0]);
+  ASSERT_EQ(0u, snap_features[0]);
+
+  // snap with same id, different name
+  ASSERT_EQ(-EEXIST, snapshot_add(&ioctx, "foo", 0, "snap2"));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(1u, snapc.snaps.size());
+  ASSERT_EQ(0u, snapc.snaps[0]);
+  ASSERT_EQ(0u, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(1u, snap_names.size());
+  ASSERT_EQ("snap1", snap_names[0]);
+  ASSERT_EQ(10u, snap_sizes[0]);
+  ASSERT_EQ(0u, snap_features[0]);
+
+  // snap with different id, same name
+  ASSERT_EQ(-EEXIST, snapshot_add(&ioctx, "foo", 1, "snap1"));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(1u, snapc.snaps.size());
+  ASSERT_EQ(0u, snapc.snaps[0]);
+  ASSERT_EQ(0u, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(snap_names.size(), 1u);
+  ASSERT_EQ(snap_names[0], "snap1");
+  ASSERT_EQ(snap_sizes[0], 10u);
+  ASSERT_EQ(snap_features[0], 0u);
+
+  // snap with different id, different name
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 1, "snap2"));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(2u, snapc.snaps.size());
+  ASSERT_EQ(1u, snapc.snaps[0]);
+  ASSERT_EQ(0u, snapc.snaps[1]);
+  ASSERT_EQ(1u, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(2u, snap_names.size());
+  ASSERT_EQ("snap2", snap_names[0]);
+  ASSERT_EQ(10u, snap_sizes[0]);
+  ASSERT_EQ(0u, snap_features[0]);
+  ASSERT_EQ("snap1", snap_names[1]);
+  ASSERT_EQ(10u, snap_sizes[1]);
+  ASSERT_EQ(0u, snap_features[1]);
+
+  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", 0));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(1u, snapc.snaps.size());
+  ASSERT_EQ(1u, snapc.snaps[0]);
+  ASSERT_EQ(1u, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(1u, snap_names.size());
+  ASSERT_EQ("snap2", snap_names[0]);
+  ASSERT_EQ(10u, snap_sizes[0]);
+  ASSERT_EQ(0u, snap_features[0]);
+
+  uint64_t size;
+  uint8_t order;
+  ASSERT_EQ(0, set_size(&ioctx, "foo", 0));
+  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
+  ASSERT_EQ(0u, size);
+  ASSERT_EQ(22u, order);
+
+  uint64_t large_snap_id = 1ull << 63;
+  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", large_snap_id, "snap3"));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(2u, snapc.snaps.size());
+  ASSERT_EQ(large_snap_id, snapc.snaps[0]);
+  ASSERT_EQ(1u, snapc.snaps[1]);
+  ASSERT_EQ(large_snap_id, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(2u, snap_names.size());
+  ASSERT_EQ("snap3", snap_names[0]);
+  ASSERT_EQ(0u, snap_sizes[0]);
+  ASSERT_EQ(0u, snap_features[0]);
+  ASSERT_EQ("snap2", snap_names[1]);
+  ASSERT_EQ(10u, snap_sizes[1]);
+  ASSERT_EQ(0u, snap_features[1]);
+
+  ASSERT_EQ(0, get_size(&ioctx, "foo", large_snap_id, &size, &order));
+  ASSERT_EQ(0u, size);
+  ASSERT_EQ(22u, order);
+
+  ASSERT_EQ(0, get_size(&ioctx, "foo", 1, &size, &order));
+  ASSERT_EQ(10u, size);
+  ASSERT_EQ(22u, order);
+
+  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", large_snap_id));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(1u, snapc.snaps.size());
+  ASSERT_EQ(1u, snapc.snaps[0]);
+  ASSERT_EQ(large_snap_id, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(1u, snap_names.size());
+  ASSERT_EQ("snap2", snap_names[0]);
+  ASSERT_EQ(10u, snap_sizes[0]);
+  ASSERT_EQ(0u, snap_features[0]);
+
+  ASSERT_EQ(-ENOENT, snapshot_remove(&ioctx, "foo", large_snap_id));
+  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", 1));
+  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
+  ASSERT_EQ(0u, snapc.snaps.size());
+  ASSERT_EQ(large_snap_id, snapc.seq);
+  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
+                            &snap_sizes, &snap_features, &parents,
+                            &protection_status));
+  ASSERT_EQ(0u, snap_names.size());
+  ASSERT_EQ(0u, snap_sizes.size());
+  ASSERT_EQ(0u, snap_features.size());
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, snapid_race)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  buffer::list bl;
+  buffer::ptr bp(4096);
+  bp.zero();
+  bl.append(bp);
+
+  string oid = "foo";
+  ASSERT_EQ(4096, ioctx.write(oid, bl, 4096, 0));
+  ASSERT_EQ(0, old_snapshot_add(&ioctx, oid, 1, "test1"));
+  ASSERT_EQ(0, old_snapshot_add(&ioctx, oid, 3, "test3"));
+  ASSERT_EQ(-ESTALE, old_snapshot_add(&ioctx, oid, 2, "test2"));
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(cls_rbd, stripingv2)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  ASSERT_EQ(0, create_image(&ioctx, "foo", 10, 22, 0, "foo"));
+
+  uint64_t su = 65536, sc = 12;
+  ASSERT_EQ(-ENOEXEC, get_stripe_unit_count(&ioctx, "foo", &su, &sc));
+  ASSERT_EQ(-ENOEXEC, set_stripe_unit_count(&ioctx, "foo", su, sc));
+  
+  ASSERT_EQ(0, create_image(&ioctx, "bar", 10, 22, RBD_FEATURE_STRIPINGV2, "bar"));
+  ASSERT_EQ(0, get_stripe_unit_count(&ioctx, "bar", &su, &sc));
+  ASSERT_EQ(1ull << 22, su);
+  ASSERT_EQ(1ull, sc);
+  su = 8192;
+  sc = 456;
+  ASSERT_EQ(0, set_stripe_unit_count(&ioctx, "bar", su, sc));
+  su = sc = 0;
+  ASSERT_EQ(0, get_stripe_unit_count(&ioctx, "bar", &su, &sc));
+  ASSERT_EQ(8192ull, su);
+  ASSERT_EQ(456ull, sc);
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
diff --git a/src/test/librbd/fsx.c b/src/test/librbd/fsx.c
new file mode 100644 (file)
index 0000000..f895b5f
--- /dev/null
@@ -0,0 +1,1551 @@
+// -*- mode:C; tab-width:8; c-basic-offset:8; indent-tabs-mode:t -*- 
+/*
+ *     Copyright (C) 1991, NeXT Computer, Inc.  All Rights Reserverd.
+ *
+ *     File:   fsx.c
+ *     Author: Avadis Tevanian, Jr.
+ *
+ *     File system exerciser. 
+ *
+ *     Rewritten 8/98 by Conrad Minshall.
+ *
+ *     Small changes to work under Linux -- davej.
+ *
+ *     Checks for mmap last-page zero fill.
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <limits.h>
+#include <time.h>
+#include <strings.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#ifdef HAVE_ERR_H
+#include <err.h>
+#endif
+#include <signal.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <math.h>
+
+#include "include/rados/librados.h"
+#include "include/rbd/librbd.h"
+
+#define NUMPRINTCOLUMNS 32     /* # columns of data to print on each line */
+
+/*
+ *     A log entry is an operation and a bunch of arguments.
+ */
+
+struct log_entry {
+       int     operation;
+       int     args[3];
+};
+
+#define        LOGSIZE 1000
+
+struct log_entry       oplog[LOGSIZE]; /* the log */
+int                    logptr = 0;     /* current position in log */
+int                    logcount = 0;   /* total ops */
+
+/*
+ * The operation matrix is complex due to conditional execution of different
+ * features. Hence when we come to deciding what operation to run, we need to
+ * be careful in how we select the different operations. The active operations
+ * are mapped to numbers as follows:
+ *
+ *             lite    !lite
+ * READ:       0       0
+ * WRITE:      1       1
+ * MAPREAD:    2       2
+ * MAPWRITE:   3       3
+ * TRUNCATE:   -       4
+ * FALLOCATE:  -       5
+ * PUNCH HOLE: -       6
+ *
+ * When mapped read/writes are disabled, they are simply converted to normal
+ * reads and writes. When fallocate/fpunch calls are disabled, they are
+ * converted to OP_SKIPPED. Hence OP_SKIPPED needs to have a number higher than
+ * the operation selction matrix, as does the OP_CLOSEOPEN which is an
+ * operation modifier rather than an operation in itself.
+ *
+ * Because of the "lite" version, we also need to have different "maximum
+ * operation" defines to allow the ops to be selected correctly based on the
+ * mode being run.
+ */
+
+/* common operations */
+#define        OP_READ         0
+#define OP_WRITE       1
+#define OP_MAPREAD     2
+#define OP_MAPWRITE    3
+#define OP_MAX_LITE    4
+
+/* !lite operations */
+#define OP_TRUNCATE    4
+#define OP_FALLOCATE   5
+#define OP_PUNCH_HOLE  6
+/* rbd-specific operations */
+#define OP_CLONE        7
+#define OP_MAX_FULL    8
+
+/* operation modifiers */
+#define OP_CLOSEOPEN   100
+#define OP_SKIPPED     101
+
+#undef PAGE_SIZE
+#define PAGE_SIZE       getpagesize()
+#undef PAGE_MASK
+#define PAGE_MASK       (PAGE_SIZE - 1)
+
+char   *original_buf;                  /* a pointer to the original data */
+char   *good_buf;                      /* a pointer to the correct data */
+char   *temp_buf;                      /* a pointer to the current data */
+char   *pool;                          /* name of the pool our test image is in */
+char   *iname;                         /* name of our test image */
+rados_t cluster;                        /* handle for our test cluster */
+rados_ioctx_t  ioctx;                  /* handle for our test pool */
+rbd_image_t    image;                  /* handle for our test image */
+
+char   dirpath[1024];
+
+off_t          file_size = 0;
+off_t          biggest = 0;
+char           state[256];
+unsigned long  testcalls = 0;          /* calls to function "test" */
+
+unsigned long  simulatedopcount = 0;   /* -b flag */
+int    closeprob = 0;                  /* -c flag */
+int    debug = 0;                      /* -d flag */
+unsigned long  debugstart = 0;         /* -D flag */
+int    flush = 0;                      /* -f flag */
+int    do_fsync = 0;                   /* -y flag */
+unsigned long  maxfilelen = 256 * 1024;        /* -l flag */
+int    sizechecks = 1;                 /* -n flag disables them */
+int    maxoplen = 64 * 1024;           /* -o flag */
+int    quiet = 0;                      /* -q flag */
+unsigned long progressinterval = 0;    /* -p flag */
+int    readbdy = 1;                    /* -r flag */
+int    style = 0;                      /* -s flag */
+int    prealloc = 0;                   /* -x flag */
+int    truncbdy = 1;                   /* -t flag */
+int    writebdy = 1;                   /* -w flag */
+long   monitorstart = -1;              /* -m flag */
+long   monitorend = -1;                /* -m flag */
+int    lite = 0;                       /* -L flag */
+long   numops = -1;                    /* -N flag */
+int    randomoplen = 1;                /* -O flag disables it */
+int    seed = 1;                       /* -S flag */
+int     mapped_writes = 0;              /* -W flag disables */
+int     fallocate_calls = 0;            /* -F flag disables */
+int     punch_hole_calls = 1;           /* -H flag disables */
+int    clone_calls = 1;                /* -C flag disables */
+int     randomize_striping = 1;
+int    mapped_reads = 0;               /* -R flag disables it */
+int    fsxgoodfd = 0;
+int    o_direct;                       /* -Z */
+int    aio = 0;
+
+int num_clones = 0;
+
+int page_size;
+int page_mask;
+int mmap_mask;
+#ifdef AIO
+int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset);
+#define READ 0
+#define WRITE 1
+#define fsxread(a,b,c,d)       aio_rw(READ, a,b,c,d)
+#define fsxwrite(a,b,c,d)      aio_rw(WRITE, a,b,c,d)
+#else
+#define fsxread(a,b,c,d)       read(a,b,c)
+#define fsxwrite(a,b,c,d)      write(a,b,c)
+#endif
+
+FILE * fsxlogf = NULL;
+int badoff = -1;
+int closeopen = 0;
+
+static void *round_ptr_up(void *ptr, unsigned long align, unsigned long offset)
+{
+       unsigned long ret = (unsigned long)ptr;
+
+       ret = ((ret + align - 1) & ~(align - 1));
+       ret += offset;
+       return (void *)ret;
+}
+
+void
+vwarnc(int code, const char *fmt, va_list ap) {
+  fprintf(stderr, "fsx: ");
+  if (fmt != NULL) {
+       vfprintf(stderr, fmt, ap);
+       fprintf(stderr, ": ");
+  }
+  fprintf(stderr, "%s\n", strerror(code));
+}
+
+void
+warn(const char * fmt, ...)  {
+       va_list ap;
+       va_start(ap, fmt);
+       vwarnc(errno, fmt, ap);
+       va_end(ap);
+}
+
+#define BUF_SIZE 1024
+
+void
+prt(char *fmt, ...)
+{
+       va_list args;
+       char buffer[BUF_SIZE];
+
+       va_start(args, fmt);
+       vsnprintf(buffer, BUF_SIZE, fmt, args);
+       va_end(args);
+       fprintf(stdout, buffer);
+       if (fsxlogf)
+               fprintf(fsxlogf, buffer);
+}
+
+void
+prterr(char *prefix)
+{
+       prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno));
+}
+
+void
+log4(int operation, int arg0, int arg1, int arg2)
+{
+       struct log_entry *le;
+
+       le = &oplog[logptr];
+       le->operation = operation;
+       if (closeopen)
+               le->operation = ~ le->operation;
+       le->args[0] = arg0;
+       le->args[1] = arg1;
+       le->args[2] = arg2;
+       logptr++;
+       logcount++;
+       if (logptr >= LOGSIZE)
+               logptr = 0;
+}
+
+void
+simple_err(const char *msg, int err)
+{
+    fprintf(stderr, "%s: %s\n", msg, strerror(-err));
+}
+
+void
+logdump(void)
+{
+       int     i, count, down;
+       struct log_entry        *lp;
+       char *falloc_type[3] = {"PAST_EOF", "EXTENDING", "INTERIOR"};
+
+       prt("LOG DUMP (%d total operations):\n", logcount);
+       if (logcount < LOGSIZE) {
+               i = 0;
+               count = logcount;
+       } else {
+               i = logptr;
+               count = LOGSIZE;
+       }
+       for ( ; count > 0; count--) {
+               int opnum;
+
+               opnum = i+1 + (logcount/LOGSIZE)*LOGSIZE;
+               prt("%d(%3d mod 256): ", opnum, opnum%256);
+               lp = &oplog[i];
+               if ((closeopen = lp->operation < 0))
+                       lp->operation = ~ lp->operation;
+                       
+               switch (lp->operation) {
+               case OP_MAPREAD:
+                       prt("MAPREAD  0x%x thru 0x%x\t(0x%x bytes)",
+                           lp->args[0], lp->args[0] + lp->args[1] - 1,
+                           lp->args[1]);
+                       if (badoff >= lp->args[0] && badoff <
+                                                    lp->args[0] + lp->args[1])
+                               prt("\t***RRRR***");
+                       break;
+               case OP_MAPWRITE:
+                       prt("MAPWRITE 0x%x thru 0x%x\t(0x%x bytes)",
+                           lp->args[0], lp->args[0] + lp->args[1] - 1,
+                           lp->args[1]);
+                       if (badoff >= lp->args[0] && badoff <
+                                                    lp->args[0] + lp->args[1])
+                               prt("\t******WWWW");
+                       break;
+               case OP_READ:
+                       prt("READ     0x%x thru 0x%x\t(0x%x bytes)",
+                           lp->args[0], lp->args[0] + lp->args[1] - 1,
+                           lp->args[1]);
+                       if (badoff >= lp->args[0] &&
+                           badoff < lp->args[0] + lp->args[1])
+                               prt("\t***RRRR***");
+                       break;
+               case OP_WRITE:
+                       prt("WRITE    0x%x thru 0x%x\t(0x%x bytes)",
+                           lp->args[0], lp->args[0] + lp->args[1] - 1,
+                           lp->args[1]);
+                       if (lp->args[0] > lp->args[2])
+                               prt(" HOLE");
+                       else if (lp->args[0] + lp->args[1] > lp->args[2])
+                               prt(" EXTEND");
+                       if ((badoff >= lp->args[0] || badoff >=lp->args[2]) &&
+                           badoff < lp->args[0] + lp->args[1])
+                               prt("\t***WWWW");
+                       break;
+               case OP_TRUNCATE:
+                       down = lp->args[0] < lp->args[1];
+                       prt("TRUNCATE %s\tfrom 0x%x to 0x%x",
+                           down ? "DOWN" : "UP", lp->args[1], lp->args[0]);
+                       if (badoff >= lp->args[!down] &&
+                           badoff < lp->args[!!down])
+                               prt("\t******WWWW");
+                       break;
+               case OP_FALLOCATE:
+                       /* 0: offset 1: length 2: where alloced */
+                       prt("FALLOC   0x%x thru 0x%x\t(0x%x bytes) %s",
+                               lp->args[0], lp->args[0] + lp->args[1],
+                               lp->args[1], falloc_type[lp->args[2]]);
+                       if (badoff >= lp->args[0] &&
+                           badoff < lp->args[0] + lp->args[1])
+                               prt("\t******FFFF");
+                       break;
+               case OP_PUNCH_HOLE:
+                       prt("PUNCH    0x%x thru 0x%x\t(0x%x bytes)",
+                           lp->args[0], lp->args[0] + lp->args[1] - 1,
+                           lp->args[1]);
+                       if (badoff >= lp->args[0] && badoff <
+                                                    lp->args[0] + lp->args[1])
+                               prt("\t******PPPP");
+                       break;
+               case OP_CLONE:
+                       prt("CLONE");
+                       break;
+               case OP_SKIPPED:
+                       prt("SKIPPED (no operation)");
+                       break;
+               default:
+                       prt("BOGUS LOG ENTRY (operation code = %d)!",
+                           lp->operation);
+               }
+               if (closeopen)
+                       prt("\n\t\tCLOSE/OPEN");
+               prt("\n");
+               i++;
+               if (i == LOGSIZE)
+                       i = 0;
+       }
+}
+
+void
+prterrcode(char *prefix, int code)
+{
+       prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(-code));
+}
+
+void
+save_buffer(char *buffer, off_t bufferlength, int fd)
+{
+       off_t ret;
+       ssize_t byteswritten;
+
+       if (fd <= 0 || bufferlength == 0)
+               return;
+
+       if (bufferlength > SSIZE_MAX) {
+               prt("fsx flaw: overflow in save_buffer\n");
+               exit(67);
+       }
+
+       ret = lseek(fd, (off_t)0, SEEK_SET);
+       if (ret == (off_t)-1)
+               prterr("save_buffer: lseek 0");
+       
+       byteswritten = write(fd, buffer, (size_t)bufferlength);
+       if (byteswritten != bufferlength) {
+               if (byteswritten == -1)
+                       prterr("save_buffer write");
+               else
+                       warn("save_buffer: short write, 0x%x bytes instead of 0x%llx\n",
+                            (unsigned)byteswritten,
+                            (unsigned long long)bufferlength);
+       }
+}
+
+
+void
+report_failure(int status)
+{
+       logdump();
+       
+       if (fsxgoodfd) {
+               if (good_buf) {
+                       save_buffer(good_buf, file_size, fsxgoodfd);
+                       prt("Correct content saved for comparison\n");
+                       prt("(maybe hexdump \"%s\" vs \"%s.fsxgood\")\n",
+                           iname, iname);
+               }
+               close(fsxgoodfd);
+       }
+       sleep(3);   // so the log can flush to disk.  KLUDGEY!
+       exit(status);
+}
+
+#define short_at(cp) ((unsigned short)((*((unsigned char *)(cp)) << 8) | \
+                                       *(((unsigned char *)(cp)) + 1)))
+
+void
+check_buffers(unsigned offset, unsigned size)
+{
+       unsigned char c, t;
+       unsigned i = 0;
+       unsigned n = 0;
+       unsigned op = 0;
+       unsigned bad = 0;
+
+       if (memcmp(good_buf + offset, temp_buf, size) != 0) {
+               prt("READ BAD DATA: offset = 0x%x, size = 0x%x, fname = %s\n",
+                   offset, size, iname);
+               prt("OFFSET\tGOOD\tBAD\tRANGE\n");
+               while (size > 0) {
+                       c = good_buf[offset];
+                       t = temp_buf[i];
+                       if (c != t) {
+                               if (n < 16) {
+                                       bad = short_at(&temp_buf[i]);
+                                       prt("0x%5x\t0x%04x\t0x%04x", offset,
+                                           short_at(&good_buf[offset]), bad);
+                                       op = temp_buf[offset & 1 ? i+1 : i];
+                                       prt("\t0x%5x\n", n);
+                                       if (op)
+                                               prt("operation# (mod 256) for "
+                                                 "the bad data may be %u\n",
+                                               ((unsigned)op & 0xff));
+                                       else
+                                               prt("operation# (mod 256) for "
+                                                 "the bad data unknown, check"
+                                                 " HOLE and EXTEND ops\n");
+                               }
+                               n++;
+                               badoff = offset;
+                       }
+                       offset++;
+                       i++;
+                       size--;
+               }
+               report_failure(110);
+       }
+}
+
+
+void
+check_size(void)
+{
+       rbd_image_info_t statbuf;
+       int ret;
+
+       if ((ret = rbd_stat(image, &statbuf, sizeof(statbuf))) < 0) {
+               prterrcode("check_size: fstat", ret);
+       }
+       if ((uint64_t)file_size != statbuf.size) {
+               prt("Size error: expected 0x%llx stat 0x%llx\n",
+                   (unsigned long long)file_size,
+                   (unsigned long long)statbuf.size);
+               report_failure(120);
+       }
+}
+
+
+void
+check_trunc_hack(void)
+{
+       rbd_image_info_t statbuf;
+
+       rbd_resize(image, (off_t)0);
+       rbd_resize(image, (off_t)100000);
+       rbd_stat(image, &statbuf, sizeof(statbuf));
+       if (statbuf.size != (off_t)100000) {
+               prt("no extend on truncate! not posix!\n");
+               exit(130);
+       }
+       rbd_resize(image, (off_t)0);
+}
+
+int
+create_image()
+{
+       int r;
+       int order = 0;
+       r = rados_create(&cluster, NULL);
+       if (r < 0) {
+               simple_err("Could not create cluster handle", r);
+               return r;
+       }
+       rados_conf_parse_env(cluster, NULL);
+       r = rados_conf_read_file(cluster, NULL);
+       if (r < 0) {
+               simple_err("Error reading ceph config file", r);
+               goto failed_shutdown;
+       }
+       r = rados_connect(cluster);
+       if (r < 0) {
+               simple_err("Error connecting to cluster", r);
+               goto failed_shutdown;
+       }
+       r = rados_pool_create(cluster, pool);
+       if (r < 0 && r != -EEXIST) {
+               simple_err("Error creating pool", r);
+               goto failed_shutdown;
+       }
+       r = rados_ioctx_create(cluster, pool, &ioctx);
+       if (r < 0) {
+               simple_err("Error creating ioctx", r);
+               goto failed_shutdown;
+       }
+       if (clone_calls) {
+               r = rbd_create2(ioctx, iname, 0, RBD_FEATURE_LAYERING, &order);
+       } else {
+               r = rbd_create(ioctx, iname, 0, &order);
+       }
+       if (r < 0) {
+               simple_err("Error creating image", r);
+               goto failed_open;
+       }
+
+       return 0;
+
+ failed_open:
+       rados_ioctx_destroy(ioctx);
+ failed_shutdown:
+       rados_shutdown(cluster);
+       return r;
+}
+
+void
+doflush(unsigned offset, unsigned size)
+{
+       if (o_direct == O_DIRECT)
+               return;
+
+       rbd_flush(image);
+}
+
+void
+doread(unsigned offset, unsigned size)
+{
+       int ret;
+
+       offset -= offset % readbdy;
+       if (o_direct)
+               size -= size % readbdy;
+       if (size == 0) {
+               if (!quiet && testcalls > simulatedopcount && !o_direct)
+                       prt("skipping zero size read\n");
+               log4(OP_SKIPPED, OP_READ, offset, size);
+               return;
+       }
+       if (size + offset > file_size) {
+               if (!quiet && testcalls > simulatedopcount)
+                       prt("skipping seek/read past end of file\n");
+               log4(OP_SKIPPED, OP_READ, offset, size);
+               return;
+       }
+
+       log4(OP_READ, offset, size, 0);
+
+       if (testcalls <= simulatedopcount)
+               return;
+
+       if (!quiet &&
+               ((progressinterval && testcalls % progressinterval == 0)  ||
+               (debug &&
+                      (monitorstart == -1 ||
+                       (offset + size > monitorstart &&
+                       (monitorend == -1 || offset <= monitorend))))))
+               prt("%lu read\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
+                   offset, offset + size - 1, size);
+       ret = rbd_read(image, offset, size, temp_buf);
+       if (ret != (int)size) {
+               if (ret < 0)
+                       prterrcode("doread: read", ret);
+               else
+                       prt("short read: 0x%x bytes instead of 0x%x\n",
+                           ret, size);
+               report_failure(141);
+       }
+       check_buffers(offset, size);
+}
+
+
+void
+check_eofpage(char *s, unsigned offset, char *p, int size)
+{
+       unsigned long last_page, should_be_zero;
+
+       if (offset + size <= (file_size & ~page_mask))
+               return;
+       /*
+        * we landed in the last page of the file
+        * test to make sure the VM system provided 0's 
+        * beyond the true end of the file mapping
+        * (as required by mmap def in 1996 posix 1003.1)
+        */
+       last_page = ((unsigned long)p + (offset & page_mask) + size) & ~page_mask;
+
+       for (should_be_zero = last_page + (file_size & page_mask);
+            should_be_zero < last_page + page_size;
+            should_be_zero++)
+               if (*(char *)should_be_zero) {
+                       prt("Mapped %s: non-zero data past EOF (0x%llx) page offset 0x%x is 0x%04x\n",
+                           s, file_size - 1, should_be_zero & page_mask,
+                           short_at(should_be_zero));
+                       report_failure(205);
+               }
+}
+
+
+void
+gendata(char *original_buf, char *good_buf, unsigned offset, unsigned size)
+{
+       while (size--) {
+               good_buf[offset] = testcalls % 256; 
+               if (offset % 2)
+                       good_buf[offset] += original_buf[offset];
+               offset++;
+       }
+}
+
+
+void
+dowrite(unsigned offset, unsigned size)
+{
+       ssize_t ret;
+       off_t newsize;
+
+       offset -= offset % writebdy;
+       if (o_direct)
+               size -= size % writebdy;
+       if (size == 0) {
+               if (!quiet && testcalls > simulatedopcount && !o_direct)
+                       prt("skipping zero size write\n");
+               log4(OP_SKIPPED, OP_WRITE, offset, size);
+               return;
+       }
+
+       log4(OP_WRITE, offset, size, file_size);
+
+       gendata(original_buf, good_buf, offset, size);
+       if (file_size < offset + size) {
+               newsize = ceil(((double)offset + size) / truncbdy) * truncbdy;
+               if (file_size < newsize)
+                       memset(good_buf + file_size, '\0', newsize - file_size);
+               file_size = newsize;
+               if (lite) {
+                       warn("Lite file size bug in fsx!");
+                       report_failure(149);
+               }
+               ret = rbd_resize(image, newsize);
+               if (ret < 0) {
+                       prterrcode("dowrite: resize", ret);
+                       report_failure(150);
+               }
+       }
+
+       if (testcalls <= simulatedopcount)
+               return;
+
+       if (!quiet &&
+               ((progressinterval && testcalls % progressinterval == 0) ||
+                      (debug &&
+                      (monitorstart == -1 ||
+                       (offset + size > monitorstart &&
+                       (monitorend == -1 || offset <= monitorend))))))
+               prt("%lu write\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
+                   offset, offset + size - 1, size);
+
+       ret = rbd_write(image, offset, size, good_buf + offset);
+       if (ret != size) {
+               if (ret < 0)
+                       prterrcode("dowrite: rbd_write", ret);
+               else
+                       prt("short write: 0x%x bytes instead of 0x%x\n",
+                           ret, size);
+               report_failure(151);
+       }
+       if (flush) {
+               doflush(offset, size);
+       }
+}
+
+
+void
+dotruncate(unsigned size)
+{
+       int oldsize = file_size;
+       int ret;
+
+       size -= size % truncbdy;
+       if (size > biggest) {
+               biggest = size;
+               if (!quiet && testcalls > simulatedopcount)
+                       prt("truncating to largest ever: 0x%x\n", size);
+       }
+
+       log4(OP_TRUNCATE, size, (unsigned)file_size, 0);
+
+       if (size > file_size)
+               memset(good_buf + file_size, '\0', size - file_size);
+       else if (size < file_size)
+               memset(good_buf + size, '\0', file_size - size);
+       file_size = size;
+
+       if (testcalls <= simulatedopcount)
+               return;
+       
+       if ((progressinterval && testcalls % progressinterval == 0) ||
+           (debug && (monitorstart == -1 || monitorend == -1 ||
+                     size <= monitorend)))
+               prt("%lu trunc\tfrom 0x%x to 0x%x\n", testcalls, oldsize, size);
+       if ((ret = rbd_resize(image, size)) < 0) {
+               prt("rbd_resize: %x\n", size);
+               prterrcode("dotruncate: ftruncate", ret);
+               report_failure(160);
+       }
+}
+
+void
+do_punch_hole(unsigned offset, unsigned length)
+{
+       unsigned end_offset;
+       int max_offset = 0;
+       int max_len = 0;
+       int ret;
+
+       if (length == 0) {
+               if (!quiet && testcalls > simulatedopcount)
+                       prt("skipping zero length punch hole\n");
+                       log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length);
+               return;
+       }
+
+       if (file_size <= (loff_t)offset) {
+               if (!quiet && testcalls > simulatedopcount)
+                       prt("skipping hole punch off the end of the file\n");
+                       log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length);
+               return;
+       }
+
+       end_offset = offset + length;
+
+       log4(OP_PUNCH_HOLE, offset, length, 0);
+
+       if (testcalls <= simulatedopcount)
+               return;
+
+       if ((progressinterval && testcalls % progressinterval == 0) ||
+           (debug && (monitorstart == -1 || monitorend == -1 ||
+                     end_offset <= monitorend))) {
+               prt("%lu punch\tfrom 0x%x to 0x%x, (0x%x bytes)\n", testcalls,
+                       offset, offset+length, length);
+       }
+       if ((ret = rbd_discard(image, (unsigned long long) offset,
+                              (unsigned long long) length)) < 0) {
+               prt("%punch hole: %x to %x\n", offset, length);
+               prterrcode("do_punch_hole: discard", ret);
+               report_failure(161);
+       }
+
+
+       max_offset = offset < file_size ? offset : file_size;
+       max_len = max_offset + length <= file_size ? length :
+                       file_size - max_offset;
+       memset(good_buf + max_offset, '\0', max_len);
+}
+
+void clone_filename(char *buf, size_t len, int clones)
+{
+       snprintf(buf, len, "%s/fsx-%s-parent%d",
+                dirpath, iname, clones);
+}
+
+void clone_imagename(char *buf, size_t len, int clones)
+{
+       if (clones > 0)
+               snprintf(buf, len, "%s-clone%d", iname, clones);
+       else
+               strncpy(buf, iname, len);
+}
+
+void
+do_clone()
+{
+       char filename[1024];
+       char imagename[1024];
+       char lastimagename[1024];
+       int ret, fd;
+       int order = 0, stripe_unit = 0, stripe_count = 0;
+
+       if (randomize_striping) {
+               order = 18 + rand() % 8;
+               stripe_unit = 1ull << (order - 1 - (rand() % 8));
+               stripe_count = 2 + rand() % 14;
+       }
+
+       log4(OP_CLONE, 0, 0, 0);
+       ++num_clones;
+       prt("%lu clone\t%d order %d su %d sc %d\n", testcalls, num_clones, order, stripe_unit, stripe_count);
+
+       clone_filename(filename, sizeof(filename), num_clones);
+       if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
+               simple_err("do_clone: open", -errno);
+               exit(162);
+       }
+       save_buffer(good_buf, file_size, fd);
+       if ((ret = close(fd)) < 0) {
+               simple_err("do_clone: close", -errno);
+               exit(163);
+       }
+
+       if ((ret = rbd_snap_create(image, "snap")) < 0) {
+               simple_err("do_clone: rbd create snap", ret);
+               exit(164);
+       }
+
+       if ((ret = rbd_snap_protect(image, "snap")) < 0) {
+               simple_err("do_clone: rbd protect snap", ret);
+               exit(164);
+       }
+
+       clone_imagename(imagename, sizeof(imagename), num_clones);
+       clone_imagename(lastimagename, sizeof(lastimagename),
+                       num_clones - 1);
+
+       ret = rbd_clone2(ioctx, lastimagename, "snap", ioctx, imagename,
+                        RBD_FEATURES_ALL, &order, stripe_unit, stripe_count);
+       if (ret < 0) {
+               simple_err("do_clone: rbd clone", ret);
+               exit(165);
+       }
+       rbd_close(image);
+       if ((ret = rbd_open(ioctx, imagename, &image, NULL)) < 0) {
+               simple_err("do_clone: rbd open", ret);
+               exit(166);
+       }
+}
+
+void
+check_clones()
+{
+       char filename[1024];
+       char imagename[1024];
+       int ret, fd;
+       rbd_image_t cur_image;
+       struct stat file_info;
+       while (num_clones > 0) {
+               prt("checking clone #%d\n", num_clones);
+               --num_clones;
+
+               clone_imagename(imagename, sizeof(imagename), num_clones);
+               if ((ret = rbd_open(ioctx, imagename, &cur_image, NULL)) < 0) {
+                       simple_err("check_clones: rbd open", ret);
+                       exit(167);
+               }
+
+               clone_filename(filename, sizeof(filename), num_clones + 1);
+               if ((fd = open(filename, O_RDONLY)) < 0) {
+                       simple_err("check_clones: open", -errno);
+                       exit(168);
+               }
+
+               prt("checking image %s against file %s\n", imagename, filename);
+               if ((ret = fstat(fd, &file_info)) < 0) {
+                       simple_err("check_clones: fstat", -errno);
+                       exit(169);
+               }
+
+               if ((ret = pread(fd, good_buf, file_info.st_size, 0)) < 0) {
+                       simple_err("check_clones: pread", -errno);
+                       exit(170);
+               }
+
+               if ((ret = rbd_read(cur_image, 0, file_info.st_size, temp_buf)) < 0) {
+                       simple_err("check_clones: rbd_read", ret);
+                       exit(171);
+               }
+               close(fd);
+               check_buffers(0, file_info.st_size);
+
+               unlink(filename);
+               /* remove the snapshot if it exists, ignore
+                  the error from the last clone. */
+               rbd_snap_unprotect(cur_image, "snap");
+               rbd_snap_remove(cur_image, "snap");
+               rbd_close(cur_image);
+               rbd_remove(ioctx, imagename);
+       }
+}
+
+void
+writefileimage()
+{
+       ssize_t ret;
+
+       ret = rbd_write(image, 0, file_size, good_buf);
+       if (ret != file_size) {
+               if (ret < 0)
+                       prterrcode("writefileimage: write", ret);
+               else
+                       prt("short write: 0x%x bytes instead of 0x%llx\n",
+                           ret, (unsigned long long)file_size);
+               report_failure(172);
+       }
+       if (lite ? 0 : (ret = rbd_resize(image, file_size)) < 0) {
+               prt("rbd_resize: %llx\n", (unsigned long long)file_size);
+               prterrcode("writefileimage: rbd_resize", ret);
+               report_failure(173);
+       }
+}
+
+
+void
+docloseopen(void)
+{
+       int ret;
+
+       if (testcalls <= simulatedopcount)
+               return;
+
+       if (debug)
+               prt("%lu close/open\n", testcalls);
+       if ((ret = rbd_close(image)) < 0) {
+               prterrcode("docloseopen: close", ret);
+               report_failure(180);
+       }
+       ret = rbd_open(ioctx, iname, &image, NULL);
+       if (ret < 0) {
+               prterrcode("docloseopen: open", ret);
+               report_failure(181);
+       }
+}
+
+#define TRIM_OFF_LEN(off, len, size)   \
+do {                                   \
+       if (size)                       \
+               (off) %= (size);        \
+       else                            \
+               (off) = 0;              \
+       if ((unsigned)(off) + (unsigned)(len) > (unsigned)(size))       \
+               (len) = (size) - (off); \
+} while (0)
+
+void
+test(void)
+{
+       unsigned long   offset;
+       unsigned long   size = maxoplen;
+       unsigned long   rv = random();
+       unsigned long   op;
+
+       if (simulatedopcount > 0 && testcalls == simulatedopcount)
+               writefileimage();
+
+       testcalls++;
+
+       if (closeprob)
+               closeopen = (rv >> 3) < (1u << 28) / (unsigned)closeprob;
+
+       if (debugstart > 0 && testcalls >= debugstart)
+               debug = 1;
+
+       if (!quiet && testcalls < simulatedopcount && testcalls % 100000 == 0)
+               prt("%lu...\n", testcalls);
+
+       offset = random();
+       if (randomoplen)
+               size = random() % (maxoplen + 1);
+
+       /* calculate appropriate op to run */
+       if (lite)
+               op = rv % OP_MAX_LITE;
+       else
+               op = rv % OP_MAX_FULL;
+
+       switch (op) {
+       case OP_MAPREAD:
+               if (!mapped_reads)
+                       op = OP_READ;
+               break;
+       case OP_MAPWRITE:
+               if (!mapped_writes)
+                       op = OP_WRITE;
+               break;
+       case OP_FALLOCATE:
+               if (!fallocate_calls) {
+                       log4(OP_SKIPPED, OP_FALLOCATE, offset, size);
+                       goto out;
+               }
+               break;
+       case OP_PUNCH_HOLE:
+               if (!punch_hole_calls) {
+                       log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, size);
+                       goto out;
+               }
+               break;
+       case OP_CLONE:
+               if (!clone_calls || random() % 100 > 5 || file_size == 0) {
+                       log4(OP_SKIPPED, OP_CLONE, 0, 0);
+                       goto out;
+               }
+               break;
+       }
+
+       switch (op) {
+       case OP_READ:
+               TRIM_OFF_LEN(offset, size, file_size);
+               doread(offset, size);
+               break;
+
+       case OP_WRITE:
+               TRIM_OFF_LEN(offset, size, maxfilelen);
+               dowrite(offset, size);
+               break;
+
+       case OP_MAPREAD:
+               TRIM_OFF_LEN(offset, size, file_size);
+               exit(183);
+               break;
+
+       case OP_MAPWRITE:
+               TRIM_OFF_LEN(offset, size, maxfilelen);
+               exit(182);
+               break;
+
+       case OP_TRUNCATE:
+               if (!style)
+                       size = random() % maxfilelen;
+               dotruncate(size);
+               break;
+
+       case OP_PUNCH_HOLE:
+               TRIM_OFF_LEN(offset, size, file_size);
+               do_punch_hole(offset, size);
+               break;
+
+       case OP_CLONE:
+               do_clone();
+               break;
+
+       default:
+               prterr("test: unknown operation");
+               report_failure(42);
+               break;
+       }
+
+out:
+       if (sizechecks && testcalls > simulatedopcount)
+               check_size();
+       if (closeopen)
+               docloseopen();
+}
+
+
+void
+cleanup(sig)
+       int     sig;
+{
+       if (sig)
+               prt("signal %d\n", sig);
+       prt("testcalls = %lu\n", testcalls);
+       exit(sig);
+}
+
+
+void
+usage(void)
+{
+       fprintf(stdout, "usage: %s",
+               "fsx [-dnqxAFLOWZ] [-b opnum] [-c Prob] [-l flen] [-m start:end] [-o oplen] [-p progressinterval] [-r readbdy] [-s style] [-t truncbdy] [-w writebdy] [-D startingop] [-N numops] [-P dirpath] [-S seed] pname iname\n\
+       -b opnum: beginning operation number (default 1)\n\
+       -c P: 1 in P chance of file close+open at each op (default infinity)\n\
+       -d: debug output for all operations\n\
+       -f flush and invalidate cache after I/O\n\
+       -l flen: the upper bound on file size (default 262144)\n\
+       -m startop:endop: monitor (print debug output) specified byte range (default 0:infinity)\n\
+       -n: no verifications of file size\n\
+       -o oplen: the upper bound on operation size (default 65536)\n\
+       -p progressinterval: debug output at specified operation interval\n\
+       -q: quieter operation\n\
+       -r readbdy: 4096 would make reads page aligned (default 1)\n\
+       -s style: 1 gives smaller truncates (default 0)\n\
+       -t truncbdy: 4096 would make truncates page aligned (default 1)\n\
+       -w writebdy: 4096 would make writes page aligned (default 1)\n\
+       -x: preallocate file space before starting, XFS only (default 0)\n\
+       -y synchronize changes to a file\n"
+
+#ifdef AIO
+"      -A: Use the AIO system calls\n"
+#endif
+"      -D startingop: debug output starting at specified operation\n"
+#ifdef FALLOCATE
+"      -F: Do not use fallocate (preallocation) calls\n"
+#endif
+"        -H: Do not use punch hole calls\n"
+"        -C: Do not use clone calls\n"
+"      -L: fsxLite - no file creations & no file size changes\n\
+       -N numops: total # operations to do (default infinity)\n\
+       -O: use oplen (see -o flag) for every op (default random)\n\
+       -P: save .fsxlog and .fsxgood files in dirpath (default ./)\n\
+       -S seed: for random # generator (default 1) 0 gets timestamp\n\
+       -W: mapped write operations DISabled\n\
+        -R: read() system calls only (mapped reads disabled)\n\
+        -Z: O_DIRECT (use -R, -W, -r and -w too)\n\
+       poolname: this is REQUIRED (no default)\n\
+       imagename: this is REQUIRED (no default)\n");
+       exit(89);
+}
+
+
+int
+getnum(char *s, char **e)
+{
+       int ret;
+
+       *e = (char *) 0;
+       ret = strtol(s, e, 0);
+       if (*e)
+               switch (**e) {
+               case 'b':
+               case 'B':
+                       ret *= 512;
+                       *e = *e + 1;
+                       break;
+               case 'k':
+               case 'K':
+                       ret *= 1024;
+                       *e = *e + 1;
+                       break;
+               case 'm':
+               case 'M':
+                       ret *= 1024*1024;
+                       *e = *e + 1;
+                       break;
+               case 'w':
+               case 'W':
+                       ret *= 4;
+                       *e = *e + 1;
+                       break;
+               }
+       return (ret);
+}
+
+#ifdef AIO
+
+#define QSZ     1024
+io_context_t   io_ctx;
+struct iocb    iocb;
+
+int aio_setup()
+{
+       int ret;
+       ret = io_queue_init(QSZ, &io_ctx);
+       if (ret != 0) {
+               fprintf(stderr, "aio_setup: io_queue_init failed: %s\n",
+                        strerror(ret));
+                return(-1);
+        }
+        return(0);
+}
+
+int
+__aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset)
+{
+       struct io_event event;
+       static struct timespec ts;
+       struct iocb *iocbs[] = { &iocb };
+       int ret;
+       long res;
+
+       if (rw == READ) {
+               io_prep_pread(&iocb, fd, buf, len, offset);
+       } else {
+               io_prep_pwrite(&iocb, fd, buf, len, offset);
+       }
+
+       ts.tv_sec = 30;
+       ts.tv_nsec = 0;
+       ret = io_submit(io_ctx, 1, iocbs);
+       if (ret != 1) {
+               fprintf(stderr, "errcode=%d\n", ret);
+               fprintf(stderr, "aio_rw: io_submit failed: %s\n",
+                               strerror(ret));
+               goto out_error;
+       }
+
+       ret = io_getevents(io_ctx, 1, 1, &event, &ts);
+       if (ret != 1) {
+               if (ret == 0)
+                       fprintf(stderr, "aio_rw: no events available\n");
+               else {
+                       fprintf(stderr, "errcode=%d\n", -ret);
+                       fprintf(stderr, "aio_rw: io_getevents failed: %s\n",
+                                       strerror(-ret));
+               }
+               goto out_error;
+       }
+       if (len != event.res) {
+               /*
+                * The b0rked libaio defines event.res as unsigned.
+                * However the kernel strucuture has it signed,
+                * and it's used to pass negated error value.
+                * Till the library is fixed use the temp var.
+                */
+               res = (long)event.res;
+               if (res >= 0)
+                       fprintf(stderr, "bad io length: %lu instead of %u\n",
+                                       res, len);
+               else {
+                       fprintf(stderr, "errcode=%ld\n", -res);
+                       fprintf(stderr, "aio_rw: async io failed: %s\n",
+                                       strerror(-res));
+                       ret = res;
+                       goto out_error;
+               }
+
+       }
+       return event.res;
+
+out_error:
+       /*
+        * The caller expects error return in traditional libc
+        * convention, i.e. -1 and the errno set to error.
+        */
+       errno = -ret;
+       return -1;
+}
+
+int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset)
+{
+       int ret;
+
+       if (aio) {
+               ret = __aio_rw(rw, fd, buf, len, offset);
+       } else {
+               if (rw == READ)
+                       ret = read(fd, buf, len);
+               else
+                       ret = write(fd, buf, len);
+       }
+       return ret;
+}
+
+#endif
+
+void
+test_fallocate()
+{
+#ifdef FALLOCATE
+       if (!lite && fallocate_calls) {
+               if (fallocate(fd, 0, 0, 1) && errno == EOPNOTSUPP) {
+                       if(!quiet)
+                               warn("main: filesystem does not support fallocate, disabling\n");
+                       fallocate_calls = 0;
+               } else {
+                       ftruncate(fd, 0);
+               }
+       }
+#else /* ! FALLOCATE */
+       fallocate_calls = 0;
+#endif
+
+}
+
+int
+main(int argc, char **argv)
+{
+       int     i, style, ch, ret;
+       char    *endp;
+       char goodfile[1024];
+       char logfile[1024];
+       char finaliname[1024];
+
+       goodfile[0] = 0;
+       logfile[0] = 0;
+
+       page_size = getpagesize();
+       page_mask = page_size - 1;
+       mmap_mask = page_mask;
+
+       setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
+
+       while ((ch = getopt(argc, argv, "b:c:dfl:m:no:p:qr:s:t:w:xyACD:FHLN:OP:RS:WZ"))
+              != EOF)
+               switch (ch) {
+               case 'b':
+                       simulatedopcount = getnum(optarg, &endp);
+                       if (!quiet)
+                               fprintf(stdout, "Will begin at operation %ld\n",
+                                       simulatedopcount);
+                       if (simulatedopcount == 0)
+                               usage();
+                       simulatedopcount -= 1;
+                       break;
+               case 'c':
+                       closeprob = getnum(optarg, &endp);
+                       if (!quiet)
+                               fprintf(stdout,
+                                       "Chance of close/open is 1 in %d\n",
+                                       closeprob);
+                       if (closeprob <= 0)
+                               usage();
+                       break;
+               case 'd':
+                       debug = 1;
+                       break;
+               case 'f':
+                       flush = 1;
+                       break;
+               case 'l':
+                       maxfilelen = getnum(optarg, &endp);
+                       if (maxfilelen <= 0)
+                               usage();
+                       break;
+               case 'm':
+                       monitorstart = getnum(optarg, &endp);
+                       if (monitorstart < 0)
+                               usage();
+                       if (!endp || *endp++ != ':')
+                               usage();
+                       monitorend = getnum(endp, &endp);
+                       if (monitorend < 0)
+                               usage();
+                       if (monitorend == 0)
+                               monitorend = -1; /* aka infinity */
+                       debug = 1;
+                       break;
+               case 'n':
+                       sizechecks = 0;
+                       break;
+               case 'o':
+                       maxoplen = getnum(optarg, &endp);
+                       if (maxoplen <= 0)
+                               usage();
+                       break;
+               case 'p':
+                       progressinterval = getnum(optarg, &endp);
+                       if (progressinterval == 0)
+                               usage();
+                       break;
+               case 'q':
+                       quiet = 1;
+                       break;
+               case 'r':
+                       readbdy = getnum(optarg, &endp);
+                       if (readbdy <= 0)
+                               usage();
+                       break;
+               case 's':
+                       style = getnum(optarg, &endp);
+                       if (style < 0 || style > 1)
+                               usage();
+                       break;
+               case 't':
+                       truncbdy = getnum(optarg, &endp);
+                       if (truncbdy <= 0)
+                               usage();
+                       break;
+               case 'w':
+                       writebdy = getnum(optarg, &endp);
+                       if (writebdy <= 0)
+                               usage();
+                       break;
+               case 'x':
+                       prealloc = 1;
+                       break;
+               case 'y':
+                       do_fsync = 1;
+                       break;
+               case 'A':
+                       aio = 1;
+                       break;
+               case 'C':
+                       clone_calls = 0;
+                       break;
+               case 'D':
+                       debugstart = getnum(optarg, &endp);
+                       if (debugstart < 1)
+                               usage();
+                       break;
+               case 'F':
+                       fallocate_calls = 0;
+                       break;
+               case 'H':
+                       punch_hole_calls = 0;
+                       break;
+               case 'L':
+                       prt("lite mode not supported for rbd\n");
+                       exit(1);
+                       break;
+               case 'N':
+                       numops = getnum(optarg, &endp);
+                       if (numops < 0)
+                               usage();
+                       break;
+               case 'O':
+                       randomoplen = 0;
+                       break;
+               case 'P':
+                       strncpy(dirpath, optarg, sizeof(dirpath));
+                       strncpy(goodfile, dirpath, sizeof(goodfile));
+                       strcat(goodfile, "/");
+                       strncpy(logfile, dirpath, sizeof(logfile));
+                       strcat(logfile, "/");
+                       break;
+                case 'R':
+                        mapped_reads = 0;
+                        break;
+               case 'S':
+                        seed = getnum(optarg, &endp);
+                       if (seed == 0)
+                               seed = time(0) % 10000;
+                       if (!quiet)
+                               fprintf(stdout, "Seed set to %d\n", seed);
+                       if (seed < 0)
+                               usage();
+                       break;
+               case 'W':
+                       mapped_writes = 0;
+                       if (!quiet)
+                               fprintf(stdout, "mapped writes DISABLED\n");
+                       break;
+               case 'Z':
+                       o_direct = O_DIRECT;
+                       break;
+               default:
+                       usage();
+                       /* NOTREACHED */
+               }
+       argc -= optind;
+       argv += optind;
+       if (argc != 2)
+               usage();
+       pool = argv[0];
+       iname = argv[1];
+
+       signal(SIGHUP,  cleanup);
+       signal(SIGINT,  cleanup);
+       signal(SIGPIPE, cleanup);
+       signal(SIGALRM, cleanup);
+       signal(SIGTERM, cleanup);
+       signal(SIGXCPU, cleanup);
+       signal(SIGXFSZ, cleanup);
+       signal(SIGVTALRM,       cleanup);
+       signal(SIGUSR1, cleanup);
+       signal(SIGUSR2, cleanup);
+
+       initstate(seed, state, 256);
+       setstate(state);
+
+       ret = create_image();
+       if (ret < 0) {
+               prterrcode(iname, ret);
+               exit(90);
+       }
+       ret = rbd_open(ioctx, iname, &image, NULL);
+       if (ret < 0) {
+               simple_err("Error opening image", ret);
+               exit(91);
+       }
+       if (!dirpath[0])
+               strcat(dirpath, ".");
+       strncat(goodfile, iname, 256);
+       strcat (goodfile, ".fsxgood");
+       fsxgoodfd = open(goodfile, O_RDWR|O_CREAT|O_TRUNC, 0666);
+       if (fsxgoodfd < 0) {
+               prterr(goodfile);
+               exit(92);
+       }
+       strncat(logfile, iname, 256);
+       strcat (logfile, ".fsxlog");
+       fsxlogf = fopen(logfile, "w");
+       if (fsxlogf == NULL) {
+               prterr(logfile);
+               exit(93);
+       }
+
+#ifdef AIO
+       if (aio) 
+               aio_setup();
+#endif
+
+       original_buf = (char *) malloc(maxfilelen);
+       for (i = 0; i < (int)maxfilelen; i++)
+               original_buf[i] = random() % 256;
+       good_buf = (char *) malloc(maxfilelen + writebdy);
+       good_buf = round_ptr_up(good_buf, writebdy, 0);
+       memset(good_buf, '\0', maxfilelen);
+       temp_buf = (char *) malloc(maxfilelen + writebdy);
+       temp_buf = round_ptr_up(temp_buf, writebdy, 0);
+       memset(temp_buf, '\0', maxfilelen);
+       if (lite) {     /* zero entire existing file */
+               ssize_t written;
+
+               written = rbd_write(image, 0, (size_t)maxfilelen, good_buf);
+               if (written != (ssize_t)maxfilelen) {
+                       if (written < 0) {
+                               prterrcode(iname, written);
+                               warn("main: error on write");
+                       } else
+                               warn("main: short write, 0x%x bytes instead "
+                                       "of 0x%lx\n",
+                                       (unsigned)written,
+                                       maxfilelen);
+                       exit(98);
+               }
+       } else 
+               check_trunc_hack();
+
+       //test_fallocate();
+
+       while (numops == -1 || numops--)
+               test();
+
+       if ((ret = rbd_close(image)) < 0) {
+               prterrcode("rbd_close", ret);
+               report_failure(99);
+       }
+
+       clone_imagename(finaliname, sizeof(finaliname), num_clones);
+       if ((ret = rbd_remove(ioctx, finaliname)) < 0) {
+               prterrcode("rbd_remove final image", ret);
+               report_failure(100);
+       }
+
+       if (clone_calls)
+               check_clones();
+
+       rados_ioctx_destroy(ioctx);
+       rados_shutdown(cluster);
+
+       free(original_buf);
+       free(good_buf);
+       free(temp_buf);
+
+       prt("All operations completed A-OK!\n");
+       fclose(fsxlogf);
+
+       exit(0);
+       return 0;
+}
diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc
new file mode 100644 (file)
index 0000000..96dcfc2
--- /dev/null
@@ -0,0 +1,1316 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This 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.  See file COPYING.
+ *
+ */
+
+#include "include/rados/librados.h"
+#include "include/rbd_types.h"
+#include "include/rbd/librbd.h"
+#include "include/rbd/librbd.hpp"
+
+#include "gtest/gtest.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <iostream>
+#include <algorithm>
+#include <sstream>
+
+#include "rados-api/test.h"
+#include "common/errno.h"
+#include "include/stringify.h"
+
+using namespace std;
+
+static int get_features(bool *old_format, uint64_t *features)
+{
+  const char *c = getenv("RBD_FEATURES");
+  if (c) {
+    stringstream ss;
+    ss << c;
+    ss >> *features;
+    if (ss.fail())
+      return -EINVAL;
+    *old_format = false;
+    cout << "using new format!" << std::endl;
+  } else {
+    *old_format = true;
+    cout << "using old format" << std::endl;
+  }
+
+  return 0;
+}
+
+static int create_image_full(rados_ioctx_t ioctx, const char *name,
+                             uint64_t size, int *order, int old_format,
+                             uint64_t features)
+{
+  if (old_format) {
+    return rbd_create(ioctx, name, size, order);
+  } else {
+    return rbd_create2(ioctx, name, size, features, order);
+  }
+}
+
+static int create_image(rados_ioctx_t ioctx, const char *name,
+                       uint64_t size, int *order)
+{
+  bool old_format;
+  uint64_t features;
+
+  int r = get_features(&old_format, &features);
+  if (r < 0)
+    return r;
+  return create_image_full(ioctx, name, size, order, old_format, features);
+}
+
+static int create_image_pp(librbd::RBD &rbd,
+                          librados::IoCtx &ioctx,
+                          const char *name,
+                          uint64_t size, int *order) {
+  bool old_format;
+  uint64_t features;
+  int r = get_features(&old_format, &features);
+  if (r < 0)
+    return r;
+  if (old_format) {
+    return rbd.create(ioctx, name, size, order);
+  } else {
+    return rbd.create2(ioctx, name, size, features, order);
+  }
+}
+
+TEST(LibRBD, CreateAndStat)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+
+  rbd_image_info_t info;
+  rbd_image_t image;
+  int order = 0;
+  const char *name = "testimg";
+  uint64_t size = 2 << 20;
+  
+  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
+  ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
+  printf("image has size %llu and order %d\n", (unsigned long long) info.size, info.order);
+  ASSERT_EQ(info.size, size);
+  ASSERT_EQ(info.order, order);
+  ASSERT_EQ(0, rbd_close(image));
+
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRBD, CreateAndStatPP)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  {
+    librbd::RBD rbd;
+    librbd::image_info_t info;
+    librbd::Image image;
+    int order = 0;
+    const char *name = "testimg";
+    uint64_t size = 2 << 20;
+    
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
+    ASSERT_EQ(0, image.stat(info, sizeof(info)));
+    ASSERT_EQ(info.size, size);
+    ASSERT_EQ(info.order, order);
+  }
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+TEST(LibRBD, ResizeAndStat)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+  rbd_image_info_t info;
+  rbd_image_t image;
+  int order = 0;
+  const char *name = "testimg";
+  uint64_t size = 2 << 20;
+  
+  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
+
+  ASSERT_EQ(0, rbd_resize(image, size * 4));
+  ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
+  ASSERT_EQ(info.size, size * 4);
+
+  ASSERT_EQ(0, rbd_resize(image, size / 2));
+  ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
+  ASSERT_EQ(info.size, size / 2);
+  
+  ASSERT_EQ(0, rbd_close(image));
+
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRBD, ResizeAndStatPP)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  {
+    librbd::RBD rbd;
+    librbd::image_info_t info;
+    librbd::Image image;
+    int order = 0;
+    const char *name = "testimg";
+    uint64_t size = 2 << 20;
+    
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
+    
+    ASSERT_EQ(0, image.resize(size * 4));
+    ASSERT_EQ(0, image.stat(info, sizeof(info)));
+    ASSERT_EQ(info.size, size * 4);
+    
+    ASSERT_EQ(0, image.resize(size / 2));
+    ASSERT_EQ(0, image.stat(info, sizeof(info)));
+    ASSERT_EQ(info.size, size / 2);
+  }
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+int test_ls(rados_ioctx_t io_ctx, size_t num_expected, ...)
+{
+  int num_images, i, j;
+  char *expected, *names, *cur_name;
+  va_list ap;
+  size_t max_size = 1024;
+
+  names = (char *) malloc(sizeof(char *) * 1024);
+  int len = rbd_list(io_ctx, names, &max_size);
+
+  for (i = 0, num_images = 0, cur_name = names; cur_name < names + len; i++) {
+    printf("image: %s\n", cur_name);
+    cur_name += strlen(cur_name) + 1;
+    num_images++;
+  }
+
+  va_start(ap, num_expected);
+  for (i = num_expected; i > 0; i--) {
+    expected = va_arg(ap, char *);
+    printf("expected = %s\n", expected);
+    int found = 0;
+    for (j = 0, cur_name = names; j < num_images; j++) {
+      if (cur_name[0] == '_') {
+       cur_name += strlen(cur_name) + 1;
+       continue;
+      }
+      if (strcmp(cur_name, expected) == 0) {
+       printf("found %s\n", cur_name);
+       cur_name[0] = '_';
+       found = 1;
+       break;
+      }
+    }
+    assert(found);
+  }
+  va_end(ap);
+
+  for (i = 0, cur_name = names; cur_name < names + len; i++) {
+    assert(cur_name[0] == '_');
+    cur_name += strlen(cur_name) + 1;
+  }
+  free(names);
+
+  return num_images;
+}
+
+TEST(LibRBD, TestCreateLsDelete)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+  int order = 0;
+  const char *name = "testimg";
+  const char *name2 = "testimg2";
+  uint64_t size = 2 << 20;
+  
+  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
+  ASSERT_EQ(1, test_ls(ioctx, 1, name));
+  ASSERT_EQ(0, create_image(ioctx, name2, size, &order));
+  ASSERT_EQ(2, test_ls(ioctx, 2, name, name2));
+  ASSERT_EQ(0, rbd_remove(ioctx, name));
+  ASSERT_EQ(1, test_ls(ioctx, 1, name2));
+
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+int test_ls_pp(librbd::RBD& rbd, librados::IoCtx& io_ctx, size_t num_expected, ...)
+{
+  int r;
+  size_t i;
+  char *expected;
+  va_list ap;
+  vector<string> names;
+  r = rbd.list(io_ctx, names);
+  if (r == -ENOENT)
+    r = 0;
+  assert(r >= 0);
+  cout << "num images is: " << names.size() << endl
+           << "expected: " << num_expected << endl;
+  int num = names.size();
+
+  for (i = 0; i < names.size(); i++) {
+    cout << "image: " << names[i] << endl;
+  }
+
+  va_start(ap, num_expected);
+  for (i = num_expected; i > 0; i--) {
+    expected = va_arg(ap, char *);
+    cout << "expected = " << expected << endl;
+    vector<string>::iterator listed_name = find(names.begin(), names.end(), string(expected));
+    assert(listed_name != names.end());
+    names.erase(listed_name);
+  }
+  assert(names.empty());
+
+  return num;
+}
+
+TEST(LibRBD, TestCreateLsDeletePP)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  {
+    librbd::RBD rbd;
+    librbd::Image image;
+    int order = 0;
+    const char *name = "testimg";
+    const char *name2 = "testimg2";
+    uint64_t size = 2 << 20;  
+    
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
+    ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name));
+    ASSERT_EQ(0, rbd.create(ioctx, name2, size, &order));
+    ASSERT_EQ(2, test_ls_pp(rbd, ioctx, 2, name, name2));
+    ASSERT_EQ(0, rbd.remove(ioctx, name));
+    ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name2));
+  }
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+
+static int print_progress_percent(uint64_t offset, uint64_t src_size,
+                                    void *data)
+{
+  float percent = ((float)offset * 100) / src_size;
+  printf("%3.2f%% done\n", percent);
+  return 0; 
+}
+
+TEST(LibRBD, TestCopy)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+  rbd_image_t image;
+  int order = 0;
+  const char *name = "testimg";
+  const char *name2 = "testimg2";
+  const char *name3 = "testimg3";
+
+  uint64_t size = 2 << 20;
+  
+  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
+  ASSERT_EQ(1, test_ls(ioctx, 1, name));
+  ASSERT_EQ(0, rbd_copy(image, ioctx, name2));
+  ASSERT_EQ(2, test_ls(ioctx, 2, name, name2));
+  ASSERT_EQ(0, rbd_copy_with_progress(image, ioctx, name3, print_progress_percent, NULL));
+  ASSERT_EQ(3, test_ls(ioctx, 3, name, name2, name3));
+
+  ASSERT_EQ(0, rbd_close(image));
+
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+class PrintProgress : public librbd::ProgressContext
+{
+public:
+  int update_progress(uint64_t offset, uint64_t src_size)
+  {
+    float percent = ((float)offset * 100) / src_size;
+    printf("%3.2f%% done\n", percent);
+    return 0;
+  }
+};
+
+TEST(LibRBD, TestCopyPP)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+  
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  {
+    librbd::RBD rbd;
+    librbd::Image image;
+    int order = 0;
+    const char *name = "testimg";
+    const char *name2 = "testimg2";
+    const char *name3 = "testimg3";
+    uint64_t size = 2 << 20;
+    PrintProgress pp;
+
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
+    ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name));
+    ASSERT_EQ(0, image.copy(ioctx, name2));
+    ASSERT_EQ(2, test_ls_pp(rbd, ioctx, 2, name, name2));
+    ASSERT_EQ(0, image.copy_with_progress(ioctx, name3, pp));
+    ASSERT_EQ(3, test_ls_pp(rbd, ioctx, 3, name, name2, name3));
+  }
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+int test_ls_snaps(rbd_image_t image, int num_expected, ...)
+{
+  rbd_snap_info_t *snaps;
+  int num_snaps, i, j, expected_size, max_size = 10;
+  char *expected;
+  va_list ap;
+  snaps = (rbd_snap_info_t *) malloc(sizeof(rbd_snap_info_t *) * 10);
+  num_snaps = rbd_snap_list(image, snaps, &max_size);
+  printf("num snaps is: %d\nexpected: %d\n", num_snaps, num_expected);
+
+  for (i = 0; i < num_snaps; i++) {
+    printf("snap: %s\n", snaps[i].name);
+  }
+
+  va_start(ap, num_expected);
+  for (i = num_expected; i > 0; i--) {
+    expected = va_arg(ap, char *);
+    expected_size = va_arg(ap, int);
+    int found = 0;
+    for (j = 0; j < num_snaps; j++) {
+      if (snaps[j].name == NULL)
+       continue;
+      if (strcmp(snaps[j].name, expected) == 0) {
+       printf("found %s with size %llu\n", snaps[j].name, (unsigned long long) snaps[j].size);
+       assert((int)snaps[j].size == expected_size);
+       free((void *) snaps[j].name);
+       snaps[j].name = NULL;
+       found = 1;
+       break;
+      }
+    }
+    assert(found);
+  }
+  va_end(ap);
+
+  for (i = 0; i < num_snaps; i++) {
+    assert(snaps[i].name == NULL);
+  }
+  free(snaps);
+
+  return num_snaps;
+}
+
+TEST(LibRBD, TestCreateLsDeleteSnap)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+  rbd_image_t image;
+  int order = 0;
+  const char *name = "testimg";
+  uint64_t size = 2 << 20;
+  uint64_t size2 = 4 << 20;
+  
+  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
+
+  ASSERT_EQ(0, rbd_snap_create(image, "snap1"));
+  ASSERT_EQ(1, test_ls_snaps(image, 1, "snap1", size));
+  ASSERT_EQ(0, rbd_resize(image, size2));
+  ASSERT_EQ(0, rbd_snap_create(image, "snap2"));
+  ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1", size, "snap2", size2));
+  ASSERT_EQ(0, rbd_snap_remove(image, "snap1"));
+  ASSERT_EQ(1, test_ls_snaps(image, 1, "snap2", size2));
+  ASSERT_EQ(0, rbd_snap_remove(image, "snap2"));
+  ASSERT_EQ(0, test_ls_snaps(image, 0));
+  
+  ASSERT_EQ(0, rbd_close(image));
+
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+int test_ls_snaps(librbd::Image& image, size_t num_expected, ...)
+{
+  int r;
+  size_t i, j, expected_size;
+  char *expected;
+  va_list ap;
+  vector<librbd::snap_info_t> snaps;
+  r = image.snap_list(snaps);
+  assert(r >= 0);
+  cout << "num snaps is: " << snaps.size() << endl
+           << "expected: " << num_expected << endl;
+
+  for (i = 0; i < snaps.size(); i++) {
+    cout << "snap: " << snaps[i].name << endl;
+  }
+
+  va_start(ap, num_expected);
+  for (i = num_expected; i > 0; i--) {
+    expected = va_arg(ap, char *);
+    expected_size = va_arg(ap, int);
+    int found = 0;
+    for (j = 0; j < snaps.size(); j++) {
+      if (snaps[j].name == "")
+       continue;
+      if (strcmp(snaps[j].name.c_str(), expected) == 0) {
+       cout << "found " << snaps[j].name << " with size " << snaps[j].size << endl;
+       assert(snaps[j].size == (size_t) expected_size);
+       snaps[j].name = "";
+       found = 1;
+       break;
+      }
+    }
+    assert(found);
+  }
+  va_end(ap);
+
+  for (i = 0; i < snaps.size(); i++) {
+    assert(snaps[i].name == "");
+  }
+
+  return snaps.size();
+}
+
+TEST(LibRBD, TestCreateLsDeleteSnapPP)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  {
+    librbd::RBD rbd;
+    librbd::Image image;
+    int order = 0;
+    const char *name = "testimg";
+    uint64_t size = 2 << 20;
+    uint64_t size2 = 4 << 20;
+    
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
+
+    ASSERT_EQ(0, image.snap_create("snap1"));
+    ASSERT_EQ(1, test_ls_snaps(image, 1, "snap1", size));
+    ASSERT_EQ(0, image.resize(size2));
+    ASSERT_EQ(0, image.snap_create("snap2"));
+    ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1", size, "snap2", size2));
+    ASSERT_EQ(0, image.snap_remove("snap1"));
+    ASSERT_EQ(1, test_ls_snaps(image, 1, "snap2", size2));
+    ASSERT_EQ(0, image.snap_remove("snap2"));
+    ASSERT_EQ(0, test_ls_snaps(image, 0));
+  }
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+
+
+#define TEST_IO_SIZE 512
+#define TEST_IO_TO_SNAP_SIZE 80
+
+void simple_write_cb(rbd_completion_t cb, void *arg)
+{
+  printf("write completion cb called!\n");
+}
+
+void simple_read_cb(rbd_completion_t cb, void *arg)
+{
+  printf("read completion cb called!\n");
+}
+
+void aio_write_test_data(rbd_image_t image, const char *test_data, uint64_t off, size_t len)
+{
+  rbd_completion_t comp;
+  rbd_aio_create_completion(NULL, (rbd_callback_t) simple_write_cb, &comp);
+  printf("created completion\n");
+  rbd_aio_write(image, off, len, test_data, comp);
+  printf("started write\n");
+  rbd_aio_wait_for_complete(comp);
+  int r = rbd_aio_get_return_value(comp);
+  printf("return value is: %d\n", r);
+  assert(r == 0);
+  printf("finished write\n");
+  rbd_aio_release(comp);
+}
+
+void write_test_data(rbd_image_t image, const char *test_data, uint64_t off, size_t len)
+{
+  ssize_t written;
+  written = rbd_write(image, off, len, test_data);
+  printf("wrote: %d\n", (int) written);
+  assert(written == (ssize_t)len);
+}
+
+void aio_discard_test_data(rbd_image_t image, uint64_t off, uint64_t len)
+{
+  rbd_completion_t comp;
+  rbd_aio_create_completion(NULL, (rbd_callback_t) simple_write_cb, &comp);
+  rbd_aio_discard(image, off, len, comp);
+  rbd_aio_wait_for_complete(comp);
+  int r = rbd_aio_get_return_value(comp);
+  assert(r == 0);
+  printf("aio discard: %d~%d = %d\n", (int)off, (int)len, (int)r);
+  rbd_aio_release(comp);
+}
+
+void discard_test_data(rbd_image_t image, uint64_t off, size_t len)
+{
+  ssize_t written;
+  written = rbd_discard(image, off, len);
+  printf("discard: %d~%d = %d\n", (int)off, (int)len, (int)written);
+  assert(written == (ssize_t)len);
+}
+
+void aio_read_test_data(rbd_image_t image, const char *expected, uint64_t off, size_t len)
+{
+  rbd_completion_t comp;
+  char *result = (char *)malloc(len + 1);
+
+  assert(result);
+  rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp);
+  printf("created completion\n");
+  rbd_aio_read(image, off, len, result, comp);
+  printf("started read\n");
+  rbd_aio_wait_for_complete(comp);
+  int r = rbd_aio_get_return_value(comp);
+  printf("return value is: %d\n", r);
+  assert(r == (ssize_t)len);
+  rbd_aio_release(comp);
+  if (memcmp(result, expected, len)) {
+    printf("read: %s\nexpected: %s\n", result, expected);
+    assert(memcmp(result, expected, len) == 0);
+  }
+  free(result);
+}
+
+void read_test_data(rbd_image_t image, const char *expected, uint64_t off, size_t len)
+{
+  ssize_t read;
+  char *result = (char *)malloc(len + 1);
+
+  assert(result);
+  read = rbd_read(image, off, len, result);
+  printf("read: %d\n", (int) read);
+  assert(read == (ssize_t)len);
+  result[len] = '\0';
+  if (memcmp(result, expected, len)) {
+    printf("read: %s\nexpected: %s\n", result, expected);
+    assert(memcmp(result, expected, len) == 0);
+  }
+  free(result);
+}
+
+TEST(LibRBD, TestIO)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+  rbd_image_t image;
+  int order = 0;
+  const char *name = "testimg";
+  uint64_t size = 2 << 20;
+  
+  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
+
+  char test_data[TEST_IO_SIZE + 1];
+  char zero_data[TEST_IO_SIZE + 1];
+  int i;
+
+  for (i = 0; i < TEST_IO_SIZE; ++i) {
+    test_data[i] = (char) (rand() % (126 - 33) + 33);
+  }
+  test_data[TEST_IO_SIZE] = '\0';
+  memset(zero_data, 0, sizeof(zero_data));
+
+  for (i = 0; i < 5; ++i)
+    write_test_data(image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE);
+
+  for (i = 5; i < 10; ++i)
+    aio_write_test_data(image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE);
+
+  for (i = 0; i < 5; ++i)
+    read_test_data(image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE);
+
+  for (i = 5; i < 10; ++i)
+    aio_read_test_data(image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE);
+
+  // discard 2nd, 4th sections.
+  discard_test_data(image, TEST_IO_SIZE, TEST_IO_SIZE);
+  aio_discard_test_data(image, TEST_IO_SIZE*3, TEST_IO_SIZE);
+
+  read_test_data(image, test_data,  0, TEST_IO_SIZE);
+  read_test_data(image,  zero_data, TEST_IO_SIZE, TEST_IO_SIZE);
+  read_test_data(image, test_data,  TEST_IO_SIZE*2, TEST_IO_SIZE);
+  read_test_data(image,  zero_data, TEST_IO_SIZE*3, TEST_IO_SIZE);
+  read_test_data(image, test_data,  TEST_IO_SIZE*4, TEST_IO_SIZE);
+  
+  rbd_image_info_t info;
+  rbd_completion_t comp;
+  ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
+  // can't read or write starting past end
+  ASSERT_EQ(-EINVAL, rbd_write(image, info.size, 1, test_data));
+  ASSERT_EQ(-EINVAL, rbd_read(image, info.size, 1, test_data));
+  // reading through end returns amount up to end
+  ASSERT_EQ(10, rbd_read(image, info.size - 10, 100, test_data));
+  // writing through end returns amount up to end
+  ASSERT_EQ(10, rbd_write(image, info.size - 10, 100, test_data));
+
+  rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp);
+  ASSERT_EQ(-EINVAL, rbd_aio_write(image, info.size, 1, test_data, comp));
+  ASSERT_EQ(-EINVAL, rbd_aio_read(image, info.size, 1, test_data, comp));
+
+  ASSERT_EQ(0, rbd_close(image));
+
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRBD, TestEmptyDiscard)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+  rbd_image_t image;
+  int order = 0;
+  const char *name = "testimg";
+  uint64_t size = 20 << 20;
+  
+  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
+
+  aio_discard_test_data(image, 0, 1*1024*1024);
+  aio_discard_test_data(image, 0, 4*1024*1024);
+
+  ASSERT_EQ(0, rbd_close(image));
+
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+
+void simple_write_cb_pp(librbd::completion_t cb, void *arg)
+{
+  cout << "write completion cb called!" << endl;
+}
+
+void simple_read_cb_pp(librbd::completion_t cb, void *arg)
+{
+  cout << "read completion cb called!" << endl;
+}
+
+void aio_write_test_data(librbd::Image& image, const char *test_data, off_t off)
+{
+  ceph::bufferlist bl;
+  bl.append(test_data, strlen(test_data));
+  librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_write_cb_pp);
+  printf("created completion\n");
+  image.aio_write(off, strlen(test_data), bl, comp);
+  printf("started write\n");
+  comp->wait_for_complete();
+  int r = comp->get_return_value();
+  printf("return value is: %d\n", r);
+  assert(r >= 0);
+  printf("finished write\n");
+  comp->release();
+}
+
+void aio_discard_test_data(librbd::Image& image, off_t off, size_t len)
+{
+  librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_write_cb_pp);
+  image.aio_discard(off, len, comp);
+  comp->wait_for_complete();
+  int r = comp->get_return_value();
+  assert(r >= 0);
+  comp->release();
+}
+
+void write_test_data(librbd::Image& image, const char *test_data, off_t off)
+{
+  size_t written;
+  size_t len = strlen(test_data);
+  ceph::bufferlist bl;
+  bl.append(test_data, len);
+  written = image.write(off, len, bl);
+  printf("wrote: %u\n", (unsigned int) written);
+  assert(written == bl.length());
+}
+
+void discard_test_data(librbd::Image& image, off_t off, size_t len)
+{
+  size_t written;
+  written = image.discard(off, len);
+  printf("discard: %u~%u\n", (unsigned)off, (unsigned)len);
+  assert(written == len);
+}
+
+void aio_read_test_data(librbd::Image& image, const char *expected, off_t off, size_t expected_len)
+{
+  librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_read_cb_pp);
+  ceph::bufferlist bl;
+  printf("created completion\n");
+  image.aio_read(off, expected_len, bl, comp);
+  printf("started read\n");
+  comp->wait_for_complete();
+  int r = comp->get_return_value();
+  printf("return value is: %d\n", r);
+  assert(r == TEST_IO_SIZE);
+  assert(strncmp(expected, bl.c_str(), TEST_IO_SIZE) == 0);
+  printf("finished read\n");
+  comp->release();
+}
+
+void read_test_data(librbd::Image& image, const char *expected, off_t off, size_t expected_len)
+{
+  int read, total_read = 0;
+  size_t len = expected_len;
+  ceph::bufferlist bl;
+  read = image.read(off + total_read, len, bl);
+  assert(read >= 0);
+  printf("read: %u\n", (unsigned int) read);
+  printf("read: %s\nexpected: %s\n", bl.c_str(), expected);
+  assert(strncmp(bl.c_str(), expected, expected_len) == 0);
+}
+
+TEST(LibRBD, TestIOPP) 
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  {
+    librbd::RBD rbd;
+    librbd::Image image;
+    int order = 0;
+    const char *name = "testimg";
+    uint64_t size = 2 << 20;
+    
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
+
+    char test_data[TEST_IO_SIZE + 1];
+    char zero_data[TEST_IO_SIZE + 1];
+    int i;
+    
+    srand(time(0));
+    for (i = 0; i < TEST_IO_SIZE; ++i) {
+      test_data[i] = (char) (rand() % (126 - 33) + 33);
+    }
+    test_data[TEST_IO_SIZE] = '\0';
+    memset(zero_data, 0, sizeof(zero_data));
+
+    for (i = 0; i < 5; ++i)
+      write_test_data(image, test_data, strlen(test_data) * i);
+    
+    for (i = 5; i < 10; ++i)
+      aio_write_test_data(image, test_data, strlen(test_data) * i);
+    
+    for (i = 0; i < 5; ++i)
+      read_test_data(image, test_data, strlen(test_data) * i, TEST_IO_SIZE);
+    
+    for (i = 5; i < 10; ++i)
+      aio_read_test_data(image, test_data, strlen(test_data) * i, TEST_IO_SIZE);
+
+    // discard 2nd, 4th sections.
+    discard_test_data(image, TEST_IO_SIZE, TEST_IO_SIZE);
+    aio_discard_test_data(image, TEST_IO_SIZE*3, TEST_IO_SIZE);
+    
+    read_test_data(image, test_data,  0, TEST_IO_SIZE);
+    read_test_data(image,  zero_data, TEST_IO_SIZE, TEST_IO_SIZE);
+    read_test_data(image, test_data,  TEST_IO_SIZE*2, TEST_IO_SIZE);
+    read_test_data(image,  zero_data, TEST_IO_SIZE*3, TEST_IO_SIZE);
+    read_test_data(image, test_data,  TEST_IO_SIZE*4, TEST_IO_SIZE);
+  }
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
+
+
+TEST(LibRBD, TestIOToSnapshot)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+  rbd_image_t image;
+  int order = 0;
+  const char *name = "testimg";
+  uint64_t isize = 2 << 20;
+  
+  ASSERT_EQ(0, create_image(ioctx, name, isize, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
+
+  int i, r;
+  rbd_image_t image_at_snap;
+  char orig_data[TEST_IO_TO_SNAP_SIZE + 1];
+  char test_data[TEST_IO_TO_SNAP_SIZE + 1];
+
+  for (i = 0; i < TEST_IO_TO_SNAP_SIZE; ++i)
+    test_data[i] = (char) (i + 48);
+  test_data[TEST_IO_TO_SNAP_SIZE] = '\0';
+  orig_data[TEST_IO_TO_SNAP_SIZE] = '\0';
+
+  r = rbd_read(image, 0, TEST_IO_TO_SNAP_SIZE, orig_data);
+  ASSERT_EQ(r, TEST_IO_TO_SNAP_SIZE);
+
+  ASSERT_EQ(0, test_ls_snaps(image, 0));
+  ASSERT_EQ(0, rbd_snap_create(image, "orig"));
+  ASSERT_EQ(1, test_ls_snaps(image, 1, "orig", isize));
+  read_test_data(image, orig_data, 0, TEST_IO_TO_SNAP_SIZE);
+
+  printf("write test data!\n");
+  write_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
+  ASSERT_EQ(0, rbd_snap_create(image, "written"));
+  ASSERT_EQ(2, test_ls_snaps(image, 2, "orig", isize, "written", isize));
+
+  read_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
+
+  rbd_snap_set(image, "orig");
+  read_test_data(image, orig_data, 0, TEST_IO_TO_SNAP_SIZE);
+
+  rbd_snap_set(image, "written");
+  read_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
+
+  rbd_snap_set(image, "orig");
+
+  r = rbd_write(image, 0, TEST_IO_TO_SNAP_SIZE, test_data);
+  printf("write to snapshot returned %d\n", r);
+  ASSERT_LT(r, 0);
+  cout << cpp_strerror(-r) << std::endl;
+
+  read_test_data(image, orig_data, 0, TEST_IO_TO_SNAP_SIZE);
+  rbd_snap_set(image, "written");
+  read_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
+
+  r = rbd_snap_rollback(image, "orig");
+  ASSERT_EQ(r, -EROFS);
+
+  r = rbd_snap_set(image, NULL);
+  ASSERT_EQ(r, 0);
+  r = rbd_snap_rollback(image, "orig");
+  ASSERT_EQ(r, 0);
+
+  write_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
+
+  rbd_flush(image);
+
+  printf("opening testimg@orig\n");
+  ASSERT_EQ(0, rbd_open(ioctx, name, &image_at_snap, "orig"));
+  read_test_data(image_at_snap, orig_data, 0, TEST_IO_TO_SNAP_SIZE);
+  r = rbd_write(image_at_snap, 0, TEST_IO_TO_SNAP_SIZE, test_data);
+  printf("write to snapshot returned %d\n", r);
+  ASSERT_LT(r, 0);
+  cout << cpp_strerror(-r) << std::endl;
+  ASSERT_EQ(0, rbd_close(image_at_snap));
+
+  ASSERT_EQ(2, test_ls_snaps(image, 2, "orig", isize, "written", isize));
+  ASSERT_EQ(0, rbd_snap_remove(image, "written"));
+  ASSERT_EQ(1, test_ls_snaps(image, 1, "orig", isize));
+  ASSERT_EQ(0, rbd_snap_remove(image, "orig"));
+  ASSERT_EQ(0, test_ls_snaps(image, 0));
+
+  ASSERT_EQ(0, rbd_close(image));
+
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRBD, TestClone)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx;
+  rbd_image_info_t pinfo, cinfo;
+  string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+  int features = RBD_FEATURE_LAYERING;
+  rbd_image_t parent, child;
+  int order = 0;
+
+  // make a parent to clone from
+  ASSERT_EQ(0, create_image_full(ioctx, "parent", 4<<20, &order, false, features));
+  ASSERT_EQ(0, rbd_open(ioctx, "parent", &parent, NULL));
+  printf("made parent image \"parent\"\n");
+
+  char *data = (char *)"testdata";
+  ASSERT_EQ((ssize_t)strlen(data), rbd_write(parent, 0, strlen(data), data));
+
+  // can't clone a non-snapshot, expect failure
+  EXPECT_NE(0, rbd_clone(ioctx, "parent", NULL, ioctx, "child", features, &order));
+
+  // verify that there is no parent info on "parent"
+  char ppool[1], pname[1], psnapname[1];
+  ASSERT_EQ(-ENOENT, rbd_get_parent_info(parent, ppool, sizeof(ppool),
+           pname, sizeof(pname), psnapname, sizeof(psnapname)));
+  printf("parent has no parent info\n");
+
+  // create a snapshot, reopen as the parent we're interested in
+  ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap"));
+  printf("made snapshot \"parent@parent_snap\"\n");
+  ASSERT_EQ(0, rbd_close(parent));
+  ASSERT_EQ(0, rbd_open(ioctx, "parent", &parent, "parent_snap"));
+
+  ASSERT_EQ(-EINVAL, rbd_clone(ioctx, "parent", "parent_snap", ioctx, "child",
+           features, &order));
+
+  // unprotected image should fail unprotect
+  ASSERT_EQ(-EINVAL, rbd_snap_unprotect(parent, "parent_snap"));
+  printf("can't unprotect an unprotected snap\n");
+
+  ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap"));
+  // protecting again should fail
+  ASSERT_EQ(-EBUSY, rbd_snap_protect(parent, "parent_snap"));
+  printf("can't protect a protected snap\n");
+
+  // This clone and open should work
+  ASSERT_EQ(0, rbd_clone(ioctx, "parent", "parent_snap", ioctx, "child",
+           features, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, "child", &child, NULL));
+  printf("made and opened clone \"child\"\n");
+
+  // check read
+  read_test_data(child, data, 0, strlen(data));
+
+  // check write
+  ASSERT_EQ((ssize_t)strlen(data), rbd_write(child, 20, strlen(data), data));
+  read_test_data(child, data, 20, strlen(data));
+  read_test_data(child, data, 0, strlen(data));
+
+  // check attributes
+  ASSERT_EQ(0, rbd_stat(parent, &pinfo, sizeof(pinfo)));
+  ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo)));
+  EXPECT_EQ(cinfo.size, pinfo.size);
+  uint64_t overlap;
+  rbd_get_overlap(child, &overlap);
+  EXPECT_EQ(overlap, pinfo.size);
+  EXPECT_EQ(cinfo.obj_size, pinfo.obj_size);
+  EXPECT_EQ(cinfo.order, pinfo.order);
+  printf("sizes and overlaps are good between parent and child\n");
+
+  // sizing down child results in changing overlap and size, not parent size
+  ASSERT_EQ(0, rbd_resize(child, 2UL<<20));
+  ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo)));
+  rbd_get_overlap(child, &overlap);
+  ASSERT_EQ(overlap, 2UL<<20);
+  ASSERT_EQ(cinfo.size, 2UL<<20);
+  ASSERT_EQ(0, rbd_resize(child, 4UL<<20));
+  ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo)));
+  rbd_get_overlap(child, &overlap);
+  ASSERT_EQ(overlap, 2UL<<20);
+  ASSERT_EQ(cinfo.size, 4UL<<20);
+  printf("sized down clone, changed overlap\n");
+
+  // sizing back up doesn't change that
+  ASSERT_EQ(0, rbd_resize(child, 5UL<<20));
+  ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo)));
+  rbd_get_overlap(child, &overlap);
+  ASSERT_EQ(overlap, 2UL<<20);
+  ASSERT_EQ(cinfo.size, 5UL<<20);
+  ASSERT_EQ(0, rbd_stat(parent, &pinfo, sizeof(pinfo)));
+  printf("parent info: size %lld obj_size %lld parent_pool %lld\n",
+        (unsigned long long)pinfo.size, (unsigned long long)pinfo.obj_size,
+        (unsigned long long)pinfo.parent_pool);
+  ASSERT_EQ(pinfo.size, 4UL<<20);
+  printf("sized up clone, changed size but not overlap or parent's size\n");
+  
+  ASSERT_EQ(0, rbd_close(child));
+
+  ASSERT_EQ(-EBUSY, rbd_snap_remove(parent, "parent_snap"));
+  printf("can't remove parent while child still exists\n");
+  ASSERT_EQ(0, rbd_remove(ioctx, "child"));
+  ASSERT_EQ(-EBUSY, rbd_snap_remove(parent, "parent_snap"));
+  printf("can't remove parent while still protected\n");
+  ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap"));
+  ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap"));
+  printf("removed parent snap after unprotecting\n");
+
+  ASSERT_EQ(0, rbd_close(parent));
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+static void test_list_children(rbd_image_t image, ssize_t num_expected, ...)
+{
+  va_list ap;
+  va_start(ap, num_expected);
+  size_t pools_len = 100;
+  size_t children_len = 100;
+  char *pools = NULL;
+  char *children = NULL;
+  ssize_t num_children;
+
+  do {
+    free(pools);
+    free(children);
+    pools = (char *) malloc(pools_len);
+    children = (char *) malloc(children_len);
+    num_children = rbd_list_children(image, pools, &pools_len,
+                                    children, &children_len);
+  } while (num_children == -ERANGE);
+
+  ASSERT_EQ(num_expected, num_children);
+  for (ssize_t i = num_expected; i > 0; --i) {
+    char *expected_pool = va_arg(ap, char *);
+    char *expected_image = va_arg(ap, char *);
+    char *pool = pools;
+    char *image = children;
+    bool found = 0;
+    printf("\ntrying to find %s/%s\n", expected_pool, expected_image);
+    for (ssize_t j = 0; j < num_children; ++j) {
+      printf("checking %s/%s\n", pool, image);
+      if (strcmp(expected_pool, pool) == 0 &&
+         strcmp(expected_image, image) == 0) {
+       printf("found child %s/%s\n\n", pool, image);
+       found = 1;
+       break;
+      }
+      pool += strlen(pool) + 1;
+      image += strlen(image) + 1;
+      if (j == num_children - 1) {
+       ASSERT_EQ(pool - pools - 1, (ssize_t) pools_len);
+       ASSERT_EQ(image - children - 1, (ssize_t) children_len);
+      }
+    }
+    ASSERT_TRUE(found);
+  }
+  va_end(ap);
+}
+
+TEST(LibRBD, ListChildren)
+{
+  rados_t cluster;
+  rados_ioctx_t ioctx1, ioctx2;
+  string pool_name1 = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name1, &cluster));
+  string pool_name2 = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool(pool_name2, &cluster));
+  rados_ioctx_create(cluster, pool_name1.c_str(), &ioctx1);
+  rados_ioctx_create(cluster, pool_name2.c_str(), &ioctx2);
+
+  int features = RBD_FEATURE_LAYERING;
+  rbd_image_t parent;
+  int order = 0;
+
+  // make a parent to clone from
+  ASSERT_EQ(0, create_image_full(ioctx1, "parent", 4<<20, &order,
+                                false, features));
+  ASSERT_EQ(0, rbd_open(ioctx1, "parent", &parent, NULL));
+  // create a snapshot, reopen as the parent we're interested in
+  ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap"));
+  ASSERT_EQ(0, rbd_snap_set(parent, "parent_snap"));
+  ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap"));
+
+  ASSERT_EQ(0, rbd_close(parent));
+  ASSERT_EQ(0, rbd_open(ioctx1, "parent", &parent, "parent_snap"));
+
+  ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child1",
+           features, &order));
+  test_list_children(parent, 1, pool_name2.c_str(), "child1");
+
+  ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx1, "child2",
+           features, &order));
+  test_list_children(parent, 2, pool_name2.c_str(), "child1",
+                    pool_name1.c_str(), "child2");
+
+  ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child3",
+           features, &order));
+  test_list_children(parent, 3, pool_name2.c_str(), "child1",
+                    pool_name1.c_str(), "child2",
+                    pool_name2.c_str(), "child3");
+
+  ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child4",
+           features, &order));
+  test_list_children(parent, 4, pool_name2.c_str(), "child1",
+                    pool_name1.c_str(), "child2",
+                    pool_name2.c_str(), "child3",
+                    pool_name2.c_str(), "child4");
+
+  ASSERT_EQ(0, rbd_remove(ioctx2, "child1"));
+  test_list_children(parent, 3,
+                    pool_name1.c_str(), "child2",
+                    pool_name2.c_str(), "child3",
+                    pool_name2.c_str(), "child4");
+
+  ASSERT_EQ(0, rbd_remove(ioctx2, "child3"));
+  test_list_children(parent, 2,
+                    pool_name1.c_str(), "child2",
+                    pool_name2.c_str(), "child4");
+
+  ASSERT_EQ(0, rbd_remove(ioctx2, "child4"));
+  test_list_children(parent, 1,
+                    pool_name1.c_str(), "child2");
+
+  ASSERT_EQ(0, rbd_remove(ioctx1, "child2"));
+  test_list_children(parent, 0);
+
+  ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap"));
+  ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap"));
+  ASSERT_EQ(0, rbd_close(parent));
+  ASSERT_EQ(0, rbd_remove(ioctx1, "parent"));
+  rados_ioctx_destroy(ioctx1);
+  rados_ioctx_destroy(ioctx2);
+  // destroy_one_pool also closes the cluster; do this one step at a time
+  ASSERT_EQ(0, rados_pool_delete(cluster, pool_name1.c_str()));
+  ASSERT_EQ(0, destroy_one_pool(pool_name2, &cluster));
+}
+
+TEST(LibRBD, LockingPP)
+{
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  string pool_name = get_temp_pool_name();
+
+  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+
+  {
+    librbd::RBD rbd;
+    librbd::Image image;
+    int order = 0;
+    const char *name = "testimg";
+    uint64_t size = 2 << 20;
+    std::string cookie1 = "foo";
+    std::string cookie2 = "bar";
+
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
+
+    // no lockers initially
+    std::list<librbd::locker_t> lockers;
+    std::string tag;
+    bool exclusive;
+    ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag));
+    ASSERT_EQ(0u, lockers.size());
+    ASSERT_EQ("", tag);
+
+    // exclusive lock is exclusive
+    ASSERT_EQ(0, image.lock_exclusive(cookie1));
+    ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie1));
+    ASSERT_EQ(-EBUSY, image.lock_exclusive(""));
+    ASSERT_EQ(-EEXIST, image.lock_shared(cookie1, ""));
+    ASSERT_EQ(-EBUSY, image.lock_shared(cookie1, "test"));
+    ASSERT_EQ(-EBUSY, image.lock_shared("", "test"));
+    ASSERT_EQ(-EBUSY, image.lock_shared("", ""));
+
+    // list exclusive
+    ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag));
+    ASSERT_TRUE(exclusive);
+    ASSERT_EQ("", tag);
+    ASSERT_EQ(1u, lockers.size());
+    ASSERT_EQ(cookie1, lockers.front().cookie);
+
+    // unlock
+    ASSERT_EQ(-ENOENT, image.unlock(""));
+    ASSERT_EQ(-ENOENT, image.unlock(cookie2));
+    ASSERT_EQ(0, image.unlock(cookie1));
+    ASSERT_EQ(-ENOENT, image.unlock(cookie1));
+    ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag));
+    ASSERT_EQ(0u, lockers.size());
+
+    ASSERT_EQ(0, image.lock_shared(cookie1, ""));
+    ASSERT_EQ(-EEXIST, image.lock_shared(cookie1, ""));
+    ASSERT_EQ(0, image.lock_shared(cookie2, ""));
+    ASSERT_EQ(-EEXIST, image.lock_shared(cookie2, ""));
+    ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie1));
+    ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie2));
+    ASSERT_EQ(-EBUSY, image.lock_exclusive(""));
+    ASSERT_EQ(-EBUSY, image.lock_exclusive("test"));
+
+    // list shared
+    ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag));
+    ASSERT_EQ(2u, lockers.size());
+  }
+
+  ioctx.close();
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+}
diff --git a/src/test/rbd/fsx.c b/src/test/rbd/fsx.c
deleted file mode 100644 (file)
index f895b5f..0000000
+++ /dev/null
@@ -1,1551 +0,0 @@
-// -*- mode:C; tab-width:8; c-basic-offset:8; indent-tabs-mode:t -*- 
-/*
- *     Copyright (C) 1991, NeXT Computer, Inc.  All Rights Reserverd.
- *
- *     File:   fsx.c
- *     Author: Avadis Tevanian, Jr.
- *
- *     File system exerciser. 
- *
- *     Rewritten 8/98 by Conrad Minshall.
- *
- *     Small changes to work under Linux -- davej.
- *
- *     Checks for mmap last-page zero fill.
- */
-
-#include <sys/types.h>
-#include <unistd.h>
-#include <limits.h>
-#include <time.h>
-#include <strings.h>
-#include <sys/file.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#ifdef HAVE_ERR_H
-#include <err.h>
-#endif
-#include <signal.h>
-#include <stdio.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <math.h>
-
-#include "include/rados/librados.h"
-#include "include/rbd/librbd.h"
-
-#define NUMPRINTCOLUMNS 32     /* # columns of data to print on each line */
-
-/*
- *     A log entry is an operation and a bunch of arguments.
- */
-
-struct log_entry {
-       int     operation;
-       int     args[3];
-};
-
-#define        LOGSIZE 1000
-
-struct log_entry       oplog[LOGSIZE]; /* the log */
-int                    logptr = 0;     /* current position in log */
-int                    logcount = 0;   /* total ops */
-
-/*
- * The operation matrix is complex due to conditional execution of different
- * features. Hence when we come to deciding what operation to run, we need to
- * be careful in how we select the different operations. The active operations
- * are mapped to numbers as follows:
- *
- *             lite    !lite
- * READ:       0       0
- * WRITE:      1       1
- * MAPREAD:    2       2
- * MAPWRITE:   3       3
- * TRUNCATE:   -       4
- * FALLOCATE:  -       5
- * PUNCH HOLE: -       6
- *
- * When mapped read/writes are disabled, they are simply converted to normal
- * reads and writes. When fallocate/fpunch calls are disabled, they are
- * converted to OP_SKIPPED. Hence OP_SKIPPED needs to have a number higher than
- * the operation selction matrix, as does the OP_CLOSEOPEN which is an
- * operation modifier rather than an operation in itself.
- *
- * Because of the "lite" version, we also need to have different "maximum
- * operation" defines to allow the ops to be selected correctly based on the
- * mode being run.
- */
-
-/* common operations */
-#define        OP_READ         0
-#define OP_WRITE       1
-#define OP_MAPREAD     2
-#define OP_MAPWRITE    3
-#define OP_MAX_LITE    4
-
-/* !lite operations */
-#define OP_TRUNCATE    4
-#define OP_FALLOCATE   5
-#define OP_PUNCH_HOLE  6
-/* rbd-specific operations */
-#define OP_CLONE        7
-#define OP_MAX_FULL    8
-
-/* operation modifiers */
-#define OP_CLOSEOPEN   100
-#define OP_SKIPPED     101
-
-#undef PAGE_SIZE
-#define PAGE_SIZE       getpagesize()
-#undef PAGE_MASK
-#define PAGE_MASK       (PAGE_SIZE - 1)
-
-char   *original_buf;                  /* a pointer to the original data */
-char   *good_buf;                      /* a pointer to the correct data */
-char   *temp_buf;                      /* a pointer to the current data */
-char   *pool;                          /* name of the pool our test image is in */
-char   *iname;                         /* name of our test image */
-rados_t cluster;                        /* handle for our test cluster */
-rados_ioctx_t  ioctx;                  /* handle for our test pool */
-rbd_image_t    image;                  /* handle for our test image */
-
-char   dirpath[1024];
-
-off_t          file_size = 0;
-off_t          biggest = 0;
-char           state[256];
-unsigned long  testcalls = 0;          /* calls to function "test" */
-
-unsigned long  simulatedopcount = 0;   /* -b flag */
-int    closeprob = 0;                  /* -c flag */
-int    debug = 0;                      /* -d flag */
-unsigned long  debugstart = 0;         /* -D flag */
-int    flush = 0;                      /* -f flag */
-int    do_fsync = 0;                   /* -y flag */
-unsigned long  maxfilelen = 256 * 1024;        /* -l flag */
-int    sizechecks = 1;                 /* -n flag disables them */
-int    maxoplen = 64 * 1024;           /* -o flag */
-int    quiet = 0;                      /* -q flag */
-unsigned long progressinterval = 0;    /* -p flag */
-int    readbdy = 1;                    /* -r flag */
-int    style = 0;                      /* -s flag */
-int    prealloc = 0;                   /* -x flag */
-int    truncbdy = 1;                   /* -t flag */
-int    writebdy = 1;                   /* -w flag */
-long   monitorstart = -1;              /* -m flag */
-long   monitorend = -1;                /* -m flag */
-int    lite = 0;                       /* -L flag */
-long   numops = -1;                    /* -N flag */
-int    randomoplen = 1;                /* -O flag disables it */
-int    seed = 1;                       /* -S flag */
-int     mapped_writes = 0;              /* -W flag disables */
-int     fallocate_calls = 0;            /* -F flag disables */
-int     punch_hole_calls = 1;           /* -H flag disables */
-int    clone_calls = 1;                /* -C flag disables */
-int     randomize_striping = 1;
-int    mapped_reads = 0;               /* -R flag disables it */
-int    fsxgoodfd = 0;
-int    o_direct;                       /* -Z */
-int    aio = 0;
-
-int num_clones = 0;
-
-int page_size;
-int page_mask;
-int mmap_mask;
-#ifdef AIO
-int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset);
-#define READ 0
-#define WRITE 1
-#define fsxread(a,b,c,d)       aio_rw(READ, a,b,c,d)
-#define fsxwrite(a,b,c,d)      aio_rw(WRITE, a,b,c,d)
-#else
-#define fsxread(a,b,c,d)       read(a,b,c)
-#define fsxwrite(a,b,c,d)      write(a,b,c)
-#endif
-
-FILE * fsxlogf = NULL;
-int badoff = -1;
-int closeopen = 0;
-
-static void *round_ptr_up(void *ptr, unsigned long align, unsigned long offset)
-{
-       unsigned long ret = (unsigned long)ptr;
-
-       ret = ((ret + align - 1) & ~(align - 1));
-       ret += offset;
-       return (void *)ret;
-}
-
-void
-vwarnc(int code, const char *fmt, va_list ap) {
-  fprintf(stderr, "fsx: ");
-  if (fmt != NULL) {
-       vfprintf(stderr, fmt, ap);
-       fprintf(stderr, ": ");
-  }
-  fprintf(stderr, "%s\n", strerror(code));
-}
-
-void
-warn(const char * fmt, ...)  {
-       va_list ap;
-       va_start(ap, fmt);
-       vwarnc(errno, fmt, ap);
-       va_end(ap);
-}
-
-#define BUF_SIZE 1024
-
-void
-prt(char *fmt, ...)
-{
-       va_list args;
-       char buffer[BUF_SIZE];
-
-       va_start(args, fmt);
-       vsnprintf(buffer, BUF_SIZE, fmt, args);
-       va_end(args);
-       fprintf(stdout, buffer);
-       if (fsxlogf)
-               fprintf(fsxlogf, buffer);
-}
-
-void
-prterr(char *prefix)
-{
-       prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno));
-}
-
-void
-log4(int operation, int arg0, int arg1, int arg2)
-{
-       struct log_entry *le;
-
-       le = &oplog[logptr];
-       le->operation = operation;
-       if (closeopen)
-               le->operation = ~ le->operation;
-       le->args[0] = arg0;
-       le->args[1] = arg1;
-       le->args[2] = arg2;
-       logptr++;
-       logcount++;
-       if (logptr >= LOGSIZE)
-               logptr = 0;
-}
-
-void
-simple_err(const char *msg, int err)
-{
-    fprintf(stderr, "%s: %s\n", msg, strerror(-err));
-}
-
-void
-logdump(void)
-{
-       int     i, count, down;
-       struct log_entry        *lp;
-       char *falloc_type[3] = {"PAST_EOF", "EXTENDING", "INTERIOR"};
-
-       prt("LOG DUMP (%d total operations):\n", logcount);
-       if (logcount < LOGSIZE) {
-               i = 0;
-               count = logcount;
-       } else {
-               i = logptr;
-               count = LOGSIZE;
-       }
-       for ( ; count > 0; count--) {
-               int opnum;
-
-               opnum = i+1 + (logcount/LOGSIZE)*LOGSIZE;
-               prt("%d(%3d mod 256): ", opnum, opnum%256);
-               lp = &oplog[i];
-               if ((closeopen = lp->operation < 0))
-                       lp->operation = ~ lp->operation;
-                       
-               switch (lp->operation) {
-               case OP_MAPREAD:
-                       prt("MAPREAD  0x%x thru 0x%x\t(0x%x bytes)",
-                           lp->args[0], lp->args[0] + lp->args[1] - 1,
-                           lp->args[1]);
-                       if (badoff >= lp->args[0] && badoff <
-                                                    lp->args[0] + lp->args[1])
-                               prt("\t***RRRR***");
-                       break;
-               case OP_MAPWRITE:
-                       prt("MAPWRITE 0x%x thru 0x%x\t(0x%x bytes)",
-                           lp->args[0], lp->args[0] + lp->args[1] - 1,
-                           lp->args[1]);
-                       if (badoff >= lp->args[0] && badoff <
-                                                    lp->args[0] + lp->args[1])
-                               prt("\t******WWWW");
-                       break;
-               case OP_READ:
-                       prt("READ     0x%x thru 0x%x\t(0x%x bytes)",
-                           lp->args[0], lp->args[0] + lp->args[1] - 1,
-                           lp->args[1]);
-                       if (badoff >= lp->args[0] &&
-                           badoff < lp->args[0] + lp->args[1])
-                               prt("\t***RRRR***");
-                       break;
-               case OP_WRITE:
-                       prt("WRITE    0x%x thru 0x%x\t(0x%x bytes)",
-                           lp->args[0], lp->args[0] + lp->args[1] - 1,
-                           lp->args[1]);
-                       if (lp->args[0] > lp->args[2])
-                               prt(" HOLE");
-                       else if (lp->args[0] + lp->args[1] > lp->args[2])
-                               prt(" EXTEND");
-                       if ((badoff >= lp->args[0] || badoff >=lp->args[2]) &&
-                           badoff < lp->args[0] + lp->args[1])
-                               prt("\t***WWWW");
-                       break;
-               case OP_TRUNCATE:
-                       down = lp->args[0] < lp->args[1];
-                       prt("TRUNCATE %s\tfrom 0x%x to 0x%x",
-                           down ? "DOWN" : "UP", lp->args[1], lp->args[0]);
-                       if (badoff >= lp->args[!down] &&
-                           badoff < lp->args[!!down])
-                               prt("\t******WWWW");
-                       break;
-               case OP_FALLOCATE:
-                       /* 0: offset 1: length 2: where alloced */
-                       prt("FALLOC   0x%x thru 0x%x\t(0x%x bytes) %s",
-                               lp->args[0], lp->args[0] + lp->args[1],
-                               lp->args[1], falloc_type[lp->args[2]]);
-                       if (badoff >= lp->args[0] &&
-                           badoff < lp->args[0] + lp->args[1])
-                               prt("\t******FFFF");
-                       break;
-               case OP_PUNCH_HOLE:
-                       prt("PUNCH    0x%x thru 0x%x\t(0x%x bytes)",
-                           lp->args[0], lp->args[0] + lp->args[1] - 1,
-                           lp->args[1]);
-                       if (badoff >= lp->args[0] && badoff <
-                                                    lp->args[0] + lp->args[1])
-                               prt("\t******PPPP");
-                       break;
-               case OP_CLONE:
-                       prt("CLONE");
-                       break;
-               case OP_SKIPPED:
-                       prt("SKIPPED (no operation)");
-                       break;
-               default:
-                       prt("BOGUS LOG ENTRY (operation code = %d)!",
-                           lp->operation);
-               }
-               if (closeopen)
-                       prt("\n\t\tCLOSE/OPEN");
-               prt("\n");
-               i++;
-               if (i == LOGSIZE)
-                       i = 0;
-       }
-}
-
-void
-prterrcode(char *prefix, int code)
-{
-       prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(-code));
-}
-
-void
-save_buffer(char *buffer, off_t bufferlength, int fd)
-{
-       off_t ret;
-       ssize_t byteswritten;
-
-       if (fd <= 0 || bufferlength == 0)
-               return;
-
-       if (bufferlength > SSIZE_MAX) {
-               prt("fsx flaw: overflow in save_buffer\n");
-               exit(67);
-       }
-
-       ret = lseek(fd, (off_t)0, SEEK_SET);
-       if (ret == (off_t)-1)
-               prterr("save_buffer: lseek 0");
-       
-       byteswritten = write(fd, buffer, (size_t)bufferlength);
-       if (byteswritten != bufferlength) {
-               if (byteswritten == -1)
-                       prterr("save_buffer write");
-               else
-                       warn("save_buffer: short write, 0x%x bytes instead of 0x%llx\n",
-                            (unsigned)byteswritten,
-                            (unsigned long long)bufferlength);
-       }
-}
-
-
-void
-report_failure(int status)
-{
-       logdump();
-       
-       if (fsxgoodfd) {
-               if (good_buf) {
-                       save_buffer(good_buf, file_size, fsxgoodfd);
-                       prt("Correct content saved for comparison\n");
-                       prt("(maybe hexdump \"%s\" vs \"%s.fsxgood\")\n",
-                           iname, iname);
-               }
-               close(fsxgoodfd);
-       }
-       sleep(3);   // so the log can flush to disk.  KLUDGEY!
-       exit(status);
-}
-
-#define short_at(cp) ((unsigned short)((*((unsigned char *)(cp)) << 8) | \
-                                       *(((unsigned char *)(cp)) + 1)))
-
-void
-check_buffers(unsigned offset, unsigned size)
-{
-       unsigned char c, t;
-       unsigned i = 0;
-       unsigned n = 0;
-       unsigned op = 0;
-       unsigned bad = 0;
-
-       if (memcmp(good_buf + offset, temp_buf, size) != 0) {
-               prt("READ BAD DATA: offset = 0x%x, size = 0x%x, fname = %s\n",
-                   offset, size, iname);
-               prt("OFFSET\tGOOD\tBAD\tRANGE\n");
-               while (size > 0) {
-                       c = good_buf[offset];
-                       t = temp_buf[i];
-                       if (c != t) {
-                               if (n < 16) {
-                                       bad = short_at(&temp_buf[i]);
-                                       prt("0x%5x\t0x%04x\t0x%04x", offset,
-                                           short_at(&good_buf[offset]), bad);
-                                       op = temp_buf[offset & 1 ? i+1 : i];
-                                       prt("\t0x%5x\n", n);
-                                       if (op)
-                                               prt("operation# (mod 256) for "
-                                                 "the bad data may be %u\n",
-                                               ((unsigned)op & 0xff));
-                                       else
-                                               prt("operation# (mod 256) for "
-                                                 "the bad data unknown, check"
-                                                 " HOLE and EXTEND ops\n");
-                               }
-                               n++;
-                               badoff = offset;
-                       }
-                       offset++;
-                       i++;
-                       size--;
-               }
-               report_failure(110);
-       }
-}
-
-
-void
-check_size(void)
-{
-       rbd_image_info_t statbuf;
-       int ret;
-
-       if ((ret = rbd_stat(image, &statbuf, sizeof(statbuf))) < 0) {
-               prterrcode("check_size: fstat", ret);
-       }
-       if ((uint64_t)file_size != statbuf.size) {
-               prt("Size error: expected 0x%llx stat 0x%llx\n",
-                   (unsigned long long)file_size,
-                   (unsigned long long)statbuf.size);
-               report_failure(120);
-       }
-}
-
-
-void
-check_trunc_hack(void)
-{
-       rbd_image_info_t statbuf;
-
-       rbd_resize(image, (off_t)0);
-       rbd_resize(image, (off_t)100000);
-       rbd_stat(image, &statbuf, sizeof(statbuf));
-       if (statbuf.size != (off_t)100000) {
-               prt("no extend on truncate! not posix!\n");
-               exit(130);
-       }
-       rbd_resize(image, (off_t)0);
-}
-
-int
-create_image()
-{
-       int r;
-       int order = 0;
-       r = rados_create(&cluster, NULL);
-       if (r < 0) {
-               simple_err("Could not create cluster handle", r);
-               return r;
-       }
-       rados_conf_parse_env(cluster, NULL);
-       r = rados_conf_read_file(cluster, NULL);
-       if (r < 0) {
-               simple_err("Error reading ceph config file", r);
-               goto failed_shutdown;
-       }
-       r = rados_connect(cluster);
-       if (r < 0) {
-               simple_err("Error connecting to cluster", r);
-               goto failed_shutdown;
-       }
-       r = rados_pool_create(cluster, pool);
-       if (r < 0 && r != -EEXIST) {
-               simple_err("Error creating pool", r);
-               goto failed_shutdown;
-       }
-       r = rados_ioctx_create(cluster, pool, &ioctx);
-       if (r < 0) {
-               simple_err("Error creating ioctx", r);
-               goto failed_shutdown;
-       }
-       if (clone_calls) {
-               r = rbd_create2(ioctx, iname, 0, RBD_FEATURE_LAYERING, &order);
-       } else {
-               r = rbd_create(ioctx, iname, 0, &order);
-       }
-       if (r < 0) {
-               simple_err("Error creating image", r);
-               goto failed_open;
-       }
-
-       return 0;
-
- failed_open:
-       rados_ioctx_destroy(ioctx);
- failed_shutdown:
-       rados_shutdown(cluster);
-       return r;
-}
-
-void
-doflush(unsigned offset, unsigned size)
-{
-       if (o_direct == O_DIRECT)
-               return;
-
-       rbd_flush(image);
-}
-
-void
-doread(unsigned offset, unsigned size)
-{
-       int ret;
-
-       offset -= offset % readbdy;
-       if (o_direct)
-               size -= size % readbdy;
-       if (size == 0) {
-               if (!quiet && testcalls > simulatedopcount && !o_direct)
-                       prt("skipping zero size read\n");
-               log4(OP_SKIPPED, OP_READ, offset, size);
-               return;
-       }
-       if (size + offset > file_size) {
-               if (!quiet && testcalls > simulatedopcount)
-                       prt("skipping seek/read past end of file\n");
-               log4(OP_SKIPPED, OP_READ, offset, size);
-               return;
-       }
-
-       log4(OP_READ, offset, size, 0);
-
-       if (testcalls <= simulatedopcount)
-               return;
-
-       if (!quiet &&
-               ((progressinterval && testcalls % progressinterval == 0)  ||
-               (debug &&
-                      (monitorstart == -1 ||
-                       (offset + size > monitorstart &&
-                       (monitorend == -1 || offset <= monitorend))))))
-               prt("%lu read\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
-                   offset, offset + size - 1, size);
-       ret = rbd_read(image, offset, size, temp_buf);
-       if (ret != (int)size) {
-               if (ret < 0)
-                       prterrcode("doread: read", ret);
-               else
-                       prt("short read: 0x%x bytes instead of 0x%x\n",
-                           ret, size);
-               report_failure(141);
-       }
-       check_buffers(offset, size);
-}
-
-
-void
-check_eofpage(char *s, unsigned offset, char *p, int size)
-{
-       unsigned long last_page, should_be_zero;
-
-       if (offset + size <= (file_size & ~page_mask))
-               return;
-       /*
-        * we landed in the last page of the file
-        * test to make sure the VM system provided 0's 
-        * beyond the true end of the file mapping
-        * (as required by mmap def in 1996 posix 1003.1)
-        */
-       last_page = ((unsigned long)p + (offset & page_mask) + size) & ~page_mask;
-
-       for (should_be_zero = last_page + (file_size & page_mask);
-            should_be_zero < last_page + page_size;
-            should_be_zero++)
-               if (*(char *)should_be_zero) {
-                       prt("Mapped %s: non-zero data past EOF (0x%llx) page offset 0x%x is 0x%04x\n",
-                           s, file_size - 1, should_be_zero & page_mask,
-                           short_at(should_be_zero));
-                       report_failure(205);
-               }
-}
-
-
-void
-gendata(char *original_buf, char *good_buf, unsigned offset, unsigned size)
-{
-       while (size--) {
-               good_buf[offset] = testcalls % 256; 
-               if (offset % 2)
-                       good_buf[offset] += original_buf[offset];
-               offset++;
-       }
-}
-
-
-void
-dowrite(unsigned offset, unsigned size)
-{
-       ssize_t ret;
-       off_t newsize;
-
-       offset -= offset % writebdy;
-       if (o_direct)
-               size -= size % writebdy;
-       if (size == 0) {
-               if (!quiet && testcalls > simulatedopcount && !o_direct)
-                       prt("skipping zero size write\n");
-               log4(OP_SKIPPED, OP_WRITE, offset, size);
-               return;
-       }
-
-       log4(OP_WRITE, offset, size, file_size);
-
-       gendata(original_buf, good_buf, offset, size);
-       if (file_size < offset + size) {
-               newsize = ceil(((double)offset + size) / truncbdy) * truncbdy;
-               if (file_size < newsize)
-                       memset(good_buf + file_size, '\0', newsize - file_size);
-               file_size = newsize;
-               if (lite) {
-                       warn("Lite file size bug in fsx!");
-                       report_failure(149);
-               }
-               ret = rbd_resize(image, newsize);
-               if (ret < 0) {
-                       prterrcode("dowrite: resize", ret);
-                       report_failure(150);
-               }
-       }
-
-       if (testcalls <= simulatedopcount)
-               return;
-
-       if (!quiet &&
-               ((progressinterval && testcalls % progressinterval == 0) ||
-                      (debug &&
-                      (monitorstart == -1 ||
-                       (offset + size > monitorstart &&
-                       (monitorend == -1 || offset <= monitorend))))))
-               prt("%lu write\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
-                   offset, offset + size - 1, size);
-
-       ret = rbd_write(image, offset, size, good_buf + offset);
-       if (ret != size) {
-               if (ret < 0)
-                       prterrcode("dowrite: rbd_write", ret);
-               else
-                       prt("short write: 0x%x bytes instead of 0x%x\n",
-                           ret, size);
-               report_failure(151);
-       }
-       if (flush) {
-               doflush(offset, size);
-       }
-}
-
-
-void
-dotruncate(unsigned size)
-{
-       int oldsize = file_size;
-       int ret;
-
-       size -= size % truncbdy;
-       if (size > biggest) {
-               biggest = size;
-               if (!quiet && testcalls > simulatedopcount)
-                       prt("truncating to largest ever: 0x%x\n", size);
-       }
-
-       log4(OP_TRUNCATE, size, (unsigned)file_size, 0);
-
-       if (size > file_size)
-               memset(good_buf + file_size, '\0', size - file_size);
-       else if (size < file_size)
-               memset(good_buf + size, '\0', file_size - size);
-       file_size = size;
-
-       if (testcalls <= simulatedopcount)
-               return;
-       
-       if ((progressinterval && testcalls % progressinterval == 0) ||
-           (debug && (monitorstart == -1 || monitorend == -1 ||
-                     size <= monitorend)))
-               prt("%lu trunc\tfrom 0x%x to 0x%x\n", testcalls, oldsize, size);
-       if ((ret = rbd_resize(image, size)) < 0) {
-               prt("rbd_resize: %x\n", size);
-               prterrcode("dotruncate: ftruncate", ret);
-               report_failure(160);
-       }
-}
-
-void
-do_punch_hole(unsigned offset, unsigned length)
-{
-       unsigned end_offset;
-       int max_offset = 0;
-       int max_len = 0;
-       int ret;
-
-       if (length == 0) {
-               if (!quiet && testcalls > simulatedopcount)
-                       prt("skipping zero length punch hole\n");
-                       log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length);
-               return;
-       }
-
-       if (file_size <= (loff_t)offset) {
-               if (!quiet && testcalls > simulatedopcount)
-                       prt("skipping hole punch off the end of the file\n");
-                       log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length);
-               return;
-       }
-
-       end_offset = offset + length;
-
-       log4(OP_PUNCH_HOLE, offset, length, 0);
-
-       if (testcalls <= simulatedopcount)
-               return;
-
-       if ((progressinterval && testcalls % progressinterval == 0) ||
-           (debug && (monitorstart == -1 || monitorend == -1 ||
-                     end_offset <= monitorend))) {
-               prt("%lu punch\tfrom 0x%x to 0x%x, (0x%x bytes)\n", testcalls,
-                       offset, offset+length, length);
-       }
-       if ((ret = rbd_discard(image, (unsigned long long) offset,
-                              (unsigned long long) length)) < 0) {
-               prt("%punch hole: %x to %x\n", offset, length);
-               prterrcode("do_punch_hole: discard", ret);
-               report_failure(161);
-       }
-
-
-       max_offset = offset < file_size ? offset : file_size;
-       max_len = max_offset + length <= file_size ? length :
-                       file_size - max_offset;
-       memset(good_buf + max_offset, '\0', max_len);
-}
-
-void clone_filename(char *buf, size_t len, int clones)
-{
-       snprintf(buf, len, "%s/fsx-%s-parent%d",
-                dirpath, iname, clones);
-}
-
-void clone_imagename(char *buf, size_t len, int clones)
-{
-       if (clones > 0)
-               snprintf(buf, len, "%s-clone%d", iname, clones);
-       else
-               strncpy(buf, iname, len);
-}
-
-void
-do_clone()
-{
-       char filename[1024];
-       char imagename[1024];
-       char lastimagename[1024];
-       int ret, fd;
-       int order = 0, stripe_unit = 0, stripe_count = 0;
-
-       if (randomize_striping) {
-               order = 18 + rand() % 8;
-               stripe_unit = 1ull << (order - 1 - (rand() % 8));
-               stripe_count = 2 + rand() % 14;
-       }
-
-       log4(OP_CLONE, 0, 0, 0);
-       ++num_clones;
-       prt("%lu clone\t%d order %d su %d sc %d\n", testcalls, num_clones, order, stripe_unit, stripe_count);
-
-       clone_filename(filename, sizeof(filename), num_clones);
-       if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
-               simple_err("do_clone: open", -errno);
-               exit(162);
-       }
-       save_buffer(good_buf, file_size, fd);
-       if ((ret = close(fd)) < 0) {
-               simple_err("do_clone: close", -errno);
-               exit(163);
-       }
-
-       if ((ret = rbd_snap_create(image, "snap")) < 0) {
-               simple_err("do_clone: rbd create snap", ret);
-               exit(164);
-       }
-
-       if ((ret = rbd_snap_protect(image, "snap")) < 0) {
-               simple_err("do_clone: rbd protect snap", ret);
-               exit(164);
-       }
-
-       clone_imagename(imagename, sizeof(imagename), num_clones);
-       clone_imagename(lastimagename, sizeof(lastimagename),
-                       num_clones - 1);
-
-       ret = rbd_clone2(ioctx, lastimagename, "snap", ioctx, imagename,
-                        RBD_FEATURES_ALL, &order, stripe_unit, stripe_count);
-       if (ret < 0) {
-               simple_err("do_clone: rbd clone", ret);
-               exit(165);
-       }
-       rbd_close(image);
-       if ((ret = rbd_open(ioctx, imagename, &image, NULL)) < 0) {
-               simple_err("do_clone: rbd open", ret);
-               exit(166);
-       }
-}
-
-void
-check_clones()
-{
-       char filename[1024];
-       char imagename[1024];
-       int ret, fd;
-       rbd_image_t cur_image;
-       struct stat file_info;
-       while (num_clones > 0) {
-               prt("checking clone #%d\n", num_clones);
-               --num_clones;
-
-               clone_imagename(imagename, sizeof(imagename), num_clones);
-               if ((ret = rbd_open(ioctx, imagename, &cur_image, NULL)) < 0) {
-                       simple_err("check_clones: rbd open", ret);
-                       exit(167);
-               }
-
-               clone_filename(filename, sizeof(filename), num_clones + 1);
-               if ((fd = open(filename, O_RDONLY)) < 0) {
-                       simple_err("check_clones: open", -errno);
-                       exit(168);
-               }
-
-               prt("checking image %s against file %s\n", imagename, filename);
-               if ((ret = fstat(fd, &file_info)) < 0) {
-                       simple_err("check_clones: fstat", -errno);
-                       exit(169);
-               }
-
-               if ((ret = pread(fd, good_buf, file_info.st_size, 0)) < 0) {
-                       simple_err("check_clones: pread", -errno);
-                       exit(170);
-               }
-
-               if ((ret = rbd_read(cur_image, 0, file_info.st_size, temp_buf)) < 0) {
-                       simple_err("check_clones: rbd_read", ret);
-                       exit(171);
-               }
-               close(fd);
-               check_buffers(0, file_info.st_size);
-
-               unlink(filename);
-               /* remove the snapshot if it exists, ignore
-                  the error from the last clone. */
-               rbd_snap_unprotect(cur_image, "snap");
-               rbd_snap_remove(cur_image, "snap");
-               rbd_close(cur_image);
-               rbd_remove(ioctx, imagename);
-       }
-}
-
-void
-writefileimage()
-{
-       ssize_t ret;
-
-       ret = rbd_write(image, 0, file_size, good_buf);
-       if (ret != file_size) {
-               if (ret < 0)
-                       prterrcode("writefileimage: write", ret);
-               else
-                       prt("short write: 0x%x bytes instead of 0x%llx\n",
-                           ret, (unsigned long long)file_size);
-               report_failure(172);
-       }
-       if (lite ? 0 : (ret = rbd_resize(image, file_size)) < 0) {
-               prt("rbd_resize: %llx\n", (unsigned long long)file_size);
-               prterrcode("writefileimage: rbd_resize", ret);
-               report_failure(173);
-       }
-}
-
-
-void
-docloseopen(void)
-{
-       int ret;
-
-       if (testcalls <= simulatedopcount)
-               return;
-
-       if (debug)
-               prt("%lu close/open\n", testcalls);
-       if ((ret = rbd_close(image)) < 0) {
-               prterrcode("docloseopen: close", ret);
-               report_failure(180);
-       }
-       ret = rbd_open(ioctx, iname, &image, NULL);
-       if (ret < 0) {
-               prterrcode("docloseopen: open", ret);
-               report_failure(181);
-       }
-}
-
-#define TRIM_OFF_LEN(off, len, size)   \
-do {                                   \
-       if (size)                       \
-               (off) %= (size);        \
-       else                            \
-               (off) = 0;              \
-       if ((unsigned)(off) + (unsigned)(len) > (unsigned)(size))       \
-               (len) = (size) - (off); \
-} while (0)
-
-void
-test(void)
-{
-       unsigned long   offset;
-       unsigned long   size = maxoplen;
-       unsigned long   rv = random();
-       unsigned long   op;
-
-       if (simulatedopcount > 0 && testcalls == simulatedopcount)
-               writefileimage();
-
-       testcalls++;
-
-       if (closeprob)
-               closeopen = (rv >> 3) < (1u << 28) / (unsigned)closeprob;
-
-       if (debugstart > 0 && testcalls >= debugstart)
-               debug = 1;
-
-       if (!quiet && testcalls < simulatedopcount && testcalls % 100000 == 0)
-               prt("%lu...\n", testcalls);
-
-       offset = random();
-       if (randomoplen)
-               size = random() % (maxoplen + 1);
-
-       /* calculate appropriate op to run */
-       if (lite)
-               op = rv % OP_MAX_LITE;
-       else
-               op = rv % OP_MAX_FULL;
-
-       switch (op) {
-       case OP_MAPREAD:
-               if (!mapped_reads)
-                       op = OP_READ;
-               break;
-       case OP_MAPWRITE:
-               if (!mapped_writes)
-                       op = OP_WRITE;
-               break;
-       case OP_FALLOCATE:
-               if (!fallocate_calls) {
-                       log4(OP_SKIPPED, OP_FALLOCATE, offset, size);
-                       goto out;
-               }
-               break;
-       case OP_PUNCH_HOLE:
-               if (!punch_hole_calls) {
-                       log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, size);
-                       goto out;
-               }
-               break;
-       case OP_CLONE:
-               if (!clone_calls || random() % 100 > 5 || file_size == 0) {
-                       log4(OP_SKIPPED, OP_CLONE, 0, 0);
-                       goto out;
-               }
-               break;
-       }
-
-       switch (op) {
-       case OP_READ:
-               TRIM_OFF_LEN(offset, size, file_size);
-               doread(offset, size);
-               break;
-
-       case OP_WRITE:
-               TRIM_OFF_LEN(offset, size, maxfilelen);
-               dowrite(offset, size);
-               break;
-
-       case OP_MAPREAD:
-               TRIM_OFF_LEN(offset, size, file_size);
-               exit(183);
-               break;
-
-       case OP_MAPWRITE:
-               TRIM_OFF_LEN(offset, size, maxfilelen);
-               exit(182);
-               break;
-
-       case OP_TRUNCATE:
-               if (!style)
-                       size = random() % maxfilelen;
-               dotruncate(size);
-               break;
-
-       case OP_PUNCH_HOLE:
-               TRIM_OFF_LEN(offset, size, file_size);
-               do_punch_hole(offset, size);
-               break;
-
-       case OP_CLONE:
-               do_clone();
-               break;
-
-       default:
-               prterr("test: unknown operation");
-               report_failure(42);
-               break;
-       }
-
-out:
-       if (sizechecks && testcalls > simulatedopcount)
-               check_size();
-       if (closeopen)
-               docloseopen();
-}
-
-
-void
-cleanup(sig)
-       int     sig;
-{
-       if (sig)
-               prt("signal %d\n", sig);
-       prt("testcalls = %lu\n", testcalls);
-       exit(sig);
-}
-
-
-void
-usage(void)
-{
-       fprintf(stdout, "usage: %s",
-               "fsx [-dnqxAFLOWZ] [-b opnum] [-c Prob] [-l flen] [-m start:end] [-o oplen] [-p progressinterval] [-r readbdy] [-s style] [-t truncbdy] [-w writebdy] [-D startingop] [-N numops] [-P dirpath] [-S seed] pname iname\n\
-       -b opnum: beginning operation number (default 1)\n\
-       -c P: 1 in P chance of file close+open at each op (default infinity)\n\
-       -d: debug output for all operations\n\
-       -f flush and invalidate cache after I/O\n\
-       -l flen: the upper bound on file size (default 262144)\n\
-       -m startop:endop: monitor (print debug output) specified byte range (default 0:infinity)\n\
-       -n: no verifications of file size\n\
-       -o oplen: the upper bound on operation size (default 65536)\n\
-       -p progressinterval: debug output at specified operation interval\n\
-       -q: quieter operation\n\
-       -r readbdy: 4096 would make reads page aligned (default 1)\n\
-       -s style: 1 gives smaller truncates (default 0)\n\
-       -t truncbdy: 4096 would make truncates page aligned (default 1)\n\
-       -w writebdy: 4096 would make writes page aligned (default 1)\n\
-       -x: preallocate file space before starting, XFS only (default 0)\n\
-       -y synchronize changes to a file\n"
-
-#ifdef AIO
-"      -A: Use the AIO system calls\n"
-#endif
-"      -D startingop: debug output starting at specified operation\n"
-#ifdef FALLOCATE
-"      -F: Do not use fallocate (preallocation) calls\n"
-#endif
-"        -H: Do not use punch hole calls\n"
-"        -C: Do not use clone calls\n"
-"      -L: fsxLite - no file creations & no file size changes\n\
-       -N numops: total # operations to do (default infinity)\n\
-       -O: use oplen (see -o flag) for every op (default random)\n\
-       -P: save .fsxlog and .fsxgood files in dirpath (default ./)\n\
-       -S seed: for random # generator (default 1) 0 gets timestamp\n\
-       -W: mapped write operations DISabled\n\
-        -R: read() system calls only (mapped reads disabled)\n\
-        -Z: O_DIRECT (use -R, -W, -r and -w too)\n\
-       poolname: this is REQUIRED (no default)\n\
-       imagename: this is REQUIRED (no default)\n");
-       exit(89);
-}
-
-
-int
-getnum(char *s, char **e)
-{
-       int ret;
-
-       *e = (char *) 0;
-       ret = strtol(s, e, 0);
-       if (*e)
-               switch (**e) {
-               case 'b':
-               case 'B':
-                       ret *= 512;
-                       *e = *e + 1;
-                       break;
-               case 'k':
-               case 'K':
-                       ret *= 1024;
-                       *e = *e + 1;
-                       break;
-               case 'm':
-               case 'M':
-                       ret *= 1024*1024;
-                       *e = *e + 1;
-                       break;
-               case 'w':
-               case 'W':
-                       ret *= 4;
-                       *e = *e + 1;
-                       break;
-               }
-       return (ret);
-}
-
-#ifdef AIO
-
-#define QSZ     1024
-io_context_t   io_ctx;
-struct iocb    iocb;
-
-int aio_setup()
-{
-       int ret;
-       ret = io_queue_init(QSZ, &io_ctx);
-       if (ret != 0) {
-               fprintf(stderr, "aio_setup: io_queue_init failed: %s\n",
-                        strerror(ret));
-                return(-1);
-        }
-        return(0);
-}
-
-int
-__aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset)
-{
-       struct io_event event;
-       static struct timespec ts;
-       struct iocb *iocbs[] = { &iocb };
-       int ret;
-       long res;
-
-       if (rw == READ) {
-               io_prep_pread(&iocb, fd, buf, len, offset);
-       } else {
-               io_prep_pwrite(&iocb, fd, buf, len, offset);
-       }
-
-       ts.tv_sec = 30;
-       ts.tv_nsec = 0;
-       ret = io_submit(io_ctx, 1, iocbs);
-       if (ret != 1) {
-               fprintf(stderr, "errcode=%d\n", ret);
-               fprintf(stderr, "aio_rw: io_submit failed: %s\n",
-                               strerror(ret));
-               goto out_error;
-       }
-
-       ret = io_getevents(io_ctx, 1, 1, &event, &ts);
-       if (ret != 1) {
-               if (ret == 0)
-                       fprintf(stderr, "aio_rw: no events available\n");
-               else {
-                       fprintf(stderr, "errcode=%d\n", -ret);
-                       fprintf(stderr, "aio_rw: io_getevents failed: %s\n",
-                                       strerror(-ret));
-               }
-               goto out_error;
-       }
-       if (len != event.res) {
-               /*
-                * The b0rked libaio defines event.res as unsigned.
-                * However the kernel strucuture has it signed,
-                * and it's used to pass negated error value.
-                * Till the library is fixed use the temp var.
-                */
-               res = (long)event.res;
-               if (res >= 0)
-                       fprintf(stderr, "bad io length: %lu instead of %u\n",
-                                       res, len);
-               else {
-                       fprintf(stderr, "errcode=%ld\n", -res);
-                       fprintf(stderr, "aio_rw: async io failed: %s\n",
-                                       strerror(-res));
-                       ret = res;
-                       goto out_error;
-               }
-
-       }
-       return event.res;
-
-out_error:
-       /*
-        * The caller expects error return in traditional libc
-        * convention, i.e. -1 and the errno set to error.
-        */
-       errno = -ret;
-       return -1;
-}
-
-int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset)
-{
-       int ret;
-
-       if (aio) {
-               ret = __aio_rw(rw, fd, buf, len, offset);
-       } else {
-               if (rw == READ)
-                       ret = read(fd, buf, len);
-               else
-                       ret = write(fd, buf, len);
-       }
-       return ret;
-}
-
-#endif
-
-void
-test_fallocate()
-{
-#ifdef FALLOCATE
-       if (!lite && fallocate_calls) {
-               if (fallocate(fd, 0, 0, 1) && errno == EOPNOTSUPP) {
-                       if(!quiet)
-                               warn("main: filesystem does not support fallocate, disabling\n");
-                       fallocate_calls = 0;
-               } else {
-                       ftruncate(fd, 0);
-               }
-       }
-#else /* ! FALLOCATE */
-       fallocate_calls = 0;
-#endif
-
-}
-
-int
-main(int argc, char **argv)
-{
-       int     i, style, ch, ret;
-       char    *endp;
-       char goodfile[1024];
-       char logfile[1024];
-       char finaliname[1024];
-
-       goodfile[0] = 0;
-       logfile[0] = 0;
-
-       page_size = getpagesize();
-       page_mask = page_size - 1;
-       mmap_mask = page_mask;
-
-       setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
-
-       while ((ch = getopt(argc, argv, "b:c:dfl:m:no:p:qr:s:t:w:xyACD:FHLN:OP:RS:WZ"))
-              != EOF)
-               switch (ch) {
-               case 'b':
-                       simulatedopcount = getnum(optarg, &endp);
-                       if (!quiet)
-                               fprintf(stdout, "Will begin at operation %ld\n",
-                                       simulatedopcount);
-                       if (simulatedopcount == 0)
-                               usage();
-                       simulatedopcount -= 1;
-                       break;
-               case 'c':
-                       closeprob = getnum(optarg, &endp);
-                       if (!quiet)
-                               fprintf(stdout,
-                                       "Chance of close/open is 1 in %d\n",
-                                       closeprob);
-                       if (closeprob <= 0)
-                               usage();
-                       break;
-               case 'd':
-                       debug = 1;
-                       break;
-               case 'f':
-                       flush = 1;
-                       break;
-               case 'l':
-                       maxfilelen = getnum(optarg, &endp);
-                       if (maxfilelen <= 0)
-                               usage();
-                       break;
-               case 'm':
-                       monitorstart = getnum(optarg, &endp);
-                       if (monitorstart < 0)
-                               usage();
-                       if (!endp || *endp++ != ':')
-                               usage();
-                       monitorend = getnum(endp, &endp);
-                       if (monitorend < 0)
-                               usage();
-                       if (monitorend == 0)
-                               monitorend = -1; /* aka infinity */
-                       debug = 1;
-                       break;
-               case 'n':
-                       sizechecks = 0;
-                       break;
-               case 'o':
-                       maxoplen = getnum(optarg, &endp);
-                       if (maxoplen <= 0)
-                               usage();
-                       break;
-               case 'p':
-                       progressinterval = getnum(optarg, &endp);
-                       if (progressinterval == 0)
-                               usage();
-                       break;
-               case 'q':
-                       quiet = 1;
-                       break;
-               case 'r':
-                       readbdy = getnum(optarg, &endp);
-                       if (readbdy <= 0)
-                               usage();
-                       break;
-               case 's':
-                       style = getnum(optarg, &endp);
-                       if (style < 0 || style > 1)
-                               usage();
-                       break;
-               case 't':
-                       truncbdy = getnum(optarg, &endp);
-                       if (truncbdy <= 0)
-                               usage();
-                       break;
-               case 'w':
-                       writebdy = getnum(optarg, &endp);
-                       if (writebdy <= 0)
-                               usage();
-                       break;
-               case 'x':
-                       prealloc = 1;
-                       break;
-               case 'y':
-                       do_fsync = 1;
-                       break;
-               case 'A':
-                       aio = 1;
-                       break;
-               case 'C':
-                       clone_calls = 0;
-                       break;
-               case 'D':
-                       debugstart = getnum(optarg, &endp);
-                       if (debugstart < 1)
-                               usage();
-                       break;
-               case 'F':
-                       fallocate_calls = 0;
-                       break;
-               case 'H':
-                       punch_hole_calls = 0;
-                       break;
-               case 'L':
-                       prt("lite mode not supported for rbd\n");
-                       exit(1);
-                       break;
-               case 'N':
-                       numops = getnum(optarg, &endp);
-                       if (numops < 0)
-                               usage();
-                       break;
-               case 'O':
-                       randomoplen = 0;
-                       break;
-               case 'P':
-                       strncpy(dirpath, optarg, sizeof(dirpath));
-                       strncpy(goodfile, dirpath, sizeof(goodfile));
-                       strcat(goodfile, "/");
-                       strncpy(logfile, dirpath, sizeof(logfile));
-                       strcat(logfile, "/");
-                       break;
-                case 'R':
-                        mapped_reads = 0;
-                        break;
-               case 'S':
-                        seed = getnum(optarg, &endp);
-                       if (seed == 0)
-                               seed = time(0) % 10000;
-                       if (!quiet)
-                               fprintf(stdout, "Seed set to %d\n", seed);
-                       if (seed < 0)
-                               usage();
-                       break;
-               case 'W':
-                       mapped_writes = 0;
-                       if (!quiet)
-                               fprintf(stdout, "mapped writes DISABLED\n");
-                       break;
-               case 'Z':
-                       o_direct = O_DIRECT;
-                       break;
-               default:
-                       usage();
-                       /* NOTREACHED */
-               }
-       argc -= optind;
-       argv += optind;
-       if (argc != 2)
-               usage();
-       pool = argv[0];
-       iname = argv[1];
-
-       signal(SIGHUP,  cleanup);
-       signal(SIGINT,  cleanup);
-       signal(SIGPIPE, cleanup);
-       signal(SIGALRM, cleanup);
-       signal(SIGTERM, cleanup);
-       signal(SIGXCPU, cleanup);
-       signal(SIGXFSZ, cleanup);
-       signal(SIGVTALRM,       cleanup);
-       signal(SIGUSR1, cleanup);
-       signal(SIGUSR2, cleanup);
-
-       initstate(seed, state, 256);
-       setstate(state);
-
-       ret = create_image();
-       if (ret < 0) {
-               prterrcode(iname, ret);
-               exit(90);
-       }
-       ret = rbd_open(ioctx, iname, &image, NULL);
-       if (ret < 0) {
-               simple_err("Error opening image", ret);
-               exit(91);
-       }
-       if (!dirpath[0])
-               strcat(dirpath, ".");
-       strncat(goodfile, iname, 256);
-       strcat (goodfile, ".fsxgood");
-       fsxgoodfd = open(goodfile, O_RDWR|O_CREAT|O_TRUNC, 0666);
-       if (fsxgoodfd < 0) {
-               prterr(goodfile);
-               exit(92);
-       }
-       strncat(logfile, iname, 256);
-       strcat (logfile, ".fsxlog");
-       fsxlogf = fopen(logfile, "w");
-       if (fsxlogf == NULL) {
-               prterr(logfile);
-               exit(93);
-       }
-
-#ifdef AIO
-       if (aio) 
-               aio_setup();
-#endif
-
-       original_buf = (char *) malloc(maxfilelen);
-       for (i = 0; i < (int)maxfilelen; i++)
-               original_buf[i] = random() % 256;
-       good_buf = (char *) malloc(maxfilelen + writebdy);
-       good_buf = round_ptr_up(good_buf, writebdy, 0);
-       memset(good_buf, '\0', maxfilelen);
-       temp_buf = (char *) malloc(maxfilelen + writebdy);
-       temp_buf = round_ptr_up(temp_buf, writebdy, 0);
-       memset(temp_buf, '\0', maxfilelen);
-       if (lite) {     /* zero entire existing file */
-               ssize_t written;
-
-               written = rbd_write(image, 0, (size_t)maxfilelen, good_buf);
-               if (written != (ssize_t)maxfilelen) {
-                       if (written < 0) {
-                               prterrcode(iname, written);
-                               warn("main: error on write");
-                       } else
-                               warn("main: short write, 0x%x bytes instead "
-                                       "of 0x%lx\n",
-                                       (unsigned)written,
-                                       maxfilelen);
-                       exit(98);
-               }
-       } else 
-               check_trunc_hack();
-
-       //test_fallocate();
-
-       while (numops == -1 || numops--)
-               test();
-
-       if ((ret = rbd_close(image)) < 0) {
-               prterrcode("rbd_close", ret);
-               report_failure(99);
-       }
-
-       clone_imagename(finaliname, sizeof(finaliname), num_clones);
-       if ((ret = rbd_remove(ioctx, finaliname)) < 0) {
-               prterrcode("rbd_remove final image", ret);
-               report_failure(100);
-       }
-
-       if (clone_calls)
-               check_clones();
-
-       rados_ioctx_destroy(ioctx);
-       rados_shutdown(cluster);
-
-       free(original_buf);
-       free(good_buf);
-       free(temp_buf);
-
-       prt("All operations completed A-OK!\n");
-       fclose(fsxlogf);
-
-       exit(0);
-       return 0;
-}
diff --git a/src/test/rbd/test_cls_rbd.cc b/src/test/rbd/test_cls_rbd.cc
deleted file mode 100644 (file)
index a0ded60..0000000
+++ /dev/null
@@ -1,911 +0,0 @@
-// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
-// vim: ts=8 sw=2 smarttab
-
-#include "common/snap_types.h"
-#include "include/encoding.h"
-#include "include/rados.h"
-#include "include/rados/librados.h"
-#include "include/types.h"
-#include "librbd/cls_rbd.h"
-#include "librbd/cls_rbd_client.h"
-
-#include "gtest/gtest.h"
-#include "test/rados-api/test.h"
-
-#include <errno.h>
-#include <string>
-#include <vector>
-
-using namespace std;
-using ::librbd::cls_client::create_image;
-using ::librbd::cls_client::get_features;
-using ::librbd::cls_client::get_size;
-using ::librbd::cls_client::get_object_prefix;
-using ::librbd::cls_client::set_size;
-using ::librbd::cls_client::get_parent;
-using ::librbd::cls_client::set_parent;
-using ::librbd::cls_client::remove_parent;
-using ::librbd::cls_client::snapshot_add;
-using ::librbd::cls_client::snapshot_remove;
-using ::librbd::cls_client::add_child;
-using ::librbd::cls_client::remove_child;
-using ::librbd::cls_client::get_children;
-using ::librbd::cls_client::get_snapcontext;
-using ::librbd::cls_client::snapshot_list;
-using ::librbd::cls_client::copyup;
-using ::librbd::cls_client::get_id;
-using ::librbd::cls_client::set_id;
-using ::librbd::cls_client::dir_get_id;
-using ::librbd::cls_client::dir_get_name;
-using ::librbd::cls_client::dir_list;
-using ::librbd::cls_client::dir_add_image;
-using ::librbd::cls_client::dir_remove_image;
-using ::librbd::cls_client::dir_rename_image;
-using ::librbd::parent_info;
-using ::librbd::parent_spec;
-using ::librbd::cls_client::get_protection_status;
-using ::librbd::cls_client::set_protection_status;
-using ::librbd::cls_client::get_stripe_unit_count;
-using ::librbd::cls_client::set_stripe_unit_count;
-using ::librbd::cls_client::old_snapshot_add;
-
-static char *random_buf(size_t len)
-{
-  char *b = new char[len];
-  for (size_t i = 0; i < len; i++)
-    b[i] = (rand() % (128 - 32)) + 32;
-  return b;
-}
-
-TEST(cls_rbd, copyup)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  string oid = "rbd_copyup_test";
-  bufferlist inbl, outbl;
-
-  // copyup of 0-len nonexistent object should create new 0-len object
-  ioctx.remove(oid);
-  ASSERT_EQ(0, copyup(&ioctx, oid, inbl));
-  uint64_t size;
-  ASSERT_EQ(0, ioctx.stat(oid, &size, NULL));
-  ASSERT_EQ(0U, size);
-
-  // create some random data to write
-  size_t l = 4 << 20;
-  char *b = random_buf(l);
-  inbl.append(b, l);
-  delete b;
-  ASSERT_EQ(l, inbl.length());
-
-  // copyup to nonexistent object should create new object
-  ioctx.remove(oid);
-  ASSERT_EQ(-ENOENT, ioctx.remove(oid));
-  ASSERT_EQ(0, copyup(&ioctx, oid, inbl));
-  // and its contents should match
-  ASSERT_EQ(l, (size_t)ioctx.read(oid, outbl, l, 0));
-  ASSERT_TRUE(outbl.contents_equal(inbl));
-
-  // now send different data, but with a preexisting object
-  bufferlist inbl2;
-  b = random_buf(l);
-  inbl2.append(b, l);
-  delete b;
-  ASSERT_EQ(l, inbl2.length());
-
-  // should still succeed
-  ASSERT_EQ(0, copyup(&ioctx, oid, inbl));
-  ASSERT_EQ(l, (size_t)ioctx.read(oid, outbl, l, 0));
-  // but contents should not have changed
-  ASSERT_FALSE(outbl.contents_equal(inbl2));
-  ASSERT_TRUE(outbl.contents_equal(inbl));
-
-  ASSERT_EQ(0, ioctx.remove(oid));
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, get_and_set_id)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  string oid = "rbd_id_test";
-  string id;
-  string valid_id = "0123abcxyzZYXCBA";
-  string invalid_id = ".abc";
-  string empty_id;
-
-  ASSERT_EQ(-ENOENT, get_id(&ioctx, oid, &id));
-  ASSERT_EQ(-ENOENT, set_id(&ioctx, oid, valid_id));
-
-  ASSERT_EQ(0, ioctx.create(oid, true));
-  ASSERT_EQ(-EINVAL, set_id(&ioctx, oid, invalid_id));
-  ASSERT_EQ(-EINVAL, set_id(&ioctx, oid, empty_id));
-  ASSERT_EQ(-ENOENT, get_id(&ioctx, oid, &id));
-
-  ASSERT_EQ(0, set_id(&ioctx, oid, valid_id));
-  ASSERT_EQ(-EEXIST, set_id(&ioctx, oid, valid_id));
-  ASSERT_EQ(-EEXIST, set_id(&ioctx, oid, valid_id + valid_id));
-  ASSERT_EQ(0, get_id(&ioctx, oid, &id));
-  ASSERT_EQ(id, valid_id);
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, add_remove_child)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  string oid = "rbd_children_test";
-  ASSERT_EQ(0, ioctx.create(oid, true));
-
-  string snapname = "parent_snap";
-  snapid_t snapid(10);
-  string parent_image = "parent_id";
-  set<string>children;
-  parent_spec pspec(ioctx.get_id(), parent_image, snapid);
-
-  // nonexistent children cannot be listed or removed
-  ASSERT_EQ(-ENOENT, get_children(&ioctx, oid, pspec, children));
-  ASSERT_EQ(-ENOENT, remove_child(&ioctx, oid, pspec, "child1"));
-
-  // create the parent and snapshot
-  ASSERT_EQ(0, create_image(&ioctx, parent_image, 2<<20, 0,
-                           RBD_FEATURE_LAYERING, parent_image));
-  ASSERT_EQ(0, snapshot_add(&ioctx, parent_image, snapid, snapname));
-
-  // add child to it, verify it showed up
-  ASSERT_EQ(0, add_child(&ioctx, oid, pspec, "child1"));
-  ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children));
-  ASSERT_TRUE(children.find("child1") != children.end());
-  // add another child to it, verify it showed up
-  ASSERT_EQ(0, add_child(&ioctx, oid, pspec, "child2"));
-  ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children));
-  ASSERT_TRUE(children.find("child2") != children.end());
-  // add child2 again, expect -EEXIST
-  ASSERT_EQ(-EEXIST, add_child(&ioctx, oid, pspec, "child2"));
-  // remove first, verify it's gone
-  ASSERT_EQ(0, remove_child(&ioctx, oid, pspec, "child1"));
-  ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children));
-  ASSERT_FALSE(children.find("child1") != children.end());
-  // remove second, verify list empty
-  ASSERT_EQ(0, remove_child(&ioctx, oid, pspec, "child2"));
-  ASSERT_EQ(-ENOENT, get_children(&ioctx, oid, pspec, children));
-  // try to remove again, validate -ENOENT to that as well
-  ASSERT_EQ(-ENOENT, remove_child(&ioctx, oid, pspec, "child2"));
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, directory_methods)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  string oid = "rbd_id_test";
-  string id, name;
-  string imgname = "bar";
-  string imgname2 = "foo";
-  string imgname3 = "baz";
-  string valid_id = "0123abcxyzZYXCBA";
-  string valid_id2 = "5";
-  string invalid_id = ".abc";
-  string empty;
-
-  ASSERT_EQ(-ENOENT, dir_get_id(&ioctx, oid, imgname, &id));
-  ASSERT_EQ(-ENOENT, dir_get_name(&ioctx, oid, valid_id, &name));
-  ASSERT_EQ(-ENOENT, dir_remove_image(&ioctx, oid, imgname, valid_id));
-
-  ASSERT_EQ(-EINVAL, dir_add_image(&ioctx, oid, imgname, invalid_id));
-  ASSERT_EQ(-EINVAL, dir_add_image(&ioctx, oid, imgname, empty));
-  ASSERT_EQ(-EINVAL, dir_add_image(&ioctx, oid, empty, valid_id));
-
-  map<string, string> images;
-  ASSERT_EQ(-ENOENT, dir_list(&ioctx, oid, "", 30, &images));
-
-  ASSERT_EQ(0, ioctx.create(oid, true));
-  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
-  ASSERT_EQ(0u, images.size());
-  ASSERT_EQ(0, ioctx.remove(oid));
-
-  ASSERT_EQ(0, dir_add_image(&ioctx, oid, imgname, valid_id));
-  ASSERT_EQ(-EEXIST, dir_add_image(&ioctx, oid, imgname, valid_id2));
-  ASSERT_EQ(-EBADF, dir_add_image(&ioctx, oid, imgname2, valid_id));
-  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
-  ASSERT_EQ(1u, images.size());
-  ASSERT_EQ(valid_id, images[imgname]);
-  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 0, &images));
-  ASSERT_EQ(0u, images.size());
-  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id, &name));
-  ASSERT_EQ(imgname, name);
-  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname, &id));
-  ASSERT_EQ(valid_id, id);
-
-  ASSERT_EQ(0, dir_add_image(&ioctx, oid, imgname2, valid_id2));
-  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
-  ASSERT_EQ(2u, images.size());
-  ASSERT_EQ(valid_id, images[imgname]);
-  ASSERT_EQ(valid_id2, images[imgname2]);
-  ASSERT_EQ(0, dir_list(&ioctx, oid, imgname, 0, &images));
-  ASSERT_EQ(0u, images.size());
-  ASSERT_EQ(0, dir_list(&ioctx, oid, imgname, 2, &images));
-  ASSERT_EQ(1u, images.size());
-  ASSERT_EQ(valid_id2, images[imgname2]);
-  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id2, &name));
-  ASSERT_EQ(imgname2, name);
-  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname2, &id));
-  ASSERT_EQ(valid_id2, id);
-
-  ASSERT_EQ(-ESTALE, dir_rename_image(&ioctx, oid, imgname, imgname2, valid_id2));
-  ASSERT_EQ(-ESTALE, dir_remove_image(&ioctx, oid, imgname, valid_id2));
-  ASSERT_EQ(-EEXIST, dir_rename_image(&ioctx, oid, imgname, imgname2, valid_id));
-  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname, &id));
-  ASSERT_EQ(valid_id, id);
-  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id2, &name));
-  ASSERT_EQ(imgname2, name);
-
-  ASSERT_EQ(0, dir_rename_image(&ioctx, oid, imgname, imgname3, valid_id));
-  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname3, &id));
-  ASSERT_EQ(valid_id, id);
-  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id, &name));
-  ASSERT_EQ(imgname3, name);
-  ASSERT_EQ(0, dir_rename_image(&ioctx, oid, imgname3, imgname, valid_id));
-
-  ASSERT_EQ(0, dir_remove_image(&ioctx, oid, imgname, valid_id));
-  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
-  ASSERT_EQ(1u, images.size());
-  ASSERT_EQ(valid_id2, images[imgname2]);
-  ASSERT_EQ(0, dir_list(&ioctx, oid, imgname2, 30, &images));
-  ASSERT_EQ(0u, images.size());
-  ASSERT_EQ(0, dir_get_name(&ioctx, oid, valid_id2, &name));
-  ASSERT_EQ(imgname2, name);
-  ASSERT_EQ(0, dir_get_id(&ioctx, oid, imgname2, &id));
-  ASSERT_EQ(valid_id2, id);
-  ASSERT_EQ(-ENOENT, dir_get_name(&ioctx, oid, valid_id, &name));
-  ASSERT_EQ(-ENOENT, dir_get_id(&ioctx, oid, imgname, &id));
-
-  ASSERT_EQ(0, dir_add_image(&ioctx, oid, imgname, valid_id));
-  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
-  ASSERT_EQ(2u, images.size());
-  ASSERT_EQ(valid_id, images[imgname]);
-  ASSERT_EQ(valid_id2, images[imgname2]);
-  ASSERT_EQ(0, dir_remove_image(&ioctx, oid, imgname, valid_id));
-  ASSERT_EQ(-ENOENT, dir_remove_image(&ioctx, oid, imgname, valid_id));
-  ASSERT_EQ(0, dir_remove_image(&ioctx, oid, imgname2, valid_id2));
-  ASSERT_EQ(0, dir_list(&ioctx, oid, "", 30, &images));
-  ASSERT_EQ(0u, images.size());
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, create)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  string oid = "testobj";
-  uint64_t size = 20 << 30;
-  uint64_t features = 0;
-  uint8_t order = 22;
-  string object_prefix = "foo";
-
-  ASSERT_EQ(0, create_image(&ioctx, oid, size, order,
-                           features, object_prefix));
-  ASSERT_EQ(-EEXIST, create_image(&ioctx, oid, size, order,
-                                 features, object_prefix));
-  ASSERT_EQ(0, ioctx.remove(oid));
-
-  ASSERT_EQ(-EINVAL, create_image(&ioctx, oid, size, order,
-                                 features, ""));
-  ASSERT_EQ(-ENOENT, ioctx.remove(oid));
-
-  ASSERT_EQ(0, create_image(&ioctx, oid, 0, order,
-                           features, object_prefix));
-  ASSERT_EQ(0, ioctx.remove(oid));
-
-  ASSERT_EQ(-ENOSYS, create_image(&ioctx, oid, size, order,
-                                 -1, object_prefix));
-  ASSERT_EQ(-ENOENT, ioctx.remove(oid));
-
-  bufferlist inbl, outbl;
-  ASSERT_EQ(-EINVAL, ioctx.exec(oid, "rbd", "create", inbl, outbl));
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, get_features)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  uint64_t features;
-  ASSERT_EQ(-ENOENT, get_features(&ioctx, "foo", CEPH_NOSNAP, &features));
-
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, 0, "foo"));
-  ASSERT_EQ(0, get_features(&ioctx, "foo", CEPH_NOSNAP, &features));
-  ASSERT_EQ(0u, features);
-
-  ASSERT_EQ(-ENOENT, get_features(&ioctx, "foo", 1, &features));
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, get_object_prefix)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  string object_prefix;
-  ASSERT_EQ(-ENOENT, get_object_prefix(&ioctx, "foo", &object_prefix));
-
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, 0, "foo"));
-  ASSERT_EQ(0, get_object_prefix(&ioctx, "foo", &object_prefix));
-  ASSERT_EQ("foo", object_prefix);
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, get_size)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  uint64_t size;
-  uint8_t order;
-  ASSERT_EQ(-ENOENT, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
-
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, 0, "foo"));
-  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
-  ASSERT_EQ(0u, size);
-  ASSERT_EQ(22, order);
-  ASSERT_EQ(0, ioctx.remove("foo"));
-
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 2 << 22, 0, 0, "foo"));
-  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
-  ASSERT_EQ(2u << 22, size);
-  ASSERT_EQ(0, order);
-
-  ASSERT_EQ(-ENOENT, get_size(&ioctx, "foo", 1, &size, &order));
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, set_size)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  ASSERT_EQ(-ENOENT, set_size(&ioctx, "foo", 5));
-
-  uint64_t size;
-  uint8_t order;
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, 0, "foo"));
-  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
-  ASSERT_EQ(0u, size);
-  ASSERT_EQ(22, order);
-
-  ASSERT_EQ(0, set_size(&ioctx, "foo", 0));
-  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
-  ASSERT_EQ(0u, size);
-  ASSERT_EQ(22, order);
-
-  ASSERT_EQ(0, set_size(&ioctx, "foo", 3 << 22));
-  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
-  ASSERT_EQ(3u << 22, size);
-  ASSERT_EQ(22, order);
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, protection_status)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  uint8_t status = RBD_PROTECTION_STATUS_UNPROTECTED;
-  ASSERT_EQ(-ENOENT, get_protection_status(&ioctx, "foo",
-                                          CEPH_NOSNAP, &status));
-  ASSERT_EQ(-ENOENT, set_protection_status(&ioctx, "foo",
-                                          CEPH_NOSNAP, status));
-
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 0, 22, RBD_FEATURE_LAYERING, "foo"));
-  ASSERT_EQ(0, create_image(&ioctx, "bar", 0, 22, 0, "foo"));
-  ASSERT_EQ(-EINVAL, get_protection_status(&ioctx, "bar",
-                                          CEPH_NOSNAP, &status));
-  ASSERT_EQ(-ENOEXEC, set_protection_status(&ioctx, "bar",
-                                          CEPH_NOSNAP, status));
-  ASSERT_EQ(-EINVAL, get_protection_status(&ioctx, "foo",
-                                          CEPH_NOSNAP, &status));
-  ASSERT_EQ(-EINVAL, set_protection_status(&ioctx, "foo",
-                                          CEPH_NOSNAP, status));
-  ASSERT_EQ(-ENOENT, get_protection_status(&ioctx, "foo",
-                                          2, &status));
-  ASSERT_EQ(-ENOENT, set_protection_status(&ioctx, "foo",
-                                          2, status));
-
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 10, "snap1"));
-  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
-                                    10, &status));
-  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTED, status);
-
-  ASSERT_EQ(0, set_protection_status(&ioctx, "foo",
-                                    10, RBD_PROTECTION_STATUS_PROTECTED));
-  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
-                                    10, &status));
-  ASSERT_EQ(RBD_PROTECTION_STATUS_PROTECTED, status);
-  ASSERT_EQ(-EBUSY, snapshot_remove(&ioctx, "foo", 10));
-
-  ASSERT_EQ(0, set_protection_status(&ioctx, "foo",
-                                    10, RBD_PROTECTION_STATUS_UNPROTECTING));
-  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
-                                    10, &status));
-  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTING, status);
-  ASSERT_EQ(-EBUSY, snapshot_remove(&ioctx, "foo", 10));
-
-  ASSERT_EQ(-EINVAL, set_protection_status(&ioctx, "foo",
-                                          10, RBD_PROTECTION_STATUS_LAST));
-  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
-                                    10, &status));
-  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTING, status);
-
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 20, "snap2"));
-  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
-                                    20, &status));
-  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTED, status);
-  ASSERT_EQ(0, set_protection_status(&ioctx, "foo",
-                                    10, RBD_PROTECTION_STATUS_UNPROTECTED));
-  ASSERT_EQ(0, get_protection_status(&ioctx, "foo",
-                                    10, &status));
-  ASSERT_EQ(RBD_PROTECTION_STATUS_UNPROTECTED, status);
-
-  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", 10));
-  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", 20));
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, parents)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  parent_spec pspec;
-  uint64_t size;
-
-  ASSERT_EQ(-ENOENT, get_parent(&ioctx, "doesnotexist", CEPH_NOSNAP, &pspec, &size));
-
-  // old image should fail
-  ASSERT_EQ(0, create_image(&ioctx, "old", 33<<20, 22, 0, "old_blk."));
-  // get nonexistent parent: succeed, return (-1, "", CEPH_NOSNAP), overlap 0
-  ASSERT_EQ(0, get_parent(&ioctx, "old", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, -1);
-  ASSERT_STREQ("", pspec.image_id.c_str());
-  ASSERT_EQ(pspec.snap_id, CEPH_NOSNAP);
-  ASSERT_EQ(size, 0ULL);
-  pspec = parent_spec(-1, "parent", 3);
-  ASSERT_EQ(-ENOEXEC, set_parent(&ioctx, "old", parent_spec(-1, "parent", 3), 10<<20));
-  ASSERT_EQ(-ENOEXEC, remove_parent(&ioctx, "old"));
-
-  // new image will work
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 33<<20, 22, RBD_FEATURE_LAYERING, "foo."));
-
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(-1, pspec.pool_id);
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 123, &pspec, &size));
-  ASSERT_EQ(-1, pspec.pool_id);
-
-  ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(-1, "parent", 3), 10<<20));
-  ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "", 3), 10<<20));
-  ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "parent", CEPH_NOSNAP), 10<<20));
-  ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 0));
-
-  pspec = parent_spec(1, "parent", 3);
-  ASSERT_EQ(0, set_parent(&ioctx, "foo", pspec, 10<<20));
-  ASSERT_EQ(-EEXIST, set_parent(&ioctx, "foo", pspec, 10<<20));
-  ASSERT_EQ(-EEXIST, set_parent(&ioctx, "foo", parent_spec(2, "parent", 34), 10<<20));
-
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-
-  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
-  ASSERT_EQ(-ENOENT, remove_parent(&ioctx, "foo"));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(-1, pspec.pool_id);
-
-  // snapshots
-  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 10<<20));
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 10, "snap1"));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 10ull<<20);
-
-  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
-  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(4, "parent2", 6), 5<<20));
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 11, "snap2"));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 10ull<<20);
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 11, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 4);
-  ASSERT_EQ(pspec.image_id, "parent2");
-  ASSERT_EQ(pspec.snap_id, snapid_t(6));
-  ASSERT_EQ(size, 5ull<<20);
-
-  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 12, "snap3"));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 10ull<<20);
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 11, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 4);
-  ASSERT_EQ(pspec.image_id, "parent2");
-  ASSERT_EQ(pspec.snap_id, snapid_t(6));
-  ASSERT_EQ(size, 5ull<<20);
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 12, &pspec, &size));
-  ASSERT_EQ(-1, pspec.pool_id);
-
-  // make sure set_parent takes min of our size and parent's size
-  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 1<<20));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 1ull<<20);
-  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
-
-  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 100<<20));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 33ull<<20);
-  ASSERT_EQ(0, remove_parent(&ioctx, "foo"));
-
-  // make sure resize adjust parent overlap
-  ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 10<<20));
-
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 14, "snap4"));
-  ASSERT_EQ(0, set_size(&ioctx, "foo", 3 << 20));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 3ull<<20);
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 14, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 10ull<<20);
-
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 15, "snap5"));
-  ASSERT_EQ(0, set_size(&ioctx, "foo", 30 << 20));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 3ull<<20);
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 14, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 10ull<<20);
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 15, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 3ull<<20);
-
-  ASSERT_EQ(0, set_size(&ioctx, "foo", 2 << 20));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 2ull<<20);
-
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 16, "snap6"));
-  ASSERT_EQ(0, get_parent(&ioctx, "foo", 16, &pspec, &size));
-  ASSERT_EQ(pspec.pool_id, 1);
-  ASSERT_EQ(pspec.image_id, "parent");
-  ASSERT_EQ(pspec.snap_id, snapid_t(3));
-  ASSERT_EQ(size, 2ull<<20);
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, snapshots)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  ASSERT_EQ(-ENOENT, snapshot_add(&ioctx, "foo", 0, "snap1"));
-
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 10, 22, 0, "foo"));
-
-  vector<string> snap_names;
-  vector<uint64_t> snap_sizes;
-  vector<uint64_t> snap_features;
-  SnapContext snapc;
-  vector<parent_info> parents;
-  vector<uint8_t> protection_status;
-
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(0u, snapc.snaps.size());
-  ASSERT_EQ(0u, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(0u, snap_names.size());
-  ASSERT_EQ(0u, snap_sizes.size());
-  ASSERT_EQ(0u, snap_features.size());
-
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 0, "snap1"));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(1u, snapc.snaps.size());
-  ASSERT_EQ(0u, snapc.snaps[0]);
-  ASSERT_EQ(0u, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(1u, snap_names.size());
-  ASSERT_EQ("snap1", snap_names[0]);
-  ASSERT_EQ(10u, snap_sizes[0]);
-  ASSERT_EQ(0u, snap_features[0]);
-
-  // snap with same id and name
-  ASSERT_EQ(-EEXIST, snapshot_add(&ioctx, "foo", 0, "snap1"));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(1u, snapc.snaps.size());
-  ASSERT_EQ(0u, snapc.snaps[0]);
-  ASSERT_EQ(0u, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(1u, snap_names.size());
-  ASSERT_EQ("snap1", snap_names[0]);
-  ASSERT_EQ(10u, snap_sizes[0]);
-  ASSERT_EQ(0u, snap_features[0]);
-
-  // snap with same id, different name
-  ASSERT_EQ(-EEXIST, snapshot_add(&ioctx, "foo", 0, "snap2"));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(1u, snapc.snaps.size());
-  ASSERT_EQ(0u, snapc.snaps[0]);
-  ASSERT_EQ(0u, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(1u, snap_names.size());
-  ASSERT_EQ("snap1", snap_names[0]);
-  ASSERT_EQ(10u, snap_sizes[0]);
-  ASSERT_EQ(0u, snap_features[0]);
-
-  // snap with different id, same name
-  ASSERT_EQ(-EEXIST, snapshot_add(&ioctx, "foo", 1, "snap1"));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(1u, snapc.snaps.size());
-  ASSERT_EQ(0u, snapc.snaps[0]);
-  ASSERT_EQ(0u, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(snap_names.size(), 1u);
-  ASSERT_EQ(snap_names[0], "snap1");
-  ASSERT_EQ(snap_sizes[0], 10u);
-  ASSERT_EQ(snap_features[0], 0u);
-
-  // snap with different id, different name
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 1, "snap2"));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(2u, snapc.snaps.size());
-  ASSERT_EQ(1u, snapc.snaps[0]);
-  ASSERT_EQ(0u, snapc.snaps[1]);
-  ASSERT_EQ(1u, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(2u, snap_names.size());
-  ASSERT_EQ("snap2", snap_names[0]);
-  ASSERT_EQ(10u, snap_sizes[0]);
-  ASSERT_EQ(0u, snap_features[0]);
-  ASSERT_EQ("snap1", snap_names[1]);
-  ASSERT_EQ(10u, snap_sizes[1]);
-  ASSERT_EQ(0u, snap_features[1]);
-
-  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", 0));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(1u, snapc.snaps.size());
-  ASSERT_EQ(1u, snapc.snaps[0]);
-  ASSERT_EQ(1u, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(1u, snap_names.size());
-  ASSERT_EQ("snap2", snap_names[0]);
-  ASSERT_EQ(10u, snap_sizes[0]);
-  ASSERT_EQ(0u, snap_features[0]);
-
-  uint64_t size;
-  uint8_t order;
-  ASSERT_EQ(0, set_size(&ioctx, "foo", 0));
-  ASSERT_EQ(0, get_size(&ioctx, "foo", CEPH_NOSNAP, &size, &order));
-  ASSERT_EQ(0u, size);
-  ASSERT_EQ(22u, order);
-
-  uint64_t large_snap_id = 1ull << 63;
-  ASSERT_EQ(0, snapshot_add(&ioctx, "foo", large_snap_id, "snap3"));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(2u, snapc.snaps.size());
-  ASSERT_EQ(large_snap_id, snapc.snaps[0]);
-  ASSERT_EQ(1u, snapc.snaps[1]);
-  ASSERT_EQ(large_snap_id, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(2u, snap_names.size());
-  ASSERT_EQ("snap3", snap_names[0]);
-  ASSERT_EQ(0u, snap_sizes[0]);
-  ASSERT_EQ(0u, snap_features[0]);
-  ASSERT_EQ("snap2", snap_names[1]);
-  ASSERT_EQ(10u, snap_sizes[1]);
-  ASSERT_EQ(0u, snap_features[1]);
-
-  ASSERT_EQ(0, get_size(&ioctx, "foo", large_snap_id, &size, &order));
-  ASSERT_EQ(0u, size);
-  ASSERT_EQ(22u, order);
-
-  ASSERT_EQ(0, get_size(&ioctx, "foo", 1, &size, &order));
-  ASSERT_EQ(10u, size);
-  ASSERT_EQ(22u, order);
-
-  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", large_snap_id));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(1u, snapc.snaps.size());
-  ASSERT_EQ(1u, snapc.snaps[0]);
-  ASSERT_EQ(large_snap_id, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(1u, snap_names.size());
-  ASSERT_EQ("snap2", snap_names[0]);
-  ASSERT_EQ(10u, snap_sizes[0]);
-  ASSERT_EQ(0u, snap_features[0]);
-
-  ASSERT_EQ(-ENOENT, snapshot_remove(&ioctx, "foo", large_snap_id));
-  ASSERT_EQ(0, snapshot_remove(&ioctx, "foo", 1));
-  ASSERT_EQ(0, get_snapcontext(&ioctx, "foo", &snapc));
-  ASSERT_EQ(0u, snapc.snaps.size());
-  ASSERT_EQ(large_snap_id, snapc.seq);
-  ASSERT_EQ(0, snapshot_list(&ioctx, "foo", snapc.snaps, &snap_names,
-                            &snap_sizes, &snap_features, &parents,
-                            &protection_status));
-  ASSERT_EQ(0u, snap_names.size());
-  ASSERT_EQ(0u, snap_sizes.size());
-  ASSERT_EQ(0u, snap_features.size());
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, snapid_race)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  buffer::list bl;
-  buffer::ptr bp(4096);
-  bp.zero();
-  bl.append(bp);
-
-  string oid = "foo";
-  ASSERT_EQ(4096, ioctx.write(oid, bl, 4096, 0));
-  ASSERT_EQ(0, old_snapshot_add(&ioctx, oid, 1, "test1"));
-  ASSERT_EQ(0, old_snapshot_add(&ioctx, oid, 3, "test3"));
-  ASSERT_EQ(-ESTALE, old_snapshot_add(&ioctx, oid, 2, "test2"));
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(cls_rbd, stripingv2)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  ASSERT_EQ(0, create_image(&ioctx, "foo", 10, 22, 0, "foo"));
-
-  uint64_t su = 65536, sc = 12;
-  ASSERT_EQ(-ENOEXEC, get_stripe_unit_count(&ioctx, "foo", &su, &sc));
-  ASSERT_EQ(-ENOEXEC, set_stripe_unit_count(&ioctx, "foo", su, sc));
-  
-  ASSERT_EQ(0, create_image(&ioctx, "bar", 10, 22, RBD_FEATURE_STRIPINGV2, "bar"));
-  ASSERT_EQ(0, get_stripe_unit_count(&ioctx, "bar", &su, &sc));
-  ASSERT_EQ(1ull << 22, su);
-  ASSERT_EQ(1ull, sc);
-  su = 8192;
-  sc = 456;
-  ASSERT_EQ(0, set_stripe_unit_count(&ioctx, "bar", su, sc));
-  su = sc = 0;
-  ASSERT_EQ(0, get_stripe_unit_count(&ioctx, "bar", &su, &sc));
-  ASSERT_EQ(8192ull, su);
-  ASSERT_EQ(456ull, sc);
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
diff --git a/src/test/test_librbd.cc b/src/test/test_librbd.cc
deleted file mode 100644 (file)
index 96dcfc2..0000000
+++ /dev/null
@@ -1,1316 +0,0 @@
-// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
-// vim: ts=8 sw=2 smarttab
-/*
- * Ceph - scalable distributed file system
- *
- * Copyright (C) 2011 New Dream Network
- *
- * This 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.  See file COPYING.
- *
- */
-
-#include "include/rados/librados.h"
-#include "include/rbd_types.h"
-#include "include/rbd/librbd.h"
-#include "include/rbd/librbd.hpp"
-
-#include "gtest/gtest.h"
-
-#include <errno.h>
-#include <inttypes.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-#include <iostream>
-#include <algorithm>
-#include <sstream>
-
-#include "rados-api/test.h"
-#include "common/errno.h"
-#include "include/stringify.h"
-
-using namespace std;
-
-static int get_features(bool *old_format, uint64_t *features)
-{
-  const char *c = getenv("RBD_FEATURES");
-  if (c) {
-    stringstream ss;
-    ss << c;
-    ss >> *features;
-    if (ss.fail())
-      return -EINVAL;
-    *old_format = false;
-    cout << "using new format!" << std::endl;
-  } else {
-    *old_format = true;
-    cout << "using old format" << std::endl;
-  }
-
-  return 0;
-}
-
-static int create_image_full(rados_ioctx_t ioctx, const char *name,
-                             uint64_t size, int *order, int old_format,
-                             uint64_t features)
-{
-  if (old_format) {
-    return rbd_create(ioctx, name, size, order);
-  } else {
-    return rbd_create2(ioctx, name, size, features, order);
-  }
-}
-
-static int create_image(rados_ioctx_t ioctx, const char *name,
-                       uint64_t size, int *order)
-{
-  bool old_format;
-  uint64_t features;
-
-  int r = get_features(&old_format, &features);
-  if (r < 0)
-    return r;
-  return create_image_full(ioctx, name, size, order, old_format, features);
-}
-
-static int create_image_pp(librbd::RBD &rbd,
-                          librados::IoCtx &ioctx,
-                          const char *name,
-                          uint64_t size, int *order) {
-  bool old_format;
-  uint64_t features;
-  int r = get_features(&old_format, &features);
-  if (r < 0)
-    return r;
-  if (old_format) {
-    return rbd.create(ioctx, name, size, order);
-  } else {
-    return rbd.create2(ioctx, name, size, features, order);
-  }
-}
-
-TEST(LibRBD, CreateAndStat)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
-
-  rbd_image_info_t info;
-  rbd_image_t image;
-  int order = 0;
-  const char *name = "testimg";
-  uint64_t size = 2 << 20;
-  
-  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
-  ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
-  printf("image has size %llu and order %d\n", (unsigned long long) info.size, info.order);
-  ASSERT_EQ(info.size, size);
-  ASSERT_EQ(info.order, order);
-  ASSERT_EQ(0, rbd_close(image));
-
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-TEST(LibRBD, CreateAndStatPP)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  {
-    librbd::RBD rbd;
-    librbd::image_info_t info;
-    librbd::Image image;
-    int order = 0;
-    const char *name = "testimg";
-    uint64_t size = 2 << 20;
-    
-    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
-    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
-    ASSERT_EQ(0, image.stat(info, sizeof(info)));
-    ASSERT_EQ(info.size, size);
-    ASSERT_EQ(info.order, order);
-  }
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-TEST(LibRBD, ResizeAndStat)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
-
-  rbd_image_info_t info;
-  rbd_image_t image;
-  int order = 0;
-  const char *name = "testimg";
-  uint64_t size = 2 << 20;
-  
-  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
-
-  ASSERT_EQ(0, rbd_resize(image, size * 4));
-  ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
-  ASSERT_EQ(info.size, size * 4);
-
-  ASSERT_EQ(0, rbd_resize(image, size / 2));
-  ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
-  ASSERT_EQ(info.size, size / 2);
-  
-  ASSERT_EQ(0, rbd_close(image));
-
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-TEST(LibRBD, ResizeAndStatPP)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  {
-    librbd::RBD rbd;
-    librbd::image_info_t info;
-    librbd::Image image;
-    int order = 0;
-    const char *name = "testimg";
-    uint64_t size = 2 << 20;
-    
-    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
-    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
-    
-    ASSERT_EQ(0, image.resize(size * 4));
-    ASSERT_EQ(0, image.stat(info, sizeof(info)));
-    ASSERT_EQ(info.size, size * 4);
-    
-    ASSERT_EQ(0, image.resize(size / 2));
-    ASSERT_EQ(0, image.stat(info, sizeof(info)));
-    ASSERT_EQ(info.size, size / 2);
-  }
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-int test_ls(rados_ioctx_t io_ctx, size_t num_expected, ...)
-{
-  int num_images, i, j;
-  char *expected, *names, *cur_name;
-  va_list ap;
-  size_t max_size = 1024;
-
-  names = (char *) malloc(sizeof(char *) * 1024);
-  int len = rbd_list(io_ctx, names, &max_size);
-
-  for (i = 0, num_images = 0, cur_name = names; cur_name < names + len; i++) {
-    printf("image: %s\n", cur_name);
-    cur_name += strlen(cur_name) + 1;
-    num_images++;
-  }
-
-  va_start(ap, num_expected);
-  for (i = num_expected; i > 0; i--) {
-    expected = va_arg(ap, char *);
-    printf("expected = %s\n", expected);
-    int found = 0;
-    for (j = 0, cur_name = names; j < num_images; j++) {
-      if (cur_name[0] == '_') {
-       cur_name += strlen(cur_name) + 1;
-       continue;
-      }
-      if (strcmp(cur_name, expected) == 0) {
-       printf("found %s\n", cur_name);
-       cur_name[0] = '_';
-       found = 1;
-       break;
-      }
-    }
-    assert(found);
-  }
-  va_end(ap);
-
-  for (i = 0, cur_name = names; cur_name < names + len; i++) {
-    assert(cur_name[0] == '_');
-    cur_name += strlen(cur_name) + 1;
-  }
-  free(names);
-
-  return num_images;
-}
-
-TEST(LibRBD, TestCreateLsDelete)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
-
-  int order = 0;
-  const char *name = "testimg";
-  const char *name2 = "testimg2";
-  uint64_t size = 2 << 20;
-  
-  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
-  ASSERT_EQ(1, test_ls(ioctx, 1, name));
-  ASSERT_EQ(0, create_image(ioctx, name2, size, &order));
-  ASSERT_EQ(2, test_ls(ioctx, 2, name, name2));
-  ASSERT_EQ(0, rbd_remove(ioctx, name));
-  ASSERT_EQ(1, test_ls(ioctx, 1, name2));
-
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-int test_ls_pp(librbd::RBD& rbd, librados::IoCtx& io_ctx, size_t num_expected, ...)
-{
-  int r;
-  size_t i;
-  char *expected;
-  va_list ap;
-  vector<string> names;
-  r = rbd.list(io_ctx, names);
-  if (r == -ENOENT)
-    r = 0;
-  assert(r >= 0);
-  cout << "num images is: " << names.size() << endl
-           << "expected: " << num_expected << endl;
-  int num = names.size();
-
-  for (i = 0; i < names.size(); i++) {
-    cout << "image: " << names[i] << endl;
-  }
-
-  va_start(ap, num_expected);
-  for (i = num_expected; i > 0; i--) {
-    expected = va_arg(ap, char *);
-    cout << "expected = " << expected << endl;
-    vector<string>::iterator listed_name = find(names.begin(), names.end(), string(expected));
-    assert(listed_name != names.end());
-    names.erase(listed_name);
-  }
-  assert(names.empty());
-
-  return num;
-}
-
-TEST(LibRBD, TestCreateLsDeletePP)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  {
-    librbd::RBD rbd;
-    librbd::Image image;
-    int order = 0;
-    const char *name = "testimg";
-    const char *name2 = "testimg2";
-    uint64_t size = 2 << 20;  
-    
-    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
-    ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name));
-    ASSERT_EQ(0, rbd.create(ioctx, name2, size, &order));
-    ASSERT_EQ(2, test_ls_pp(rbd, ioctx, 2, name, name2));
-    ASSERT_EQ(0, rbd.remove(ioctx, name));
-    ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name2));
-  }
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-
-static int print_progress_percent(uint64_t offset, uint64_t src_size,
-                                    void *data)
-{
-  float percent = ((float)offset * 100) / src_size;
-  printf("%3.2f%% done\n", percent);
-  return 0; 
-}
-
-TEST(LibRBD, TestCopy)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
-
-  rbd_image_t image;
-  int order = 0;
-  const char *name = "testimg";
-  const char *name2 = "testimg2";
-  const char *name3 = "testimg3";
-
-  uint64_t size = 2 << 20;
-  
-  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
-  ASSERT_EQ(1, test_ls(ioctx, 1, name));
-  ASSERT_EQ(0, rbd_copy(image, ioctx, name2));
-  ASSERT_EQ(2, test_ls(ioctx, 2, name, name2));
-  ASSERT_EQ(0, rbd_copy_with_progress(image, ioctx, name3, print_progress_percent, NULL));
-  ASSERT_EQ(3, test_ls(ioctx, 3, name, name2, name3));
-
-  ASSERT_EQ(0, rbd_close(image));
-
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-class PrintProgress : public librbd::ProgressContext
-{
-public:
-  int update_progress(uint64_t offset, uint64_t src_size)
-  {
-    float percent = ((float)offset * 100) / src_size;
-    printf("%3.2f%% done\n", percent);
-    return 0;
-  }
-};
-
-TEST(LibRBD, TestCopyPP)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-  
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  {
-    librbd::RBD rbd;
-    librbd::Image image;
-    int order = 0;
-    const char *name = "testimg";
-    const char *name2 = "testimg2";
-    const char *name3 = "testimg3";
-    uint64_t size = 2 << 20;
-    PrintProgress pp;
-
-    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
-    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
-    ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name));
-    ASSERT_EQ(0, image.copy(ioctx, name2));
-    ASSERT_EQ(2, test_ls_pp(rbd, ioctx, 2, name, name2));
-    ASSERT_EQ(0, image.copy_with_progress(ioctx, name3, pp));
-    ASSERT_EQ(3, test_ls_pp(rbd, ioctx, 3, name, name2, name3));
-  }
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-int test_ls_snaps(rbd_image_t image, int num_expected, ...)
-{
-  rbd_snap_info_t *snaps;
-  int num_snaps, i, j, expected_size, max_size = 10;
-  char *expected;
-  va_list ap;
-  snaps = (rbd_snap_info_t *) malloc(sizeof(rbd_snap_info_t *) * 10);
-  num_snaps = rbd_snap_list(image, snaps, &max_size);
-  printf("num snaps is: %d\nexpected: %d\n", num_snaps, num_expected);
-
-  for (i = 0; i < num_snaps; i++) {
-    printf("snap: %s\n", snaps[i].name);
-  }
-
-  va_start(ap, num_expected);
-  for (i = num_expected; i > 0; i--) {
-    expected = va_arg(ap, char *);
-    expected_size = va_arg(ap, int);
-    int found = 0;
-    for (j = 0; j < num_snaps; j++) {
-      if (snaps[j].name == NULL)
-       continue;
-      if (strcmp(snaps[j].name, expected) == 0) {
-       printf("found %s with size %llu\n", snaps[j].name, (unsigned long long) snaps[j].size);
-       assert((int)snaps[j].size == expected_size);
-       free((void *) snaps[j].name);
-       snaps[j].name = NULL;
-       found = 1;
-       break;
-      }
-    }
-    assert(found);
-  }
-  va_end(ap);
-
-  for (i = 0; i < num_snaps; i++) {
-    assert(snaps[i].name == NULL);
-  }
-  free(snaps);
-
-  return num_snaps;
-}
-
-TEST(LibRBD, TestCreateLsDeleteSnap)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
-
-  rbd_image_t image;
-  int order = 0;
-  const char *name = "testimg";
-  uint64_t size = 2 << 20;
-  uint64_t size2 = 4 << 20;
-  
-  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
-
-  ASSERT_EQ(0, rbd_snap_create(image, "snap1"));
-  ASSERT_EQ(1, test_ls_snaps(image, 1, "snap1", size));
-  ASSERT_EQ(0, rbd_resize(image, size2));
-  ASSERT_EQ(0, rbd_snap_create(image, "snap2"));
-  ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1", size, "snap2", size2));
-  ASSERT_EQ(0, rbd_snap_remove(image, "snap1"));
-  ASSERT_EQ(1, test_ls_snaps(image, 1, "snap2", size2));
-  ASSERT_EQ(0, rbd_snap_remove(image, "snap2"));
-  ASSERT_EQ(0, test_ls_snaps(image, 0));
-  
-  ASSERT_EQ(0, rbd_close(image));
-
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-int test_ls_snaps(librbd::Image& image, size_t num_expected, ...)
-{
-  int r;
-  size_t i, j, expected_size;
-  char *expected;
-  va_list ap;
-  vector<librbd::snap_info_t> snaps;
-  r = image.snap_list(snaps);
-  assert(r >= 0);
-  cout << "num snaps is: " << snaps.size() << endl
-           << "expected: " << num_expected << endl;
-
-  for (i = 0; i < snaps.size(); i++) {
-    cout << "snap: " << snaps[i].name << endl;
-  }
-
-  va_start(ap, num_expected);
-  for (i = num_expected; i > 0; i--) {
-    expected = va_arg(ap, char *);
-    expected_size = va_arg(ap, int);
-    int found = 0;
-    for (j = 0; j < snaps.size(); j++) {
-      if (snaps[j].name == "")
-       continue;
-      if (strcmp(snaps[j].name.c_str(), expected) == 0) {
-       cout << "found " << snaps[j].name << " with size " << snaps[j].size << endl;
-       assert(snaps[j].size == (size_t) expected_size);
-       snaps[j].name = "";
-       found = 1;
-       break;
-      }
-    }
-    assert(found);
-  }
-  va_end(ap);
-
-  for (i = 0; i < snaps.size(); i++) {
-    assert(snaps[i].name == "");
-  }
-
-  return snaps.size();
-}
-
-TEST(LibRBD, TestCreateLsDeleteSnapPP)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  {
-    librbd::RBD rbd;
-    librbd::Image image;
-    int order = 0;
-    const char *name = "testimg";
-    uint64_t size = 2 << 20;
-    uint64_t size2 = 4 << 20;
-    
-    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
-    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
-
-    ASSERT_EQ(0, image.snap_create("snap1"));
-    ASSERT_EQ(1, test_ls_snaps(image, 1, "snap1", size));
-    ASSERT_EQ(0, image.resize(size2));
-    ASSERT_EQ(0, image.snap_create("snap2"));
-    ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1", size, "snap2", size2));
-    ASSERT_EQ(0, image.snap_remove("snap1"));
-    ASSERT_EQ(1, test_ls_snaps(image, 1, "snap2", size2));
-    ASSERT_EQ(0, image.snap_remove("snap2"));
-    ASSERT_EQ(0, test_ls_snaps(image, 0));
-  }
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-
-
-#define TEST_IO_SIZE 512
-#define TEST_IO_TO_SNAP_SIZE 80
-
-void simple_write_cb(rbd_completion_t cb, void *arg)
-{
-  printf("write completion cb called!\n");
-}
-
-void simple_read_cb(rbd_completion_t cb, void *arg)
-{
-  printf("read completion cb called!\n");
-}
-
-void aio_write_test_data(rbd_image_t image, const char *test_data, uint64_t off, size_t len)
-{
-  rbd_completion_t comp;
-  rbd_aio_create_completion(NULL, (rbd_callback_t) simple_write_cb, &comp);
-  printf("created completion\n");
-  rbd_aio_write(image, off, len, test_data, comp);
-  printf("started write\n");
-  rbd_aio_wait_for_complete(comp);
-  int r = rbd_aio_get_return_value(comp);
-  printf("return value is: %d\n", r);
-  assert(r == 0);
-  printf("finished write\n");
-  rbd_aio_release(comp);
-}
-
-void write_test_data(rbd_image_t image, const char *test_data, uint64_t off, size_t len)
-{
-  ssize_t written;
-  written = rbd_write(image, off, len, test_data);
-  printf("wrote: %d\n", (int) written);
-  assert(written == (ssize_t)len);
-}
-
-void aio_discard_test_data(rbd_image_t image, uint64_t off, uint64_t len)
-{
-  rbd_completion_t comp;
-  rbd_aio_create_completion(NULL, (rbd_callback_t) simple_write_cb, &comp);
-  rbd_aio_discard(image, off, len, comp);
-  rbd_aio_wait_for_complete(comp);
-  int r = rbd_aio_get_return_value(comp);
-  assert(r == 0);
-  printf("aio discard: %d~%d = %d\n", (int)off, (int)len, (int)r);
-  rbd_aio_release(comp);
-}
-
-void discard_test_data(rbd_image_t image, uint64_t off, size_t len)
-{
-  ssize_t written;
-  written = rbd_discard(image, off, len);
-  printf("discard: %d~%d = %d\n", (int)off, (int)len, (int)written);
-  assert(written == (ssize_t)len);
-}
-
-void aio_read_test_data(rbd_image_t image, const char *expected, uint64_t off, size_t len)
-{
-  rbd_completion_t comp;
-  char *result = (char *)malloc(len + 1);
-
-  assert(result);
-  rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp);
-  printf("created completion\n");
-  rbd_aio_read(image, off, len, result, comp);
-  printf("started read\n");
-  rbd_aio_wait_for_complete(comp);
-  int r = rbd_aio_get_return_value(comp);
-  printf("return value is: %d\n", r);
-  assert(r == (ssize_t)len);
-  rbd_aio_release(comp);
-  if (memcmp(result, expected, len)) {
-    printf("read: %s\nexpected: %s\n", result, expected);
-    assert(memcmp(result, expected, len) == 0);
-  }
-  free(result);
-}
-
-void read_test_data(rbd_image_t image, const char *expected, uint64_t off, size_t len)
-{
-  ssize_t read;
-  char *result = (char *)malloc(len + 1);
-
-  assert(result);
-  read = rbd_read(image, off, len, result);
-  printf("read: %d\n", (int) read);
-  assert(read == (ssize_t)len);
-  result[len] = '\0';
-  if (memcmp(result, expected, len)) {
-    printf("read: %s\nexpected: %s\n", result, expected);
-    assert(memcmp(result, expected, len) == 0);
-  }
-  free(result);
-}
-
-TEST(LibRBD, TestIO)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
-
-  rbd_image_t image;
-  int order = 0;
-  const char *name = "testimg";
-  uint64_t size = 2 << 20;
-  
-  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
-
-  char test_data[TEST_IO_SIZE + 1];
-  char zero_data[TEST_IO_SIZE + 1];
-  int i;
-
-  for (i = 0; i < TEST_IO_SIZE; ++i) {
-    test_data[i] = (char) (rand() % (126 - 33) + 33);
-  }
-  test_data[TEST_IO_SIZE] = '\0';
-  memset(zero_data, 0, sizeof(zero_data));
-
-  for (i = 0; i < 5; ++i)
-    write_test_data(image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE);
-
-  for (i = 5; i < 10; ++i)
-    aio_write_test_data(image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE);
-
-  for (i = 0; i < 5; ++i)
-    read_test_data(image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE);
-
-  for (i = 5; i < 10; ++i)
-    aio_read_test_data(image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE);
-
-  // discard 2nd, 4th sections.
-  discard_test_data(image, TEST_IO_SIZE, TEST_IO_SIZE);
-  aio_discard_test_data(image, TEST_IO_SIZE*3, TEST_IO_SIZE);
-
-  read_test_data(image, test_data,  0, TEST_IO_SIZE);
-  read_test_data(image,  zero_data, TEST_IO_SIZE, TEST_IO_SIZE);
-  read_test_data(image, test_data,  TEST_IO_SIZE*2, TEST_IO_SIZE);
-  read_test_data(image,  zero_data, TEST_IO_SIZE*3, TEST_IO_SIZE);
-  read_test_data(image, test_data,  TEST_IO_SIZE*4, TEST_IO_SIZE);
-  
-  rbd_image_info_t info;
-  rbd_completion_t comp;
-  ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
-  // can't read or write starting past end
-  ASSERT_EQ(-EINVAL, rbd_write(image, info.size, 1, test_data));
-  ASSERT_EQ(-EINVAL, rbd_read(image, info.size, 1, test_data));
-  // reading through end returns amount up to end
-  ASSERT_EQ(10, rbd_read(image, info.size - 10, 100, test_data));
-  // writing through end returns amount up to end
-  ASSERT_EQ(10, rbd_write(image, info.size - 10, 100, test_data));
-
-  rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp);
-  ASSERT_EQ(-EINVAL, rbd_aio_write(image, info.size, 1, test_data, comp));
-  ASSERT_EQ(-EINVAL, rbd_aio_read(image, info.size, 1, test_data, comp));
-
-  ASSERT_EQ(0, rbd_close(image));
-
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-TEST(LibRBD, TestEmptyDiscard)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
-
-  rbd_image_t image;
-  int order = 0;
-  const char *name = "testimg";
-  uint64_t size = 20 << 20;
-  
-  ASSERT_EQ(0, create_image(ioctx, name, size, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
-
-  aio_discard_test_data(image, 0, 1*1024*1024);
-  aio_discard_test_data(image, 0, 4*1024*1024);
-
-  ASSERT_EQ(0, rbd_close(image));
-
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-
-void simple_write_cb_pp(librbd::completion_t cb, void *arg)
-{
-  cout << "write completion cb called!" << endl;
-}
-
-void simple_read_cb_pp(librbd::completion_t cb, void *arg)
-{
-  cout << "read completion cb called!" << endl;
-}
-
-void aio_write_test_data(librbd::Image& image, const char *test_data, off_t off)
-{
-  ceph::bufferlist bl;
-  bl.append(test_data, strlen(test_data));
-  librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_write_cb_pp);
-  printf("created completion\n");
-  image.aio_write(off, strlen(test_data), bl, comp);
-  printf("started write\n");
-  comp->wait_for_complete();
-  int r = comp->get_return_value();
-  printf("return value is: %d\n", r);
-  assert(r >= 0);
-  printf("finished write\n");
-  comp->release();
-}
-
-void aio_discard_test_data(librbd::Image& image, off_t off, size_t len)
-{
-  librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_write_cb_pp);
-  image.aio_discard(off, len, comp);
-  comp->wait_for_complete();
-  int r = comp->get_return_value();
-  assert(r >= 0);
-  comp->release();
-}
-
-void write_test_data(librbd::Image& image, const char *test_data, off_t off)
-{
-  size_t written;
-  size_t len = strlen(test_data);
-  ceph::bufferlist bl;
-  bl.append(test_data, len);
-  written = image.write(off, len, bl);
-  printf("wrote: %u\n", (unsigned int) written);
-  assert(written == bl.length());
-}
-
-void discard_test_data(librbd::Image& image, off_t off, size_t len)
-{
-  size_t written;
-  written = image.discard(off, len);
-  printf("discard: %u~%u\n", (unsigned)off, (unsigned)len);
-  assert(written == len);
-}
-
-void aio_read_test_data(librbd::Image& image, const char *expected, off_t off, size_t expected_len)
-{
-  librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_read_cb_pp);
-  ceph::bufferlist bl;
-  printf("created completion\n");
-  image.aio_read(off, expected_len, bl, comp);
-  printf("started read\n");
-  comp->wait_for_complete();
-  int r = comp->get_return_value();
-  printf("return value is: %d\n", r);
-  assert(r == TEST_IO_SIZE);
-  assert(strncmp(expected, bl.c_str(), TEST_IO_SIZE) == 0);
-  printf("finished read\n");
-  comp->release();
-}
-
-void read_test_data(librbd::Image& image, const char *expected, off_t off, size_t expected_len)
-{
-  int read, total_read = 0;
-  size_t len = expected_len;
-  ceph::bufferlist bl;
-  read = image.read(off + total_read, len, bl);
-  assert(read >= 0);
-  printf("read: %u\n", (unsigned int) read);
-  printf("read: %s\nexpected: %s\n", bl.c_str(), expected);
-  assert(strncmp(bl.c_str(), expected, expected_len) == 0);
-}
-
-TEST(LibRBD, TestIOPP) 
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  {
-    librbd::RBD rbd;
-    librbd::Image image;
-    int order = 0;
-    const char *name = "testimg";
-    uint64_t size = 2 << 20;
-    
-    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
-    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
-
-    char test_data[TEST_IO_SIZE + 1];
-    char zero_data[TEST_IO_SIZE + 1];
-    int i;
-    
-    srand(time(0));
-    for (i = 0; i < TEST_IO_SIZE; ++i) {
-      test_data[i] = (char) (rand() % (126 - 33) + 33);
-    }
-    test_data[TEST_IO_SIZE] = '\0';
-    memset(zero_data, 0, sizeof(zero_data));
-
-    for (i = 0; i < 5; ++i)
-      write_test_data(image, test_data, strlen(test_data) * i);
-    
-    for (i = 5; i < 10; ++i)
-      aio_write_test_data(image, test_data, strlen(test_data) * i);
-    
-    for (i = 0; i < 5; ++i)
-      read_test_data(image, test_data, strlen(test_data) * i, TEST_IO_SIZE);
-    
-    for (i = 5; i < 10; ++i)
-      aio_read_test_data(image, test_data, strlen(test_data) * i, TEST_IO_SIZE);
-
-    // discard 2nd, 4th sections.
-    discard_test_data(image, TEST_IO_SIZE, TEST_IO_SIZE);
-    aio_discard_test_data(image, TEST_IO_SIZE*3, TEST_IO_SIZE);
-    
-    read_test_data(image, test_data,  0, TEST_IO_SIZE);
-    read_test_data(image,  zero_data, TEST_IO_SIZE, TEST_IO_SIZE);
-    read_test_data(image, test_data,  TEST_IO_SIZE*2, TEST_IO_SIZE);
-    read_test_data(image,  zero_data, TEST_IO_SIZE*3, TEST_IO_SIZE);
-    read_test_data(image, test_data,  TEST_IO_SIZE*4, TEST_IO_SIZE);
-  }
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
-
-TEST(LibRBD, TestIOToSnapshot)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
-
-  rbd_image_t image;
-  int order = 0;
-  const char *name = "testimg";
-  uint64_t isize = 2 << 20;
-  
-  ASSERT_EQ(0, create_image(ioctx, name, isize, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, name, &image, NULL));
-
-  int i, r;
-  rbd_image_t image_at_snap;
-  char orig_data[TEST_IO_TO_SNAP_SIZE + 1];
-  char test_data[TEST_IO_TO_SNAP_SIZE + 1];
-
-  for (i = 0; i < TEST_IO_TO_SNAP_SIZE; ++i)
-    test_data[i] = (char) (i + 48);
-  test_data[TEST_IO_TO_SNAP_SIZE] = '\0';
-  orig_data[TEST_IO_TO_SNAP_SIZE] = '\0';
-
-  r = rbd_read(image, 0, TEST_IO_TO_SNAP_SIZE, orig_data);
-  ASSERT_EQ(r, TEST_IO_TO_SNAP_SIZE);
-
-  ASSERT_EQ(0, test_ls_snaps(image, 0));
-  ASSERT_EQ(0, rbd_snap_create(image, "orig"));
-  ASSERT_EQ(1, test_ls_snaps(image, 1, "orig", isize));
-  read_test_data(image, orig_data, 0, TEST_IO_TO_SNAP_SIZE);
-
-  printf("write test data!\n");
-  write_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
-  ASSERT_EQ(0, rbd_snap_create(image, "written"));
-  ASSERT_EQ(2, test_ls_snaps(image, 2, "orig", isize, "written", isize));
-
-  read_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
-
-  rbd_snap_set(image, "orig");
-  read_test_data(image, orig_data, 0, TEST_IO_TO_SNAP_SIZE);
-
-  rbd_snap_set(image, "written");
-  read_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
-
-  rbd_snap_set(image, "orig");
-
-  r = rbd_write(image, 0, TEST_IO_TO_SNAP_SIZE, test_data);
-  printf("write to snapshot returned %d\n", r);
-  ASSERT_LT(r, 0);
-  cout << cpp_strerror(-r) << std::endl;
-
-  read_test_data(image, orig_data, 0, TEST_IO_TO_SNAP_SIZE);
-  rbd_snap_set(image, "written");
-  read_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
-
-  r = rbd_snap_rollback(image, "orig");
-  ASSERT_EQ(r, -EROFS);
-
-  r = rbd_snap_set(image, NULL);
-  ASSERT_EQ(r, 0);
-  r = rbd_snap_rollback(image, "orig");
-  ASSERT_EQ(r, 0);
-
-  write_test_data(image, test_data, 0, TEST_IO_TO_SNAP_SIZE);
-
-  rbd_flush(image);
-
-  printf("opening testimg@orig\n");
-  ASSERT_EQ(0, rbd_open(ioctx, name, &image_at_snap, "orig"));
-  read_test_data(image_at_snap, orig_data, 0, TEST_IO_TO_SNAP_SIZE);
-  r = rbd_write(image_at_snap, 0, TEST_IO_TO_SNAP_SIZE, test_data);
-  printf("write to snapshot returned %d\n", r);
-  ASSERT_LT(r, 0);
-  cout << cpp_strerror(-r) << std::endl;
-  ASSERT_EQ(0, rbd_close(image_at_snap));
-
-  ASSERT_EQ(2, test_ls_snaps(image, 2, "orig", isize, "written", isize));
-  ASSERT_EQ(0, rbd_snap_remove(image, "written"));
-  ASSERT_EQ(1, test_ls_snaps(image, 1, "orig", isize));
-  ASSERT_EQ(0, rbd_snap_remove(image, "orig"));
-  ASSERT_EQ(0, test_ls_snaps(image, 0));
-
-  ASSERT_EQ(0, rbd_close(image));
-
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-TEST(LibRBD, TestClone)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx;
-  rbd_image_info_t pinfo, cinfo;
-  string pool_name = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name, &cluster));
-  rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
-
-  int features = RBD_FEATURE_LAYERING;
-  rbd_image_t parent, child;
-  int order = 0;
-
-  // make a parent to clone from
-  ASSERT_EQ(0, create_image_full(ioctx, "parent", 4<<20, &order, false, features));
-  ASSERT_EQ(0, rbd_open(ioctx, "parent", &parent, NULL));
-  printf("made parent image \"parent\"\n");
-
-  char *data = (char *)"testdata";
-  ASSERT_EQ((ssize_t)strlen(data), rbd_write(parent, 0, strlen(data), data));
-
-  // can't clone a non-snapshot, expect failure
-  EXPECT_NE(0, rbd_clone(ioctx, "parent", NULL, ioctx, "child", features, &order));
-
-  // verify that there is no parent info on "parent"
-  char ppool[1], pname[1], psnapname[1];
-  ASSERT_EQ(-ENOENT, rbd_get_parent_info(parent, ppool, sizeof(ppool),
-           pname, sizeof(pname), psnapname, sizeof(psnapname)));
-  printf("parent has no parent info\n");
-
-  // create a snapshot, reopen as the parent we're interested in
-  ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap"));
-  printf("made snapshot \"parent@parent_snap\"\n");
-  ASSERT_EQ(0, rbd_close(parent));
-  ASSERT_EQ(0, rbd_open(ioctx, "parent", &parent, "parent_snap"));
-
-  ASSERT_EQ(-EINVAL, rbd_clone(ioctx, "parent", "parent_snap", ioctx, "child",
-           features, &order));
-
-  // unprotected image should fail unprotect
-  ASSERT_EQ(-EINVAL, rbd_snap_unprotect(parent, "parent_snap"));
-  printf("can't unprotect an unprotected snap\n");
-
-  ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap"));
-  // protecting again should fail
-  ASSERT_EQ(-EBUSY, rbd_snap_protect(parent, "parent_snap"));
-  printf("can't protect a protected snap\n");
-
-  // This clone and open should work
-  ASSERT_EQ(0, rbd_clone(ioctx, "parent", "parent_snap", ioctx, "child",
-           features, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, "child", &child, NULL));
-  printf("made and opened clone \"child\"\n");
-
-  // check read
-  read_test_data(child, data, 0, strlen(data));
-
-  // check write
-  ASSERT_EQ((ssize_t)strlen(data), rbd_write(child, 20, strlen(data), data));
-  read_test_data(child, data, 20, strlen(data));
-  read_test_data(child, data, 0, strlen(data));
-
-  // check attributes
-  ASSERT_EQ(0, rbd_stat(parent, &pinfo, sizeof(pinfo)));
-  ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo)));
-  EXPECT_EQ(cinfo.size, pinfo.size);
-  uint64_t overlap;
-  rbd_get_overlap(child, &overlap);
-  EXPECT_EQ(overlap, pinfo.size);
-  EXPECT_EQ(cinfo.obj_size, pinfo.obj_size);
-  EXPECT_EQ(cinfo.order, pinfo.order);
-  printf("sizes and overlaps are good between parent and child\n");
-
-  // sizing down child results in changing overlap and size, not parent size
-  ASSERT_EQ(0, rbd_resize(child, 2UL<<20));
-  ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo)));
-  rbd_get_overlap(child, &overlap);
-  ASSERT_EQ(overlap, 2UL<<20);
-  ASSERT_EQ(cinfo.size, 2UL<<20);
-  ASSERT_EQ(0, rbd_resize(child, 4UL<<20));
-  ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo)));
-  rbd_get_overlap(child, &overlap);
-  ASSERT_EQ(overlap, 2UL<<20);
-  ASSERT_EQ(cinfo.size, 4UL<<20);
-  printf("sized down clone, changed overlap\n");
-
-  // sizing back up doesn't change that
-  ASSERT_EQ(0, rbd_resize(child, 5UL<<20));
-  ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo)));
-  rbd_get_overlap(child, &overlap);
-  ASSERT_EQ(overlap, 2UL<<20);
-  ASSERT_EQ(cinfo.size, 5UL<<20);
-  ASSERT_EQ(0, rbd_stat(parent, &pinfo, sizeof(pinfo)));
-  printf("parent info: size %lld obj_size %lld parent_pool %lld\n",
-        (unsigned long long)pinfo.size, (unsigned long long)pinfo.obj_size,
-        (unsigned long long)pinfo.parent_pool);
-  ASSERT_EQ(pinfo.size, 4UL<<20);
-  printf("sized up clone, changed size but not overlap or parent's size\n");
-  
-  ASSERT_EQ(0, rbd_close(child));
-
-  ASSERT_EQ(-EBUSY, rbd_snap_remove(parent, "parent_snap"));
-  printf("can't remove parent while child still exists\n");
-  ASSERT_EQ(0, rbd_remove(ioctx, "child"));
-  ASSERT_EQ(-EBUSY, rbd_snap_remove(parent, "parent_snap"));
-  printf("can't remove parent while still protected\n");
-  ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap"));
-  ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap"));
-  printf("removed parent snap after unprotecting\n");
-
-  ASSERT_EQ(0, rbd_close(parent));
-  rados_ioctx_destroy(ioctx);
-  ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
-}
-
-static void test_list_children(rbd_image_t image, ssize_t num_expected, ...)
-{
-  va_list ap;
-  va_start(ap, num_expected);
-  size_t pools_len = 100;
-  size_t children_len = 100;
-  char *pools = NULL;
-  char *children = NULL;
-  ssize_t num_children;
-
-  do {
-    free(pools);
-    free(children);
-    pools = (char *) malloc(pools_len);
-    children = (char *) malloc(children_len);
-    num_children = rbd_list_children(image, pools, &pools_len,
-                                    children, &children_len);
-  } while (num_children == -ERANGE);
-
-  ASSERT_EQ(num_expected, num_children);
-  for (ssize_t i = num_expected; i > 0; --i) {
-    char *expected_pool = va_arg(ap, char *);
-    char *expected_image = va_arg(ap, char *);
-    char *pool = pools;
-    char *image = children;
-    bool found = 0;
-    printf("\ntrying to find %s/%s\n", expected_pool, expected_image);
-    for (ssize_t j = 0; j < num_children; ++j) {
-      printf("checking %s/%s\n", pool, image);
-      if (strcmp(expected_pool, pool) == 0 &&
-         strcmp(expected_image, image) == 0) {
-       printf("found child %s/%s\n\n", pool, image);
-       found = 1;
-       break;
-      }
-      pool += strlen(pool) + 1;
-      image += strlen(image) + 1;
-      if (j == num_children - 1) {
-       ASSERT_EQ(pool - pools - 1, (ssize_t) pools_len);
-       ASSERT_EQ(image - children - 1, (ssize_t) children_len);
-      }
-    }
-    ASSERT_TRUE(found);
-  }
-  va_end(ap);
-}
-
-TEST(LibRBD, ListChildren)
-{
-  rados_t cluster;
-  rados_ioctx_t ioctx1, ioctx2;
-  string pool_name1 = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name1, &cluster));
-  string pool_name2 = get_temp_pool_name();
-  ASSERT_EQ("", create_one_pool(pool_name2, &cluster));
-  rados_ioctx_create(cluster, pool_name1.c_str(), &ioctx1);
-  rados_ioctx_create(cluster, pool_name2.c_str(), &ioctx2);
-
-  int features = RBD_FEATURE_LAYERING;
-  rbd_image_t parent;
-  int order = 0;
-
-  // make a parent to clone from
-  ASSERT_EQ(0, create_image_full(ioctx1, "parent", 4<<20, &order,
-                                false, features));
-  ASSERT_EQ(0, rbd_open(ioctx1, "parent", &parent, NULL));
-  // create a snapshot, reopen as the parent we're interested in
-  ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap"));
-  ASSERT_EQ(0, rbd_snap_set(parent, "parent_snap"));
-  ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap"));
-
-  ASSERT_EQ(0, rbd_close(parent));
-  ASSERT_EQ(0, rbd_open(ioctx1, "parent", &parent, "parent_snap"));
-
-  ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child1",
-           features, &order));
-  test_list_children(parent, 1, pool_name2.c_str(), "child1");
-
-  ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx1, "child2",
-           features, &order));
-  test_list_children(parent, 2, pool_name2.c_str(), "child1",
-                    pool_name1.c_str(), "child2");
-
-  ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child3",
-           features, &order));
-  test_list_children(parent, 3, pool_name2.c_str(), "child1",
-                    pool_name1.c_str(), "child2",
-                    pool_name2.c_str(), "child3");
-
-  ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child4",
-           features, &order));
-  test_list_children(parent, 4, pool_name2.c_str(), "child1",
-                    pool_name1.c_str(), "child2",
-                    pool_name2.c_str(), "child3",
-                    pool_name2.c_str(), "child4");
-
-  ASSERT_EQ(0, rbd_remove(ioctx2, "child1"));
-  test_list_children(parent, 3,
-                    pool_name1.c_str(), "child2",
-                    pool_name2.c_str(), "child3",
-                    pool_name2.c_str(), "child4");
-
-  ASSERT_EQ(0, rbd_remove(ioctx2, "child3"));
-  test_list_children(parent, 2,
-                    pool_name1.c_str(), "child2",
-                    pool_name2.c_str(), "child4");
-
-  ASSERT_EQ(0, rbd_remove(ioctx2, "child4"));
-  test_list_children(parent, 1,
-                    pool_name1.c_str(), "child2");
-
-  ASSERT_EQ(0, rbd_remove(ioctx1, "child2"));
-  test_list_children(parent, 0);
-
-  ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap"));
-  ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap"));
-  ASSERT_EQ(0, rbd_close(parent));
-  ASSERT_EQ(0, rbd_remove(ioctx1, "parent"));
-  rados_ioctx_destroy(ioctx1);
-  rados_ioctx_destroy(ioctx2);
-  // destroy_one_pool also closes the cluster; do this one step at a time
-  ASSERT_EQ(0, rados_pool_delete(cluster, pool_name1.c_str()));
-  ASSERT_EQ(0, destroy_one_pool(pool_name2, &cluster));
-}
-
-TEST(LibRBD, LockingPP)
-{
-  librados::Rados rados;
-  librados::IoCtx ioctx;
-  string pool_name = get_temp_pool_name();
-
-  ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
-  ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
-  {
-    librbd::RBD rbd;
-    librbd::Image image;
-    int order = 0;
-    const char *name = "testimg";
-    uint64_t size = 2 << 20;
-    std::string cookie1 = "foo";
-    std::string cookie2 = "bar";
-
-    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name, size, &order));
-    ASSERT_EQ(0, rbd.open(ioctx, image, name, NULL));
-
-    // no lockers initially
-    std::list<librbd::locker_t> lockers;
-    std::string tag;
-    bool exclusive;
-    ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag));
-    ASSERT_EQ(0u, lockers.size());
-    ASSERT_EQ("", tag);
-
-    // exclusive lock is exclusive
-    ASSERT_EQ(0, image.lock_exclusive(cookie1));
-    ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie1));
-    ASSERT_EQ(-EBUSY, image.lock_exclusive(""));
-    ASSERT_EQ(-EEXIST, image.lock_shared(cookie1, ""));
-    ASSERT_EQ(-EBUSY, image.lock_shared(cookie1, "test"));
-    ASSERT_EQ(-EBUSY, image.lock_shared("", "test"));
-    ASSERT_EQ(-EBUSY, image.lock_shared("", ""));
-
-    // list exclusive
-    ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag));
-    ASSERT_TRUE(exclusive);
-    ASSERT_EQ("", tag);
-    ASSERT_EQ(1u, lockers.size());
-    ASSERT_EQ(cookie1, lockers.front().cookie);
-
-    // unlock
-    ASSERT_EQ(-ENOENT, image.unlock(""));
-    ASSERT_EQ(-ENOENT, image.unlock(cookie2));
-    ASSERT_EQ(0, image.unlock(cookie1));
-    ASSERT_EQ(-ENOENT, image.unlock(cookie1));
-    ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag));
-    ASSERT_EQ(0u, lockers.size());
-
-    ASSERT_EQ(0, image.lock_shared(cookie1, ""));
-    ASSERT_EQ(-EEXIST, image.lock_shared(cookie1, ""));
-    ASSERT_EQ(0, image.lock_shared(cookie2, ""));
-    ASSERT_EQ(-EEXIST, image.lock_shared(cookie2, ""));
-    ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie1));
-    ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie2));
-    ASSERT_EQ(-EBUSY, image.lock_exclusive(""));
-    ASSERT_EQ(-EBUSY, image.lock_exclusive("test"));
-
-    // list shared
-    ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag));
-    ASSERT_EQ(2u, lockers.size());
-  }
-
-  ioctx.close();
-  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}