/************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2019 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 "namespace/ns_quarkdb/inspector/Inspector.hh" #include "namespace/ns_quarkdb/explorer/NamespaceExplorer.hh" #include "namespace/ns_quarkdb/persistency/MetadataFetcher.hh" #include "namespace/ns_quarkdb/inspector/ContainerScanner.hh" #include "namespace/ns_quarkdb/inspector/FileScanner.hh" #include "namespace/ns_quarkdb/inspector/Printing.hh" #include "namespace/ns_quarkdb/inspector/OutputSink.hh" #include "namespace/ns_quarkdb/inspector/FileMetadataFilter.hh" #include "namespace/ns_quarkdb/FileMD.hh" #include "namespace/ns_quarkdb/ContainerMD.hh" #include "namespace/ns_quarkdb/persistency/RequestBuilder.hh" #include "namespace/ns_quarkdb/persistency/FileSystemIterator.hh" #include "namespace/ns_quarkdb/accounting/FileSystemHandler.hh" #include "namespace/ns_quarkdb/Constants.hh" #include "namespace/utils/Checksum.hh" #include "namespace/Constants.hh" #include "common/LayoutId.hh" #include "common/IntervalStopwatch.hh" #include "common/InodeTranslator.hh" #include "common/ParseUtils.hh" #include "common/StringUtils.hh" #include "common/config/ConfigParsing.hh" #include #include #include #include #include #include EOSNSNAMESPACE_BEGIN //------------------------------------------------------------------------------ // Escape non-printable string //------------------------------------------------------------------------------ static std::string escapeNonPrintable(const std::string& str) { std::stringstream ss; for (size_t i = 0; i < str.size(); i++) { if (isprint(str[i])) { ss << str[i]; } else if (str[i] == '\0') { ss << "\\x00"; } else { char buff[16]; snprintf(buff, 16, "\\x%02X", (unsigned char) str[i]); ss << buff; } } return ss.str(); } //------------------------------------------------------------------------------ // Turn bool to yes / no //------------------------------------------------------------------------------ static std::string toYesOrNo(bool val) { if (val) { return "Yes"; } return "No"; } //------------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------------ Inspector::Inspector(qclient::QClient& qcl, OutputSink& sink) : mQcl(qcl), mOutputSink(sink) { } //------------------------------------------------------------------------------ // Load configuration //------------------------------------------------------------------------------ bool Inspector::loadConfiguration() { qclient::redisReplyPtr reply = mQcl.exec("HGETALL", "eos-config:default").get(); qclient::HgetallParser parser(reply); if (!parser.ok()) { return false; } mgmConfiguration = parser.value(); for (auto it = mgmConfiguration.begin(); it != mgmConfiguration.end(); it++) { if (eos::common::startsWith(it->first, "fs:")) { std::map fsConfig; if (eos::common::ConfigParsing::parseFilesystemConfig(it->second, fsConfig)) { if (fsConfig.find("id") != fsConfig.end()) { int64_t fsid; if (common::ParseInt64(fsConfig["id"], fsid)) { validFsIds.insert(fsid); } } } } } return true; } //------------------------------------------------------------------------------ // Activate the given metadata filter //------------------------------------------------------------------------------ void Inspector::setMetadataFilter(std::unique_ptr filter) { mMetadataFilter = std::move(filter); } //------------------------------------------------------------------------------ // Is the connection to QDB ok? If not, pointless to run anything else. //------------------------------------------------------------------------------ bool Inspector::checkConnection(std::string& err) { qclient::redisReplyPtr reply = mQcl.exec("PING").get(); if (!reply) { err = "Could not connect to the given QDB cluster"; return false; } if (reply->type != REDIS_REPLY_STATUS || std::string(reply->str, reply->len) != "PONG") { err = SSTR("Received unexpected response in checkConnection: " << qclient::describeRedisReply(reply)); return false; } return true; } //------------------------------------------------------------------------------ // Construct path //------------------------------------------------------------------------------ static std::string constructPath(const std::string& rootPath, const std::string &fullPath, bool relative) { if (relative) { return fullPath.substr(rootPath.size()); } return fullPath; } class ExpansionTrim : public ExpansionDecider { public: ExpansionTrim(std::regex pathsToTrim) : trimRegex(pathsToTrim) {} virtual bool shouldExpandContainer(const eos::ns::ContainerMdProto& proto, const eos::IContainerMD::XAttrMap& attrs, const std::string& fullPath) override { return !std::regex_search(fullPath, trimRegex); } private: std::regex trimRegex; }; //------------------------------------------------------------------------------ // Scan contents of the given path. //------------------------------------------------------------------------------ int Inspector::scan(const std::string& rootPath, bool relative, bool rawPaths, bool noDirs, bool noFiles, uint32_t maxDepth, const std::string& trimPaths) { FilePrintingOptions filePrintingOpts; ContainerPrintingOptions containerPrintingOpts; ExplorationOptions explorerOpts; explorerOpts.ignoreFiles = noFiles; explorerOpts.depthLimit = maxDepth; if (!trimPaths.empty()) { try { auto expT = new ExpansionTrim(std::regex(trimPaths)); explorerOpts.expansionDecider.reset(expT); } catch (const std::regex_error& e) { std::cout << "regex_error caught: " << e.what() << '\n'; if (e.code() == std::regex_constants::error_brack) { std::cout << "The code was error_brack\n"; } } } NamespaceItem item; std::unique_ptr executor(new folly::IOThreadPoolExecutor(4)); std::unique_ptr explorer = nullptr; try { explorer = std::unique_ptr(new NamespaceExplorer(rootPath, explorerOpts, mQcl, executor.get())); } catch (const eos::MDException& exc) { mOutputSink.err(SSTR("NamespaceExplorer -- " << exc.what())); return 1; } while (explorer->fetch(item)) { if (noDirs && !item.isFile) { continue; } std::string outputPath = constructPath(rootPath, item.fullPath, relative); if (rawPaths) { mOutputSink.print(outputPath); continue; } if (item.isFile) { mOutputSink.printWithCustomPath(item.fileMd, filePrintingOpts, outputPath); continue; } if (!item.isFile) { mOutputSink.printWithCustomPath(item.containerMd, containerPrintingOpts, outputPath); continue; } } return 0; } //------------------------------------------------------------------------------ // Dump contents of the given path. ERRNO-like integer return value, 0 // means no error. //------------------------------------------------------------------------------ int Inspector::dump(const std::string& dumpPath, bool relative, bool rawPaths, bool noDirs, bool noFiles, bool showSize, bool showMtime, const std::string& attrQuery, std::ostream& out) { ExplorationOptions explorerOpts; explorerOpts.ignoreFiles = noFiles; std::unique_ptr executor(new folly::IOThreadPoolExecutor(4)); NamespaceItem item; std::unique_ptr explorer = nullptr; try { explorer = std::unique_ptr(new NamespaceExplorer(dumpPath, explorerOpts, mQcl, executor.get())); } catch (const eos::MDException& exc) { mOutputSink.err(SSTR("NamespaceExplorer -- " << exc.what())); return 1; } while (explorer->fetch(item)) { if (noDirs && !item.isFile) { continue; } if (!attrQuery.empty()) { out << " " << attrQuery << "="; if (!item.isFile) { if (item.containerMd.xattrs().count(attrQuery) != 0) { out << item.containerMd.xattrs().at(attrQuery) << " "; } else { out << " "; } } else { if (item.fileMd.xattrs().count(attrQuery) != 0) { out << item.fileMd.xattrs().at(attrQuery) << " "; } else { out << " "; } } } if (!rawPaths) { out << "path="; } if (relative) { out << item.fullPath.substr(dumpPath.size()); } else { out << item.fullPath; } if (!rawPaths && item.isFile) { out << " id=" << item.fileMd.id(); std::string xs; eos::appendChecksumOnStringProtobuf(item.fileMd, xs); out << " xs=" << xs; } if (showSize && item.isFile) { out << " size=" << item.fileMd.size(); } if (showMtime && item.isFile) { out << " mtime=" << Printing::timespecToTimestamp(Printing::parseTimespec( item.fileMd.mtime())); } out << std::endl; } return 0; } //------------------------------------------------------------------------------ // Fetch path or name from a combination of ContainerMdProto + // ContainerScanner::Item, return as much information as is available //------------------------------------------------------------------------------ std::string fetchNameOrPath(const eos::ns::ContainerMdProto& proto, ContainerScanner::Item& item) { item.fullPath.wait(); if (item.fullPath.hasException()) { return proto.name(); } std::string fullPath = std::move(item.fullPath).get(); if (fullPath.empty()) { return proto.name(); } return fullPath; } //------------------------------------------------------------------------------ // Safe uint64_t get, without exceptions. Return 0 in case of exception. //------------------------------------------------------------------------------ uint64_t safeGet(folly::Future& fut) { fut.wait(); if (fut.hasException()) { return 0; } uint64_t val = std::move(fut).get(); fut = val; return val; } //------------------------------------------------------------------------------ // Scan all directories in the namespace, and print out some information // about each one. (even potentially unreachable directories) //------------------------------------------------------------------------------ int Inspector::scanDirs(bool onlyNoAttrs, bool fullPaths, bool countContents, size_t countThreshold) { if (countThreshold > 0) { countContents = true; } ContainerPrintingOptions opts; ContainerScanner containerScanner(mQcl, fullPaths, countContents); while (containerScanner.valid()) { eos::ns::ContainerMdProto proto; ContainerScanner::Item item; if (!containerScanner.getItem(proto, &item)) { break; } if (onlyNoAttrs && !proto.xattrs().empty()) { containerScanner.next(); continue; } if (countThreshold > 0 && (safeGet(item.fileCount) + safeGet(item.containerCount)) < countThreshold) { containerScanner.next(); continue; } mOutputSink.print(proto, opts, item, countContents); containerScanner.next(); } std::string errorString; if (containerScanner.hasError(errorString)) { mOutputSink.err(errorString); return 1; } return 0; } //------------------------------------------------------------------------------ // Fetch path or name from a combination of FileMdProto + // FileScanner::Item, return as much information as is available //------------------------------------------------------------------------------ std::string fetchNameOrPath(const eos::ns::FileMdProto& proto, FileScanner::Item& item) { item.fullPath.wait(); if (item.fullPath.hasException()) { return proto.name(); } std::string fullPath = std::move(item.fullPath).get(); if (fullPath.empty()) { return proto.name(); } return SSTR(fullPath << proto.name()); } //------------------------------------------------------------------------------ // Are all locations in the given set? //------------------------------------------------------------------------------ template bool allInSet(const T& vec, const std::set& targetSet) { for (auto it = vec.begin(); it != vec.end(); it++) { if (targetSet.find(*it) == targetSet.end()) { return false; } } return true; } //------------------------------------------------------------------------------ // Cross-check locations with MGM config //------------------------------------------------------------------------------ static bool checkLocations(const eos::ns::FileMdProto& proto, const std::set& validFsIds) { if (proto.cont_id() == 0) { return true; } return allInSet(proto.locations(), validFsIds); } //------------------------------------------------------------------------------ // Scan all file metadata in the namespace, and print out some information // about each one. (even potentially unreachable ones) //------------------------------------------------------------------------------ int Inspector::scanFileMetadata(bool onlySizes, bool fullPaths, bool findUnknownFsids) { if (findUnknownFsids && !loadConfiguration()) { mOutputSink.err("could not load MGM configuration -- necessary when using --find-unknown-fsids"); return -1; } FileScanner fileScanner(mQcl, fullPaths); FilePrintingOptions opts; while (fileScanner.valid()) { FileScanner::Item item; eos::ns::FileMdProto proto; if (!fileScanner.getItem(proto, &item)) { break; } if (findUnknownFsids && checkLocations(proto, validFsIds)) { fileScanner.next(); continue; } if (mMetadataFilter && !mMetadataFilter->check(proto)) { fileScanner.next(); continue; } if (onlySizes) { mOutputSink.print(std::to_string(proto.size())); } else { mOutputSink.print(proto, opts, item); } fileScanner.next(); } std::string errorString; if (fileScanner.hasError(errorString)) { mOutputSink.err(errorString); return 1; } return 0; } //------------------------------------------------------------------------------ // Scan all deathrow entries //------------------------------------------------------------------------------ int Inspector::scanDeathrow(std::ostream& out, std::ostream& err) { FileScanner fileScanner(mQcl); while (fileScanner.valid()) { FileScanner::Item item; eos::ns::FileMdProto proto; if (!fileScanner.getItem(proto, &item)) { break; } if (proto.cont_id() != 0) { break; } std::string xs; eos::appendChecksumOnStringProtobuf(proto, xs); out << "fid=" << proto.id() << " name=" << fetchNameOrPath(proto, item) << " pid=" << proto.cont_id() << " uid=" << proto.uid() << " size=" << proto.size() << " xs=" << xs << std::endl; fileScanner.next(); } std::string errorString; if (fileScanner.hasError(errorString)) { err << errorString; return 1; } return 0; } //------------------------------------------------------------------------------ // Forcefully overwrite the given ContainerMD - USE WITH CAUTION //------------------------------------------------------------------------------ int Inspector::overwriteContainerMD(bool dryRun, uint64_t id, uint64_t parentId, const std::string& name, std::ostream& out, std::ostream& err) { eos::ns::ContainerMdProto val; val.set_id(id); val.set_parent_id(parentId); val.set_name(name); QuarkContainerMD containerMD; containerMD.initialize(std::move(val), IContainerMD::FileMap(), IContainerMD::ContainerMap()); std::vector requests; requests.emplace_back(RequestBuilder::writeContainerProto(&containerMD)); executeRequestBatch(requests, {}, dryRun, out, err); return 0; } //------------------------------------------------------------------------------ // Serialize locations vector //------------------------------------------------------------------------------ template static std::string serializeLocations(const T& vec) { std::ostringstream stream; for (int i = 0; i < vec.size(); i++) { stream << vec[i]; if (i != vec.size() - 1) { stream << ","; } } return stream.str(); } //------------------------------------------------------------------------------ // Check if we should print based on name and internal filter //------------------------------------------------------------------------------ bool shouldPrint(bool filterInternal, const std::string& fullPath) { if (!filterInternal) { return true; } //---------------------------------------------------------------------------- // Filter out aborted atomic uploads.. //---------------------------------------------------------------------------- if (fullPath.find("/.sys.a#.") != std::string::npos) { return false; } //---------------------------------------------------------------------------- // Filter out files under proc.. //---------------------------------------------------------------------------- if (common::startsWith(fullPath, "/eos/")) { std::string chopped = std::string(fullPath.c_str() + 5, fullPath.size() - 5); size_t nextSlash = chopped.find("/"); if (nextSlash != std::string::npos) { chopped = std::string(chopped.c_str() + nextSlash, chopped.size() - nextSlash); if (common::startsWith(chopped, "/proc/")) { return false; } } } //---------------------------------------------------------------------------- // Filter out versioned files.. //---------------------------------------------------------------------------- if (fullPath.find("/.sys.v#.") != std::string::npos) { return false; } return true; } //------------------------------------------------------------------------------ // Find files with layout = 1 replica //------------------------------------------------------------------------------ int Inspector::oneReplicaLayout(bool showName, bool showPaths, bool filterInternal, std::ostream& out, std::ostream& err, bool json = false) { FileScanner fileScanner(mQcl, showPaths | filterInternal); common::IntervalStopwatch stopwatch(std::chrono::seconds(10)); Json::StreamWriterBuilder builder; builder["indentation"] = ""; // or whatever you like std::unique_ptr writer(builder.newStreamWriter()); while (fileScanner.valid()) { eos::ns::FileMdProto proto; FileScanner::Item item; Json::Value jsonObj; if (!fileScanner.getItem(proto, &item)) { break; } int64_t actual = proto.locations().size(); int64_t expected = eos::common::LayoutId::GetStripeNumber( proto.layout_id()) + 1; int64_t unlinked = proto.unlink_locations().size(); int64_t size = proto.size(); if (!proto.link_name().empty()) { expected = 0; } if (expected == 1 && size != 0 && shouldPrint(filterInternal, fetchNameOrPath(proto, item))) { if (json) { jsonObj["fid"] = (Json::Value::UInt64) proto.id(); if (showName || showPaths) { jsonObj["path"] = fetchNameOrPath(proto, item); } jsonObj["pid"] = (Json::Value::UInt64) proto.cont_id(); jsonObj["size"] = (Json::Value::UInt64) size; jsonObj["actual_stripes"] = (Json::Value::UInt64) actual; jsonObj["expected_stripes"] = (Json::Value::UInt64) expected; jsonObj["unlinked_stripes"] = (Json::Value::UInt64) unlinked; jsonObj["locations"] = serializeLocations(proto.locations()); jsonObj["unlinked_locations"] = serializeLocations(proto.unlink_locations()); jsonObj["mtime"] = std::stod(Printing::timespecToTimestamp( Printing::parseTimespec(proto.mtime()))); jsonObj["ctime"] = std::stod(Printing::timespecToTimestamp( Printing::parseTimespec(proto.ctime()))); writer->write(jsonObj, &out); out << std::endl; } else { out << "id=" << proto.id(); if (showName || showPaths) { out << " name=" << fetchNameOrPath(proto, item); } out << " pid=" << proto.cont_id() << " size=" << size << " actual_stripes=" << actual << " expected_stripes=" << expected << " unlinked_stripes=" << unlinked << " locations=" << serializeLocations(proto.locations()) << " unlinked_locations=" << serializeLocations(proto.unlink_locations()) << " mtime=" << Printing::timespecToTimestamp(Printing::parseTimespec( proto.mtime())) << " ctime=" << Printing::timespecToTimestamp(Printing::parseTimespec( proto.ctime())) << std::endl; } } fileScanner.next(); if (stopwatch.restartIfExpired()) { err << "Progress: Processed " << fileScanner.getScannedSoFar() << " files so far..." << std::endl; } } std::string errorString; if (fileScanner.hasError(errorString)) { err << errorString; return 1; } return 0; } //---------------------------------------------------------------------------- // Find files with non-nominal number of stripes (replicas) //---------------------------------------------------------------------------- int Inspector::stripediff(bool json = false, bool minimal = false) { FilePrintingOptions filePrintingOpts; FileScanner fileScanner(mQcl, !minimal); while (fileScanner.valid()) { FileScanner::Item item; eos::ns::FileMdProto proto; if (minimal) { if (!fileScanner.getItem(proto)) { break; } } else { if (!fileScanner.getItem(proto, &item)) { break; } } int64_t actual = proto.locations().size(); int64_t expected = eos::common::LayoutId::GetStripeNumber( proto.layout_id()) + 1; int64_t unlinked = proto.unlink_locations().size(); int64_t size = proto.size(); Json::Value jsonObj; if (!proto.link_name().empty()) { expected = 0; } if (actual != expected && size != 0) { if (!minimal) { // Use output sink for complete report / json std::map extended; extended["path"] = fetchNameOrPath(proto, item); extended["actual_stripes"] = std::to_string(actual); extended["expected_stripes"] = std::to_string(expected); extended["unlinked_stripes"] = std::to_string(unlinked); mOutputSink.printWithAdditionalFields(proto, filePrintingOpts, extended); } else { if (json) { jsonObj["fid"] = (Json::Value::UInt64) proto.id(); jsonObj["pid"] = (Json::Value::UInt64) proto.cont_id(); jsonObj["size"] = (Json::Value::UInt64) size; jsonObj["actual_stripes"] = (Json::Value::UInt64) actual; jsonObj["expected_stripes"] = (Json::Value::UInt64) expected; jsonObj["unlinked_stripes"] = (Json::Value::UInt64) unlinked; jsonObj["locations"] = serializeLocations(proto.locations()); jsonObj["unlinked_locations"] = serializeLocations(proto.unlink_locations()); jsonObj["mtime"] = std::stod(Printing::timespecToTimestamp( Printing::parseTimespec(proto.mtime()))); jsonObj["ctime"] = std::stod(Printing::timespecToTimestamp( Printing::parseTimespec(proto.ctime()))); mOutputSink.print(jsonObj); // writer->write(jsonObj,&out); // out << std::endl; } else { std::stringstream out; out << "fid=" << proto.id() << " container=" << proto.cont_id() << " size=" << size << " actual_stripes=" << actual << " expected_stripes=" << expected << " unlinked_stripes=" << unlinked << " locations=" << serializeLocations( proto.locations()) << " unlinked_locations=" << serializeLocations(proto.unlink_locations()) << " mtime=" << Printing::timespecToTimestamp(Printing::parseTimespec( proto.mtime())) << " ctime=" << Printing::timespecToTimestamp(Printing::parseTimespec( proto.ctime())); mOutputSink.print(out.str()); } } } else if (!minimal) { (void) fetchNameOrPath(proto, item); //Not to leak when path resolution was triggered } fileScanner.next(); } std::string errorString; if (fileScanner.hasError(errorString)) { mOutputSink.err(errorString); return 1; } return 0; } class ConflictSet { public: std::set files; std::set containers; bool hasConflict() const { return (files.size() + containers.size()) > 1; } std::string serializeFiles() const { return serialize(files); } std::string serializeContainers() const { return serialize(containers); } void printMultipleLines(const std::string& name, uint64_t parentContainer, std::ostream& out) const { if (!hasConflict()) { return; } for (auto it = files.begin(); it != files.end(); it++) { out << "name=" << name << " under-container=" << parentContainer << " conflicting-file=" << *it << std::endl; } for (auto it = containers.begin(); it != containers.end(); it++) { out << "name=" << name << " under-container=" << parentContainer << " conflicting-container=" << *it << std::endl; } } void printSingleLine(const std::string& name, uint64_t parentContainer, std::ostream& out) const { if (!hasConflict()) { return; } out << "name=" << name << " under-container=" << parentContainer; if (!files.empty()) { out << " conflicting-files=" << serializeFiles(); } if (!containers.empty()) { out << " conflicting-containers=" << serializeContainers(); } out << std::endl; } private: static std::string serialize(const std::set& target) { std::ostringstream ss; for (auto it = target.begin(); it != target.end(); it++) { if (std::next(it) == target.end()) { ss << *it; } else { ss << *it << ","; } } return ss.str(); } }; //------------------------------------------------------------------------------ // Find conflicts //------------------------------------------------------------------------------ void findConflicts(bool onePerLine, std::ostream& out, uint64_t parentContainer, const std::map& nameMapping) { for (auto it = nameMapping.begin(); it != nameMapping.end(); it++) { const ConflictSet& conflictSet = it->second; if (!onePerLine) { conflictSet.printSingleLine(it->first, parentContainer, out); } else { conflictSet.printMultipleLines(it->first, parentContainer, out); } } } //------------------------------------------------------------------------------ // Check intra-container conflicts, such as a container having two entries // with the name name. //------------------------------------------------------------------------------ int Inspector::checkNamingConflicts(bool onePerLine, std::ostream& out, std::ostream& err) { std::string errorString; ContainerScanner containerScanner(mQcl); FileScanner fileScanner(mQcl); common::IntervalStopwatch stopwatch(std::chrono::seconds(10)); eos::ns::FileMdProto fileProto; fileProto.set_cont_id(0); while (containerScanner.valid()) { eos::ns::ContainerMdProto proto; if (!containerScanner.getItem(proto)) { break; } if (proto.parent_id() == 0) { containerScanner.next(); continue; } uint64_t currentParentId = proto.parent_id(); std::map nameMapping; while (containerScanner.valid() && proto.parent_id() == currentParentId) { nameMapping[proto.name()].containers.insert(proto.id()); containerScanner.next(); containerScanner.getItem(proto); } while (fileScanner.valid() && fileProto.cont_id() <= currentParentId) { if (fileProto.cont_id() == currentParentId) { nameMapping[fileProto.name()].files.insert(fileProto.id()); } fileScanner.next(); fileScanner.getItem(fileProto); } findConflicts(onePerLine, out, currentParentId, nameMapping); nameMapping.clear(); } if (containerScanner.hasError(errorString) || fileScanner.hasError(errorString)) { err << errorString; return 1; } return 0; } //------------------------------------------------------------------------------ // Check if file / container name is cursed //------------------------------------------------------------------------------ static bool isCursedName(const std::string& name) { if (name == "" || name == "." || name == ".." || name.find("/") != std::string::npos) { return true; } return false; } //------------------------------------------------------------------------------ // Search for files / containers with cursed names //------------------------------------------------------------------------------ int Inspector::checkCursedNames(std::ostream& out, std::ostream& err) { ContainerScanner containerScanner(mQcl); while (containerScanner.valid()) { eos::ns::ContainerMdProto proto; if (!containerScanner.getItem(proto)) { break; } if (proto.id() != 1 && isCursedName(proto.name())) { out << "cid=" << proto.id() << " cursed-name=" << escapeNonPrintable( proto.name()) << std::endl; } containerScanner.next(); } FileScanner fileScanner(mQcl); while (fileScanner.valid()) { eos::ns::FileMdProto proto; if (!fileScanner.getItem(proto)) { break; } if (isCursedName(proto.name())) { out << "fid=" << proto.id() << " cursed-name=" << escapeNonPrintable( proto.name()) << std::endl; } fileScanner.next(); } std::string errorString; if (containerScanner.hasError(errorString) || fileScanner.hasError(errorString)) { err << errorString; return 1; } return 0; } //------------------------------------------------------------------------------ // Helper struct used in checkOrphans //------------------------------------------------------------------------------ struct PendingFile { folly::Future validParent; eos::ns::FileMdProto proto; PendingFile(folly::Future&& f, const eos::ns::FileMdProto& p) : validParent(std::move(f)), proto(p) {} }; void consumePendingEntries(std::deque& futs, bool unconditional, std::ostream& out) { while (!futs.empty() && (unconditional || futs.front().validParent.isReady())) { PendingFile& entry = futs.front(); entry.validParent.wait(); if (entry.validParent.hasException()) { out << "ERROR: Exception occurred when fetching container " << entry.proto.cont_id() << " as part of checking existence of parent of container " << entry.proto.id() << std::endl; } else if (std::move(entry.validParent).get() == false) { out << "file-id=" << entry.proto.id() << " invalid-parent-id=" << entry.proto.cont_id() << " size=" << entry.proto.size() << " locations=" << serializeLocations(entry.proto.locations()) << " unlinked-locations=" << serializeLocations(entry.proto.unlink_locations()) << std::endl; } futs.pop_front(); } } struct PendingContainer { folly::Future validParent; eos::ns::ContainerMdProto proto; PendingContainer(folly::Future&& f, const eos::ns::ContainerMdProto& p) : validParent(std::move(f)), proto(p) {} }; void consumePendingEntries(std::deque& futs, bool unconditional, std::ostream& out) { while (!futs.empty() && (unconditional || futs.front().validParent.isReady())) { PendingContainer& entry = futs.front(); entry.validParent.wait(); if (entry.validParent.hasException()) { out << "ERROR: Exception occurred when fetching container " << entry.proto.parent_id() << " as part of checking existence of parent of container " << entry.proto.id() << std::endl; } else if (std::move(entry.validParent).get() == false) { out << "container-id=" << entry.proto.id() << " invalid-parent-id=" << entry.proto.parent_id() << std::endl; } futs.pop_front(); } } //------------------------------------------------------------------------------ // Find orphan files and orphan directories //------------------------------------------------------------------------------ int Inspector::checkOrphans(std::ostream& out, std::ostream& err) { //---------------------------------------------------------------------------- // Look for orphan containers.. //---------------------------------------------------------------------------- std::string errorString; ContainerScanner containerScanner(mQcl); common::IntervalStopwatch stopwatch(std::chrono::seconds(10)); std::deque containers; while (containerScanner.valid()) { consumePendingEntries(containers, false, out); eos::ns::ContainerMdProto proto; if (!containerScanner.getItem(proto)) { break; } containers.emplace_back( MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(proto.parent_id())), proto ); if (stopwatch.restartIfExpired()) { err << "Progress: Processed " << containerScanner.getScannedSoFar() << " containers so far..." << std::endl; } containerScanner.next(); } consumePendingEntries(containers, true, out); if (containerScanner.hasError(errorString)) { err << errorString; return 1; } err << "All containers processed, checking files..." << std::endl; //---------------------------------------------------------------------------- // Look for orphan files.. //---------------------------------------------------------------------------- FileScanner fileScanner(mQcl); std::deque files; while (fileScanner.valid()) { consumePendingEntries(files, false, out); eos::ns::FileMdProto proto; if (!fileScanner.getItem(proto)) { break; } files.emplace_back( MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(proto.cont_id())), proto ); if (stopwatch.restartIfExpired()) { err << "Progress: Processed " << fileScanner.getScannedSoFar() << " files so far..." << std::endl; } fileScanner.next(); } consumePendingEntries(files, true, out); if (fileScanner.hasError(errorString)) { err << errorString; return 1; } return 0; } //------------------------------------------------------------------------------ // Helper struct for checkFsViewMissing //------------------------------------------------------------------------------ struct FsViewItemExists { folly::Future valid; eos::ns::FileMdProto proto; int64_t location; bool unlinked; FsViewItemExists(folly::Future&& v, const eos::ns::FileMdProto& pr, int64_t loc, bool unl) : valid(std::move(v)), proto(pr), location(loc), unlinked(unl) {} }; void consumeFsViewQueue(std::deque& futs, bool unconditional, std::ostream& out) { while (!futs.empty() && (unconditional || futs.front().valid.isReady())) { FsViewItemExists& entry = futs.front(); entry.valid.wait(); if (entry.valid.hasException()) { out << "ERROR: Exception occurred when checking validity of location " << entry.location << " (unlinked=" << entry.unlinked << ") of FileMD " << entry.proto.id() << std::endl; } else if (std::move(entry.valid).get() == false) { if (entry.unlinked) { out << "id=" << entry.proto.id() << " parent-id=" << entry.proto.cont_id() << " size=" << entry.proto.size() << " locations=" << serializeLocations( entry.proto.locations()) << " unlinked-locations=" << serializeLocations( entry.proto.unlink_locations()) << " missing-unlinked-location=" << entry.location << std::endl; } else { out << "id=" << entry.proto.id() << " parent-id=" << entry.proto.cont_id() << " size=" << entry.proto.size() << " locations=" << serializeLocations( entry.proto.locations()) << " unlinked-locations=" << serializeLocations( entry.proto.unlink_locations()) << " missing-location=" << entry.location << std::endl; } } futs.pop_front(); } } //------------------------------------------------------------------------------ // Search for holes in FsView: Items which should be in FsView according to // FMD locations / unlinked locations, but are not there. //------------------------------------------------------------------------------ int Inspector::checkFsViewMissing(std::ostream& out, std::ostream& err) { //---------------------------------------------------------------------------- // Search through all FileMDs.. //---------------------------------------------------------------------------- std::deque queue; FileScanner fileScanner(mQcl); common::IntervalStopwatch stopwatch(std::chrono::seconds(10)); while (fileScanner.valid()) { consumeFsViewQueue(queue, false, out); eos::ns::FileMdProto proto; if (!fileScanner.getItem(proto)) { break; } for (auto it = proto.locations().cbegin(); it != proto.locations().cend(); it++) { queue.emplace_back(MetadataFetcher::locationExistsInFsView(mQcl, FileIdentifier(proto.id()), *it, false), proto, *it, false); } for (auto it = proto.unlink_locations().cbegin(); it != proto.unlink_locations().cend(); it++) { queue.emplace_back(MetadataFetcher::locationExistsInFsView(mQcl, FileIdentifier(proto.id()), *it, true), proto, *it, true); } if (stopwatch.restartIfExpired()) { err << "Progress: Processed " << fileScanner.getScannedSoFar() << " files so far" << std::endl; } fileScanner.next(); } consumeFsViewQueue(queue, true, out); std::string errorString; if (fileScanner.hasError(errorString)) { err << errorString; return 1; } return 0; } struct FsViewExpectInLocations { folly::Future proto; int64_t futureFid; int64_t expectedLocation; bool unlinked; FsViewExpectInLocations(folly::Future&& p, int64_t fid, int64_t expected, bool unl) : proto(std::move(p)), futureFid(fid), expectedLocation(expected), unlinked(unl) {} }; void consumeFsViewQueue(std::deque& futs, bool unconditional, std::ostream& out) { while (!futs.empty() && (unconditional || futs.front().proto.isReady())) { FsViewExpectInLocations& entry = futs.front(); entry.proto.wait(); if (entry.proto.hasException()) { out << "ERROR: Exception occurred when fetching file with id " << entry.futureFid << std::endl; } else if (!entry.unlinked) { eos::ns::FileMdProto proto = std::move(entry.proto).get(); bool found = false; for (auto it = proto.locations().cbegin(); it != proto.locations().cend(); it++) { if (*it == entry.expectedLocation) { found = true; break; } } if (!found) { out << "id=" << proto.id() << " parent-id=" << proto.cont_id() << " size=" << proto.size() << " locations=" << serializeLocations(proto.locations()) << " unlinked-locations=" << serializeLocations(proto.unlink_locations()) << " extra-location=" << entry.expectedLocation << std::endl; } } else { eos::ns::FileMdProto proto = std::move(entry.proto).get(); bool found = false; for (auto it = proto.unlink_locations().cbegin(); it != proto.unlink_locations().cend(); it++) { if (*it == entry.expectedLocation) { found = true; break; } } if (!found) { out << "id=" << proto.id() << " parent-id=" << proto.cont_id() << " size=" << proto.size() << " locations=" << serializeLocations(proto.locations()) << " unlinked-locations=" << serializeLocations(proto.unlink_locations()) << " extra-unlink-location=" << entry.expectedLocation << std::endl; } } futs.pop_front(); } } //------------------------------------------------------------------------------ // Search for elements which are present in FsView, but not FMD locations //------------------------------------------------------------------------------ int Inspector::checkFsViewExtra(std::ostream& out, std::ostream& err) { //---------------------------------------------------------------------------- // Scan through the entire filesystem view.. //---------------------------------------------------------------------------- std::deque queue; FileSystemIterator fsIter(mQcl); while (fsIter.valid()) { StreamingFileListIterator fsScanner(mQcl, fsIter.getRedisKey()); while (fsScanner.valid()) { consumeFsViewQueue(queue, false, out); queue.emplace_back(MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fsScanner.getElement())), fsScanner.getElement(), fsIter.getFileSystemID(), fsIter.isUnlinked() ); fsScanner.next(); } fsIter.next(); } consumeFsViewQueue(queue, true, out); return 0; } //------------------------------------------------------------------------------ // Search for shadow directories //------------------------------------------------------------------------------ int Inspector::checkShadowDirectories(std::ostream& out, std::ostream& err) { ContainerScanner containerScanner(mQcl); common::IntervalStopwatch stopwatch(std::chrono::seconds(10)); eos::ns::ContainerMdProto prevContainer; while (containerScanner.valid()) { eos::ns::ContainerMdProto proto; if (!containerScanner.getItem(proto)) { break; } if (proto.parent_id() != 0 && proto.name() == prevContainer.name() && proto.parent_id() == prevContainer.parent_id()) { out << "id=" << proto.id() << " name=" << proto.name() << " parent=" << proto.parent_id() << " mtime=" << Printing::timespecToTimestamp(Printing::parseTimespec( proto.mtime())) << " ctime=" << Printing::timespecToTimestamp(Printing::parseTimespec( proto.ctime())) << " is-quotanode=" << (proto.flags() & QUOTA_NODE_FLAG) << " conflicts-with=" << prevContainer.id() << std::endl; } prevContainer = std::move(proto); containerScanner.next(); } std::string errorString; if (containerScanner.hasError(errorString)) { err << errorString; return 1; } return 0; } //------------------------------------------------------------------------------ // Helper class to run next on destruction //------------------------------------------------------------------------------ class NextGuard { public: NextGuard(FileScanner& sc) : scanner(sc) {} ~NextGuard() { scanner.next(); } private: FileScanner& scanner; }; //------------------------------------------------------------------------------ // Hardlink information //------------------------------------------------------------------------------ struct HardlinkInfo { HardlinkInfo() {} HardlinkInfo(uint64_t t, const std::string& n) : target(t), name(n) {} uint64_t target; std::string name; }; struct InodeUseCount { InodeUseCount() : count(0) {} InodeUseCount(int64_t c, const std::string& n) : count(c), name(n) {} int64_t count; std::string name; }; //------------------------------------------------------------------------------ // Cross-check inode use counts against hardlink mappings //------------------------------------------------------------------------------ void crossCheckHardlinkMaps(std::map& inodeUseCount, std::map& hardlinkMapping, uint64_t parent, std::ostream& out) { std::set zeroGroup; for (auto it = inodeUseCount.begin(); it != inodeUseCount.end(); it++) { if (it->second.count == 0) { zeroGroup.insert(it->first); } } for (auto it = hardlinkMapping.begin(); it != hardlinkMapping.end(); it++) { auto useCount = inodeUseCount.find(it->second.target); if (useCount == inodeUseCount.end()) { out << "id=" << it->first << " name=" << it->second.name << " parent=" << parent << " invalid-target=" << it->second.target << std::endl; } else { inodeUseCount[it->second.target].count--; } } for (auto it = inodeUseCount.begin(); it != inodeUseCount.end(); it++) { if (it->second.count != 0) { out << "id=" << it->first << " name=" << it->second.name << " parent=" << parent << " reference-count-diff=" << it->second.count << std::endl; zeroGroup.erase(it->first); } } for (auto it = zeroGroup.begin(); it != zeroGroup.end(); it++) { out << "id=" << *it << " name=" << inodeUseCount[*it].name << " parent=" << parent << " true-zero-count" << std::endl; } } //------------------------------------------------------------------------------ // Check for corrupted ...eos.ino... hardlink-simulation files //------------------------------------------------------------------------------ int Inspector::checkSimulatedHardlinks(std::ostream& out, std::ostream& err) { FileScanner fileScanner(mQcl); common::InodeTranslator translator; std::map inodeUseCount; std::map hardlinkMapping; uint64_t currentContainer = 0; while (fileScanner.valid()) { eos::ns::FileMdProto proto; if (!fileScanner.getItem(proto)) { break; } NextGuard nextGuard(fileScanner); if (proto.cont_id() == 0) { continue; } if (proto.cont_id() != currentContainer) { crossCheckHardlinkMaps(inodeUseCount, hardlinkMapping, currentContainer, out); inodeUseCount.clear(); hardlinkMapping.clear(); } currentContainer = proto.cont_id(); auto it = proto.xattrs().find("sys.eos.mdino"); if (it != proto.xattrs().end()) { uint64_t inode = 0; if (!common::ParseUInt64(it->second.c_str(), inode)) { err << "Could not parse sys.eos.mdino: " << it->second.c_str() << std::endl; continue; } uint64_t target = translator.InodeToFid(inode); hardlinkMapping[proto.id()] = HardlinkInfo(target, proto.name()); continue; } it = proto.xattrs().find("sys.eos.nlink"); if (it != proto.xattrs().end()) { size_t count = atoi(it->second.c_str()); inodeUseCount[proto.id()] = InodeUseCount(count, proto.name()); } } return 0; } //------------------------------------------------------------------------------ // Print out _everything_ known about the given directory. //------------------------------------------------------------------------------ int Inspector::printContainerMD(uint64_t cid, bool withParents, std::ostream& out, std::ostream& err) { eos::ns::ContainerMdProto val; try { val = MetadataFetcher::getContainerFromId(mQcl, ContainerIdentifier(cid)).get(); } catch (const MDException& e) { err << "Error while fetching metadata for ContainerMD #" << cid << ": " << e.what() << std::endl; } Printing::printMultiline(val, out); try { std::string fullPath = MetadataFetcher::resolveFullPath(mQcl, ContainerIdentifier(val.id())).get(); out << "Full path: " << fullPath << std::endl; } catch (const MDException& e) { err << "Full path: Could not reconstruct" << std::endl; } IContainerMD::FileMap fileMap; IContainerMD::FileMap containerMap; try { fileMap = MetadataFetcher::getFileMap(mQcl, ContainerIdentifier(cid)).get(); } catch (const MDException& e) { err << "Error while fetching file map for ContainerMD #" << cid << ": " << e.what() << std::endl; } try { containerMap = MetadataFetcher::getContainerMap(mQcl, ContainerIdentifier(cid)).get(); } catch (const MDException& e) { err << "Error while fetching container map for ContainerMD #" << cid << ": " << e.what() << std::endl; } out << "------------------------------------------------" << std::endl; out << "FileMap:" << std::endl; for (auto it = fileMap.begin(); it != fileMap.end(); ++it) { out << it->first << ": " << it->second << std::endl; } out << "------------------------------------------------" << std::endl; out << "ContainerMap:" << std::endl; for (auto it = containerMap.begin(); it != containerMap.end(); ++it) { out << it->first << ": " << it->second << std::endl; } if (withParents && val.parent_id() != 0 && val.id() != val.parent_id()) { out << std::endl << std::endl << std::endl << std::endl << std::endl; return printContainerMD(val.parent_id(), withParents, out, err); } return 0; } //------------------------------------------------------------------------------ // Print out _everything_ known about the given file. //------------------------------------------------------------------------------ int Inspector::printFileMD(uint64_t fid, bool withParents, std::ostream& out, std::ostream& err) { eos::ns::FileMdProto val; try { val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get(); } catch (const MDException& e) { err << "Error while fetching metadata for FileMD #" << fid << ": " << e.what() << std::endl; return 1; } Printing::printMultiline(val, out); try { std::string fullPath = MetadataFetcher::resolveFullPath(mQcl, ContainerIdentifier(val.cont_id())).get(); out << "Full path: " << fullPath << val.name() << std::endl; } catch (const MDException& e) { err << "Full path: Could not reconstruct" << std::endl; } if (withParents && val.cont_id() != 0) { out << std::endl << std::endl << std::endl << std::endl << std::endl; return printContainerMD(val.cont_id(), withParents, out, err); } return 0; } //------------------------------------------------------------------------------ // Serialize RedisRequest //------------------------------------------------------------------------------ static std::string serializeRequest(const RedisRequest& req) { std::ostringstream ss; for (size_t i = 0; i < req.size(); i++) { ss << "\"" << escapeNonPrintable(req[i]) << "\"" << " "; } return ss.str(); } //------------------------------------------------------------------------------ //! Check if given path is a good choice as a destination for repaired //! files / containers //------------------------------------------------------------------------------ bool Inspector::isDestinationPathSane(const std::string& path, ContainerIdentifier& cid, std::ostream& out) { try { FileOrContainerIdentifier id = MetadataFetcher::resolvePathToID(mQcl, path).get(); if (id.isFile()) { out << "Destination path '" << path << "' is a file, not a directory." << std::endl; return false; } cid = id.toContainerIdentifier(); } catch (const MDException& e) { out << "Destination path '" << path << "' does not exist." << std::endl; return false; } if (cid == ContainerIdentifier(1) || cid == ContainerIdentifier(2) || cid == ContainerIdentifier(3)) { out << "Destination path '" << path << "' does not look like a good place, too top-level." << std::endl; return false; } return true; } //------------------------------------------------------------------------------ // Attempt to fix detached parent //------------------------------------------------------------------------------ int Inspector::fixDetachedParentContainer(bool dryRun, uint64_t cid, const std::string& destinationPath, std::ostream& out, std::ostream& err) { //---------------------------------------------------------------------------- // Ensure given container exists //---------------------------------------------------------------------------- if (!MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(cid)).get()) { out << "Container #" << cid << " does not exist." << std::endl; return 1; } //---------------------------------------------------------------------------- // Ensure given destination path is sane //---------------------------------------------------------------------------- ContainerIdentifier destination; if (!isDestinationPathSane(destinationPath, destination, out)) { return 1; } //---------------------------------------------------------------------------- // Try and figure out what the problem with the given container is //---------------------------------------------------------------------------- out << "Finding all parents of Container #" << cid << "..." << std::endl; uint64_t nextToCheck = cid; eos::ns::ContainerMdProto val; while (nextToCheck != 0 && nextToCheck != 1) { try { val = MetadataFetcher::getContainerFromId(mQcl, ContainerIdentifier(nextToCheck)).get(); out << val.name() << ": #" << val.id() << " with parent #" << val.parent_id() << std::endl; nextToCheck = val.parent_id(); } catch (const MDException& e) { break; } } if (nextToCheck == 1 || nextToCheck == 0) { err << "Unable to continue - given container (" << cid << ") looks fine? No changes have been made." << std::endl; return 1; } //---------------------------------------------------------------------------- // One of its parents is detached from the main tree, rename //---------------------------------------------------------------------------- out << std::endl << std::endl << "Found detached container #" << val.id() << " since its parent #" << val.parent_id() << " does not exist." << std::endl; // Paranoid check eos_assert(!MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(val.parent_id())).get()); // Go std::string newName = SSTR("recovered-dir___id=" << val.id() << "___name=" << val.name() << "___detached-parent=" << val.parent_id()); return renameCid(dryRun, val.id(), destination.getUnderlyingUInt64(), newName, out, err); } //------------------------------------------------------------------------------ // Attempt to fix naming conflict //------------------------------------------------------------------------------ int Inspector::fixShadowFile(bool dryRun, uint64_t fid, const std::string& destinationPath, std::ostream& out, std::ostream& err) { //---------------------------------------------------------------------------- // Ensure given file exists.. //---------------------------------------------------------------------------- eos::ns::FileMdProto val; try { val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get(); } catch (const MDException& e) { err << "Error while fetching metadata for FileMD #" << fid << ": " << e.what() << std::endl; return 1; } //---------------------------------------------------------------------------- // Ensure given destination path is sane //---------------------------------------------------------------------------- ContainerIdentifier destination; if (!isDestinationPathSane(destinationPath, destination, out)) { return 1; } //---------------------------------------------------------------------------- // Ensure the given fid is indeed shadowed //---------------------------------------------------------------------------- bool cidExists = MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(val.cont_id())).get(); IContainerMD::FileMap cidFilemap = MetadataFetcher::getFileMap(mQcl, ContainerIdentifier(val.cont_id())).get(); bool filemapEntryExists = cidFilemap.find(val.name()) != cidFilemap.end(); bool filemapEntryValid = (cidFilemap[val.name()] == val.id()); IContainerMD::ContainerMap cidContainermap = MetadataFetcher::getContainerMap( mQcl, ContainerIdentifier(val.cont_id())).get(); bool containerMapConflict = cidContainermap.find(val.name()) != cidContainermap.end(); out << "Parent exists? " << toYesOrNo(cidExists) << std::endl; out << "Filemap entry exists? " << toYesOrNo(filemapEntryExists) << std::endl; out << "Filemap entry valid? " << toYesOrNo(filemapEntryValid) << std::endl; out << "Containermap conflict? " << toYesOrNo(containerMapConflict) << std::endl; if (!cidExists) { err << "Parent container does not exist, use fix-detached-parent." << std::endl; return 1; } if (filemapEntryExists && filemapEntryValid && !containerMapConflict) { err << "File looks fine? No naming conflict detected, nothing to be done." << std::endl; return 1; } if (!filemapEntryExists) { out << "Detected problem: Filemap entry does not exist." << std::endl; } else if (!filemapEntryValid) { out << "Detected problem: Filemap entry is not valid, and instead points to fid " << cidFilemap[val.name()] << std::endl; } if (containerMapConflict) { out << "Detected problem: Conflict with containermap entry, points to cid " << cidContainermap[val.name()] << std::endl; } // Go std::string newName = SSTR("recovered-file___id=" << val.id() << "___name=" << val.name() << "___naming-conflict-in-parent=" << val.cont_id()); return renameFid(dryRun, val.id(), destination.getUnderlyingUInt64(), newName, out, err); } //------------------------------------------------------------------------------ // Attempt to fix detached parent //------------------------------------------------------------------------------ int Inspector::fixDetachedParentFile(bool dryRun, uint64_t fid, const std::string& destinationPath, std::ostream& out, std::ostream& err) { //---------------------------------------------------------------------------- // Ensure given file exists.. //---------------------------------------------------------------------------- eos::ns::FileMdProto val; try { val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get(); } catch (const MDException& e) { err << "Error while fetching metadata for FileMD #" << fid << ": " << e.what() << std::endl; return 1; } //---------------------------------------------------------------------------- // Ensure given destination path is sane //---------------------------------------------------------------------------- ContainerIdentifier destination; if (!isDestinationPathSane(destinationPath, destination, out)) { return 1; } //---------------------------------------------------------------------------- // If immediate parent is not missing, // switch over to fixDetachedParentContainer //---------------------------------------------------------------------------- if (MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(val.cont_id())).get()) { out << "File #" << val.id() << " not detached, but one of its parents might be." << std::endl; out << "Continuing search onto its parent, container #" << val.cont_id() << "..." << std::endl; return fixDetachedParentContainer(dryRun, val.cont_id(), destinationPath, out, err); } //---------------------------------------------------------------------------- // Immediate parent is missing, rename fid itself. //---------------------------------------------------------------------------- out << "Found detached file #" << val.id() << ", its direct parent #" << val.cont_id() << " is missing." << std::endl; // Paranoid check eos_assert(!MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(val.cont_id())).get()); // Go std::string newName = SSTR("recovered-file___id=" << val.id() << "___name=" << val.name() << "___detached-parent=" << val.cont_id()); return renameFid(dryRun, val.id(), destination.getUnderlyingUInt64(), newName, out, err); } //------------------------------------------------------------------------------ // Drop file currently stuck in deathrow. Please note that any pending // replicas, if they exist, are not deleted. //------------------------------------------------------------------------------ int Inspector::dropFromDeathrow(bool dryRun, uint64_t fid, std::ostream& out, std::ostream& err) { eos::ns::FileMdProto val; try { val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get(); } catch (const MDException& e) { err << "Error while fetching metadata for FileMD #" << fid << ": " << e.what() << std::endl; return 1; } Printing::printMultiline(val, out); if (val.cont_id() != 0) { err << "Parent is not 0 - the given file is not on deathrow, refusing to delete." << std::endl; return 1; } std::vector requests; requests.emplace_back(RequestBuilder::deleteFileProto(FileIdentifier(fid))); CacheNotifications notifications; notifications.fids.emplace_back(fid); executeRequestBatch(requests, notifications, dryRun, out, err); return 0; } //------------------------------------------------------------------------------ // Drop empty container //------------------------------------------------------------------------------ int Inspector::dropEmptyCid(bool dryRun, uint64_t cid) { eos::ns::ContainerMdProto val; try { val = MetadataFetcher::getContainerFromId(mQcl, ContainerIdentifier(cid)).get(); } catch (const MDException& e) { mOutputSink.err(SSTR("Error while fetching metadata for ContainerMD #" << cid << ": " << e.what())); return 1; } mOutputSink.print(val, {}); IContainerMD::ContainerMap parentContainermap = MetadataFetcher::getContainerMap(mQcl, ContainerIdentifier(val.parent_id())).get(); bool containermapEntryExists = parentContainermap.find(val.name()) != parentContainermap.end(); bool containermapEntryValid = (parentContainermap[val.name()] == val.id()); mOutputSink.print(SSTR("ContainerMap entry exists? " << toYesOrNo( containermapEntryExists))); mOutputSink.print(SSTR("ContainerMap entry valid? " << toYesOrNo( containermapEntryValid))); IContainerMD::ContainerMap targetContainerMap = MetadataFetcher::getContainerMap(mQcl, ContainerIdentifier(val.id())).get(); IContainerMD::ContainerMap targetFileMap = MetadataFetcher::getFileMap(mQcl, ContainerIdentifier(val.id())).get(); mOutputSink.print(SSTR("Target has containers? " << toYesOrNo( !targetContainerMap.empty()))); mOutputSink.print(SSTR("Target has files? " << toYesOrNo( !targetFileMap.empty()))); if (!targetContainerMap.empty() || !targetFileMap.empty()) { mOutputSink.err(SSTR("Target contains " << targetContainerMap.size() << " containers, and " << targetFileMap.size() << " files. Not empty, aborting operation.")); return 1; } std::vector requests; CacheNotifications notifications; requests.emplace_back(RequestBuilder::deleteContainerProto(ContainerIdentifier( cid))); if (containermapEntryValid) { RedisRequest req { "HDEL", SSTR(val.parent_id() << constants::sMapDirsSuffix), val.name() }; requests.emplace_back(req); notifications.cids.emplace_back(val.parent_id()); } notifications.cids.emplace_back(val.id()); executeRequestBatch(requests, notifications, dryRun, std::cout, std::cerr); return 0; } //------------------------------------------------------------------------------ // Change the given fid - USE WITH CAUTION //------------------------------------------------------------------------------ int Inspector::changeFid(bool dryRun, uint64_t fid, uint64_t newParent, const std::string& newChecksum, int64_t newSize, uint64_t newLayoutId, std::ostream& out, std::ostream& err) { eos::ns::FileMdProto val; try { val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get(); } catch (const MDException& e) { err << "Error while fetching metadata for FileMD #" << fid << ": " << e.what() << std::endl; return 1; } Printing::printMultiline(val, out); bool ok = false; out << "----- CHANGING THE FOLLOWING ATTRIBUTES:" << std::endl; if (newParent != 0) { ok = true; err << " Container ID: " << val.cont_id() << " --> " << newParent << std::endl; val.set_cont_id(newParent); } if (!newChecksum.empty()) { std::string existingChecksum; eos::appendChecksumOnStringProtobuf(val, existingChecksum); std::string newChecksumBytes; if (!eos::hexArrayToByteArray(newChecksum.c_str(), newChecksum.size(), newChecksumBytes)) { err << "Error: Could not decode checksum, needs to be in hex: " << newChecksum << std::endl; return 1; } ok = true; err << " Checksum: " << existingChecksum << " --> " << newChecksum << std::endl; val.set_checksum(newChecksumBytes.c_str(), newChecksumBytes.size()); } if (newSize >= 0) { ok = true; err << " Size: " << val.size() << " --> " << newSize << std::endl; val.set_size(newSize); } if (newLayoutId) { ok = true; err << " Layout id: " << val.layout_id() << " --> " << newLayoutId << std::endl; val.set_layout_id(newLayoutId); } if (!ok) { err << "Error: No attributes specified to update." << std::endl; return 1; } QuarkFileMD fileMD; fileMD.initialize(std::move(val)); std::vector requests; requests.emplace_back(RequestBuilder::writeFileProto(&fileMD)); executeRequestBatch(requests, {}, dryRun, out, err); CacheNotifications notifications; notifications.fids.emplace_back(fid); executeRequestBatch(requests, notifications, dryRun, out, err); return 0; } //------------------------------------------------------------------------------ // Rename the given cid fully, taking care of the container maps as well. //------------------------------------------------------------------------------ int Inspector::renameCid(bool dryRun, uint64_t cid, uint64_t newParent, const std::string& newName, std::ostream& out, std::ostream& err) { eos::ns::ContainerMdProto val; bool protoExists = false; try { val = MetadataFetcher::getContainerFromId(mQcl, ContainerIdentifier(cid)).get(); protoExists = true; } catch (const MDException& e) { val.set_id(cid); err << "Error while fetching metadata for ContainerMD #" << cid << ": " << e.what(); } out << "------------------------------------------------------ Container overview" << std::endl; bool parentExists = false; IContainerMD::ContainerMap parentContainermap; bool containermapEntryExists = false; bool containermapEntryValid = false; std::string oldName = ""; uint64_t oldContainer = 0; if (protoExists) { Printing::printMultiline(val, out); parentExists = MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(val.parent_id())).get(); parentContainermap = MetadataFetcher::getContainerMap(mQcl, ContainerIdentifier(val.parent_id())).get(); containermapEntryExists = parentContainermap.find(val.name()) != parentContainermap.end(); containermapEntryValid = (parentContainermap[val.name()] == val.id()); oldName = val.name(); oldContainer = val.parent_id(); out << "------------------------------------------------------ Sanity check" << std::endl; out << "Parent (" << (val.parent_id()) << ") exists? " << toYesOrNo( parentExists) << std::endl; out << "Containermap entry exists? " << toYesOrNo(containermapEntryExists) << std::endl; if (containermapEntryExists) { out << "Containermap entry (" << val.name() << " -> " << parentContainermap[val.name()] << ") valid? " << toYesOrNo( containermapEntryValid) << std::endl; } } else { out << "Protobuf for cid=" << cid << " does not exist!" << std::endl; } if (!protoExists && newName.empty()) { out << "Name needs to be specified if original container did not exist! Aborting operation." << std::endl; return 1; } val.set_parent_id(newParent); if (!newName.empty()) { val.set_name(newName); } std::vector requests; CacheNotifications notifications; QuarkContainerMD containerMD; containerMD.initialize(std::move(val), IContainerMD::FileMap(), IContainerMD::ContainerMap()); requests.emplace_back(RequestBuilder::writeContainerProto(&containerMD)); if (containermapEntryExists && containermapEntryValid) { RedisRequest req = {"HDEL", SSTR(oldContainer << constants::sMapDirsSuffix), oldName}; notifications.cids.emplace_back(oldContainer); requests.emplace_back(req); } RedisRequest req = {"HSET", SSTR(newParent << constants::sMapDirsSuffix), containerMD.getName(), SSTR(containerMD.getId()) }; notifications.cids.emplace_back(newParent); notifications.cids.emplace_back(containerMD.getId()); requests.emplace_back(req); executeRequestBatch(requests, notifications, dryRun, out, err); return 0; } //------------------------------------------------------------------------------ // Rename the given fid fully, taking care of the container maps as well //------------------------------------------------------------------------------ int Inspector::renameFid(bool dryRun, uint64_t fid, uint64_t newParent, const std::string& newName, std::ostream& out, std::ostream& err) { eos::ns::FileMdProto val; try { val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get(); } catch (const MDException& e) { err << "Error while fetching metadata for FileMD #" << fid << ": " << e.what() << std::endl; return 1; } out << "------------------------------------------------------ FMD overview" << std::endl; Printing::printMultiline(val, out); bool cidExists = MetadataFetcher::doesContainerMdExist(mQcl, ContainerIdentifier(val.cont_id())).get(); IContainerMD::FileMap cidFilemap = MetadataFetcher::getFileMap(mQcl, ContainerIdentifier(val.cont_id())).get(); bool filemapEntryExists = cidFilemap.find(val.name()) != cidFilemap.end(); bool filemapEntryValid = (cidFilemap[val.name()] == val.id()); std::string oldName = val.name(); uint64_t oldContainer = val.cont_id(); out << "------------------------------------------------------ Sanity check" << std::endl; out << "Old container (" << (val.cont_id()) << ") exists? " << toYesOrNo( cidExists) << std::endl; out << "Filemap entry exists? " << toYesOrNo(filemapEntryExists) << std::endl; if (filemapEntryExists) { out << "Filemap entry (" << val.name() << " -> " << cidFilemap[val.name()] << ") valid? " << toYesOrNo(filemapEntryValid) << std::endl; } out << "------------------------------------------------------ FMD changes" << std::endl; out << " Parent ID: " << val.cont_id() << " --> " << newParent << std::endl; val.set_cont_id(newParent); if (!newName.empty()) { out << " Name: " << val.name() << " --> " << newName << std::endl; val.set_name(newName); } std::vector requests; CacheNotifications notifications; QuarkFileMD fileMD; fileMD.initialize(std::move(val)); requests.emplace_back(RequestBuilder::writeFileProto(&fileMD)); if (filemapEntryExists && filemapEntryValid) { RedisRequest req = {"HDEL", SSTR(oldContainer << constants::sMapFilesSuffix), oldName}; requests.emplace_back(req); notifications.cids.emplace_back(oldContainer); } RedisRequest req = {"HSET", SSTR(newParent << constants::sMapFilesSuffix), fileMD.getName(), SSTR(fileMD.getId()) }; requests.emplace_back(req); notifications.cids.emplace_back(newParent); notifications.fids.emplace_back(fileMD.getId()); executeRequestBatch(requests, notifications, dryRun, out, err); return 0; } //------------------------------------------------------------------------------ // Run the given write batch towards QDB - print the requests, as well as the // output. //------------------------------------------------------------------------------ void Inspector::executeRequestBatch(const std::vector& requests, const CacheNotifications& notif, bool dryRun, std::ostream& out, std::ostream& err) { out << "------------------------------------------------------ QDB commands to execute" << std::endl; for (size_t i = 0; i < requests.size(); i++) { out << i + 1 << ". " << serializeRequest(requests[i]) << std::endl; } std::vector cacheNotifications; if (notif.cids.size() + notif.fids.size() != 0) { out << "------------------------------------------------------ Cache notifications" << std::endl; for (size_t i = 0; i < notif.cids.size(); i++) { cacheNotifications.emplace_back(RequestBuilder::notifyCacheInvalidationCid( ContainerIdentifier(notif.cids[i]))); } for (size_t i = 0; i < notif.fids.size(); i++) { cacheNotifications.emplace_back(RequestBuilder::notifyCacheInvalidationFid( FileIdentifier(notif.fids[i]))); } for (size_t i = 0; i < cacheNotifications.size(); i++) { out << i + 1 << ". " << serializeRequest(cacheNotifications[i]) << std::endl; } } if (dryRun) { out << "------------------------------------------------------ DRY RUN, CHANGES NOT APPLIED" << std::endl; return; } std::vector> replies; std::vector> notificationReplies; for (size_t i = 0; i < requests.size(); i++) { replies.push_back(mQcl.execute(requests[i])); } for (size_t i = 0; i < cacheNotifications.size(); i++) { notificationReplies.push_back(mQcl.execute(cacheNotifications[i])); } out << "------------------------------------------------------ Replies" << std::endl; for (size_t i = 0; i < replies.size(); i++) { out << i + 1 << ". " << qclient::describeRedisReply(replies[i].get()) << std::endl; } if (!notificationReplies.empty()) { out << "------------------------------------------------------ Notification replies" << std::endl; for (size_t i = 0; i < notificationReplies.size(); i++) { out << i + 1 << ". " << qclient::describeRedisReply( notificationReplies[i].get()) << std::endl; } } } EOSNSNAMESPACE_END