/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2023 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see .*
************************************************************************/
#include "XrdCl/XrdClFile.hh"
#include "XrdOuc/XrdOucEnv.hh"
#include "XrdOuc/XrdOucString.hh"
#include "common/StringSplit.hh"
#include "fst/checksum/ChecksumPlugins.hh"
#include "fst/layout/HeaderCRC.hh"
#include "fst/layout/RaidDpLayout.hh"
#include "fst/layout/ReedSLayout.hh"
#include
#define DEFAULTBUFFERSIZE (4 * 1024 * 1024)
std::pair
parseLocation(std::string& location)
{
size_t spos = location.rfind("//");
std::string address = location.substr(0, spos + 1);
XrdCl::URL url(address);
if (!url.IsValid()) {
fprintf(stderr, "URL is invalid: %s", address.c_str());
exit(-1);
}
std::string path = location.substr(spos + 1, std::string::npos);
return std::make_pair(url, path);
}
std::string
openOpaque(XrdCl::URL& url, std::string& filePath)
{
XrdCl::FileSystem fs(url);
std::string request = filePath;
if (request.find('?') == std::string::npos) {
request += "?mgm.pcmd=open";
} else {
request += "&mgm.pcmd=open";
}
XrdCl::Buffer arg;
arg.FromString(request);
XrdCl::Buffer* response = nullptr;
XrdCl::XRootDStatus status =
fs.Query(XrdCl::QueryCode::OpaqueFile, arg, response);
if (!status.IsOK()) {
fprintf(stderr, "Could not open file %s: %s", filePath.c_str(),
status.GetErrorMessage().c_str());
exit(-1);
}
std::string res(response->GetBuffer(), response->GetSize());
delete response;
return res;
}
std::string
getCheckSum(XrdCl::URL& url, std::string& filePath)
{
XrdCl::FileSystem fs(url);
XrdCl::Buffer arg;
arg.FromString(filePath);
XrdCl::Buffer* response = nullptr;
XrdCl::XRootDStatus status =
fs.Query(XrdCl::QueryCode::Checksum, arg, response);
if (!status.IsOK()) {
fprintf(stderr, "Could not open file %s: %s", filePath.c_str(),
status.GetErrorMessage().c_str());
exit(-1);
}
std::string checkSumResponse = response->GetBuffer();
delete response;
auto checksum = eos::common::StringSplit(checkSumResponse, " ");
if (checksum.size() != 2) {
fprintf(stderr, "Could not get checksum of file\n");
exit(-1);
}
return std::string(checksum[1]);
}
bool
isValidStripeCombination(const std::vector& stripes,
const std::string& XS, eos::fst::CheckSum* xsObj,
eos::common::LayoutId::layoutid_t layout,
const std::string& opaqueInfo, char* buffer)
{
eos::fst::RainMetaLayout* redundancyObj = nullptr;
if (eos::common::LayoutId::GetLayoutType(layout) ==
eos::common::LayoutId::kRaidDP) {
redundancyObj = new eos::fst::RaidDpLayout(
nullptr, layout, nullptr, nullptr, stripes.front().c_str(), 0, false);
} else {
redundancyObj = new eos::fst::ReedSLayout(
nullptr, layout, nullptr, nullptr, stripes.front().c_str(), 0, false);
}
if (redundancyObj->OpenPio(stripes, 0, 0, opaqueInfo.c_str())) {
redundancyObj->Close();
delete redundancyObj;
return false;
}
uint32_t offsetXrd = 0;
xsObj->Reset();
while (true) {
int64_t nread = redundancyObj->Read(offsetXrd, buffer, DEFAULTBUFFERSIZE);
if (nread == 0) {
break;
}
if (nread == -1) {
fprintf(stderr, "error: could not read from local stripes\n");
redundancyObj->Close();
delete redundancyObj;
return false;
}
xsObj->Add(buffer, nread, offsetXrd);
offsetXrd += nread;
}
redundancyObj->Close();
delete redundancyObj;
xsObj->Finalize();
return !strcmp(xsObj->GetHexChecksum(), XS.c_str());
}
void
cleanup(int code, const std::vector& stripePaths)
{
for (const auto& path : stripePaths) {
if (std::remove(path.c_str()) != 0) {
fprintf(stderr, "Could not cleanup file: %s\n", path.c_str());
}
}
exit(code);
}
int
getStripeId(const std::string& path)
{
auto file{eos::fst::FileIoPlugin::GetIoObject(path)};
if (file->fileOpen(0, 0)) {
fprintf(stderr, "Could not open file %s\n", path.c_str());
return -1;
}
auto* hd = new eos::fst::HeaderCRC(0, 0);
hd->ReadFromFile(file, 0);
int const id = hd->GetIdStripe();
delete hd;
return id;
}
//------------------------------------------------------------------------------
// Main
//------------------------------------------------------------------------------
int
main(int argc, char* argv[])
{
if (argc != 2) {
return -1;
}
std::string location = argv[1];
auto [url, filePath] = parseLocation(location);
std::string opaqueResponse = openOpaque(url, filePath);
auto* opaqueEnv = new XrdOucEnv(opaqueResponse.c_str());
std::string opaqueInfo = strstr(opaqueResponse.c_str(), "&mgm.logid");
eos::common::LayoutId::layoutid_t layout = opaqueEnv->GetInt("mgm.lid");
if (!eos::common::LayoutId::IsRain(layout)) {
fprintf(stderr, "Layout is not rain\n");
exit(-1);
}
int const nStripes = (int)eos::common::LayoutId::GetStripeNumber(layout) + 1;
int const nParityStripes =
(int)eos::common::LayoutId::GetRedundancyStripeNumber(layout);
int const nDataStripes = nStripes - nParityStripes;
fprintf(stdout, "Found file with %d stripes (%d data, %d parity)\n", nStripes,
nDataStripes, nParityStripes);
int qpos = filePath.rfind('?');
if (qpos != STR_NPOS) {
opaqueInfo += "&";
opaqueInfo += filePath.substr(qpos + 1);
filePath.erase(qpos);
}
std::string pio;
std::string tag;
std::vector stripeUrls;
stripeUrls.reserve(nStripes);
for (int i = 0; i < nStripes; i++) {
tag = SSTR("pio." << i);
if (!opaqueEnv->Get(tag.c_str())) {
fprintf(stderr, "msg=\"empty pio url in mgm response\"");
exit(-1);
}
pio = opaqueEnv->Get(tag.c_str());
stripeUrls.emplace_back(SSTR("root://" << pio << "/" << filePath));
}
delete opaqueEnv;
std::string XS = getCheckSum(url, filePath);
eos::fst::RainMetaLayout* redundancyObj = new eos::fst::ReedSLayout(
nullptr, layout, nullptr, nullptr, stripeUrls.front().c_str(), 0, false);
if (redundancyObj->OpenPio(stripeUrls, 0, 0, opaqueInfo.c_str())) {
fprintf(stderr, "error: can not open RAID object for read/write\n");
exit(-EIO);
}
char* buffer = new char[DEFAULTBUFFERSIZE];
int pos = filePath.rfind('/');
std::string fileName = filePath.substr(pos + 1);
std::vector stripePaths;
stripePaths.reserve(nStripes);
for (int i = 0; i < nStripes; ++i) {
std::string dstPath =
SSTR("/var/tmp/eos-rain-check." << fileName << '.' << std::to_string(i));
int dst = open(dstPath.c_str(), O_RDWR | O_TRUNC | O_CREAT | O_CLOEXEC,
S_IRUSR | S_IWUSR);
if (dst == -1) {
fprintf(stderr, "Could not create destination file: %s\n",
dstPath.c_str());
cleanup(-1, stripePaths);
}
stripePaths.emplace_back(dstPath);
uint32_t offsetXrd = 0;
while (true) {
int64_t nread =
redundancyObj->ReadStripe(offsetXrd, buffer, DEFAULTBUFFERSIZE, i);
offsetXrd += nread;
if (nread == 0) {
close(dst);
break;
}
if (nread == -1) {
fprintf(stderr, "stripe %d located %s has invalid data\n",
getStripeId(stripePaths[i]), stripeUrls[i].c_str());
close(dst);
break;
}
if (write(dst, buffer, nread) != nread) {
fprintf(stderr, "Could not write to file: %s\n", dstPath.c_str());
cleanup(-1, stripePaths);
}
}
}
redundancyObj->Close();
delete redundancyObj;
std::vector combinations(nStripes, false);
std::fill(combinations.begin(), combinations.begin() + nDataStripes, true);
std::vector stripeCombination(nStripes, std::string());
std::set validStripes;
std::set unknownStripes;
std::set invalidStripes;
auto* xsObj = eos::fst::ChecksumPlugins::GetXsObj(
eos::common::LayoutId::GetChecksum(layout));
if (!xsObj) {
fprintf(stderr, "invalid xs_type\n");
cleanup(-1, stripePaths);
}
// Try to find a valid stripe combination
do {
for (int i = 0; i < nStripes; i++) {
if (combinations[i]) {
stripeCombination[i] = stripePaths[i];
} else {
stripeCombination[i] = "";
}
}
if (isValidStripeCombination(stripeCombination, XS, xsObj, layout,
opaqueInfo, buffer)) {
bool markInvalid = true;
for (int i = 0; i < nStripes; i++) {
if (combinations[i]) {
markInvalid = false;
validStripes.insert(i);
} else {
if (markInvalid) {
// All combinations involving this stripe have already been checked
invalidStripes.insert(i);
} else {
unknownStripes.insert(i);
}
}
}
break;
}
} while (std::prev_permutation(combinations.begin(), combinations.end()));
if (validStripes.empty()) {
fprintf(stderr,
"could not find enough valid stripes to reconstruct the file");
cleanup(-1, stripePaths);
}
// Found a valid combination, check the rest of the stripes
for (auto stripeId : unknownStripes) {
combinations.assign(nStripes, false);
// Try combinations with 1 unknown stripe and `nDataStripes - 1` valid
// stripes
combinations[stripeId] = true;
auto vsid = validStripes.begin();
for (int i = 0; i < nDataStripes - 1; i++, vsid++) {
combinations[*vsid] = true;
}
for (int i = 0; i < nStripes; i++) {
// Paths need to keep the same idx in `stripesPath` and `pathsCombination`
if (combinations[i]) {
stripeCombination[i] = stripePaths[i];
} else {
stripeCombination[i] = "";
}
}
if (isValidStripeCombination(stripeCombination, XS, xsObj, layout,
opaqueInfo, buffer)) {
validStripes.insert(stripeId);
} else {
invalidStripes.insert(stripeId);
}
}
for (auto i : invalidStripes) {
fprintf(stderr, "stripe %d with path %s is invalid\n",
getStripeId(stripePaths[i]), stripeUrls[i].c_str());
}
cleanup(0, stripePaths);
}