// ----------------------------------------------------------------------
// File: ParanoidManifestChecker.cc
// Author: Georgios Bitzes - CERN
// ----------------------------------------------------------------------
/************************************************************************
* quarkdb - a redis-like highly available key-value store *
* Copyright (C) 2020 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 "storage/ParanoidManifestChecker.hh"
#include "utils/DirectoryIterator.hh"
#include "utils/StringUtils.hh"
#include
namespace quarkdb {
ParanoidManifestChecker::ParanoidManifestChecker(std::string_view path)
: mPath(path) {
mThread.reset(&ParanoidManifestChecker::main, this);
}
void ParanoidManifestChecker::main(ThreadAssistant &assistant) {
while(!assistant.terminationRequested()) {
Status st = checkDB(mPath);
if(!st.ok()) {
qdb_error("Potential MANIFEST corruption for DB at " << mPath << "(" << st.getMsg() << "). Note: This detection mechanism for MANIFEST corruption can be iffy, time to worry only if this message starts appearing every 5 minutes.");
}
mLastStatus.set(st);
assistant.wait_for(std::chrono::minutes(5));
}
}
bool operator<(struct timespec &one, struct timespec &two) {
if(one.tv_sec == two.tv_sec) {
return one.tv_nsec < two.tv_nsec;
}
return one.tv_sec < two.tv_sec;
}
std::string timespecToString(struct timespec &spec) {
return SSTR(spec.tv_sec << "." << spec.tv_nsec);
}
Status ParanoidManifestChecker::checkDB(std::string_view path) {
DirectoryIterator iter(path);
struct dirent* entry = nullptr;
struct timespec manifestMtime;
manifestMtime.tv_sec = 0;
struct timespec sstMtime;
sstMtime.tv_sec = 0;
while((entry = iter.next())) {
struct stat statbuf;
if(stat(SSTR(path << "/" << entry->d_name).c_str(), &statbuf) == 0) {
if(StringUtils::startsWith(entry->d_name, "MANIFEST") && manifestMtime < statbuf.st_mtim) {
manifestMtime = statbuf.st_mtim;
}
if(StringUtils::endsWith(entry->d_name, ".sst") && sstMtime < statbuf.st_mtim) {
sstMtime = statbuf.st_mtim;
}
}
}
return compareMTimes(manifestMtime, sstMtime);
}
//------------------------------------------------------------------------------
// Get last status
//------------------------------------------------------------------------------
Status ParanoidManifestChecker::getLastStatus() const {
return mLastStatus.get();
}
//------------------------------------------------------------------------------
// Compare mtimes, verify if sane
//------------------------------------------------------------------------------
Status ParanoidManifestChecker::compareMTimes(struct timespec manifest, struct timespec newestSst) {
int secDiff = newestSst.tv_sec - manifest.tv_sec;
std::string diff = SSTR(secDiff << " sec, sst:" << timespecToString(newestSst) << " vs m:" << timespecToString(manifest));
// 1 hour should be more than enough (?)
if(manifest.tv_sec != 0 && newestSst.tv_sec != 0 && secDiff >= 3600) {
return Status(1, diff);
}
return Status(0, diff);
}
}