2023-05-08 17:20:06 +03:00
|
|
|
#include "local-overlay-store.hh"
|
2023-05-09 00:30:17 +03:00
|
|
|
#include "callback.hh"
|
2023-12-11 20:12:09 +02:00
|
|
|
#include "realisation.hh"
|
|
|
|
#include "processes.hh"
|
2023-05-15 12:28:43 +03:00
|
|
|
#include "url.hh"
|
2023-05-15 12:20:16 +03:00
|
|
|
#include <regex>
|
2023-05-08 17:20:06 +03:00
|
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
2023-08-03 18:59:04 +03:00
|
|
|
std::string LocalOverlayStoreConfig::doc()
|
|
|
|
{
|
|
|
|
return
|
|
|
|
#include "local-overlay-store.md"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2023-05-09 01:50:16 +03:00
|
|
|
Path LocalOverlayStoreConfig::toUpperPath(const StorePath & path) {
|
2023-05-09 17:22:38 +03:00
|
|
|
return upperLayer + "/" + path.to_string();
|
2023-05-09 01:50:16 +03:00
|
|
|
}
|
|
|
|
|
2023-05-08 17:20:06 +03:00
|
|
|
LocalOverlayStore::LocalOverlayStore(const Params & params)
|
|
|
|
: StoreConfig(params)
|
|
|
|
, LocalFSStoreConfig(params)
|
|
|
|
, LocalStoreConfig(params)
|
|
|
|
, LocalOverlayStoreConfig(params)
|
|
|
|
, Store(params)
|
|
|
|
, LocalFSStore(params)
|
|
|
|
, LocalStore(params)
|
2023-05-15 12:28:43 +03:00
|
|
|
, lowerStore(openStore(percentDecode(lowerStoreUri.get())).dynamic_pointer_cast<LocalFSStore>())
|
2023-05-08 17:20:06 +03:00
|
|
|
{
|
2023-05-15 12:20:16 +03:00
|
|
|
if (checkMount.get()) {
|
|
|
|
std::smatch match;
|
|
|
|
std::string mountInfo;
|
|
|
|
auto mounts = readFile("/proc/self/mounts");
|
|
|
|
auto regex = std::regex(R"((^|\n)overlay )" + realStoreDir.get() + R"( .*(\n|$))");
|
|
|
|
|
|
|
|
// Mount points can be stacked, so there might be multiple matching entries.
|
|
|
|
// Loop until the last match, which will be the current state of the mount point.
|
|
|
|
while (std::regex_search(mounts, match, regex)) {
|
|
|
|
mountInfo = match.str();
|
|
|
|
mounts = match.suffix();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto checkOption = [&](std::string option, std::string value) {
|
|
|
|
return std::regex_search(mountInfo, std::regex("\\b" + option + "=" + value + "( |,)"));
|
|
|
|
};
|
|
|
|
|
|
|
|
auto expectedLowerDir = lowerStore->realStoreDir.get();
|
|
|
|
if (!checkOption("lowerdir", expectedLowerDir) || !checkOption("upperdir", upperLayer)) {
|
|
|
|
debug("expected lowerdir: %s", expectedLowerDir);
|
|
|
|
debug("expected upperdir: %s", upperLayer);
|
|
|
|
debug("actual mount: %s", mountInfo);
|
|
|
|
throw Error("overlay filesystem '%s' mounted incorrectly",
|
|
|
|
realStoreDir.get());
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 17:20:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-08 23:48:55 +03:00
|
|
|
void LocalOverlayStore::registerDrvOutput(const Realisation & info)
|
|
|
|
{
|
|
|
|
// First do queryRealisation on lower layer to populate DB
|
|
|
|
auto res = lowerStore->queryRealisation(info.id);
|
|
|
|
if (res)
|
|
|
|
LocalStore::registerDrvOutput(*res);
|
|
|
|
|
|
|
|
LocalStore::registerDrvOutput(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-09 00:30:17 +03:00
|
|
|
void LocalOverlayStore::queryPathInfoUncached(const StorePath & path,
|
|
|
|
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept
|
|
|
|
{
|
|
|
|
auto callbackPtr = std::make_shared<decltype(callback)>(std::move(callback));
|
|
|
|
|
|
|
|
LocalStore::queryPathInfoUncached(path,
|
|
|
|
{[this, path, callbackPtr](std::future<std::shared_ptr<const ValidPathInfo>> fut) {
|
|
|
|
try {
|
2023-05-09 00:37:40 +03:00
|
|
|
auto info = fut.get();
|
|
|
|
if (info)
|
|
|
|
return (*callbackPtr)(std::move(info));
|
2023-05-09 00:30:17 +03:00
|
|
|
} catch (...) {
|
2023-05-09 00:37:40 +03:00
|
|
|
return callbackPtr->rethrow();
|
2023-05-09 00:30:17 +03:00
|
|
|
}
|
2023-05-09 00:37:40 +03:00
|
|
|
// If we don't have it, check lower store
|
|
|
|
lowerStore->queryPathInfo(path,
|
|
|
|
{[path, callbackPtr](std::future<ref<const ValidPathInfo>> fut) {
|
|
|
|
try {
|
|
|
|
(*callbackPtr)(fut.get().get_ptr());
|
|
|
|
} catch (...) {
|
|
|
|
return callbackPtr->rethrow();
|
|
|
|
}
|
|
|
|
}});
|
2023-05-09 00:30:17 +03:00
|
|
|
}});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-09 01:50:16 +03:00
|
|
|
void LocalOverlayStore::queryRealisationUncached(const DrvOutput & drvOutput,
|
2023-07-24 22:39:59 +03:00
|
|
|
Callback<std::shared_ptr<const Realisation>> callback) noexcept
|
2023-05-09 01:50:16 +03:00
|
|
|
{
|
|
|
|
auto callbackPtr = std::make_shared<decltype(callback)>(std::move(callback));
|
|
|
|
|
|
|
|
LocalStore::queryRealisationUncached(drvOutput,
|
|
|
|
{[this, drvOutput, callbackPtr](std::future<std::shared_ptr<const Realisation>> fut) {
|
|
|
|
try {
|
|
|
|
auto info = fut.get();
|
|
|
|
if (info)
|
|
|
|
return (*callbackPtr)(std::move(info));
|
|
|
|
} catch (...) {
|
|
|
|
return callbackPtr->rethrow();
|
|
|
|
}
|
|
|
|
// If we don't have it, check lower store
|
|
|
|
lowerStore->queryRealisation(drvOutput,
|
|
|
|
{[callbackPtr](std::future<std::shared_ptr<const Realisation>> fut) {
|
|
|
|
try {
|
|
|
|
(*callbackPtr)(fut.get());
|
|
|
|
} catch (...) {
|
|
|
|
return callbackPtr->rethrow();
|
|
|
|
}
|
|
|
|
}});
|
|
|
|
}});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool LocalOverlayStore::isValidPathUncached(const StorePath & path)
|
|
|
|
{
|
|
|
|
auto res = LocalStore::isValidPathUncached(path);
|
|
|
|
if (res) return res;
|
2023-05-09 17:22:38 +03:00
|
|
|
res = lowerStore->isValidPath(path);
|
|
|
|
if (res) {
|
|
|
|
// Get path info from lower store so upper DB genuinely has it.
|
2023-05-09 23:42:28 +03:00
|
|
|
auto p = lowerStore->queryPathInfo(path);
|
|
|
|
// recur on references, syncing entire closure.
|
|
|
|
for (auto & r : p->references)
|
|
|
|
if (r != path)
|
|
|
|
isValidPath(r);
|
|
|
|
LocalStore::registerValidPath(*p);
|
2023-05-09 17:22:38 +03:00
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-10 00:20:58 +03:00
|
|
|
void LocalOverlayStore::queryReferrers(const StorePath & path, StorePathSet & referrers)
|
|
|
|
{
|
|
|
|
LocalStore::queryReferrers(path, referrers);
|
|
|
|
lowerStore->queryReferrers(path, referrers);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-07-26 00:09:23 +03:00
|
|
|
void LocalOverlayStore::queryGCReferrers(const StorePath & path, StorePathSet & referrers)
|
|
|
|
{
|
|
|
|
LocalStore::queryReferrers(path, referrers);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-10 00:20:58 +03:00
|
|
|
StorePathSet LocalOverlayStore::queryValidDerivers(const StorePath & path)
|
|
|
|
{
|
|
|
|
auto res = LocalStore::queryValidDerivers(path);
|
|
|
|
for (auto p : lowerStore->queryValidDerivers(path))
|
|
|
|
res.insert(p);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-09 17:40:10 +03:00
|
|
|
std::optional<StorePath> LocalOverlayStore::queryPathFromHashPart(const std::string & hashPart)
|
|
|
|
{
|
|
|
|
auto res = LocalStore::queryPathFromHashPart(hashPart);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
else
|
|
|
|
return lowerStore->queryPathFromHashPart(hashPart);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-09 17:22:38 +03:00
|
|
|
void LocalOverlayStore::registerValidPaths(const ValidPathInfos & infos)
|
|
|
|
{
|
|
|
|
// First, get any from lower store so we merge
|
|
|
|
{
|
|
|
|
StorePathSet notInUpper;
|
|
|
|
for (auto & [p, _] : infos)
|
|
|
|
if (!LocalStore::isValidPathUncached(p)) // avoid divergence
|
|
|
|
notInUpper.insert(p);
|
|
|
|
auto pathsInLower = lowerStore->queryValidPaths(notInUpper);
|
|
|
|
ValidPathInfos inLower;
|
|
|
|
for (auto & p : pathsInLower)
|
|
|
|
inLower.insert_or_assign(p, *lowerStore->queryPathInfo(p));
|
|
|
|
LocalStore::registerValidPaths(inLower);
|
|
|
|
}
|
|
|
|
// Then do original request
|
|
|
|
LocalStore::registerValidPaths(infos);
|
2023-05-09 01:50:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-07-26 16:05:54 +03:00
|
|
|
void LocalOverlayStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|
|
|
{
|
|
|
|
LocalStore::collectGarbage(options, results);
|
|
|
|
|
|
|
|
remountIfNecessary();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-07-26 15:29:31 +03:00
|
|
|
void LocalOverlayStore::deleteStorePath(const Path & path, uint64_t & bytesFreed)
|
2023-05-24 13:26:33 +03:00
|
|
|
{
|
|
|
|
auto mergedDir = realStoreDir.get() + "/";
|
|
|
|
if (path.substr(0, mergedDir.length()) != mergedDir) {
|
|
|
|
warn("local-overlay: unexpected gc path '%s' ", path);
|
|
|
|
return;
|
|
|
|
}
|
2023-07-26 15:21:38 +03:00
|
|
|
|
|
|
|
StorePath storePath = {path.substr(mergedDir.length())};
|
|
|
|
auto upperPath = toUpperPath(storePath);
|
|
|
|
|
|
|
|
if (pathExists(upperPath)) {
|
2023-07-26 16:33:47 +03:00
|
|
|
debug("upper exists: %s", path);
|
2023-07-26 15:21:38 +03:00
|
|
|
if (lowerStore->isValidPath(storePath)) {
|
2023-07-26 16:33:47 +03:00
|
|
|
debug("lower exists: %s", storePath.to_string());
|
2023-07-26 15:21:38 +03:00
|
|
|
// Path also exists in lower store.
|
|
|
|
// We must delete via upper layer to avoid creating a whiteout.
|
|
|
|
deletePath(upperPath, bytesFreed);
|
|
|
|
_remountRequired = true;
|
|
|
|
} else {
|
|
|
|
// Path does not exist in lower store.
|
|
|
|
// So we can delete via overlayfs and not need to remount.
|
2023-07-26 15:29:31 +03:00
|
|
|
LocalStore::deleteStorePath(path, bytesFreed);
|
2023-07-26 15:21:38 +03:00
|
|
|
}
|
2023-05-24 13:26:33 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-26 00:09:23 +03:00
|
|
|
|
2023-07-18 15:59:22 +03:00
|
|
|
void LocalOverlayStore::optimiseStore()
|
|
|
|
{
|
2023-07-19 14:32:32 +03:00
|
|
|
Activity act(*logger, actOptimiseStore);
|
|
|
|
|
|
|
|
// Note for LocalOverlayStore, queryAllValidPaths only returns paths in upper layer
|
|
|
|
auto paths = queryAllValidPaths();
|
|
|
|
|
|
|
|
act.progress(0, paths.size());
|
|
|
|
|
|
|
|
uint64_t done = 0;
|
|
|
|
|
|
|
|
for (auto & path : paths) {
|
|
|
|
if (lowerStore->isValidPath(path)) {
|
2023-07-26 15:29:31 +03:00
|
|
|
uint64_t bytesFreed = 0;
|
2023-07-19 14:32:32 +03:00
|
|
|
// Deduplicate store path
|
2023-07-26 15:29:31 +03:00
|
|
|
deleteStorePath(Store::toRealPath(path), bytesFreed);
|
2023-07-19 14:32:32 +03:00
|
|
|
}
|
|
|
|
done++;
|
|
|
|
act.progress(done, paths.size());
|
|
|
|
}
|
2023-07-26 16:05:54 +03:00
|
|
|
|
|
|
|
remountIfNecessary();
|
2023-07-18 15:59:22 +03:00
|
|
|
}
|
|
|
|
|
2023-07-26 15:21:38 +03:00
|
|
|
|
2023-12-11 20:28:40 +02:00
|
|
|
LocalStore::VerificationResult LocalOverlayStore::verifyAllValidPaths(RepairFlag repair)
|
2023-07-28 12:59:16 +03:00
|
|
|
{
|
|
|
|
StorePathSet done;
|
|
|
|
|
|
|
|
auto existsInStoreDir = [&](const StorePath & storePath) {
|
|
|
|
return pathExists(realStoreDir.get() + "/" + storePath.to_string());
|
|
|
|
};
|
|
|
|
|
2023-08-02 21:38:22 +03:00
|
|
|
bool errors = false;
|
|
|
|
StorePathSet validPaths;
|
|
|
|
|
2023-07-28 12:59:16 +03:00
|
|
|
for (auto & i : queryAllValidPaths())
|
|
|
|
verifyPath(i, existsInStoreDir, done, validPaths, repair, errors);
|
|
|
|
|
2023-12-11 20:28:40 +02:00
|
|
|
return {
|
|
|
|
.errors = errors,
|
|
|
|
.validPaths = validPaths,
|
|
|
|
};
|
2023-07-28 12:59:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-07-26 16:05:54 +03:00
|
|
|
void LocalOverlayStore::remountIfNecessary()
|
|
|
|
{
|
2023-07-26 16:47:44 +03:00
|
|
|
if (!_remountRequired) return;
|
|
|
|
|
2023-07-26 16:05:54 +03:00
|
|
|
if (remountHook.get().empty()) {
|
|
|
|
warn("'%s' needs remounting, set remount-hook to do this automatically", realStoreDir.get());
|
|
|
|
} else {
|
|
|
|
runProgram(remountHook, false, {realStoreDir});
|
|
|
|
}
|
|
|
|
|
|
|
|
_remountRequired = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-08 17:20:06 +03:00
|
|
|
static RegisterStoreImplementation<LocalOverlayStore, LocalOverlayStoreConfig> regLocalOverlayStore;
|
|
|
|
|
|
|
|
}
|