mirror of
https://github.com/ceph/ceph
synced 2024-12-19 18:02:46 +00:00
2454 lines
65 KiB
C++
2454 lines
65 KiB
C++
// -*- 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) 2004-2006 Sage Weil <sage@newdream.net>
|
|
*
|
|
* 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 "mon/MonClient.h"
|
|
#include "mon/MonMap.h"
|
|
#include "common/config.h"
|
|
|
|
#include "auth/KeyRing.h"
|
|
#include "common/errno.h"
|
|
#include "common/ceph_argparse.h"
|
|
#include "common/strtol.h"
|
|
#include "global/global_init.h"
|
|
#include "common/safe_io.h"
|
|
#include "common/secret.h"
|
|
#include "include/stringify.h"
|
|
#include "include/rados/librados.hpp"
|
|
#include "include/rbd/librbd.hpp"
|
|
#include "include/byteorder.h"
|
|
|
|
#include "include/intarith.h"
|
|
|
|
#include "include/compat.h"
|
|
#include "common/blkdev.h"
|
|
|
|
#include <boost/scoped_ptr.hpp>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <tr1/memory>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include "include/rbd_types.h"
|
|
#include "common/TextTable.h"
|
|
#include "include/util.h"
|
|
|
|
#include "common/Formatter.h"
|
|
|
|
#if defined(__linux__)
|
|
#include <linux/fs.h>
|
|
#endif
|
|
|
|
#if defined(__FreeBSD__)
|
|
#include <sys/param.h>
|
|
#endif
|
|
|
|
#include "include/fiemap.h"
|
|
|
|
#define MAX_SECRET_LEN 1000
|
|
#define MAX_POOL_NAME_SIZE 128
|
|
|
|
static string dir_oid = RBD_DIRECTORY;
|
|
static string dir_info_oid = RBD_INFO;
|
|
|
|
bool udevadm_settle = true;
|
|
|
|
void usage()
|
|
{
|
|
cout <<
|
|
"usage: rbd [-n <auth user>] [OPTIONS] <cmd> ...\n"
|
|
"where 'pool' is a rados pool name (default is 'rbd') and 'cmd' is one of:\n"
|
|
" (ls | list) [-l | --long ] [pool-name] list rbd images\n"
|
|
" (-l includes snapshots/clones)\n"
|
|
" info <image-name> show information about image size,\n"
|
|
" striping, etc.\n"
|
|
" create [--order <bits>] --size <MB> <name> create an empty image\n"
|
|
" clone [--order <bits>] <parentsnap> <clonename>\n"
|
|
" clone a snapshot into a COW\n"
|
|
" child image\n"
|
|
" children <snap-name> display children of snapshot\n"
|
|
" flatten <image-name> fill clone with parent data\n"
|
|
" (make it independent)\n"
|
|
" resize --size <MB> <image-name> resize (expand or contract) image\n"
|
|
" rm <image-name> delete an image\n"
|
|
" export <image-name> <path> export image to file\n"
|
|
" \"-\" for stdout\n"
|
|
" import <path> <image-name> import image from file\n"
|
|
" (dest defaults\n"
|
|
" as the filename part of file)\n"
|
|
" \"-\" for stdin\n"
|
|
" (cp | copy) <src> <dest> copy src image to dest\n"
|
|
" (mv | rename) <src> <dest> rename src image to dest\n"
|
|
" snap ls <image-name> dump list of image snapshots\n"
|
|
" snap create <snap-name> create a snapshot\n"
|
|
" snap rollback <snap-name> rollback image to snapshot\n"
|
|
" snap rm <snap-name> deletes a snapshot\n"
|
|
" snap purge <image-name> deletes all snapshots\n"
|
|
" snap protect <snap-name> prevent a snapshot from being deleted\n"
|
|
" snap unprotect <snap-name> allow a snapshot to be deleted\n"
|
|
" watch <image-name> watch events on image\n"
|
|
" map <image-name> map image to a block device\n"
|
|
" using the kernel\n"
|
|
" unmap <device> unmap a rbd device that was\n"
|
|
" mapped by the kernel\n"
|
|
" showmapped show the rbd images mapped\n"
|
|
" by the kernel\n"
|
|
" lock list <image-name> show locks held on an image\n"
|
|
" lock add <image-name> <id> [--shared <tag>] take a lock called id on an image\n"
|
|
" lock remove <image-name> <id> <locker> release a lock on an image\n"
|
|
" bench-write <image-name> --io-size <bytes> --io-threads <num> --io-total <bytes>\n"
|
|
"\n"
|
|
"<image-name>, <snap-name> are [pool/]name[@snap], or you may specify\n"
|
|
"individual pieces of names with -p/--pool, --image, and/or --snap.\n"
|
|
"\n"
|
|
"Other input options:\n"
|
|
" -p, --pool <pool> source pool name\n"
|
|
" --image <image-name> image name\n"
|
|
" --dest <image-name> destination [pool and] image name\n"
|
|
" --snap <snap-name> snapshot name\n"
|
|
" --dest-pool <name> destination pool name\n"
|
|
" --path <path-name> path name for import/export\n"
|
|
" --size <size in MB> size of image for create and resize\n"
|
|
" --order <bits> the object size in bits; object size will be\n"
|
|
" (1 << order) bytes. Default is 22 (4 MB).\n"
|
|
" --image-format <format-number> format to use when creating an image\n"
|
|
" format 1 is the original format (default)\n"
|
|
" format 2 supports cloning\n"
|
|
" --id <username> rados user (without 'client.'prefix) to\n"
|
|
" authenticate as\n"
|
|
" --keyfile <path> file containing secret key for use with cephx\n"
|
|
" --shared <tag> take a shared (rather than exclusive) lock\n"
|
|
" --format <output-format> output format (default: plain, json, xml)\n"
|
|
" --pretty-format make json or xml output more readable\n"
|
|
" --no-settle do not wait for udevadm to settle on map/unmap\n";
|
|
}
|
|
|
|
static string feature_str(uint64_t feature)
|
|
{
|
|
switch (feature) {
|
|
case RBD_FEATURE_LAYERING:
|
|
return "layering";
|
|
case RBD_FEATURE_STRIPINGV2:
|
|
return "striping";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
static string features_str(uint64_t features)
|
|
{
|
|
string s = "";
|
|
|
|
for (uint64_t feature = 1; feature <= RBD_FEATURE_STRIPINGV2;
|
|
feature <<= 1) {
|
|
if (s.size())
|
|
s += ", ";
|
|
s += feature_str(feature);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static void format_features(Formatter *f, uint64_t features)
|
|
{
|
|
f->open_array_section("features");
|
|
for (uint64_t feature = 1; feature <= RBD_FEATURE_STRIPINGV2;
|
|
feature <<= 1) {
|
|
f->dump_string("feature", feature_str(feature));
|
|
}
|
|
f->close_section();
|
|
}
|
|
|
|
struct MyProgressContext : public librbd::ProgressContext {
|
|
const char *operation;
|
|
int last_pc;
|
|
|
|
MyProgressContext(const char *o) : operation(o), last_pc(0) {
|
|
}
|
|
|
|
int update_progress(uint64_t offset, uint64_t total) {
|
|
int pc = total ? (offset * 100ull / total) : 0;
|
|
if (pc != last_pc) {
|
|
cout << "\r" << operation << ": "
|
|
// << offset << " / " << total << " "
|
|
<< pc << "% complete...";
|
|
cout.flush();
|
|
last_pc = pc;
|
|
}
|
|
return 0;
|
|
}
|
|
void finish() {
|
|
cout << "\r" << operation << ": 100% complete...done." << std::endl;
|
|
}
|
|
void fail() {
|
|
cout << "\r" << operation << ": " << last_pc << "% complete...failed."
|
|
<< std::endl;
|
|
}
|
|
};
|
|
|
|
static int get_outfmt(const char *output_format,
|
|
bool pretty,
|
|
boost::scoped_ptr<Formatter> *f)
|
|
{
|
|
if (!strcmp(output_format, "json")) {
|
|
f->reset(new JSONFormatter(pretty));
|
|
} else if (!strcmp(output_format, "xml")) {
|
|
f->reset(new XMLFormatter(pretty));
|
|
} else if (strcmp(output_format, "plain")) {
|
|
cerr << "rbd: unknown format '" << output_format << "'" << std::endl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_list(librbd::RBD &rbd, librados::IoCtx& io_ctx, bool lflag,
|
|
Formatter *f)
|
|
{
|
|
std::vector<string> names;
|
|
int r = rbd.list(io_ctx, names);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!lflag) {
|
|
if (f)
|
|
f->open_array_section("images");
|
|
for (std::vector<string>::const_iterator i = names.begin();
|
|
i != names.end(); ++i) {
|
|
if (f)
|
|
f->dump_string("name", *i);
|
|
else
|
|
cout << *i << std::endl;
|
|
}
|
|
if (f) {
|
|
f->close_section();
|
|
f->flush(cout);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
TextTable tbl;
|
|
|
|
if (f) {
|
|
f->open_array_section("images");
|
|
} else {
|
|
tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("SIZE", TextTable::RIGHT, TextTable::RIGHT);
|
|
tbl.define_column("PARENT", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("FMT", TextTable::RIGHT, TextTable::RIGHT);
|
|
tbl.define_column("PROT", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("LOCK", TextTable::LEFT, TextTable::LEFT);
|
|
}
|
|
|
|
string pool, image, snap, parent;
|
|
|
|
for (std::vector<string>::const_iterator i = names.begin();
|
|
i != names.end(); ++i) {
|
|
librbd::image_info_t info;
|
|
librbd::Image im;
|
|
|
|
r = rbd.open_read_only(io_ctx, im, i->c_str(), NULL);
|
|
// image might disappear between rbd.list() and rbd.open(); ignore
|
|
// that, warn about other possible errors (EPERM, say, for opening
|
|
// an old-format image, because you need execute permission for the
|
|
// class method)
|
|
if (r < 0) {
|
|
if (r != -ENOENT) {
|
|
cerr << "rbd: error opening " << *i << ": " << cpp_strerror(r)
|
|
<< std::endl;
|
|
}
|
|
// in any event, continue to next image
|
|
continue;
|
|
}
|
|
|
|
// handle second-nth trips through loop
|
|
parent.clear();
|
|
r = im.parent_info(&pool, &image, &snap);
|
|
if (r < 0 && r != -ENOENT)
|
|
return r;
|
|
|
|
bool has_parent = false;
|
|
if (r != -ENOENT) {
|
|
parent = pool + "/" + image + "@" + snap;
|
|
has_parent = true;
|
|
}
|
|
|
|
if (im.stat(info, sizeof(info)) < 0)
|
|
return -EINVAL;
|
|
|
|
uint8_t old_format;
|
|
im.old_format(&old_format);
|
|
|
|
list<librbd::locker_t> lockers;
|
|
bool exclusive;
|
|
r = im.list_lockers(&lockers, &exclusive, NULL);
|
|
if (r < 0)
|
|
return r;
|
|
string lockstr;
|
|
if (!lockers.empty()) {
|
|
lockstr = (exclusive) ? "excl" : "shr";
|
|
}
|
|
|
|
if (f) {
|
|
f->open_object_section("image");
|
|
f->dump_string("image", *i);
|
|
f->dump_unsigned("size", info.size);
|
|
if (has_parent) {
|
|
f->open_object_section("parent");
|
|
f->dump_string("pool", pool);
|
|
f->dump_string("image", image);
|
|
f->dump_string("snapshot", snap);
|
|
f->close_section();
|
|
}
|
|
f->dump_int("format", old_format ? 1 : 2);
|
|
if (!lockers.empty())
|
|
f->dump_string("lock_type", exclusive ? "exclusive" : "shared");
|
|
f->close_section();
|
|
} else {
|
|
tbl << *i
|
|
<< stringify(si_t(info.size))
|
|
<< parent
|
|
<< ((old_format) ? '1' : '2')
|
|
<< "" // protect doesn't apply to images
|
|
<< lockstr
|
|
<< TextTable::endrow;
|
|
}
|
|
|
|
vector<librbd::snap_info_t> snaplist;
|
|
if (im.snap_list(snaplist) >= 0 && !snaplist.empty()) {
|
|
for (std::vector<librbd::snap_info_t>::iterator s = snaplist.begin();
|
|
s != snaplist.end(); ++s) {
|
|
bool is_protected;
|
|
bool has_parent = false;
|
|
parent.clear();
|
|
im.snap_set(s->name.c_str());
|
|
r = im.snap_is_protected(s->name.c_str(), &is_protected);
|
|
if (r < 0)
|
|
return r;
|
|
if (im.parent_info(&pool, &image, &snap) >= 0) {
|
|
parent = pool + "/" + image + "@" + snap;
|
|
has_parent = true;
|
|
}
|
|
if (f) {
|
|
f->open_object_section("snapshot");
|
|
f->dump_string("image", *i);
|
|
f->dump_string("snapshot", s->name);
|
|
f->dump_unsigned("size", s->size);
|
|
if (has_parent) {
|
|
f->open_object_section("parent");
|
|
f->dump_string("pool", pool);
|
|
f->dump_string("image", image);
|
|
f->dump_string("snapshot", snap);
|
|
f->close_section();
|
|
}
|
|
f->dump_int("format", old_format ? 1 : 2);
|
|
f->dump_string("protected", is_protected ? "true" : "false");
|
|
f->close_section();
|
|
} else {
|
|
tbl << *i + "@" + s->name
|
|
<< stringify(si_t(s->size))
|
|
<< parent
|
|
<< ((old_format) ? '1' : '2')
|
|
<< (is_protected ? "yes" : "")
|
|
<< "" // locks don't apply to snaps
|
|
<< TextTable::endrow;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (f) {
|
|
f->close_section();
|
|
f->flush(cout);
|
|
} else if (!names.empty()) {
|
|
cout << tbl;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_create(librbd::RBD &rbd, librados::IoCtx& io_ctx,
|
|
const char *imgname, uint64_t size, int *order,
|
|
int format, uint64_t features,
|
|
uint64_t stripe_unit, uint64_t stripe_count)
|
|
{
|
|
int r;
|
|
|
|
if (format == 1) {
|
|
// weird striping not allowed with format 1!
|
|
if ((stripe_unit || stripe_count) &&
|
|
(stripe_unit != (1ull << *order) && stripe_count != 1)) {
|
|
cerr << "non-default striping not allowed with format 1; use --format 2"
|
|
<< std::endl;
|
|
return -EINVAL;
|
|
}
|
|
r = rbd.create(io_ctx, imgname, size, order);
|
|
} else {
|
|
if (features == 0) {
|
|
features = RBD_FEATURE_LAYERING;
|
|
}
|
|
if (stripe_unit != (1ull << *order) && stripe_count != 1)
|
|
features |= RBD_FEATURE_STRIPINGV2;
|
|
r = rbd.create3(io_ctx, imgname, size, features, order,
|
|
stripe_unit, stripe_count);
|
|
}
|
|
if (r < 0)
|
|
return r;
|
|
return 0;
|
|
}
|
|
|
|
static int do_clone(librbd::RBD &rbd, librados::IoCtx &p_ioctx,
|
|
const char *p_name, const char *p_snapname,
|
|
librados::IoCtx &c_ioctx, const char *c_name,
|
|
uint64_t features, int *c_order)
|
|
{
|
|
if (features == 0)
|
|
features = RBD_FEATURES_ALL;
|
|
else if ((features & RBD_FEATURE_LAYERING) != RBD_FEATURE_LAYERING)
|
|
return -EINVAL;
|
|
|
|
return rbd.clone(p_ioctx, p_name, p_snapname, c_ioctx, c_name, features,
|
|
c_order);
|
|
}
|
|
|
|
static int do_flatten(librbd::Image& image)
|
|
{
|
|
MyProgressContext pc("Image flatten");
|
|
int r = image.flatten_with_progress(pc);
|
|
if (r < 0) {
|
|
pc.fail();
|
|
return r;
|
|
}
|
|
pc.finish();
|
|
return 0;
|
|
}
|
|
|
|
static int do_rename(librbd::RBD &rbd, librados::IoCtx& io_ctx,
|
|
const char *imgname, const char *destname)
|
|
{
|
|
int r = rbd.rename(io_ctx, imgname, destname);
|
|
if (r < 0)
|
|
return r;
|
|
return 0;
|
|
}
|
|
|
|
static int do_show_info(const char *imgname, librbd::Image& image,
|
|
const char *snapname, Formatter *f)
|
|
{
|
|
librbd::image_info_t info;
|
|
string parent_pool, parent_name, parent_snapname;
|
|
uint8_t old_format;
|
|
uint64_t overlap, features;
|
|
bool snap_protected;
|
|
int r;
|
|
|
|
r = image.stat(info, sizeof(info));
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = image.old_format(&old_format);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = image.overlap(&overlap);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = image.features(&features);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (snapname) {
|
|
r = image.snap_is_protected(snapname, &snap_protected);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
if (f) {
|
|
f->open_object_section("image");
|
|
f->dump_string("name", imgname);
|
|
f->dump_unsigned("size", info.size);
|
|
f->dump_unsigned("objects", info.num_objs);
|
|
f->dump_int("order", info.order);
|
|
f->dump_unsigned("object_size", info.obj_size);
|
|
f->dump_string("block_name_prefix", info.block_name_prefix);
|
|
f->dump_int("format", (old_format ? 1 : 2));
|
|
} else {
|
|
cout << "rbd image '" << imgname << "':\n"
|
|
<< "\tsize " << prettybyte_t(info.size) << " in "
|
|
<< info.num_objs << " objects"
|
|
<< std::endl
|
|
<< "\torder " << info.order
|
|
<< " (" << prettybyte_t(info.obj_size) << " objects)"
|
|
<< std::endl
|
|
<< "\tblock_name_prefix: " << info.block_name_prefix
|
|
<< std::endl
|
|
<< "\tformat: " << (old_format ? "1" : "2")
|
|
<< std::endl;
|
|
}
|
|
|
|
if (!old_format) {
|
|
if (f)
|
|
format_features(f, features);
|
|
else
|
|
cout << "\tfeatures: " << features_str(features) << std::endl;
|
|
}
|
|
|
|
// snapshot info, if present
|
|
if (snapname) {
|
|
if (f) {
|
|
f->dump_string("protected", snap_protected ? "true" : "false");
|
|
} else {
|
|
cout << "\tprotected: " << (snap_protected ? "True" : "False")
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
// parent info, if present
|
|
if ((image.parent_info(&parent_pool, &parent_name, &parent_snapname) == 0) &&
|
|
parent_name.length() > 0) {
|
|
if (f) {
|
|
f->open_object_section("parent");
|
|
f->dump_string("pool", parent_pool);
|
|
f->dump_string("image", parent_name);
|
|
f->dump_string("snapshot", parent_snapname);
|
|
f->dump_unsigned("overlap", overlap);
|
|
f->close_section();
|
|
} else {
|
|
cout << "\tparent: " << parent_pool << "/" << parent_name
|
|
<< "@" << parent_snapname << std::endl;
|
|
cout << "\toverlap: " << prettybyte_t(overlap) << std::endl;
|
|
}
|
|
}
|
|
|
|
// striping info, if feature is set
|
|
if (features & RBD_FEATURE_STRIPINGV2) {
|
|
if (f) {
|
|
f->dump_unsigned("stripe_unit", image.get_stripe_unit());
|
|
f->dump_unsigned("stripe_count", image.get_stripe_count());
|
|
} else {
|
|
cout << "\tstripe unit: " << prettybyte_t(image.get_stripe_unit())
|
|
<< std::endl
|
|
<< "\tstripe count: " << image.get_stripe_count() << std::endl;
|
|
}
|
|
}
|
|
|
|
if (f) {
|
|
f->close_section();
|
|
f->flush(cout);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_delete(librbd::RBD &rbd, librados::IoCtx& io_ctx,
|
|
const char *imgname)
|
|
{
|
|
MyProgressContext pc("Removing image");
|
|
int r = rbd.remove_with_progress(io_ctx, imgname, pc);
|
|
if (r < 0) {
|
|
pc.fail();
|
|
return r;
|
|
}
|
|
pc.finish();
|
|
return 0;
|
|
}
|
|
|
|
static int do_resize(librbd::Image& image, uint64_t size)
|
|
{
|
|
MyProgressContext pc("Resizing image");
|
|
int r = image.resize_with_progress(size, pc);
|
|
if (r < 0) {
|
|
pc.fail();
|
|
return r;
|
|
}
|
|
pc.finish();
|
|
return 0;
|
|
}
|
|
|
|
static int do_list_snaps(librbd::Image& image, Formatter *f)
|
|
{
|
|
std::vector<librbd::snap_info_t> snaps;
|
|
TextTable t;
|
|
int r;
|
|
|
|
r = image.snap_list(snaps);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (f) {
|
|
f->open_array_section("snapshots");
|
|
} else {
|
|
t.define_column("SNAPID", TextTable::RIGHT, TextTable::RIGHT);
|
|
t.define_column("NAME", TextTable::LEFT, TextTable::LEFT);
|
|
t.define_column("SIZE", TextTable::RIGHT, TextTable::RIGHT);
|
|
}
|
|
|
|
for (std::vector<librbd::snap_info_t>::iterator s = snaps.begin();
|
|
s != snaps.end(); ++s) {
|
|
if (f) {
|
|
f->open_object_section("snapshot");
|
|
f->dump_unsigned("id", s->id);
|
|
f->dump_string("name", s->name);
|
|
f->dump_unsigned("size", s->size);
|
|
f->close_section();
|
|
} else {
|
|
t << s->id << s->name << stringify(prettybyte_t(s->size))
|
|
<< TextTable::endrow;
|
|
}
|
|
}
|
|
|
|
if (f) {
|
|
f->close_section();
|
|
f->flush(cout);
|
|
} else if (snaps.size()) {
|
|
cout << t;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_add_snap(librbd::Image& image, const char *snapname)
|
|
{
|
|
int r = image.snap_create(snapname);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_remove_snap(librbd::Image& image, const char *snapname)
|
|
{
|
|
int r = image.snap_remove(snapname);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_rollback_snap(librbd::Image& image, const char *snapname)
|
|
{
|
|
MyProgressContext pc("Rolling back to snapshot");
|
|
int r = image.snap_rollback_with_progress(snapname, pc);
|
|
if (r < 0) {
|
|
pc.fail();
|
|
return r;
|
|
}
|
|
pc.finish();
|
|
return 0;
|
|
}
|
|
|
|
static int do_purge_snaps(librbd::Image& image)
|
|
{
|
|
MyProgressContext pc("Removing all snapshots");
|
|
std::vector<librbd::snap_info_t> snaps;
|
|
int r = image.snap_list(snaps);
|
|
if (r < 0) {
|
|
pc.fail();
|
|
return r;
|
|
}
|
|
|
|
for (size_t i = 0; i < snaps.size(); ++i) {
|
|
r = image.snap_remove(snaps[i].name.c_str());
|
|
if (r < 0) {
|
|
pc.fail();
|
|
return r;
|
|
}
|
|
pc.update_progress(i + 1, snaps.size());
|
|
}
|
|
|
|
pc.finish();
|
|
return 0;
|
|
}
|
|
|
|
static int do_protect_snap(librbd::Image& image, const char *snapname)
|
|
{
|
|
int r = image.snap_protect(snapname);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_unprotect_snap(librbd::Image& image, const char *snapname)
|
|
{
|
|
int r = image.snap_unprotect(snapname);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_list_children(librbd::Image &image, Formatter *f)
|
|
{
|
|
set<pair<string, string> > children;
|
|
int r;
|
|
|
|
r = image.list_children(&children);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (f)
|
|
f->open_array_section("children");
|
|
|
|
for (set<pair<string, string> >::const_iterator child_it = children.begin();
|
|
child_it != children.end(); child_it++) {
|
|
if (f) {
|
|
f->open_object_section("child");
|
|
f->dump_string("pool", child_it->first);
|
|
f->dump_string("image", child_it->second);
|
|
f->close_section();
|
|
} else {
|
|
cout << child_it->first << "/" << child_it->second << std::endl;
|
|
}
|
|
}
|
|
|
|
if (f) {
|
|
f->close_section();
|
|
f->flush(cout);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_lock_list(librbd::Image& image, Formatter *f)
|
|
{
|
|
list<librbd::locker_t> lockers;
|
|
bool exclusive;
|
|
string tag;
|
|
TextTable tbl;
|
|
int r;
|
|
|
|
r = image.list_lockers(&lockers, &exclusive, &tag);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (f) {
|
|
f->open_object_section("locks");
|
|
} else {
|
|
tbl.define_column("Locker", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("ID", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("Address", TextTable::LEFT, TextTable::LEFT);
|
|
}
|
|
|
|
if (lockers.size()) {
|
|
bool one = (lockers.size() == 1);
|
|
|
|
if (!f) {
|
|
cout << "There " << (one ? "is " : "are ") << lockers.size()
|
|
<< (exclusive ? " exclusive" : " shared")
|
|
<< " lock" << (one ? "" : "s") << " on this image.\n";
|
|
if (!exclusive)
|
|
cout << "Lock tag: " << tag << "\n";
|
|
}
|
|
|
|
for (list<librbd::locker_t>::const_iterator it = lockers.begin();
|
|
it != lockers.end(); ++it) {
|
|
if (f) {
|
|
f->open_object_section(it->cookie.c_str());
|
|
f->dump_string("locker", it->client);
|
|
f->dump_string("address", it->address);
|
|
f->close_section();
|
|
} else {
|
|
tbl << it->client << it->cookie << it->address << TextTable::endrow;
|
|
}
|
|
}
|
|
if (!f)
|
|
cout << tbl;
|
|
}
|
|
|
|
if (f) {
|
|
f->close_section();
|
|
f->flush(cout);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int do_lock_add(librbd::Image& image, const char *cookie,
|
|
const char *tag)
|
|
{
|
|
if (tag)
|
|
return image.lock_shared(cookie, tag);
|
|
else
|
|
return image.lock_exclusive(cookie);
|
|
}
|
|
|
|
static int do_lock_remove(librbd::Image& image, const char *client,
|
|
const char *cookie)
|
|
{
|
|
return image.break_lock(client, cookie);
|
|
}
|
|
|
|
static void rbd_bencher_completion(void *c, void *pc);
|
|
|
|
struct rbd_bencher;
|
|
|
|
struct rbd_bencher {
|
|
librbd::Image *image;
|
|
Mutex lock;
|
|
Cond cond;
|
|
int in_flight;
|
|
|
|
rbd_bencher(librbd::Image *i)
|
|
: image(i),
|
|
lock("rbd_bencher::lock"),
|
|
in_flight(0)
|
|
{ }
|
|
|
|
bool start_write(int max, uint64_t off, uint64_t len, bufferlist& bl)
|
|
{
|
|
{
|
|
Mutex::Locker l(lock);
|
|
if (in_flight >= max)
|
|
return false;
|
|
in_flight++;
|
|
}
|
|
librbd::RBD::AioCompletion *c =
|
|
new librbd::RBD::AioCompletion((void *)this, rbd_bencher_completion);
|
|
image->aio_write(off, len, bl, c);
|
|
//cout << "start " << c << " at " << off << "~" << len << std::endl;
|
|
return true;
|
|
}
|
|
|
|
void wait_for(int max) {
|
|
Mutex::Locker l(lock);
|
|
while (in_flight > max) {
|
|
utime_t dur;
|
|
dur.set_from_double(.2);
|
|
cond.WaitInterval(g_ceph_context, lock, dur);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
void rbd_bencher_completion(void *vc, void *pc)
|
|
{
|
|
librbd::RBD::AioCompletion *c = (librbd::RBD::AioCompletion *)vc;
|
|
rbd_bencher *b = static_cast<rbd_bencher *>(pc);
|
|
//cout << "complete " << c << std::endl;
|
|
b->lock.Lock();
|
|
b->in_flight--;
|
|
b->cond.Signal();
|
|
b->lock.Unlock();
|
|
c->release();
|
|
}
|
|
|
|
static int do_bench_write(librbd::Image& image, uint64_t io_size,
|
|
uint64_t io_threads, uint64_t io_bytes)
|
|
{
|
|
rbd_bencher b(&image);
|
|
|
|
cout << "bench-write "
|
|
<< " io_size " << io_size
|
|
<< " io_threads " << io_threads
|
|
<< " bytes " << io_bytes
|
|
<< std::endl;
|
|
|
|
bufferptr bp(io_size);
|
|
bp.zero();
|
|
bufferlist bl;
|
|
bl.push_back(bp);
|
|
|
|
utime_t start = ceph_clock_now(NULL);
|
|
utime_t last;
|
|
unsigned ios = 0;
|
|
|
|
printf(" SEC OPS OPS/SEC BYTES/SEC\n");
|
|
uint64_t off;
|
|
for (off = 0; off < io_bytes; off += io_size) {
|
|
b.wait_for(io_threads - 1);
|
|
uint64_t i = 0;
|
|
while (i < io_threads &&
|
|
b.start_write(io_threads, off, io_size, bl)) {
|
|
++i;
|
|
++ios;
|
|
}
|
|
|
|
utime_t now = ceph_clock_now(NULL);
|
|
utime_t elapsed = now - start;
|
|
if (elapsed.sec() != last.sec()) {
|
|
printf("%5d %8d %8.2lf %8.2lf\n",
|
|
(int)elapsed,
|
|
(int)(ios - io_threads),
|
|
(double)(ios - io_threads) / elapsed,
|
|
(double)(off - io_threads * io_size) / elapsed);
|
|
last = elapsed;
|
|
}
|
|
}
|
|
b.wait_for(0);
|
|
int r = image.flush();
|
|
if (r < 0) {
|
|
cerr << "Error flushing data at the end: " << cpp_strerror(r) << std::endl;
|
|
}
|
|
|
|
utime_t now = ceph_clock_now(NULL);
|
|
double elapsed = now - start;
|
|
|
|
printf("elapsed: %5d ops: %8d ops/sec: %8.2lf bytes/sec: %8.2lf\n",
|
|
(int)elapsed, ios, (double)ios / elapsed, (double)off / elapsed);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct ExportContext {
|
|
int fd;
|
|
uint64_t totalsize;
|
|
MyProgressContext pc;
|
|
|
|
ExportContext(int f, uint64_t t) : fd(f), totalsize(t), pc("Exporting image")
|
|
{}
|
|
};
|
|
|
|
static int export_read_cb(uint64_t ofs, size_t len, const char *buf, void *arg)
|
|
{
|
|
ssize_t ret;
|
|
ExportContext *ec = static_cast<ExportContext *>(arg);
|
|
int fd = ec->fd;
|
|
static char *localbuf = NULL;
|
|
static size_t maplen = 0;
|
|
|
|
if (fd == 1) {
|
|
if (!buf) {
|
|
// can't seek stdout; need actual data to write
|
|
if (maplen < len) {
|
|
// never mapped, or need to map larger
|
|
int r;
|
|
if (localbuf != NULL){
|
|
if ((r = munmap(localbuf, len)) < 0) {
|
|
cerr << "rbd: error " << r << "munmap'ing buffer" << std::endl;
|
|
return errno;
|
|
}
|
|
}
|
|
|
|
maplen = len;
|
|
localbuf = (char *)mmap(NULL, maplen, PROT_READ,
|
|
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
|
if (localbuf == MAP_FAILED) {
|
|
cerr << "rbd: MAP_FAILED mmap'ing buffer for zero writes"
|
|
<< std::endl;
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
ret = write(fd, localbuf, len);
|
|
} else {
|
|
ret = write(fd, buf, len);
|
|
}
|
|
} else { // not stdout
|
|
if (!buf || buf_is_zero(buf, len)) {
|
|
/* a hole */
|
|
return 0;
|
|
}
|
|
|
|
ret = lseek64(fd, ofs, SEEK_SET);
|
|
if (ret < 0)
|
|
return -errno;
|
|
ret = write(fd, buf, len);
|
|
ec->pc.update_progress(ofs, ec->totalsize);
|
|
}
|
|
|
|
if (ret < 0)
|
|
return -errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_export(librbd::Image& image, const char *path)
|
|
{
|
|
int64_t r;
|
|
librbd::image_info_t info;
|
|
int fd;
|
|
|
|
r = image.stat(info, sizeof(info));
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (strcmp(path, "-") == 0)
|
|
fd = 1;
|
|
else
|
|
fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
|
|
if (fd < 0)
|
|
return -errno;
|
|
|
|
ExportContext ec(fd, info.size);
|
|
r = image.read_iterate(0, info.size, export_read_cb, (void *)&ec);
|
|
if (r < 0)
|
|
goto out;
|
|
|
|
if (fd != 1)
|
|
r = ftruncate(fd, info.size);
|
|
if (r < 0)
|
|
goto out;
|
|
|
|
out:
|
|
close(fd);
|
|
if (r < 0)
|
|
ec.pc.fail();
|
|
else
|
|
ec.pc.finish();
|
|
return r;
|
|
}
|
|
|
|
static const char *imgname_from_path(const char *path)
|
|
{
|
|
const char *imgname;
|
|
|
|
imgname = strrchr(path, '/');
|
|
if (imgname)
|
|
imgname++;
|
|
else
|
|
imgname = path;
|
|
|
|
return imgname;
|
|
}
|
|
|
|
static void update_snap_name(char *imgname, char **snap)
|
|
{
|
|
char *s;
|
|
|
|
s = strrchr(imgname, '@');
|
|
if (!s)
|
|
return;
|
|
|
|
*s = '\0';
|
|
|
|
if (!snap)
|
|
return;
|
|
|
|
s++;
|
|
if (*s)
|
|
*snap = s;
|
|
}
|
|
|
|
static void set_pool_image_name(const char *orig_pool, const char *orig_img,
|
|
char **new_pool, char **new_img, char **snap)
|
|
{
|
|
const char *sep;
|
|
|
|
if (!orig_img)
|
|
return;
|
|
|
|
sep = strchr(orig_img, '/');
|
|
if (!sep) {
|
|
*new_img = strdup(orig_img);
|
|
goto done_img;
|
|
}
|
|
|
|
*new_pool = strdup(orig_img);
|
|
sep = strchr(*new_pool, '/');
|
|
assert (sep);
|
|
|
|
*(char *)sep = '\0';
|
|
*new_img = strdup(sep + 1);
|
|
|
|
done_img:
|
|
update_snap_name(*new_img, snap);
|
|
}
|
|
|
|
static int do_import(librbd::RBD &rbd, librados::IoCtx& io_ctx,
|
|
const char *imgname, int *order, const char *path,
|
|
int format, uint64_t features, uint64_t size)
|
|
{
|
|
int fd, r;
|
|
struct stat stat_buf;
|
|
MyProgressContext pc("Importing image");
|
|
|
|
assert(imgname);
|
|
|
|
// default order as usual
|
|
if (*order == 0)
|
|
*order = 22;
|
|
|
|
// try to fill whole imgblklen blocks for sparsification
|
|
uint64_t image_pos = 0;
|
|
size_t imgblklen = 1 << *order;
|
|
char *p = new char[imgblklen];
|
|
size_t reqlen = imgblklen; // amount requested from read
|
|
ssize_t readlen; // amount received from one read
|
|
size_t blklen = 0; // amount accumulated from reads to fill blk
|
|
librbd::Image image;
|
|
|
|
bool from_stdin = !strcmp(path, "-");
|
|
if (from_stdin) {
|
|
fd = 0;
|
|
size = 1 << *order;
|
|
} else {
|
|
fd = open(path, O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
r = -errno;
|
|
cerr << "rbd: error opening " << path << std::endl;
|
|
return r;
|
|
}
|
|
|
|
r = fstat(fd, &stat_buf);
|
|
if (r < 0) {
|
|
r = -errno;
|
|
cerr << "rbd: stat error " << path << std::endl;
|
|
goto done;
|
|
}
|
|
if (stat_buf.st_size)
|
|
size = (uint64_t)stat_buf.st_size;
|
|
|
|
if (!size) {
|
|
int64_t bdev_size = 0;
|
|
r = get_block_device_size(fd, &bdev_size);
|
|
if (r < 0) {
|
|
cerr << "rbd: unable to get size of file/block device" << std::endl;
|
|
goto done;
|
|
}
|
|
assert(bdev_size >= 0);
|
|
size = (uint64_t) bdev_size;
|
|
}
|
|
}
|
|
r = do_create(rbd, io_ctx, imgname, size, order, format, features, 0, 0);
|
|
if (r < 0) {
|
|
cerr << "rbd: image creation failed" << std::endl;
|
|
goto done;
|
|
}
|
|
r = rbd.open(io_ctx, image, imgname);
|
|
if (r < 0) {
|
|
cerr << "rbd: failed to open image" << std::endl;
|
|
goto done;
|
|
}
|
|
|
|
// loop body handles 0 return, as we may have a block to flush
|
|
while ((readlen = ::read(fd, p + blklen, reqlen)) >= 0) {
|
|
blklen += readlen;
|
|
// if read was short, try again to fill the block before writing
|
|
if (readlen && ((size_t)readlen < reqlen)) {
|
|
reqlen -= readlen;
|
|
continue;
|
|
}
|
|
if (!from_stdin)
|
|
pc.update_progress(image_pos, size);
|
|
|
|
bufferlist bl(blklen);
|
|
bl.append(p, blklen);
|
|
// resize output image by binary expansion as we go for stdin
|
|
if (from_stdin && (image_pos + (size_t)blklen) > size) {
|
|
size *= 2;
|
|
r = image.resize(size);
|
|
if (r < 0) {
|
|
cerr << "rbd: can't resize image during import" << std::endl;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// write as much as we got; perhaps less than imgblklen
|
|
// but skip writing zeros to create sparse images
|
|
if (!bl.is_zero()) {
|
|
r = image.write(image_pos, blklen, bl);
|
|
if (r < 0) {
|
|
cerr << "rbd: error writing to image position " << image_pos
|
|
<< std::endl;
|
|
goto done;
|
|
}
|
|
}
|
|
// done with whole block, whether written or not
|
|
image_pos += blklen;
|
|
// if read had returned 0, we're at EOF and should quit
|
|
if (readlen == 0)
|
|
break;
|
|
blklen = 0;
|
|
reqlen = imgblklen;
|
|
}
|
|
if (from_stdin) {
|
|
r = image.resize(image_pos);
|
|
if (r < 0) {
|
|
cerr << "rbd: final image resize failed" << std::endl;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
r = 0;
|
|
|
|
done:
|
|
if (!from_stdin) {
|
|
if (r < 0)
|
|
pc.fail();
|
|
else
|
|
pc.finish();
|
|
close(fd);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int do_copy(librbd::Image &src, librados::IoCtx& dest_pp,
|
|
const char *destname)
|
|
{
|
|
MyProgressContext pc("Image copy");
|
|
int r = src.copy_with_progress(dest_pp, destname, pc);
|
|
if (r < 0){
|
|
pc.fail();
|
|
return r;
|
|
}
|
|
pc.finish();
|
|
return 0;
|
|
}
|
|
|
|
class RbdWatchCtx : public librados::WatchCtx {
|
|
string name;
|
|
public:
|
|
RbdWatchCtx(const char *imgname) : name(imgname) {}
|
|
virtual ~RbdWatchCtx() {}
|
|
virtual void notify(uint8_t opcode, uint64_t ver, bufferlist& bl) {
|
|
cout << name << " got notification opcode=" << (int)opcode << " ver="
|
|
<< ver << " bl.length=" << bl.length() << std::endl;
|
|
}
|
|
};
|
|
|
|
static int do_watch(librados::IoCtx& pp, const char *imgname)
|
|
{
|
|
string md_oid, dest_md_oid;
|
|
uint64_t cookie;
|
|
RbdWatchCtx ctx(imgname);
|
|
|
|
string old_header_oid = imgname;
|
|
old_header_oid += RBD_SUFFIX;
|
|
string new_header_oid = RBD_HEADER_PREFIX;
|
|
new_header_oid += imgname;
|
|
bool old_format = true;
|
|
|
|
int r = pp.stat(old_header_oid, NULL, NULL);
|
|
if (r < 0) {
|
|
r = pp.stat(new_header_oid, NULL, NULL);
|
|
if (r < 0)
|
|
return r;
|
|
old_format = false;
|
|
}
|
|
|
|
r = pp.watch(old_format ? old_header_oid : new_header_oid,
|
|
0, &cookie, &ctx);
|
|
if (r < 0) {
|
|
cerr << "rbd: watch failed" << std::endl;
|
|
return r;
|
|
}
|
|
|
|
cout << "press enter to exit..." << std::endl;
|
|
getchar();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_kernel_add(const char *poolname, const char *imgname,
|
|
const char *snapname)
|
|
{
|
|
MonMap monmap;
|
|
int r = monmap.build_initial(g_ceph_context, cerr);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
map<string, entity_addr_t>::const_iterator it = monmap.mon_addr.begin();
|
|
ostringstream oss;
|
|
for (size_t i = 0; i < monmap.mon_addr.size(); ++i, ++it) {
|
|
oss << it->second.addr;
|
|
if (i + 1 < monmap.mon_addr.size())
|
|
oss << ",";
|
|
}
|
|
|
|
const char *user = g_conf->name.get_id().c_str();
|
|
oss << " name=" << user;
|
|
|
|
char key_name[strlen(user) + strlen("client.") + 1];
|
|
snprintf(key_name, sizeof(key_name), "client.%s", user);
|
|
|
|
KeyRing keyring;
|
|
r = keyring.from_ceph_context(g_ceph_context);
|
|
if (r == -ENOENT && !(g_conf->keyfile.length() ||
|
|
g_conf->key.length()))
|
|
r = 0;
|
|
if (r < 0) {
|
|
cerr << "rbd: failed to get secret: " << cpp_strerror(r) << std::endl;
|
|
return r;
|
|
}
|
|
CryptoKey secret;
|
|
if (keyring.get_secret(g_conf->name, secret)) {
|
|
string secret_str;
|
|
secret.encode_base64(secret_str);
|
|
|
|
r = set_kernel_secret(secret_str.c_str(), key_name);
|
|
if (r >= 0) {
|
|
if (r == 0)
|
|
cerr << "rbd: warning: secret has length 0" << std::endl;
|
|
oss << ",key=" << key_name;
|
|
} else if (r == -ENODEV || r == -ENOSYS) {
|
|
/* running against older kernel; fall back to secret= in options */
|
|
oss << ",secret=" << secret_str;
|
|
} else {
|
|
cerr << "rbd: failed to add ceph secret key '" << key_name
|
|
<< "' to kernel: " << cpp_strerror(r) << std::endl;
|
|
return r;
|
|
}
|
|
} else if (is_kernel_secret(key_name)) {
|
|
oss << ",key=" << key_name;
|
|
}
|
|
|
|
oss << " " << poolname << " " << imgname;
|
|
|
|
if (snapname) {
|
|
oss << " " << snapname;
|
|
}
|
|
|
|
// modprobe the rbd module if /sys/bus/rbd doesn't exist
|
|
struct stat sb;
|
|
if ((stat("/sys/bus/rbd", &sb) < 0) || (!S_ISDIR(sb.st_mode))) {
|
|
r = system("/sbin/modprobe rbd");
|
|
if (r) {
|
|
if (r < 0)
|
|
cerr << "rbd: error executing modprobe as shell command!" << std::endl;
|
|
else
|
|
cerr << "rbd: modprobe rbd failed! (" << r << ")" <<std::endl;
|
|
return r;
|
|
}
|
|
}
|
|
|
|
// write to /sys/bus/rbd/add
|
|
int fd = open("/sys/bus/rbd/add", O_WRONLY);
|
|
if (fd < 0) {
|
|
r = -errno;
|
|
if (r == -ENOENT) {
|
|
cerr << "rbd: /sys/bus/rbd/add does not exist!" << std::endl
|
|
<< "Did you run 'modprobe rbd' or is your rbd module too old?"
|
|
<< std::endl;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
string add = oss.str();
|
|
r = safe_write(fd, add.c_str(), add.size());
|
|
close(fd);
|
|
|
|
// let udevadm do its job before we return
|
|
if (udevadm_settle) {
|
|
r = system("/sbin/udevadm settle");
|
|
if (r) {
|
|
if (r < 0)
|
|
cerr << "rbd: error executing udevadm as shell command!" << std::endl;
|
|
else
|
|
cerr << "rbd: '/sbin/udevadm settle' failed! (" << r << ")" <<std::endl;
|
|
return r;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int read_file(const char *filename, char *buf, size_t bufsize)
|
|
{
|
|
int fd = open(filename, O_RDONLY);
|
|
if (fd < 0)
|
|
return -errno;
|
|
|
|
int r = safe_read(fd, buf, bufsize);
|
|
if (r < 0) {
|
|
cerr << "rbd: could not read " << filename << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
close(fd);
|
|
return r;
|
|
}
|
|
|
|
char *end = buf;
|
|
while (end < buf + bufsize && *end && *end != '\n') {
|
|
end++;
|
|
}
|
|
*end = '\0';
|
|
|
|
close(fd);
|
|
return r;
|
|
}
|
|
|
|
void do_closedir(DIR *dp)
|
|
{
|
|
if (dp)
|
|
closedir(dp);
|
|
}
|
|
|
|
static int do_kernel_showmapped(Formatter *f)
|
|
{
|
|
int r;
|
|
bool have_output = false;
|
|
TextTable tbl;
|
|
|
|
const char *devices_path = "/sys/bus/rbd/devices";
|
|
std::tr1::shared_ptr<DIR> device_dir(opendir(devices_path), do_closedir);
|
|
if (!device_dir.get()) {
|
|
r = -errno;
|
|
cerr << "rbd: could not open " << devices_path << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
return r;
|
|
}
|
|
|
|
struct dirent *dent;
|
|
dent = readdir(device_dir.get());
|
|
if (!dent) {
|
|
r = -errno;
|
|
cerr << "rbd: error reading " << devices_path << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
return r;
|
|
}
|
|
|
|
if (f) {
|
|
f->open_object_section("devices");
|
|
} else {
|
|
tbl.define_column("id", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("pool", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("image", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("snap", TextTable::LEFT, TextTable::LEFT);
|
|
tbl.define_column("device", TextTable::LEFT, TextTable::LEFT);
|
|
}
|
|
|
|
do {
|
|
if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
|
|
continue;
|
|
|
|
char fn[PATH_MAX];
|
|
|
|
char dev[PATH_MAX];
|
|
snprintf(dev, sizeof(dev), "/dev/rbd%s", dent->d_name);
|
|
|
|
char name[RBD_MAX_IMAGE_NAME_SIZE];
|
|
snprintf(fn, sizeof(fn), "%s/%s/name", devices_path, dent->d_name);
|
|
r = read_file(fn, name, sizeof(name));
|
|
if (r < 0) {
|
|
cerr << "rbd: could not read name from " << fn << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
continue;
|
|
}
|
|
|
|
char pool[4096];
|
|
snprintf(fn, sizeof(fn), "%s/%s/pool", devices_path, dent->d_name);
|
|
r = read_file(fn, pool, sizeof(pool));
|
|
if (r < 0) {
|
|
cerr << "rbd: could not read name from " << fn << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
continue;
|
|
}
|
|
|
|
char snap[4096];
|
|
snprintf(fn, sizeof(fn), "%s/%s/current_snap", devices_path, dent->d_name);
|
|
r = read_file(fn, snap, sizeof(snap));
|
|
if (r < 0) {
|
|
cerr << "rbd: could not read name from " << fn << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
continue;
|
|
}
|
|
|
|
if (f) {
|
|
f->open_object_section(dent->d_name);
|
|
f->dump_string("pool", pool);
|
|
f->dump_string("name", name);
|
|
f->dump_string("snap", snap);
|
|
f->dump_string("device", dev);
|
|
f->close_section();
|
|
} else {
|
|
tbl << dent->d_name << pool << name << snap << dev << TextTable::endrow;
|
|
}
|
|
have_output = true;
|
|
|
|
} while ((dent = readdir(device_dir.get())));
|
|
|
|
if (f) {
|
|
f->close_section();
|
|
f->flush(cout);
|
|
} else {
|
|
if (have_output)
|
|
cout << tbl;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_rbd_seq(int major_num, string &seq)
|
|
{
|
|
int r;
|
|
const char *devices_path = "/sys/bus/rbd/devices";
|
|
DIR *device_dir = opendir(devices_path);
|
|
if (!device_dir) {
|
|
r = -errno;
|
|
cerr << "rbd: could not open " << devices_path << ": " << cpp_strerror(-r)
|
|
<< std::endl;
|
|
return r;
|
|
}
|
|
|
|
struct dirent *dent;
|
|
dent = readdir(device_dir);
|
|
if (!dent) {
|
|
r = -errno;
|
|
cerr << "Error reading " << devices_path << ": " << cpp_strerror(-r)
|
|
<< std::endl;
|
|
closedir(device_dir);
|
|
return r;
|
|
}
|
|
|
|
char major[32];
|
|
do {
|
|
if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
|
|
continue;
|
|
|
|
char fn[strlen(devices_path) + strlen(dent->d_name) + strlen("//major") + 1];
|
|
|
|
snprintf(fn, sizeof(fn), "%s/%s/major", devices_path, dent->d_name);
|
|
r = read_file(fn, major, sizeof(major));
|
|
if (r < 0) {
|
|
cerr << "rbd: could not read major number from " << fn << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
continue;
|
|
}
|
|
|
|
int cur_major = atoi(major);
|
|
if (cur_major == major_num) {
|
|
seq = string(dent->d_name);
|
|
closedir(device_dir);
|
|
return 0;
|
|
}
|
|
|
|
} while ((dent = readdir(device_dir)));
|
|
|
|
closedir(device_dir);
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int do_kernel_rm(const char *dev)
|
|
{
|
|
struct stat dev_stat;
|
|
int r = stat(dev, &dev_stat);
|
|
if (!S_ISBLK(dev_stat.st_mode)) {
|
|
cerr << "rbd: " << dev << " is not a block device" << std::endl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
int major = major(dev_stat.st_rdev);
|
|
string seq_num;
|
|
r = get_rbd_seq(major, seq_num);
|
|
if (r == -ENOENT) {
|
|
cerr << "rbd: " << dev << " is not an rbd device" << std::endl;
|
|
return -EINVAL;
|
|
}
|
|
if (r < 0)
|
|
return r;
|
|
|
|
// let udevadm do its job *before* we try to unmap
|
|
if (udevadm_settle) {
|
|
r = system("/sbin/udevadm settle");
|
|
if (r) {
|
|
if (r < 0)
|
|
cerr << "rbd: error executing udevadm as shell command!" << std::endl;
|
|
else
|
|
cerr << "rbd: '/sbin/udevadm settle' failed! (" << r << ")" <<std::endl;
|
|
// ignore the error, though.
|
|
}
|
|
}
|
|
|
|
int fd = open("/sys/bus/rbd/remove", O_WRONLY);
|
|
if (fd < 0) {
|
|
return -errno;
|
|
}
|
|
|
|
r = safe_write(fd, seq_num.c_str(), seq_num.size());
|
|
if (r < 0) {
|
|
cerr << "rbd: failed to remove rbd device" << ": " << cpp_strerror(-r)
|
|
<< std::endl;
|
|
close(fd);
|
|
return r;
|
|
}
|
|
|
|
r = close(fd);
|
|
|
|
// let udevadm finish, if present
|
|
if (udevadm_settle){
|
|
r = system("/sbin/udevadm settle");
|
|
if (r) {
|
|
if (r < 0)
|
|
cerr << "rbd: error executing udevadm as shell command!" << std::endl;
|
|
else
|
|
cerr << "rbd: '/sbin/udevadm settle' failed! (" << r << ")" <<std::endl;
|
|
return r;
|
|
}
|
|
}
|
|
|
|
if (r < 0)
|
|
r = -errno;
|
|
return r;
|
|
}
|
|
|
|
enum {
|
|
OPT_NO_CMD = 0,
|
|
OPT_LIST,
|
|
OPT_INFO,
|
|
OPT_CREATE,
|
|
OPT_CLONE,
|
|
OPT_FLATTEN,
|
|
OPT_CHILDREN,
|
|
OPT_RESIZE,
|
|
OPT_RM,
|
|
OPT_EXPORT,
|
|
OPT_IMPORT,
|
|
OPT_COPY,
|
|
OPT_RENAME,
|
|
OPT_SNAP_CREATE,
|
|
OPT_SNAP_ROLLBACK,
|
|
OPT_SNAP_REMOVE,
|
|
OPT_SNAP_LIST,
|
|
OPT_SNAP_PURGE,
|
|
OPT_SNAP_PROTECT,
|
|
OPT_SNAP_UNPROTECT,
|
|
OPT_WATCH,
|
|
OPT_MAP,
|
|
OPT_UNMAP,
|
|
OPT_SHOWMAPPED,
|
|
OPT_LOCK_LIST,
|
|
OPT_LOCK_ADD,
|
|
OPT_LOCK_REMOVE,
|
|
OPT_BENCH_WRITE,
|
|
};
|
|
|
|
static int get_cmd(const char *cmd, bool snapcmd, bool lockcmd)
|
|
{
|
|
if (!snapcmd && !lockcmd) {
|
|
if (strcmp(cmd, "ls") == 0 ||
|
|
strcmp(cmd, "list") == 0)
|
|
return OPT_LIST;
|
|
if (strcmp(cmd, "info") == 0)
|
|
return OPT_INFO;
|
|
if (strcmp(cmd, "create") == 0)
|
|
return OPT_CREATE;
|
|
if (strcmp(cmd, "clone") == 0)
|
|
return OPT_CLONE;
|
|
if (strcmp(cmd, "flatten") == 0)
|
|
return OPT_FLATTEN;
|
|
if (strcmp(cmd, "children") == 0)
|
|
return OPT_CHILDREN;
|
|
if (strcmp(cmd, "resize") == 0)
|
|
return OPT_RESIZE;
|
|
if (strcmp(cmd, "rm") == 0)
|
|
return OPT_RM;
|
|
if (strcmp(cmd, "export") == 0)
|
|
return OPT_EXPORT;
|
|
if (strcmp(cmd, "import") == 0)
|
|
return OPT_IMPORT;
|
|
if (strcmp(cmd, "copy") == 0 ||
|
|
strcmp(cmd, "cp") == 0)
|
|
return OPT_COPY;
|
|
if (strcmp(cmd, "rename") == 0 ||
|
|
strcmp(cmd, "mv") == 0)
|
|
return OPT_RENAME;
|
|
if (strcmp(cmd, "watch") == 0)
|
|
return OPT_WATCH;
|
|
if (strcmp(cmd, "map") == 0)
|
|
return OPT_MAP;
|
|
if (strcmp(cmd, "showmapped") == 0)
|
|
return OPT_SHOWMAPPED;
|
|
if (strcmp(cmd, "unmap") == 0)
|
|
return OPT_UNMAP;
|
|
if (strcmp(cmd, "bench-write") == 0)
|
|
return OPT_BENCH_WRITE;
|
|
} else if (snapcmd) {
|
|
if (strcmp(cmd, "create") == 0 ||
|
|
strcmp(cmd, "add") == 0)
|
|
return OPT_SNAP_CREATE;
|
|
if (strcmp(cmd, "rollback") == 0 ||
|
|
strcmp(cmd, "revert") == 0)
|
|
return OPT_SNAP_ROLLBACK;
|
|
if (strcmp(cmd, "remove") == 0 ||
|
|
strcmp(cmd, "rm") == 0)
|
|
return OPT_SNAP_REMOVE;
|
|
if (strcmp(cmd, "ls") == 0 ||
|
|
strcmp(cmd, "list") == 0)
|
|
return OPT_SNAP_LIST;
|
|
if (strcmp(cmd, "purge") == 0)
|
|
return OPT_SNAP_PURGE;
|
|
if (strcmp(cmd, "protect") == 0)
|
|
return OPT_SNAP_PROTECT;
|
|
if (strcmp(cmd, "unprotect") == 0)
|
|
return OPT_SNAP_UNPROTECT;
|
|
} else {
|
|
if (strcmp(cmd, "ls") == 0 ||
|
|
strcmp(cmd, "list") == 0)
|
|
return OPT_LOCK_LIST;
|
|
if (strcmp(cmd, "add") == 0)
|
|
return OPT_LOCK_ADD;
|
|
if (strcmp(cmd, "remove") == 0 ||
|
|
strcmp(cmd, "rm") == 0)
|
|
return OPT_LOCK_REMOVE;
|
|
}
|
|
|
|
return OPT_NO_CMD;
|
|
}
|
|
|
|
/*
|
|
* Called 1-N times depending on how many args the command needs. If
|
|
* the positional varN is already set, set the next one; this handles
|
|
* both --args above and unadorned args below. Calling with all args
|
|
* filled is an error.
|
|
*/
|
|
static bool set_conf_param(const char *param, const char **var1,
|
|
const char **var2, const char **var3)
|
|
{
|
|
if (!*var1)
|
|
*var1 = param;
|
|
else if (var2 && !*var2)
|
|
*var2 = param;
|
|
else if (var3 && !*var3)
|
|
*var3 = param;
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool size_set;
|
|
|
|
int main(int argc, const char **argv)
|
|
{
|
|
librados::Rados rados;
|
|
librbd::RBD rbd;
|
|
librados::IoCtx io_ctx, dest_io_ctx;
|
|
librbd::Image image;
|
|
|
|
vector<const char*> args;
|
|
|
|
argv_to_vec(argc, argv, args);
|
|
env_to_vec(args);
|
|
|
|
int opt_cmd = OPT_NO_CMD;
|
|
global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0);
|
|
|
|
const char *poolname = NULL;
|
|
uint64_t size = 0; // in bytes
|
|
int order = 0;
|
|
bool format_specified = false, output_format_specified = false;
|
|
int format = 1;
|
|
uint64_t features = RBD_FEATURE_LAYERING;
|
|
const char *imgname = NULL, *snapname = NULL, *destname = NULL,
|
|
*dest_poolname = NULL, *dest_snapname = NULL, *path = NULL,
|
|
*devpath = NULL, *lock_cookie = NULL, *lock_client = NULL,
|
|
*lock_tag = NULL, *output_format = "plain";
|
|
bool lflag = false;
|
|
int pretty_format = 0;
|
|
long long stripe_unit = 0, stripe_count = 0;
|
|
long long bench_io_size = 4096, bench_io_threads = 16, bench_bytes = 1 << 30;
|
|
|
|
std::string val;
|
|
std::ostringstream err;
|
|
long long sizell = 0;
|
|
std::vector<const char*>::iterator i;
|
|
for (i = args.begin(); i != args.end(); ) {
|
|
if (ceph_argparse_double_dash(args, i)) {
|
|
break;
|
|
} else if (ceph_argparse_witharg(args, i, &val, "--secret", (char*)NULL)) {
|
|
int r = g_conf->set_val("keyfile", val.c_str());
|
|
assert(r == 0);
|
|
} else if (ceph_argparse_flag(args, i, "-h", "--help", (char*)NULL)) {
|
|
usage();
|
|
return 0;
|
|
} else if (ceph_argparse_flag(args, i, "--new-format", (char*)NULL)) {
|
|
format = 2;
|
|
format_specified = true;
|
|
} else if (ceph_argparse_withint(args, i, &format, &err, "--image-format",
|
|
(char*)NULL)) {
|
|
if (!err.str().empty()) {
|
|
cerr << "rbd: " << err.str() << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
format_specified = true;
|
|
} else if (ceph_argparse_witharg(args, i, &val, "-p", "--pool", (char*)NULL)) {
|
|
poolname = strdup(val.c_str());
|
|
} else if (ceph_argparse_witharg(args, i, &val, "--dest-pool", (char*)NULL)) {
|
|
dest_poolname = strdup(val.c_str());
|
|
} else if (ceph_argparse_witharg(args, i, &val, "--snap", (char*)NULL)) {
|
|
snapname = strdup(val.c_str());
|
|
} else if (ceph_argparse_witharg(args, i, &val, "-i", "--image", (char*)NULL)) {
|
|
imgname = strdup(val.c_str());
|
|
} else if (ceph_argparse_withlonglong(args, i, &sizell, &err, "-s", "--size", (char*)NULL)) {
|
|
if (!err.str().empty()) {
|
|
cerr << "rbd: " << err.str() << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (sizell < 0) {
|
|
cerr << "rbd: size must be >= 0" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
size = sizell << 20; // bytes to MB
|
|
size_set = true;
|
|
} else if (ceph_argparse_flag(args, i, "-l", "--long", (char*)NULL)) {
|
|
lflag = true;
|
|
} else if (ceph_argparse_withlonglong(args, i, &stripe_unit, &err, "--stripe-unit", (char*)NULL)) {
|
|
} else if (ceph_argparse_withlonglong(args, i, &stripe_count, &err, "--stripe-count", (char*)NULL)) {
|
|
} else if (ceph_argparse_withint(args, i, &order, &err, "--order", (char*)NULL)) {
|
|
if (!err.str().empty()) {
|
|
cerr << "rbd: " << err.str() << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
} else if (ceph_argparse_withlonglong(args, i, &bench_io_size, &err, "--io-size", (char*)NULL)) {
|
|
} else if (ceph_argparse_withlonglong(args, i, &bench_io_threads, &err, "--io-threads", (char*)NULL)) {
|
|
} else if (ceph_argparse_withlonglong(args, i, &bench_bytes, &err, "--io-total", (char*)NULL)) {
|
|
} else if (ceph_argparse_withlonglong(args, i, &stripe_count, &err, "--stripe-count", (char*)NULL)) {
|
|
} else if (ceph_argparse_witharg(args, i, &val, "--path", (char*)NULL)) {
|
|
path = strdup(val.c_str());
|
|
} else if (ceph_argparse_witharg(args, i, &val, "--dest", (char*)NULL)) {
|
|
destname = strdup(val.c_str());
|
|
} else if (ceph_argparse_witharg(args, i, &val, "--parent", (char *)NULL)) {
|
|
imgname = strdup(val.c_str());
|
|
} else if (ceph_argparse_witharg(args, i, &val, "--shared", (char *)NULL)) {
|
|
lock_tag = strdup(val.c_str());
|
|
} else if (ceph_argparse_flag(args, i, "--no-settle", (char *)NULL)) {
|
|
udevadm_settle = false;
|
|
} else if (ceph_argparse_witharg(args, i, &val, "--format", (char *) NULL)) {
|
|
std::string err;
|
|
long long ret = strict_strtoll(val.c_str(), 10, &err);
|
|
if (err.empty()) {
|
|
format = ret;
|
|
format_specified = true;
|
|
cerr << "rbd: using --format for specifying the rbd image format is"
|
|
<< " deprecated, use --image-format instead"
|
|
<< std::endl;
|
|
} else {
|
|
output_format = strdup(val.c_str());
|
|
output_format_specified = true;
|
|
}
|
|
} else if (ceph_argparse_binary_flag(args, i, &pretty_format, NULL, "--pretty-format", (char*)NULL)) {
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
common_init_finish(g_ceph_context);
|
|
|
|
i = args.begin();
|
|
if (i == args.end()) {
|
|
cerr << "rbd: you must specify a command." << std::endl;
|
|
return EXIT_FAILURE;
|
|
} else if (strcmp(*i, "snap") == 0) {
|
|
i = args.erase(i);
|
|
if (i == args.end()) {
|
|
cerr << "rbd: which snap command do you want?" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
opt_cmd = get_cmd(*i, true, false);
|
|
} else if (strcmp(*i, "lock") == 0) {
|
|
i = args.erase(i);
|
|
if (i == args.end()) {
|
|
cerr << "rbd: which lock command do you want?" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
opt_cmd = get_cmd(*i, false, true);
|
|
} else {
|
|
opt_cmd = get_cmd(*i, false, false);
|
|
}
|
|
if (opt_cmd == OPT_NO_CMD) {
|
|
cerr << "rbd: error parsing command '" << *i << "'" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// loop across all remaining arguments; by command, accumulate any
|
|
// that are still missing into the appropriate variables, one at a
|
|
// time (i.e. SET_CONF_PARAM will be called N times for N remaining
|
|
// arguments).
|
|
|
|
#define SET_CONF_PARAM(v, p1, p2, p3) \
|
|
if (!set_conf_param(v, p1, p2, p3)) { \
|
|
cerr << "rbd: extraneous parameter " << v << std::endl; \
|
|
return EXIT_FAILURE; \
|
|
}
|
|
|
|
for (i = args.erase(i); i != args.end(); ++i) {
|
|
const char *v = *i;
|
|
switch (opt_cmd) {
|
|
case OPT_LIST:
|
|
SET_CONF_PARAM(v, &poolname, NULL, NULL);
|
|
break;
|
|
case OPT_INFO:
|
|
case OPT_CREATE:
|
|
case OPT_FLATTEN:
|
|
case OPT_RESIZE:
|
|
case OPT_RM:
|
|
case OPT_SNAP_CREATE:
|
|
case OPT_SNAP_ROLLBACK:
|
|
case OPT_SNAP_REMOVE:
|
|
case OPT_SNAP_LIST:
|
|
case OPT_SNAP_PURGE:
|
|
case OPT_SNAP_PROTECT:
|
|
case OPT_SNAP_UNPROTECT:
|
|
case OPT_WATCH:
|
|
case OPT_MAP:
|
|
case OPT_BENCH_WRITE:
|
|
case OPT_LOCK_LIST:
|
|
SET_CONF_PARAM(v, &imgname, NULL, NULL);
|
|
break;
|
|
case OPT_UNMAP:
|
|
SET_CONF_PARAM(v, &devpath, NULL, NULL);
|
|
break;
|
|
case OPT_EXPORT:
|
|
SET_CONF_PARAM(v, &imgname, &path, NULL);
|
|
break;
|
|
case OPT_IMPORT:
|
|
SET_CONF_PARAM(v, &path, &destname, NULL);
|
|
break;
|
|
case OPT_COPY:
|
|
case OPT_RENAME:
|
|
case OPT_CLONE:
|
|
SET_CONF_PARAM(v, &imgname, &destname, NULL);
|
|
break;
|
|
case OPT_SHOWMAPPED:
|
|
cerr << "rbd: showmapped takes no parameters" << std::endl;
|
|
return EXIT_FAILURE;
|
|
case OPT_CHILDREN:
|
|
SET_CONF_PARAM(v, &imgname, NULL, NULL);
|
|
break;
|
|
case OPT_LOCK_ADD:
|
|
SET_CONF_PARAM(v, &imgname, &lock_cookie, NULL);
|
|
break;
|
|
case OPT_LOCK_REMOVE:
|
|
SET_CONF_PARAM(v, &imgname, &lock_client, &lock_cookie);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (format_specified && opt_cmd != OPT_IMPORT && opt_cmd != OPT_CREATE) {
|
|
cerr << "rbd: image format can only be set when "
|
|
<< "creating or importing an image" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (pretty_format && !strcmp(output_format, "plain")) {
|
|
cerr << "rbd: --pretty-format only works when --format is json or xml"
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
boost::scoped_ptr<Formatter> formatter;
|
|
if (output_format_specified && opt_cmd != OPT_SHOWMAPPED &&
|
|
opt_cmd != OPT_INFO && opt_cmd != OPT_LIST && opt_cmd != OPT_SNAP_LIST &&
|
|
opt_cmd != OPT_LOCK_LIST && opt_cmd != OPT_CHILDREN) {
|
|
cerr << "rbd: command doesn't use output formatting"
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
} else if (get_outfmt(output_format, pretty_format, &formatter) < 0) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (format_specified) {
|
|
if (format < 1 || format > 2) {
|
|
cerr << "rbd: image format must be 1 or 2" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (opt_cmd == OPT_EXPORT && !imgname) {
|
|
cerr << "rbd: image name was not specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (opt_cmd == OPT_IMPORT && !path) {
|
|
cerr << "rbd: path was not specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (opt_cmd == OPT_IMPORT && !destname) {
|
|
destname = imgname;
|
|
if (!destname)
|
|
destname = imgname_from_path(path);
|
|
imgname = NULL;
|
|
}
|
|
|
|
if (opt_cmd != OPT_LOCK_ADD && lock_tag) {
|
|
cerr << "rbd: only the lock add command uses the --shared option"
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if ((opt_cmd == OPT_LOCK_ADD || opt_cmd == OPT_LOCK_REMOVE) &&
|
|
!lock_cookie) {
|
|
cerr << "rbd: lock id was not specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (opt_cmd != OPT_LIST && opt_cmd != OPT_IMPORT && opt_cmd != OPT_UNMAP &&
|
|
opt_cmd != OPT_SHOWMAPPED && !imgname) {
|
|
cerr << "rbd: image name was not specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (opt_cmd == OPT_UNMAP && !devpath) {
|
|
cerr << "rbd: device path was not specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// do this unconditionally so we can parse pool/image@snapshot into
|
|
// the relevant parts
|
|
set_pool_image_name(poolname, imgname, (char **)&poolname,
|
|
(char **)&imgname, (char **)&snapname);
|
|
if (snapname && opt_cmd != OPT_SNAP_CREATE && opt_cmd != OPT_SNAP_ROLLBACK &&
|
|
opt_cmd != OPT_SNAP_REMOVE && opt_cmd != OPT_INFO &&
|
|
opt_cmd != OPT_EXPORT && opt_cmd != OPT_COPY &&
|
|
opt_cmd != OPT_MAP && opt_cmd != OPT_CLONE &&
|
|
opt_cmd != OPT_SNAP_PROTECT && opt_cmd != OPT_SNAP_UNPROTECT &&
|
|
opt_cmd != OPT_CHILDREN) {
|
|
cerr << "rbd: snapname specified for a command that doesn't use it"
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if ((opt_cmd == OPT_SNAP_CREATE || opt_cmd == OPT_SNAP_ROLLBACK ||
|
|
opt_cmd == OPT_SNAP_REMOVE || opt_cmd == OPT_CLONE ||
|
|
opt_cmd == OPT_SNAP_PROTECT || opt_cmd == OPT_SNAP_UNPROTECT ||
|
|
opt_cmd == OPT_CHILDREN) && !snapname) {
|
|
cerr << "rbd: snap name was not specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
set_pool_image_name(dest_poolname, destname, (char **)&dest_poolname,
|
|
(char **)&destname, (char **)&dest_snapname);
|
|
|
|
if (opt_cmd == OPT_IMPORT) {
|
|
if (poolname && dest_poolname) {
|
|
cerr << "rbd: source and destination pool both specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (imgname && destname) {
|
|
cerr << "rbd: source and destination image both specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (poolname)
|
|
dest_poolname = poolname;
|
|
}
|
|
|
|
if (!poolname)
|
|
poolname = "rbd";
|
|
|
|
if (!dest_poolname)
|
|
dest_poolname = "rbd";
|
|
|
|
if (opt_cmd == OPT_EXPORT && !path)
|
|
path = imgname;
|
|
|
|
if ((opt_cmd == OPT_COPY || opt_cmd == OPT_CLONE || opt_cmd == OPT_RENAME) &&
|
|
!destname ) {
|
|
cerr << "rbd: destination image name was not specified" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if ((opt_cmd == OPT_CLONE) && dest_snapname) {
|
|
cerr << "rbd: cannot clone to a snapshot" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if ((opt_cmd == OPT_CLONE) && size) {
|
|
cerr << "rbd: clone must begin at size of parent" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if ((opt_cmd == OPT_RENAME) && (strcmp(poolname, dest_poolname) != 0)) {
|
|
cerr << "rbd: mv/rename across pools not supported" << std::endl;
|
|
cerr << "source pool: " << poolname << " dest pool: " << dest_poolname
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
bool talk_to_cluster = (opt_cmd != OPT_MAP &&
|
|
opt_cmd != OPT_UNMAP &&
|
|
opt_cmd != OPT_SHOWMAPPED);
|
|
if (talk_to_cluster && rados.init_with_context(g_ceph_context) < 0) {
|
|
cerr << "rbd: couldn't initialize rados!" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (talk_to_cluster && rados.connect() < 0) {
|
|
cerr << "rbd: couldn't connect to the cluster!" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
int r;
|
|
if (talk_to_cluster && opt_cmd != OPT_IMPORT) {
|
|
r = rados.ioctx_create(poolname, io_ctx);
|
|
if (r < 0) {
|
|
cerr << "rbd: error opening pool " << poolname << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (imgname && talk_to_cluster &&
|
|
(opt_cmd == OPT_RESIZE || opt_cmd == OPT_SNAP_CREATE ||
|
|
opt_cmd == OPT_SNAP_ROLLBACK || opt_cmd == OPT_SNAP_REMOVE ||
|
|
opt_cmd == OPT_SNAP_PURGE || opt_cmd == OPT_SNAP_PROTECT ||
|
|
opt_cmd == OPT_SNAP_UNPROTECT || opt_cmd == OPT_WATCH ||
|
|
opt_cmd == OPT_FLATTEN || opt_cmd == OPT_LOCK_ADD ||
|
|
opt_cmd == OPT_LOCK_REMOVE || opt_cmd == OPT_BENCH_WRITE ||
|
|
opt_cmd == OPT_INFO || opt_cmd == OPT_SNAP_LIST ||
|
|
opt_cmd == OPT_EXPORT || opt_cmd == OPT_COPY ||
|
|
opt_cmd == OPT_CHILDREN || opt_cmd == OPT_LOCK_LIST)) {
|
|
|
|
if (opt_cmd == OPT_INFO || opt_cmd == OPT_SNAP_LIST ||
|
|
opt_cmd == OPT_EXPORT || opt_cmd == OPT_COPY ||
|
|
opt_cmd == OPT_CHILDREN || opt_cmd == OPT_LOCK_LIST) {
|
|
r = rbd.open_read_only(io_ctx, image, imgname, NULL);
|
|
} else {
|
|
r = rbd.open(io_ctx, image, imgname);
|
|
}
|
|
if (r < 0) {
|
|
cerr << "rbd: error opening image " << imgname << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (snapname && talk_to_cluster &&
|
|
(opt_cmd == OPT_INFO || opt_cmd == OPT_EXPORT || opt_cmd == OPT_COPY ||
|
|
opt_cmd == OPT_CHILDREN)) {
|
|
r = image.snap_set(snapname);
|
|
if (r < 0) {
|
|
cerr << "rbd: error setting snapshot context: " << cpp_strerror(-r)
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (opt_cmd == OPT_COPY || opt_cmd == OPT_IMPORT || opt_cmd == OPT_CLONE) {
|
|
r = rados.ioctx_create(dest_poolname, dest_io_ctx);
|
|
if (r < 0) {
|
|
cerr << "rbd: error opening pool " << dest_poolname << ": "
|
|
<< cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (opt_cmd == OPT_CREATE || opt_cmd == OPT_RESIZE) {
|
|
if (!size_set) {
|
|
cerr << "rbd: must specify --size <MB>" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
switch (opt_cmd) {
|
|
case OPT_LIST:
|
|
r = do_list(rbd, io_ctx, lflag, formatter.get());
|
|
if (r < 0) {
|
|
switch (r) {
|
|
case -ENOENT:
|
|
cerr << "rbd: pool " << poolname << " doesn't contain rbd images"
|
|
<< std::endl;
|
|
break;
|
|
default:
|
|
cerr << "rbd: list: " << cpp_strerror(-r) << std::endl;
|
|
}
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_CREATE:
|
|
if (order && (order < 12 || order > 25)) {
|
|
cerr << "rbd: order must be between 12 (4 KB) and 25 (32 MB)"
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if ((stripe_unit && !stripe_count) || (!stripe_unit && stripe_count)) {
|
|
cerr << "must specify both (or neither) of stripe-unit and stripe-count"
|
|
<< std::endl;
|
|
usage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_create(rbd, io_ctx, imgname, size, &order, format, features,
|
|
stripe_unit, stripe_count);
|
|
if (r < 0) {
|
|
cerr << "rbd: create error: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_CLONE:
|
|
if (order && (order < 12 || order > 25)) {
|
|
cerr << "rbd: order must be between 12 (4 KB) and 25 (32 MB)"
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
r = do_clone(rbd, io_ctx, imgname, snapname, dest_io_ctx, destname,
|
|
features, &order);
|
|
if (r < 0) {
|
|
cerr << "rbd: clone error: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_FLATTEN:
|
|
r = do_flatten(image);
|
|
if (r < 0) {
|
|
cerr << "rbd: flatten error: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_RENAME:
|
|
r = do_rename(rbd, io_ctx, imgname, destname);
|
|
if (r < 0) {
|
|
cerr << "rbd: rename error: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_INFO:
|
|
r = do_show_info(imgname, image, snapname, formatter.get());
|
|
if (r < 0) {
|
|
cerr << "rbd: info: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_RM:
|
|
r = do_delete(rbd, io_ctx, imgname);
|
|
if (r < 0) {
|
|
if (r == -ENOTEMPTY) {
|
|
cerr << "rbd: image has snapshots - these must be deleted"
|
|
<< " with 'rbd snap purge' before the image can be removed."
|
|
<< std::endl;
|
|
} else if (r == -EBUSY) {
|
|
cerr << "rbd: error: image still has watchers"
|
|
<< std::endl
|
|
<< "This means the image is still open or the client using "
|
|
<< "it crashed. Try again after closing/unmapping it or "
|
|
<< "waiting 30s for the crashed client to timeout."
|
|
<< std::endl;
|
|
} else {
|
|
cerr << "rbd: delete error: " << cpp_strerror(-r) << std::endl;
|
|
}
|
|
return -r ;
|
|
}
|
|
break;
|
|
|
|
case OPT_RESIZE:
|
|
r = do_resize(image, size);
|
|
if (r < 0) {
|
|
cerr << "rbd: resize error: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_SNAP_LIST:
|
|
if (!imgname) {
|
|
cerr << "rbd: snap list requires an image parameter" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_list_snaps(image, formatter.get());
|
|
if (r < 0) {
|
|
cerr << "rbd: failed to list snapshots: " << cpp_strerror(-r)
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_SNAP_CREATE:
|
|
if (!imgname || !snapname) {
|
|
cerr << "rbd: snap create requires image and snapname" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_add_snap(image, snapname);
|
|
if (r < 0) {
|
|
cerr << "rbd: failed to create snapshot: " << cpp_strerror(-r)
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_SNAP_ROLLBACK:
|
|
if (!imgname) {
|
|
cerr << "rbd: snap rollback requires image name" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_rollback_snap(image, snapname);
|
|
if (r < 0) {
|
|
cerr << "rbd: rollback failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_SNAP_REMOVE:
|
|
if (!imgname) {
|
|
cerr << "rbd: snap remove requires image name" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_remove_snap(image, snapname);
|
|
if (r == -EBUSY) {
|
|
cerr << "rbd: snapshot '" << snapname << "' is protected from removal."
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (r < 0) {
|
|
cerr << "rbd: failed to remove snapshot: " << cpp_strerror(-r)
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_SNAP_PURGE:
|
|
if (!imgname) {
|
|
cerr << "rbd: snap purge requires image name" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_purge_snaps(image);
|
|
if (r < 0) {
|
|
cerr << "rbd: removing snaps failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_SNAP_PROTECT:
|
|
if (!imgname) {
|
|
cerr << "rbd: snap protect requires image name" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_protect_snap(image, snapname);
|
|
if (r < 0) {
|
|
cerr << "rbd: protecting snap failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_SNAP_UNPROTECT:
|
|
if (!imgname) {
|
|
cerr << "rbd: snap unprotect requires image name" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_unprotect_snap(image, snapname);
|
|
if (r < 0) {
|
|
cerr << "rbd: unprotecting snap failed: " << cpp_strerror(-r)
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_CHILDREN:
|
|
r = do_list_children(image, formatter.get());
|
|
if (r < 0) {
|
|
cerr << "rbd: listing children failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_EXPORT:
|
|
if (!path) {
|
|
cerr << "rbd: export requires pathname" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_export(image, path);
|
|
if (r < 0) {
|
|
cerr << "rbd: export error: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_IMPORT:
|
|
if (!path) {
|
|
cerr << "rbd: import requires pathname" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
r = do_import(rbd, dest_io_ctx, destname, &order, path,
|
|
format, features, size);
|
|
if (r < 0) {
|
|
cerr << "rbd: import failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_COPY:
|
|
r = do_copy(image, dest_io_ctx, destname);
|
|
if (r < 0) {
|
|
cerr << "rbd: copy failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_WATCH:
|
|
r = do_watch(io_ctx, imgname);
|
|
if (r < 0) {
|
|
cerr << "rbd: watch failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_MAP:
|
|
r = do_kernel_add(poolname, imgname, snapname);
|
|
if (r < 0) {
|
|
cerr << "rbd: add failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_UNMAP:
|
|
r = do_kernel_rm(devpath);
|
|
if (r < 0) {
|
|
cerr << "rbd: remove failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_SHOWMAPPED:
|
|
r = do_kernel_showmapped(formatter.get());
|
|
if (r < 0) {
|
|
cerr << "rbd: showmapped failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_LOCK_LIST:
|
|
r = do_lock_list(image, formatter.get());
|
|
if (r < 0) {
|
|
cerr << "rbd: listing locks failed: " << cpp_strerror(r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_LOCK_ADD:
|
|
r = do_lock_add(image, lock_cookie, lock_tag);
|
|
if (r < 0) {
|
|
if (r == -EBUSY || r == -EEXIST) {
|
|
if (lock_tag) {
|
|
cerr << "rbd: lock is alrady held by someone else"
|
|
<< " with a different tag" << std::endl;
|
|
} else {
|
|
cerr << "rbd: lock is already held by someone else" << std::endl;
|
|
}
|
|
} else {
|
|
cerr << "rbd: taking lock failed: " << cpp_strerror(r) << std::endl;
|
|
}
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_LOCK_REMOVE:
|
|
r = do_lock_remove(image, lock_cookie, lock_client);
|
|
if (r < 0) {
|
|
cerr << "rbd: releasing lock failed: " << cpp_strerror(r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
|
|
case OPT_BENCH_WRITE:
|
|
r = do_bench_write(image, bench_io_size, bench_io_threads, bench_bytes);
|
|
if (r < 0) {
|
|
cerr << "bench-write failed: " << cpp_strerror(-r) << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|