diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e8bd655..446371ee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -154,7 +154,7 @@ target_link_libraries(vitastor-nbd # vitastor-cli add_executable(vitastor-cli - cli.cpp cli_alloc_osd.cpp cli_simple_offsets.cpp cli_status.cpp cli_df.cpp + cli.cpp cli_common.cpp cli_alloc_osd.cpp cli_simple_offsets.cpp cli_status.cpp cli_df.cpp cli_ls.cpp cli_create.cpp cli_modify.cpp cli_flatten.cpp cli_merge.cpp cli_rm_data.cpp cli_rm.cpp ) target_link_libraries(vitastor-cli diff --git a/src/cli.cpp b/src/cli.cpp index 78f646fa..b238fee3 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -2,8 +2,7 @@ // License: VNPL-1.1 (see README.md for details) /** - * CLI tool - * Currently can (a) remove inodes and (b) merge snapshot/clone layers + * CLI tool and also a library for administrative tasks */ #include @@ -17,7 +16,9 @@ static const char *exe_name = NULL; -json11::Json::object cli_tool_t::parse_args(int narg, const char *args[]) +static void help(); + +static json11::Json::object parse_args(int narg, const char *args[]) { json11::Json::object cfg; json11::Json::array cmd; @@ -79,7 +80,7 @@ json11::Json::object cli_tool_t::parse_args(int narg, const char *args[]) return cfg; } -void cli_tool_t::help() +static void help() { printf( "Vitastor command-line tool\n" @@ -164,224 +165,126 @@ void cli_tool_t::help() exit(0); } -void cli_tool_t::change_parent(inode_t cur, inode_t new_parent) -{ - auto cur_cfg_it = cli->st_cli.inode_config.find(cur); - if (cur_cfg_it == cli->st_cli.inode_config.end()) - { - fprintf(stderr, "Inode 0x%lx disappeared\n", cur); - exit(1); - } - inode_config_t new_cfg = cur_cfg_it->second; - std::string cur_name = new_cfg.name; - std::string cur_cfg_key = base64_encode(cli->st_cli.etcd_prefix+ - "/config/inode/"+std::to_string(INODE_POOL(cur))+ - "/"+std::to_string(INODE_NO_POOL(cur))); - new_cfg.parent_id = new_parent; - json11::Json::object cur_cfg_json = cli->st_cli.serialize_inode_cfg(&new_cfg); - waiting++; - cli->st_cli.etcd_txn_slow(json11::Json::object { - { "compare", json11::Json::array { - json11::Json::object { - { "target", "MOD" }, - { "key", cur_cfg_key }, - { "result", "LESS" }, - { "mod_revision", new_cfg.mod_revision+1 }, - }, - } }, - { "success", json11::Json::array { - json11::Json::object { - { "request_put", json11::Json::object { - { "key", cur_cfg_key }, - { "value", base64_encode(json11::Json(cur_cfg_json).dump()) }, - } } - }, - } }, - }, [this, new_parent, cur, cur_name](std::string err, json11::Json res) - { - if (err != "") - { - fprintf(stderr, "Error changing parent of %s: %s\n", cur_name.c_str(), err.c_str()); - exit(1); - } - if (!res["succeeded"].bool_value()) - { - fprintf(stderr, "Inode %s was modified during snapshot deletion\n", cur_name.c_str()); - exit(1); - } - if (new_parent) - { - auto new_parent_it = cli->st_cli.inode_config.find(new_parent); - std::string new_parent_name = new_parent_it != cli->st_cli.inode_config.end() - ? new_parent_it->second.name : ""; - printf( - "Parent of layer %s (inode %lu in pool %u) changed to %s (inode %lu in pool %u)\n", - cur_name.c_str(), INODE_NO_POOL(cur), INODE_POOL(cur), - new_parent_name.c_str(), INODE_NO_POOL(new_parent), INODE_POOL(new_parent) - ); - } - else - { - printf( - "Parent of layer %s (inode %lu in pool %u) detached\n", - cur_name.c_str(), INODE_NO_POOL(cur), INODE_POOL(cur) - ); - } - waiting--; - ringloop->wakeup(); - }); -} - -void cli_tool_t::etcd_txn(json11::Json txn) -{ - waiting++; - cli->st_cli.etcd_txn_slow(txn, [this](std::string err, json11::Json res) - { - waiting--; - if (err != "") - { - fprintf(stderr, "Error reading from etcd: %s\n", err.c_str()); - exit(1); - } - etcd_result = res; - ringloop->wakeup(); - }); -} - -inode_config_t* cli_tool_t::get_inode_cfg(const std::string & name) -{ - for (auto & ic: cli->st_cli.inode_config) - { - if (ic.second.name == name) - { - return &ic.second; - } - } - fprintf(stderr, "Layer %s not found\n", name.c_str()); - exit(1); -} - -void cli_tool_t::run(json11::Json cfg) +static int run(cli_tool_t *p, json11::Json cfg) { + p->parse_config(cfg); json11::Json::array cmd = cfg["command"].array_items(); + std::function action_cb; if (!cmd.size()) { fprintf(stderr, "command is missing\n"); - exit(1); + return EINVAL; } else if (cmd[0] == "status") { // Show cluster status - action_cb = start_status(cfg); + action_cb = p->start_status(cfg); } else if (cmd[0] == "df") { // Show pool space stats - action_cb = start_df(cfg); + action_cb = p->start_df(cfg); } else if (cmd[0] == "ls") { // List images - action_cb = start_ls(cfg); + action_cb = p->start_ls(cfg); } else if (cmd[0] == "create" || cmd[0] == "snap-create") { // Create image/snapshot - action_cb = start_create(cfg); + action_cb = p->start_create(cfg); } else if (cmd[0] == "modify") { // Modify image - action_cb = start_modify(cfg); + action_cb = p->start_modify(cfg); } else if (cmd[0] == "rm-data") { // Delete inode data - action_cb = start_rm(cfg); + action_cb = p->start_rm(cfg); } else if (cmd[0] == "merge-data") { // Merge layer data without affecting metadata - action_cb = start_merge(cfg); + action_cb = p->start_merge(cfg); } else if (cmd[0] == "flatten") { // Merge layer data without affecting metadata - action_cb = start_flatten(cfg); + action_cb = p->start_flatten(cfg); } else if (cmd[0] == "rm") { // Remove multiple snapshots and rebase their children - action_cb = start_snap_rm(cfg); + action_cb = p->start_snap_rm(cfg); } else if (cmd[0] == "alloc-osd") { // Allocate a new OSD number - action_cb = start_alloc_osd(cfg); + action_cb = p->start_alloc_osd(cfg); } else if (cmd[0] == "simple-offsets") { // Calculate offsets for simple & stupid OSD deployment without superblock - action_cb = simple_offsets(cfg); + action_cb = p->simple_offsets(cfg); } else { fprintf(stderr, "unknown command: %s\n", cmd[0].string_value().c_str()); - exit(1); + return EINVAL; } if (action_cb == NULL) { - return; + return 0; } - color = !cfg["no-color"].bool_value(); - json_output = cfg["json"].bool_value(); - iodepth = cfg["iodepth"].uint64_value(); - if (!iodepth) - iodepth = 32; - parallel_osds = cfg["parallel_osds"].uint64_value(); - if (!parallel_osds) - parallel_osds = 4; - log_level = cfg["log_level"].int64_value(); - progress = cfg["progress"].uint64_value() ? true : false; - list_first = cfg["wait-list"].uint64_value() ? true : false; // Create client - ringloop = new ring_loop_t(512); - epmgr = new epoll_manager_t(ringloop); - cli = new cluster_client_t(ringloop, epmgr->tfd, cfg); + p->ringloop = new ring_loop_t(512); + p->epmgr = new epoll_manager_t(p->ringloop); + p->cli = new cluster_client_t(p->ringloop, p->epmgr->tfd, cfg); // Smaller timeout by default for more interactiveness - cli->st_cli.etcd_slow_timeout = cli->st_cli.etcd_quick_timeout; - cli->on_ready([this]() + p->cli->st_cli.etcd_slow_timeout = p->cli->st_cli.etcd_quick_timeout; + ring_consumer_t consumer; + cli_result_t result; + p->cli->on_ready([&]() { // Initialize job - consumer.loop = [this]() + consumer.loop = [&]() { if (action_cb != NULL) { - bool done = action_cb(); + bool done = action_cb(result); if (done) { action_cb = NULL; } } - ringloop->submit(); + p->ringloop->submit(); }; - ringloop->register_consumer(&consumer); + p->ringloop->register_consumer(&consumer); consumer.loop(); }); // Loop until it completes while (action_cb != NULL) { - ringloop->loop(); + p->ringloop->loop(); if (action_cb != NULL) - ringloop->wait(); + p->ringloop->wait(); + } + // Print result + if (result.text != "") + { + fprintf(stderr, "%s\n", result.text.c_str()); } // Destroy the client - delete cli; - delete epmgr; - delete ringloop; - cli = NULL; - epmgr = NULL; - ringloop = NULL; + delete p->cli; + delete p->epmgr; + delete p->ringloop; + p->cli = NULL; + p->epmgr = NULL; + p->ringloop = NULL; + return result.err; } int main(int narg, const char *args[]) @@ -390,7 +293,7 @@ int main(int narg, const char *args[]) setvbuf(stderr, NULL, _IONBF, 0); exe_name = args[0]; cli_tool_t *p = new cli_tool_t(); - p->run(cli_tool_t::parse_args(narg, args)); + int r = run(p, parse_args(narg, args)); delete p; - return 0; + return r; } diff --git a/src/cli.h b/src/cli.h index f7a9b8f3..53f10c5d 100644 --- a/src/cli.h +++ b/src/cli.h @@ -19,6 +19,13 @@ class epoll_manager_t; class cluster_client_t; struct inode_config_t; +struct cli_result_t +{ + int err; + std::string text; + json11::Json data; +}; + class cli_tool_t { public: @@ -35,33 +42,28 @@ public: int waiting = 0; json11::Json etcd_result; - ring_consumer_t consumer; - std::function action_cb; - void run(json11::Json cfg); + void parse_config(json11::Json cfg); void change_parent(inode_t cur, inode_t new_parent); inode_config_t* get_inode_cfg(const std::string & name); - static json11::Json::object parse_args(int narg, const char *args[]); - static void help(); - friend struct rm_inode_t; friend struct snap_merger_t; friend struct snap_flattener_t; friend struct snap_remover_t; - std::function start_status(json11::Json cfg); - std::function start_df(json11::Json); - std::function start_ls(json11::Json); - std::function start_create(json11::Json); - std::function start_modify(json11::Json); - std::function start_rm(json11::Json); - std::function start_merge(json11::Json); - std::function start_flatten(json11::Json); - std::function start_snap_rm(json11::Json); - std::function start_alloc_osd(json11::Json cfg, uint64_t *out = NULL); - std::function simple_offsets(json11::Json cfg); + std::function start_status(json11::Json); + std::function start_df(json11::Json); + std::function start_ls(json11::Json); + std::function start_create(json11::Json); + std::function start_modify(json11::Json); + std::function start_rm(json11::Json); + std::function start_merge(json11::Json); + std::function start_flatten(json11::Json); + std::function start_snap_rm(json11::Json); + std::function start_alloc_osd(json11::Json cfg); + std::function simple_offsets(json11::Json cfg); void etcd_txn(json11::Json txn); }; diff --git a/src/cli_alloc_osd.cpp b/src/cli_alloc_osd.cpp index 676de69b..ad6bc509 100644 --- a/src/cli_alloc_osd.cpp +++ b/src/cli_alloc_osd.cpp @@ -102,20 +102,20 @@ struct alloc_osd_t } }; -std::function cli_tool_t::start_alloc_osd(json11::Json cfg, uint64_t *out) +std::function cli_tool_t::start_alloc_osd(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto alloc_osd = new alloc_osd_t(); alloc_osd->parent = this; - return [alloc_osd, out]() + return [alloc_osd](cli_result_t & result) { alloc_osd->loop(); if (alloc_osd->is_done()) { - if (out) - *out = alloc_osd->new_id; - else if (alloc_osd->new_id) - printf("%lu\n", alloc_osd->new_id); + result = (cli_result_t){ + .text = std::to_string(alloc_osd->new_id), + .data = json11::Json(alloc_osd->new_id), + }; delete alloc_osd; return true; } diff --git a/src/cli_common.cpp b/src/cli_common.cpp new file mode 100644 index 00000000..518cfb2b --- /dev/null +++ b/src/cli_common.cpp @@ -0,0 +1,118 @@ +// Copyright (c) Vitaliy Filippov, 2019+ +// License: VNPL-1.1 (see README.md for details) + +#include "base64.h" +#include "cluster_client.h" +#include "cli.h" + +void cli_tool_t::change_parent(inode_t cur, inode_t new_parent) +{ + auto cur_cfg_it = cli->st_cli.inode_config.find(cur); + if (cur_cfg_it == cli->st_cli.inode_config.end()) + { + fprintf(stderr, "Inode 0x%lx disappeared\n", cur); + exit(1); + } + inode_config_t new_cfg = cur_cfg_it->second; + std::string cur_name = new_cfg.name; + std::string cur_cfg_key = base64_encode(cli->st_cli.etcd_prefix+ + "/config/inode/"+std::to_string(INODE_POOL(cur))+ + "/"+std::to_string(INODE_NO_POOL(cur))); + new_cfg.parent_id = new_parent; + json11::Json::object cur_cfg_json = cli->st_cli.serialize_inode_cfg(&new_cfg); + waiting++; + cli->st_cli.etcd_txn_slow(json11::Json::object { + { "compare", json11::Json::array { + json11::Json::object { + { "target", "MOD" }, + { "key", cur_cfg_key }, + { "result", "LESS" }, + { "mod_revision", new_cfg.mod_revision+1 }, + }, + } }, + { "success", json11::Json::array { + json11::Json::object { + { "request_put", json11::Json::object { + { "key", cur_cfg_key }, + { "value", base64_encode(json11::Json(cur_cfg_json).dump()) }, + } } + }, + } }, + }, [this, new_parent, cur, cur_name](std::string err, json11::Json res) + { + if (err != "") + { + fprintf(stderr, "Error changing parent of %s: %s\n", cur_name.c_str(), err.c_str()); + exit(1); + } + if (!res["succeeded"].bool_value()) + { + fprintf(stderr, "Inode %s was modified during snapshot deletion\n", cur_name.c_str()); + exit(1); + } + if (new_parent) + { + auto new_parent_it = cli->st_cli.inode_config.find(new_parent); + std::string new_parent_name = new_parent_it != cli->st_cli.inode_config.end() + ? new_parent_it->second.name : ""; + printf( + "Parent of layer %s (inode %lu in pool %u) changed to %s (inode %lu in pool %u)\n", + cur_name.c_str(), INODE_NO_POOL(cur), INODE_POOL(cur), + new_parent_name.c_str(), INODE_NO_POOL(new_parent), INODE_POOL(new_parent) + ); + } + else + { + printf( + "Parent of layer %s (inode %lu in pool %u) detached\n", + cur_name.c_str(), INODE_NO_POOL(cur), INODE_POOL(cur) + ); + } + waiting--; + ringloop->wakeup(); + }); +} + +void cli_tool_t::etcd_txn(json11::Json txn) +{ + waiting++; + cli->st_cli.etcd_txn_slow(txn, [this](std::string err, json11::Json res) + { + waiting--; + if (err != "") + { + fprintf(stderr, "Error reading from etcd: %s\n", err.c_str()); + exit(1); + } + etcd_result = res; + ringloop->wakeup(); + }); +} + +inode_config_t* cli_tool_t::get_inode_cfg(const std::string & name) +{ + for (auto & ic: cli->st_cli.inode_config) + { + if (ic.second.name == name) + { + return &ic.second; + } + } + fprintf(stderr, "Layer %s not found\n", name.c_str()); + exit(1); +} + +void cli_tool_t::parse_config(json11::Json cfg) +{ + color = !cfg["no-color"].bool_value(); + json_output = cfg["json"].bool_value(); + iodepth = cfg["iodepth"].uint64_value(); + if (!iodepth) + iodepth = 32; + parallel_osds = cfg["parallel_osds"].uint64_value(); + if (!parallel_osds) + parallel_osds = 4; + log_level = cfg["log_level"].int64_value(); + progress = cfg["progress"].uint64_value() ? true : false; + list_first = cfg["wait-list"].uint64_value() ? true : false; +} diff --git a/src/cli_create.cpp b/src/cli_create.cpp index 2896c602..aedf0a88 100644 --- a/src/cli_create.cpp +++ b/src/cli_create.cpp @@ -33,6 +33,7 @@ struct image_creator_t uint64_t max_id_mod_rev = 0, cfg_mod_rev = 0, idx_mod_rev = 0; int state = 0; + cli_result_t result; bool is_done() { @@ -43,13 +44,27 @@ struct image_creator_t { if (state >= 1) goto resume_1; + if (image_name == "") + { + // FIXME: EINVAL -> specific codes for every error + result = (cli_result_t){ .err = EINVAL, .text = "Image name is missing" }; + state = 100; + return; + } + if (image_name.find('@') != std::string::npos) + { + result = (cli_result_t){ .err = EINVAL, .text = "Image name can't contain @ character" }; + state = 100; + return; + } if (new_pool_id) { auto & pools = parent->cli->st_cli.pool_config; if (pools.find(new_pool_id) == pools.end()) { - fprintf(stderr, "Pool %u does not exist\n", new_pool_id); - exit(1); + result = (cli_result_t){ .err = ENOENT, .text = "Pool "+std::to_string(new_pool_id)+" does not exist" }; + state = 100; + return; } } else if (new_pool_name != "") @@ -64,8 +79,9 @@ struct image_creator_t } if (!new_pool_id) { - fprintf(stderr, "Pool %s does not exist\n", new_pool_name.c_str()); - exit(1); + result = (cli_result_t){ .err = ENOENT, .text = "Pool "+new_pool_name+" does not exist" }; + state = 100; + return; } } else if (parent->cli->st_cli.pool_config.size() == 1) @@ -91,8 +107,9 @@ struct image_creator_t { if (ic.second.name == image_name) { - fprintf(stderr, "Image %s already exists\n", image_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EEXIST, .text = "Image "+image_name+" already exists" }; + state = 100; + return; } if (ic.second.name == new_parent) { @@ -109,18 +126,21 @@ struct image_creator_t } if (new_parent != "" && !new_parent_id) { - fprintf(stderr, "Parent image not found\n"); - exit(1); + result = (cli_result_t){ .err = ENOENT, .text = "Parent image "+new_parent+" not found" }; + state = 100; + return; } if (!new_pool_id) { - fprintf(stderr, "Pool name or ID is missing\n"); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Pool name or ID is missing" }; + state = 100; + return; } if (!size) { - fprintf(stderr, "Image size is missing\n"); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Image size is missing" }; + state = 100; + return; } do { @@ -140,14 +160,12 @@ resume_3: if (!parent->etcd_result["succeeded"].bool_value() && parent->etcd_result["responses"][0]["response_range"]["kvs"].array_items().size() > 0) { - fprintf(stderr, "Image %s already exists\n", image_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EEXIST, .text = "Image "+image_name+" already exists" }; + state = 100; + return; } } while (!parent->etcd_result["succeeded"].bool_value()); - if (parent->progress) - { - printf("Image %s created\n", image_name.c_str()); - } + result = (cli_result_t){ .err = 0, .text = "Image "+image_name+" created" }; state = 100; } @@ -163,14 +181,16 @@ resume_3: { if (ic.second.name == image_name+"@"+new_snap) { - fprintf(stderr, "Snapshot %s@%s already exists\n", image_name.c_str(), new_snap.c_str()); - exit(1); + result = (cli_result_t){ .err = EEXIST, .text = "Snapshot "+image_name+"@"+new_snap+" already exists" }; + state = 100; + return; } } if (new_parent != "") { - fprintf(stderr, "--parent can't be used with snapshots\n"); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Parent can't be specified for snapshots" }; + state = 100; + return; } do { @@ -182,8 +202,9 @@ resume_3: return; if (!old_id) { - fprintf(stderr, "Image %s does not exist\n", image_name.c_str()); - exit(1); + result = (cli_result_t){ .err = ENOENT, .text = "Image "+image_name+" does not exist" }; + state = 100; + return; } if (!new_pool_id) { @@ -198,14 +219,12 @@ resume_4: if (!parent->etcd_result["succeeded"].bool_value() && parent->etcd_result["responses"][0]["response_range"]["kvs"].array_items().size() > 0) { - fprintf(stderr, "Snapshot %s@%s already exists\n", image_name.c_str(), new_snap.c_str()); - exit(1); + result = (cli_result_t){ .err = EEXIST, .text = "Snapshot "+image_name+"@"+new_snap+" already exists" }; + state = 100; + return; } } while (!parent->etcd_result["succeeded"].bool_value()); - if (parent->progress) - { - printf("Snapshot %s@%s created\n", image_name.c_str(), new_snap.c_str()); - } + result = (cli_result_t){ .err = 0, .text = "Snapshot "+image_name+"@"+new_snap+" created" }; state = 100; } @@ -288,8 +307,9 @@ resume_2: idx_mod_rev = kv.mod_revision; if (!old_id || !old_pool_id || old_pool_id >= POOL_ID_MAX) { - fprintf(stderr, "Invalid pool or inode ID in etcd key %s\n", kv.key.c_str()); - exit(1); + result = (cli_result_t){ .err = ENOENT, .text = "Invalid pool or inode ID in etcd key "+kv.key }; + state = 100; + return; } } parent->etcd_txn(json11::Json::object { @@ -469,13 +489,12 @@ uint64_t parse_size(std::string size_str) uint64_t size = json11::Json(size_str).uint64_value() * mul; if (size == 0 && size_str != "0" && (size_str != "" || mul != 1)) { - fprintf(stderr, "Invalid syntax for size: %s\n", size_str.c_str()); - exit(1); + return UINT64_MAX; } return size; } -std::function cli_tool_t::start_create(json11::Json cfg) +std::function cli_tool_t::start_create(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto image_creator = new image_creator_t(); @@ -492,8 +511,12 @@ std::function cli_tool_t::start_create(json11::Json cfg) int p = image_creator->image_name.find('@'); if (p == std::string::npos || p == image_creator->image_name.length()-1) { - fprintf(stderr, "Please specify new snapshot name after @\n"); - exit(1); + delete image_creator; + return [](cli_result_t & result) + { + result = (cli_result_t){ .err = EINVAL, .text = "Please specify new snapshot name after @" }; + return true; + }; } image_creator->new_snap = image_creator->image_name.substr(p + 1); image_creator->image_name = image_creator->image_name.substr(0, p); @@ -502,32 +525,39 @@ std::function cli_tool_t::start_create(json11::Json cfg) if (cfg["size"].string_value() != "") { image_creator->size = parse_size(cfg["size"].string_value()); + if (image_creator->size == UINT64_MAX) + { + return [size = cfg["size"].string_value()](cli_result_t & result) + { + result = (cli_result_t){ .err = EINVAL, .text = "Invalid syntax for size: "+size }; + return true; + }; + } if (image_creator->size % 4096) { - fprintf(stderr, "Size should be a multiple of 4096\n"); - exit(1); + delete image_creator; + return [](cli_result_t & result) + { + result = (cli_result_t){ .err = EINVAL, .text = "Size should be a multiple of 4096" }; + return true; + }; } if (image_creator->new_snap != "") { - fprintf(stderr, "--size can't be specified for snapshots\n"); - exit(1); + delete image_creator; + return [](cli_result_t & result) + { + result = (cli_result_t){ .err = EINVAL, .text = "Size can't be specified for snapshots" }; + return true; + }; } } - if (image_creator->image_name == "") - { - fprintf(stderr, "Image name is missing\n"); - exit(1); - } - if (image_creator->image_name.find('@') != std::string::npos) - { - fprintf(stderr, "Image name can't contain @ character\n"); - exit(1); - } - return [image_creator]() + return [image_creator](cli_result_t & result) { image_creator->loop(); if (image_creator->is_done()) { + result = image_creator->result; delete image_creator; return true; } diff --git a/src/cli_df.cpp b/src/cli_df.cpp index 423bd2f2..887cf53a 100644 --- a/src/cli_df.cpp +++ b/src/cli_df.cpp @@ -12,6 +12,7 @@ struct pool_lister_t int state = 0; json11::Json space_info; + cli_result_t result; std::map pool_stats; bool is_done() @@ -150,10 +151,10 @@ resume_1: get_stats(); if (parent->waiting > 0) return; + result.data = to_list(); if (parent->json_output) { // JSON output - printf("%s\n", json11::Json(to_list()).dump().c_str()); state = 100; return; } @@ -206,21 +207,22 @@ resume_1: : 100)+"%"; kv.second["eff_fmt"] = format_q(kv.second["space_efficiency"].number_value()*100)+"%"; } - printf("%s", print_table(to_list(), cols, parent->color).c_str()); + result.text = print_table(result.data, cols, parent->color); state = 100; } }; -std::function cli_tool_t::start_df(json11::Json cfg) +std::function cli_tool_t::start_df(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto lister = new pool_lister_t(); lister->parent = this; - return [lister]() + return [lister](cli_result_t & result) { lister->loop(); if (lister->is_done()) { + result = lister->result; delete lister; return true; } diff --git a/src/cli_flatten.cpp b/src/cli_flatten.cpp index bcd97484..b18c11b3 100644 --- a/src/cli_flatten.cpp +++ b/src/cli_flatten.cpp @@ -22,7 +22,8 @@ struct snap_flattener_t std::string top_parent_name; inode_t target_id = 0; int state = 0; - std::function merger_cb; + std::function merger_cb; + cli_result_t result; void get_merge_parents() { @@ -37,23 +38,34 @@ struct snap_flattener_t auto it = parent->cli->st_cli.inode_config.find(cur->parent_id); if (it == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Parent inode of layer %s (id %ld) not found\n", cur->name.c_str(), cur->parent_id); - exit(1); + result = (cli_result_t){ + .err = ENOENT, + .text = "Parent inode of layer "+cur->name+" (id "+std::to_string(cur->parent_id)+") does not exist", + .data = json11::Json::object { + { "error", "parent-not-found" }, + { "inode_id", cur->num }, + { "inode_name", cur->name }, + { "parent_id", cur->parent_id }, + }, + }; + state = 100; + return; } cur = &it->second; chain_list.push_back(cur->num); } if (cur->parent_id != 0) { - fprintf(stderr, "Layer %s has a loop in parents\n", target_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EBADF, .text = "Layer "+target_name+" has a loop in parents" }; + state = 100; + return; } top_parent_name = cur->name; } bool is_done() { - return state == 5; + return state == 100; } void loop() @@ -64,8 +76,16 @@ struct snap_flattener_t goto resume_2; else if (state == 3) goto resume_3; + if (target_name == "") + { + result = (cli_result_t){ .err = EINVAL, .text = "Layer to flatten not specified" }; + state = 100; + return; + } // Get parent layers get_merge_parents(); + if (state == 100) + return; // Start merger merger_cb = parent->start_merge(json11::Json::object { { "command", json11::Json::array{ "merge-data", top_parent_name, target_name } }, @@ -76,12 +96,17 @@ struct snap_flattener_t }); // Wait for it resume_1: - while (!merger_cb()) + while (!merger_cb(result)) { state = 1; return; } merger_cb = NULL; + if (result.err) + { + state = 100; + return; + } // Change parent parent->change_parent(target_id, 0); // Wait for it to complete @@ -92,31 +117,27 @@ resume_2: state = 3; resume_3: // Done - return; + state = 100; } }; -std::function cli_tool_t::start_flatten(json11::Json cfg) +std::function cli_tool_t::start_flatten(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto flattener = new snap_flattener_t(); flattener->parent = this; flattener->target_name = cmd.size() > 1 ? cmd[1].string_value() : ""; - if (flattener->target_name == "") - { - fprintf(stderr, "Layer to flatten argument is missing\n"); - exit(1); - } flattener->fsync_interval = cfg["fsync-interval"].uint64_value(); if (!flattener->fsync_interval) flattener->fsync_interval = 128; if (!cfg["cas"].is_null()) flattener->use_cas = cfg["cas"].uint64_value() ? 2 : 0; - return [flattener]() + return [flattener](cli_result_t & result) { flattener->loop(); if (flattener->is_done()) { + result = flattener->result; delete flattener; return true; } diff --git a/src/cli_ls.cpp b/src/cli_ls.cpp index 4e15aba2..585f61b8 100644 --- a/src/cli_ls.cpp +++ b/src/cli_ls.cpp @@ -24,6 +24,7 @@ struct image_lister_t int state = 0; std::map stats; json11::Json space_info; + cli_result_t result; bool is_done() { @@ -44,8 +45,9 @@ struct image_lister_t } if (!list_pool_id) { - fprintf(stderr, "Pool %s does not exist\n", list_pool_name.c_str()); - exit(1); + result = (cli_result_t){ .err = ENOENT, .text = "Pool "+list_pool_name+" does not exist" }; + state = 100; + return; } } for (auto & ic: parent->cli->st_cli.inode_config) @@ -246,10 +248,10 @@ resume_1: if (parent->waiting > 0) return; } + result.data = to_list(); if (parent->json_output) { // JSON output - printf("%s\n", json11::Json(to_list()).dump().c_str()); state = 100; return; } @@ -359,7 +361,7 @@ resume_1: kv.second["size_fmt"] = format_size(kv.second["size"].uint64_value()); kv.second["ro"] = kv.second["readonly"].bool_value() ? "RO" : "-"; } - printf("%s", print_table(to_list(), cols, parent->color).c_str()); + result.text = print_table(to_list(), cols, parent->color); state = 100; } }; @@ -546,7 +548,7 @@ back: return true; } -std::function cli_tool_t::start_ls(json11::Json cfg) +std::function cli_tool_t::start_ls(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto lister = new image_lister_t(); @@ -562,11 +564,12 @@ std::function cli_tool_t::start_ls(json11::Json cfg) { lister->only_names.insert(cmd[i].string_value()); } - return [lister]() + return [lister](cli_result_t & result) { lister->loop(); if (lister->is_done()) { + result = lister->result; delete lister; return true; } diff --git a/src/cli_merge.cpp b/src/cli_merge.cpp index 51c947fc..373f7c03 100644 --- a/src/cli_merge.cpp +++ b/src/cli_merge.cpp @@ -12,6 +12,9 @@ struct snap_rw_op_t cluster_op_t op; int todo = 0; uint32_t start = 0, end = 0; + int error_code = 0; + uint64_t error_offset = 0; + bool error_read = false; }; // Layer merge is the base for multiple operations: @@ -54,17 +57,27 @@ struct snap_merger_t uint64_t last_written_offset = 0; int deleted_unsynced = 0; uint64_t processed = 0, to_process = 0; + std::string rwo_error; + + cli_result_t result; void start_merge() { + if (from_name == "" || to_name == "") + { + result = (cli_result_t){ .err = EINVAL, .text = "Beginning or end of the merge sequence is missing" }; + state = 100; + return; + } check_delete_source = delete_source || check_delete_source; inode_config_t *from_cfg = parent->get_inode_cfg(from_name); inode_config_t *to_cfg = parent->get_inode_cfg(to_name); inode_config_t *target_cfg = target_name == "" ? from_cfg : parent->get_inode_cfg(target_name); if (to_cfg->num == from_cfg->num) { - fprintf(stderr, "Only one layer specified, nothing to merge\n"); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Only one layer specified, nothing to merge" }; + state = 100; + return; } // Check that to_cfg is actually a child of from_cfg and target_cfg is somewhere between them std::vector chain_list; @@ -78,8 +91,18 @@ struct snap_merger_t auto it = parent->cli->st_cli.inode_config.find(cur->parent_id); if (it == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Parent inode of layer %s (id %ld) not found\n", cur->name.c_str(), cur->parent_id); - exit(1); + result = (cli_result_t){ + .err = ENOENT, + .text = "Parent inode of layer "+cur->name+" (id "+std::to_string(cur->parent_id)+") does not exist", + .data = json11::Json::object { + { "error", "parent-not-found" }, + { "inode_id", cur->num }, + { "inode_name", cur->name }, + { "parent_id", cur->parent_id }, + }, + }; + state = 100; + return; } cur = &it->second; chain_list.push_back(cur->num); @@ -87,8 +110,9 @@ struct snap_merger_t } if (cur->parent_id != from_cfg->num) { - fprintf(stderr, "Layer %s is not a child of %s\n", to_name.c_str(), from_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Layer "+to_name+" is not a child of "+from_name }; + state = 100; + return; } chain_list.push_back(from_cfg->num); layer_block_size[from_cfg->num] = get_block_size(from_cfg->num); @@ -99,8 +123,9 @@ struct snap_merger_t } if (sources.find(target_cfg->num) == sources.end()) { - fprintf(stderr, "Layer %s is not between %s and %s\n", target_name.c_str(), to_name.c_str(), from_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Layer "+target_name+" is not between "+to_name+" and "+from_name }; + state = 100; + return; } target = target_cfg->num; target_rank = sources.at(target); @@ -130,14 +155,15 @@ struct snap_merger_t int parent_rank = it->second; if (parent_rank < to_rank && (parent_rank >= target_rank || check_delete_source)) { - fprintf( - stderr, "Layers at or above %s, but below %s are not allowed" - " to have other children, but %s is a child of %s\n", - (check_delete_source ? from_name.c_str() : target_name.c_str()), - to_name.c_str(), ic.second.name.c_str(), - parent->cli->st_cli.inode_config.at(ic.second.parent_id).name.c_str() - ); - exit(1); + result = (cli_result_t){ + .err = EINVAL, + .text = "Layers at or above "+(check_delete_source ? from_name : target_name)+ + ", but below "+to_name+" are not allowed to have other children, but "+ + ic.second.name+" is a child of "+ + parent->cli->st_cli.inode_config.at(ic.second.parent_id).name, + }; + state = 100; + return; } if (parent_rank >= to_rank) { @@ -152,11 +178,14 @@ struct snap_merger_t use_cas = 0; } sources.erase(target); - printf( - "Merging %ld layer(s) into target %s%s (inode %lu in pool %u)\n", - sources.size(), target_cfg->name.c_str(), - use_cas ? " online (with CAS)" : "", INODE_NO_POOL(target), INODE_POOL(target) - ); + if (parent->progress) + { + printf( + "Merging %ld layer(s) into target %s%s (inode %lu in pool %u)\n", + sources.size(), target_cfg->name.c_str(), + use_cas ? " online (with CAS)" : "", INODE_NO_POOL(target), INODE_POOL(target) + ); + } target_block_size = get_block_size(target); } @@ -253,7 +282,8 @@ struct snap_merger_t oit = merge_offsets.begin(); resume_5: // Now read, overwrite and optionally delete offsets one by one - while (in_flight < parent->iodepth*parent->parallel_osds && oit != merge_offsets.end()) + while (in_flight < parent->iodepth*parent->parallel_osds && + oit != merge_offsets.end() && !rwo_error.size()) { in_flight++; read_and_write(*oit); @@ -264,6 +294,15 @@ struct snap_merger_t printf("\rOverwriting blocks: %lu/%lu", processed, to_process); } } + if (in_flight == 0 && rwo_error.size()) + { + result = (cli_result_t){ + .err = EIO, + .text = rwo_error, + }; + state = 100; + return; + } if (in_flight > 0 || oit != merge_offsets.end()) { // Wait until overwrites finish @@ -396,8 +435,9 @@ struct snap_merger_t { if (op->retval != op->len) { - fprintf(stderr, "error reading target at offset %lx: %s\n", op->offset, strerror(-op->retval)); - exit(1); + rwo->error_code = -op->retval; + rwo->error_offset = op->offset; + rwo->error_read = true; } next_write(rwo); }; @@ -410,7 +450,7 @@ struct snap_merger_t // FIXME: Allow to use single write with "holes" (OSDs don't allow it yet) uint32_t gran = parent->cli->get_bs_bitmap_granularity(); uint64_t bitmap_size = target_block_size / gran; - while (rwo->end < bitmap_size) + while (rwo->end < bitmap_size && !rwo->error_code) { auto bit = ((*((uint8_t*)rwo->op.bitmap_buf + (rwo->end >> 3))) & (1 << (rwo->end & 0x7))); if (!bit) @@ -434,7 +474,7 @@ struct snap_merger_t rwo->end++; } } - if (rwo->end > rwo->start) + if (rwo->end > rwo->start && !rwo->error_code) { // write start->end rwo->todo++; @@ -473,8 +513,9 @@ struct snap_merger_t delete subop; return; } - fprintf(stderr, "error writing target at offset %lx: %s\n", subop->offset, strerror(-subop->retval)); - exit(1); + rwo->error_code = -subop->retval; + rwo->error_offset = subop->offset; + rwo->error_read = false; } // Increment CAS version rwo->op.version++; @@ -510,11 +551,12 @@ struct snap_merger_t { if (!rwo->todo) { - if (last_written_offset < rwo->op.offset+target_block_size) + if (!rwo->error_code && + last_written_offset < rwo->op.offset+target_block_size) { last_written_offset = rwo->op.offset+target_block_size; } - if (delete_source) + if (!rwo->error_code && delete_source) { deleted_unsynced++; if (deleted_unsynced >= fsync_interval) @@ -545,13 +587,20 @@ struct snap_merger_t } free(rwo->buf); delete rwo; + if (rwo->error_code) + { + char buf[1024]; + snprintf(buf, 1024, "Error %s target at offset %lx: %s", + rwo->error_read ? "reading" : "writing", rwo->error_offset, strerror(rwo->error_code)); + rwo_error = std::string(buf); + } in_flight--; continue_merge_reent(); } } }; -std::function cli_tool_t::start_merge(json11::Json cfg) +std::function cli_tool_t::start_merge(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto merger = new snap_merger_t(); @@ -559,22 +608,18 @@ std::function cli_tool_t::start_merge(json11::Json cfg) merger->from_name = cmd.size() > 1 ? cmd[1].string_value() : ""; merger->to_name = cmd.size() > 2 ? cmd[2].string_value() : ""; merger->target_name = cfg["target"].string_value(); - if (merger->from_name == "" || merger->to_name == "") - { - fprintf(stderr, "Beginning or end of the merge sequence is missing\n"); - exit(1); - } merger->delete_source = cfg["delete-source"].string_value() != ""; merger->fsync_interval = cfg["fsync-interval"].uint64_value(); if (!merger->fsync_interval) merger->fsync_interval = 128; if (!cfg["cas"].is_null()) merger->use_cas = cfg["cas"].uint64_value() ? 2 : 0; - return [merger]() + return [merger](cli_result_t & result) { merger->continue_merge_reent(); if (merger->is_done()) { + result = merger->result; delete merger; return true; } diff --git a/src/cli_modify.cpp b/src/cli_modify.cpp index aeb0e839..6939a7b6 100644 --- a/src/cli_modify.cpp +++ b/src/cli_modify.cpp @@ -23,7 +23,8 @@ struct image_changer_t bool has_children = false; int state = 0; - std::function cb; + std::function cb; + cli_result_t result; bool is_done() { @@ -36,6 +37,18 @@ struct image_changer_t goto resume_1; else if (state == 2) goto resume_2; + if (image_name == "") + { + result = (cli_result_t){ .err = EINVAL, .text = "Image name is missing" }; + state = 100; + return; + } + if (new_size != 0 && (new_size % 4096)) + { + result = (cli_result_t){ .err = EINVAL, .text = "Image size should be a multiple of 4096" }; + state = 100; + return; + } for (auto & ic: parent->cli->st_cli.inode_config) { if (ic.second.name == image_name) @@ -46,14 +59,16 @@ struct image_changer_t } if (new_name != "" && ic.second.name == new_name) { - fprintf(stderr, "Image %s already exists\n", new_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EEXIST, .text = "Image "+new_name+" already exists" }; + state = 100; + return; } } if (!inode_num) { - fprintf(stderr, "Image %s does not exist\n", image_name.c_str()); - exit(1); + result = (cli_result_t){ .err = ENOENT, .text = "Image "+image_name+" does not exist" }; + state = 100; + return; } for (auto & ic: parent->cli->st_cli.inode_config) { @@ -68,7 +83,7 @@ struct image_changer_t (!new_size || cfg.size == new_size) && (new_name == "" || new_name == image_name)) { - printf("No change\n"); + result = (cli_result_t){ .text = "No change" }; state = 100; return; } @@ -79,8 +94,9 @@ struct image_changer_t // Check confirmation when trimming an image with children if (has_children && !force) { - fprintf(stderr, "Image %s has children. Refusing to shrink it without --force\n", image_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Image "+image_name+" has children. Refusing to shrink it without --force" }; + state = 100; + return; } // Shrink the image first cb = parent->start_rm(json11::Json::object { @@ -90,12 +106,17 @@ struct image_changer_t { "min-offset", new_size }, }); resume_1: - while (!cb()) + while (!cb(result)) { state = 1; return; } cb = NULL; + if (result.err) + { + state = 100; + return; + } } cfg.size = new_size; } @@ -109,8 +130,9 @@ resume_1: // Check confirmation when making an image with children read-write if (has_children && !force) { - fprintf(stderr, "Image %s has children. Refusing to make it read-write without --force\n", image_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Image "+image_name+" has children. Refusing to make it read-write without --force" }; + state = 100; + return; } } if (new_name != "") @@ -180,32 +202,23 @@ resume_2: return; if (!parent->etcd_result["succeeded"].bool_value()) { - fprintf(stderr, "Image %s was modified by someone else, please repeat your request\n", image_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EAGAIN, .text = "Image "+image_name+" was modified by someone else, please repeat your request" }; + state = 100; + return; } - printf("Image %s modified\n", image_name.c_str()); + result = (cli_result_t){ .err = 0, .text = "Image "+image_name+" modified" }; state = 100; } }; -std::function cli_tool_t::start_modify(json11::Json cfg) +std::function cli_tool_t::start_modify(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto changer = new image_changer_t(); changer->parent = this; changer->image_name = cmd.size() > 1 ? cmd[1].string_value() : ""; - if (changer->image_name == "") - { - fprintf(stderr, "Image name is missing\n"); - exit(1); - } changer->new_name = cfg["rename"].string_value(); changer->new_size = parse_size(cfg["resize"].string_value()); - if (changer->new_size != 0 && (changer->new_size % 4096)) - { - fprintf(stderr, "Image size should be a multiple of 4096\n"); - exit(1); - } changer->force = cfg["force"].bool_value(); changer->set_readonly = cfg["readonly"].bool_value(); changer->set_readwrite = cfg["readwrite"].bool_value(); @@ -213,11 +226,12 @@ std::function cli_tool_t::start_modify(json11::Json cfg) if (!changer->fsync_interval) changer->fsync_interval = 128; // FIXME Check that the image doesn't have children when shrinking - return [changer]() + return [changer](cli_result_t & result) { changer->loop(); if (changer->is_done()) { + result = changer->result; delete changer; return true; } diff --git a/src/cli_rm.cpp b/src/cli_rm.cpp index a7d1ba4f..ca33943b 100644 --- a/src/cli_rm.cpp +++ b/src/cli_rm.cpp @@ -63,7 +63,9 @@ struct snap_remover_t inode_t new_parent = 0; int state = 0; int current_child = 0; - std::function cb; + std::function cb; + + cli_result_t result; bool is_done() { @@ -90,11 +92,25 @@ struct snap_remover_t goto resume_8; else if (state == 9) goto resume_9; + if (from_name == "") + { + result = (cli_result_t){ .err = EINVAL, .text = "Layer to remove argument is missing" }; + state = 100; + return; + } + if (to_name == "") + { + to_name = from_name; + } // Get children to merge get_merge_children(); + if (state == 100) + return; // Try to select an inode for the "inverse" optimized scenario // Read statistics from etcd to do it read_stats(); + if (state == 100) + return; state = 1; resume_1: if (parent->waiting > 0) @@ -106,13 +122,20 @@ resume_1: if (merge_children[current_child] == inverse_child) continue; start_merge_child(merge_children[current_child], merge_children[current_child]); + if (state == 100) + return; resume_2: - while (!cb()) + while (!cb(result)) { state = 2; return; } cb = NULL; + if (result.err) + { + state = 100; + return; + } parent->change_parent(merge_children[current_child], new_parent); state = 3; resume_3: @@ -123,25 +146,41 @@ resume_3: if (inverse_child != 0) { start_merge_child(inverse_child, inverse_parent); + if (state == 100) + return; resume_4: - while (!cb()) + while (!cb(result)) { state = 4; return; } cb = NULL; + if (result.err) + { + state = 100; + return; + } // Delete "inverse" child data start_delete_source(inverse_child); + if (state == 100) + return; resume_5: - while (!cb()) + while (!cb(result)) { state = 5; return; } cb = NULL; + if (result.err) + { + state = 100; + return; + } // Delete "inverse" child metadata, rename parent over it, // and also change parent links of the previous "inverse" child rename_inverse_parent(); + if (state == 100) + return; state = 6; resume_6: if (parent->waiting > 0) @@ -154,13 +193,20 @@ resume_6: continue; start_delete_source(chain_list[current_child]); resume_7: - while (!cb()) + while (!cb(result)) { state = 7; return; } cb = NULL; + if (result.err) + { + state = 100; + return; + } delete_inode_config(chain_list[current_child]); + if (state == 100) + return; state = 8; resume_8: if (parent->waiting > 0) @@ -186,16 +232,19 @@ resume_9: auto it = parent->cli->st_cli.inode_config.find(cur->parent_id); if (it == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Parent inode of layer %s (id %ld) not found\n", cur->name.c_str(), cur->parent_id); - exit(1); + char buf[1024]; + snprintf(buf, 1024, "Parent inode of layer %s (id 0x%lx) not found", cur->name.c_str(), cur->parent_id); + state = 100; + return; } cur = &it->second; chain_list.push_back(cur->num); } if (cur->num != from_cfg->num) { - fprintf(stderr, "Layer %s is not a child of %s\n", to_name.c_str(), from_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EINVAL, .text = "Layer "+to_name+" is not a child of "+from_name }; + state = 100; + return; } new_parent = from_cfg->parent_id; // Calculate ranks @@ -263,8 +312,9 @@ resume_9: parent->waiting--; if (err != "") { - fprintf(stderr, "Error reading layer statistics from etcd: %s\n", err.c_str()); - exit(1); + result = (cli_result_t){ .err = EIO, .text = "Error reading layer statistics from etcd: "+err }; + state = 100; + return; } for (auto inode_result: data["responses"].array_items()) { @@ -275,14 +325,16 @@ resume_9: sscanf(kv.key.c_str() + parent->cli->st_cli.etcd_prefix.length()+13, "%u/%lu%c", &pool_id, &inode, &null_byte); if (!inode || null_byte != 0) { - fprintf(stderr, "Bad key returned from etcd: %s\n", kv.key.c_str()); - exit(1); + result = (cli_result_t){ .err = EIO, .text = "Bad key returned from etcd: "+kv.key }; + state = 100; + return; } auto pool_cfg_it = parent->cli->st_cli.pool_config.find(pool_id); if (pool_cfg_it == parent->cli->st_cli.pool_config.end()) { - fprintf(stderr, "Pool %u does not exist\n", pool_id); - exit(1); + result = (cli_result_t){ .err = ENOENT, .text = "Pool "+std::to_string(pool_id)+" does not exist" }; + state = 100; + return; } inode = INODE_WITH_POOL(pool_id, inode); auto & pool_cfg = pool_cfg_it->second; @@ -324,14 +376,20 @@ resume_9: auto child_it = parent->cli->st_cli.inode_config.find(inverse_child); if (child_it == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Inode %ld disappeared\n", inverse_child); - exit(1); + char buf[1024]; + snprintf(buf, 1024, "Inode 0x%lx disappeared", inverse_child); + result = (cli_result_t){ .err = EIO, .text = std::string(buf) }; + state = 100; + return; } auto target_it = parent->cli->st_cli.inode_config.find(inverse_parent); if (target_it == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Inode %ld disappeared\n", inverse_parent); - exit(1); + char buf[1024]; + snprintf(buf, 1024, "Inode 0x%lx disappeared", inverse_parent); + result = (cli_result_t){ .err = EIO, .text = std::string(buf) }; + state = 100; + return; } inode_config_t *child_cfg = &child_it->second; inode_config_t *target_cfg = &target_it->second; @@ -422,16 +480,19 @@ resume_9: parent->waiting--; if (err != "") { - fprintf(stderr, "Error renaming %s to %s: %s\n", target_name.c_str(), child_name.c_str(), err.c_str()); - exit(1); + result = (cli_result_t){ .err = EIO, .text = "Error renaming "+target_name+" to "+child_name+": "+err }; + state = 100; + return; } if (!res["succeeded"].bool_value()) { - fprintf( - stderr, "Parent (%s), child (%s), or one of its children" - " configuration was modified during rename\n", target_name.c_str(), child_name.c_str() - ); - exit(1); + result = (cli_result_t){ + .err = EIO, + .text = "Parent ("+target_name+"), child ("+child_name+"), or one of its children" + " configuration was modified during rename", + }; + state = 100; + return; } printf("Layer %s renamed to %s\n", target_name.c_str(), child_name.c_str()); parent->ringloop->wakeup(); @@ -443,8 +504,11 @@ resume_9: auto cur_cfg_it = parent->cli->st_cli.inode_config.find(cur); if (cur_cfg_it == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Inode 0x%lx disappeared\n", cur); - exit(1); + char buf[1024]; + snprintf(buf, 1024, "Inode 0x%lx disappeared", cur); + result = (cli_result_t){ .err = EIO, .text = std::string(buf) }; + state = 100; + return; } inode_config_t *cur_cfg = &cur_cfg_it->second; std::string cur_name = cur_cfg->name; @@ -480,13 +544,15 @@ resume_9: parent->waiting--; if (err != "") { - fprintf(stderr, "Error deleting %s: %s\n", cur_name.c_str(), err.c_str()); - exit(1); + result = (cli_result_t){ .err = EIO, .text = "Error deleting "+cur_name+": "+err }; + state = 100; + return; } if (!res["succeeded"].bool_value()) { - fprintf(stderr, "Layer %s configuration was modified during deletion\n", cur_name.c_str()); - exit(1); + result = (cli_result_t){ .err = EIO, .text = "Layer "+cur_name+" was modified during deletion" }; + state = 100; + return; } printf("Layer %s deleted\n", cur_name.c_str()); parent->ringloop->wakeup(); @@ -498,14 +564,20 @@ resume_9: auto child_it = parent->cli->st_cli.inode_config.find(child_inode); if (child_it == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Inode %ld disappeared\n", child_inode); - exit(1); + char buf[1024]; + snprintf(buf, 1024, "Inode 0x%lx disappeared", child_inode); + result = (cli_result_t){ .err = EIO, .text = std::string(buf) }; + state = 100; + return; } auto target_it = parent->cli->st_cli.inode_config.find(target_inode); if (target_it == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Inode %ld disappeared\n", target_inode); - exit(1); + char buf[1024]; + snprintf(buf, 1024, "Inode 0x%lx disappeared", target_inode); + result = (cli_result_t){ .err = EIO, .text = std::string(buf) }; + state = 100; + return; } cb = parent->start_merge(json11::Json::object { { "command", json11::Json::array{ "merge-data", from_name, child_it->second.name } }, @@ -521,8 +593,11 @@ resume_9: auto source = parent->cli->st_cli.inode_config.find(inode); if (source == parent->cli->st_cli.inode_config.end()) { - fprintf(stderr, "Inode %ld disappeared\n", inode); - exit(1); + char buf[1024]; + snprintf(buf, 1024, "Inode 0x%lx disappeared", inode); + result = (cli_result_t){ .err = EIO, .text = std::string(buf) }; + state = 100; + return; } cb = parent->start_rm(json11::Json::object { { "inode", inode }, @@ -532,22 +607,13 @@ resume_9: } }; -std::function cli_tool_t::start_snap_rm(json11::Json cfg) +std::function cli_tool_t::start_snap_rm(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto snap_remover = new snap_remover_t(); snap_remover->parent = this; snap_remover->from_name = cmd.size() > 1 ? cmd[1].string_value() : ""; snap_remover->to_name = cmd.size() > 2 ? cmd[2].string_value() : ""; - if (snap_remover->from_name == "") - { - fprintf(stderr, "Layer to remove argument is missing\n"); - exit(1); - } - if (snap_remover->to_name == "") - { - snap_remover->to_name = snap_remover->from_name; - } snap_remover->fsync_interval = cfg["fsync-interval"].uint64_value(); if (!snap_remover->fsync_interval) snap_remover->fsync_interval = 128; @@ -555,11 +621,12 @@ std::function cli_tool_t::start_snap_rm(json11::Json cfg) snap_remover->use_cas = cfg["cas"].uint64_value() ? 2 : 0; if (!cfg["writers_stopped"].is_null()) snap_remover->writers_stopped = true; - return [snap_remover]() + return [snap_remover](cli_result_t & result) { snap_remover->loop(); if (snap_remover->is_done()) { + result = snap_remover->result; delete snap_remover; return true; } diff --git a/src/cli_rm_data.cpp b/src/cli_rm_data.cpp index 5cff8fd7..3671a455 100644 --- a/src/cli_rm_data.cpp +++ b/src/cli_rm_data.cpp @@ -32,6 +32,9 @@ struct rm_inode_t uint64_t pgs_to_list = 0; bool lists_done = false; int state = 0; + int error_count = 0; + + cli_result_t result; void start_delete() { @@ -74,8 +77,13 @@ struct rm_inode_t }); if (!lister) { - fprintf(stderr, "Failed to list inode %lu from pool %u objects\n", INODE_NO_POOL(inode), INODE_POOL(inode)); - exit(1); + result = (cli_result_t){ + .err = EIO, + .text = "Failed to list objects of inode "+std::to_string(INODE_NO_POOL(inode))+ + " from pool "+std::to_string(INODE_POOL(inode)), + }; + state = 100; + return; } pgs_to_list = parent->cli->list_pg_count(lister); parent->cli->list_inode_next(lister, parent->parallel_osds); @@ -118,6 +126,7 @@ struct rm_inode_t fprintf(stderr, "Failed to remove object %lx:%lx from PG %u (OSD %lu) (retval=%ld)\n", op->req.rw.inode, op->req.rw.offset, cur_list->pg_num, cur_list->rm_osd_num, op->reply.hdr.retval); + error_count++; } delete op; cur_list->obj_done++; @@ -161,31 +170,41 @@ struct rm_inode_t } if (lists_done && !lists.size()) { - printf("Done, inode %lu in pool %u data removed\n", INODE_NO_POOL(inode), pool_id); - state = 2; + result = (cli_result_t){ + .err = error_count > 0 ? EIO : 0, + .text = error_count > 0 ? "Some blocks were not removed" : ( + "Done, inode "+std::to_string(INODE_NO_POOL(inode))+" from pool "+ + std::to_string(pool_id)+" removed"), + }; + state = 100; } } - bool loop() + bool is_done() { - if (state == 0) + return state == 100; + } + + void loop() + { + if (state == 1) + goto resume_1; + if (!pool_id) { - start_delete(); - state = 1; + result = (cli_result_t){ .err = EINVAL, .text = "Pool is not specified" }; + state = 100; + return; } - else if (state == 1) - { - continue_delete(); - } - else if (state == 2) - { - return true; - } - return false; + start_delete(); + if (state == 100) + return; + state = 1; + resume_1: + continue_delete(); } }; -std::function cli_tool_t::start_rm(json11::Json cfg) +std::function cli_tool_t::start_rm(json11::Json cfg) { auto remover = new rm_inode_t(); remover->parent = this; @@ -196,16 +215,13 @@ std::function cli_tool_t::start_rm(json11::Json cfg) remover->inode = (remover->inode & (((uint64_t)1 << (64-POOL_ID_BITS)) - 1)) | (((uint64_t)remover->pool_id) << (64-POOL_ID_BITS)); } remover->pool_id = INODE_POOL(remover->inode); - if (!remover->pool_id) - { - fprintf(stderr, "pool is missing\n"); - exit(1); - } remover->min_offset = cfg["min-offset"].uint64_value(); - return [remover]() + return [remover](cli_result_t & result) { - if (remover->loop()) + remover->loop(); + if (remover->is_done()) { + result = remover->result; delete remover; return true; } diff --git a/src/cli_simple_offsets.cpp b/src/cli_simple_offsets.cpp index 6751f506..f4bdd9ca 100644 --- a/src/cli_simple_offsets.cpp +++ b/src/cli_simple_offsets.cpp @@ -11,7 +11,7 @@ #include // Calculate offsets for a block device and print OSD command line parameters -std::function cli_tool_t::simple_offsets(json11::Json cfg) +std::function cli_tool_t::simple_offsets(json11::Json cfg) { std::string device = cfg["command"][1].string_value(); uint64_t object_size = parse_size(cfg["object_size"].string_value()); diff --git a/src/cli_status.cpp b/src/cli_status.cpp index 0fd8a5df..7984bb01 100644 --- a/src/cli_status.cpp +++ b/src/cli_status.cpp @@ -277,16 +277,17 @@ resume_2: } }; -std::function cli_tool_t::start_status(json11::Json cfg) +std::function cli_tool_t::start_status(json11::Json cfg) { json11::Json::array cmd = cfg["command"].array_items(); auto printer = new status_printer_t(); printer->parent = this; - return [printer]() + return [printer](cli_result_t & result) { printer->loop(); if (printer->is_done()) { + result = { .err = 0 }; delete printer; return true; }