// Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) #include #include #include "cli.h" #include "cluster_client.h" #include "str_util.h" struct inode_rev_t { inode_t inode_num; uint64_t meta_rev; }; // Remove multiple images in correct order struct wildcard_remover_t { cli_tool_t *parent; json11::Json cfg; std::vector globs; bool exact = false; json11::Json::array deleted_ids, deleted_images, rebased_images; std::map chains; // child => parent pairs std::vector> versioned_chains; json11::Json::object sub_cfg; size_t i = 0; int state = 0; std::function sub_cb; cli_result_t result; bool is_done() { return state == 100; } void join_chains() { bool changed = true; while (changed) { changed = false; auto ino_it = chains.begin(); while (ino_it != chains.end()) { auto child_id = ino_it->first; auto parent_id = ino_it->second; auto & parent_cfg = parent->cli->st_cli.inode_config.at(parent_id); if (parent_cfg.parent_id) { auto chain_it = chains.find(parent_cfg.parent_id); if (chain_it != chains.end()) { changed = true; ino_it->second = chain_it->second; chains.erase(chain_it); } } ino_it = chains.upper_bound(child_id); } } // Remember metadata modification revisions to check for parallel changes versioned_chains.clear(); for (auto cp: chains) { auto child_id = cp.first; auto parent_id = cp.second; std::vector ver_chain; do { auto & inode_cfg = parent->cli->st_cli.inode_config.at(child_id); ver_chain.push_back((inode_rev_t){ .inode_num = child_id, .meta_rev = inode_cfg.mod_revision }); if (child_id == parent_id) break; else child_id = inode_cfg.parent_id; } while (child_id); versioned_chains.push_back(std::move(ver_chain)); } // Sort chains based on parent inode rank to first delete child-most layers std::map ranks; for (auto cp: chains) { auto parent_id = cp.second, cur_id = parent_id; uint64_t rank = 0; do { rank++; cur_id = parent->cli->st_cli.inode_config.at(cur_id).parent_id; } while (cur_id && cur_id != parent_id); ranks[parent_id] = rank; } std::sort(versioned_chains.begin(), versioned_chains.end(), [&](const std::vector & a, const std::vector & b) { return ranks[a.back().inode_num] > ranks[b.back().inode_num]; }); } void loop() { if (state == 0) goto resume_0; if (state == 1) goto resume_1; else if (state == 100) goto resume_100; resume_0: state = 0; chains.clear(); // Select images to delete for (auto & ic: parent->cli->st_cli.inode_config) { for (auto & glob: globs) { if (exact ? (ic.second.name == glob) : stupid_glob(ic.second.name, glob)) { chains[ic.first] = ic.first; break; } } } // Arrange them into chains join_chains(); // Delete each chain i = 0; while (i < versioned_chains.size()) { // Check for parallel changes for (auto & irev: versioned_chains[i]) { auto inode_it = parent->cli->st_cli.inode_config.find(irev.inode_num); if (inode_it == parent->cli->st_cli.inode_config.end() || inode_it->second.mod_revision > irev.meta_rev) { if (inode_it != parent->cli->st_cli.inode_config.end()) fprintf(stderr, "Warning: image %s modified by someone else during deletion, restarting wildcard deletion\n", inode_it->second.name.c_str()); else fprintf(stderr, "Warning: inode %jx modified by someone else during deletion, retrying wildcard deletion\n", irev.inode_num); goto resume_0; } } // Delete { auto from_cfg = parent->cli->st_cli.inode_config.at(versioned_chains[i].back().inode_num); auto to_cfg = parent->cli->st_cli.inode_config.at(versioned_chains[i].front().inode_num); sub_cfg = cfg.object_items(); sub_cfg.erase("globs"); sub_cfg.erase("exact"); sub_cfg.erase("matching"); sub_cfg["from"] = from_cfg.name; sub_cfg["to"] = to_cfg.name; sub_cb = parent->start_rm(sub_cfg); } resume_1: while (!sub_cb(result)) { state = 1; return; } sub_cb = NULL; i++; merge_result(); if (result.err) { break; } } state = 100; result.data = json11::Json::object{ { "deleted_ids", deleted_ids }, { "deleted_images", deleted_images }, { "rebased_images", rebased_images }, }; resume_100: // Done return; } void merge_result() { for (auto & item: result.data["deleted_ids"].array_items()) deleted_ids.push_back(item); for (auto & item: result.data["deleted_images"].array_items()) deleted_images.push_back(item == result.data["renamed_to"] ? result.data["renamed_from"] : item); for (auto & item: result.data["rebased_images"].array_items()) rebased_images.push_back(item); } }; std::function cli_tool_t::start_rm_wildcard(json11::Json cfg) { auto wildcard_remover = new wildcard_remover_t(); wildcard_remover->parent = this; wildcard_remover->cfg = cfg; for (auto & glob: cfg["globs"].array_items()) wildcard_remover->globs.push_back(glob.string_value()); wildcard_remover->exact = cfg["exact"].bool_value(); return [wildcard_remover](cli_result_t & result) { wildcard_remover->loop(); if (wildcard_remover->is_done()) { result = wildcard_remover->result; delete wildcard_remover; return true; } return false; }; }