213 lines
6.9 KiB
C++
213 lines
6.9 KiB
C++
// Copyright (c) Vitaliy Filippov, 2019+
|
|
// License: VNPL-1.1 (see README.md for details)
|
|
|
|
#include <fcntl.h>
|
|
#include <algorithm>
|
|
#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<std::string> globs;
|
|
bool exact = false;
|
|
|
|
json11::Json::array deleted_ids, deleted_images, rebased_images;
|
|
std::map<inode_t, inode_t> chains; // child => parent pairs
|
|
std::vector<std::vector<inode_rev_t>> versioned_chains;
|
|
json11::Json::object sub_cfg;
|
|
size_t i = 0;
|
|
int state = 0;
|
|
std::function<bool(cli_result_t &)> 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<inode_rev_t> 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<inode_t, uint64_t> 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<inode_rev_t> & a, const std::vector<inode_rev_t> & 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<bool(cli_result_t &)> 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;
|
|
};
|
|
}
|