style(3p/nix): Reformat project in Google C++ style
Reformatted with:
fd . -e hh -e cc | xargs clang-format -i
This commit is contained in:
parent
65a1aae98c
commit
0f2cf531f7
175 changed files with 32126 additions and 34689 deletions
621
third_party/nix/src/libstore/binary-cache-store.cc
vendored
621
third_party/nix/src/libstore/binary-cache-store.cc
vendored
|
|
@ -1,361 +1,364 @@
|
|||
#include "archive.hh"
|
||||
#include "binary-cache-store.hh"
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include "archive.hh"
|
||||
#include "compression.hh"
|
||||
#include "derivations.hh"
|
||||
#include "fs-accessor.hh"
|
||||
#include "globals.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "sync.hh"
|
||||
#include "remote-fs-accessor.hh"
|
||||
#include "nar-info-disk-cache.hh"
|
||||
#include "nar-accessor.hh"
|
||||
#include "json.hh"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <future>
|
||||
#include "nar-accessor.hh"
|
||||
#include "nar-info-disk-cache.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "remote-fs-accessor.hh"
|
||||
#include "sync.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
BinaryCacheStore::BinaryCacheStore(const Params & params)
|
||||
: Store(params)
|
||||
{
|
||||
if (secretKeyFile != "")
|
||||
secretKey = std::unique_ptr<SecretKey>(new SecretKey(readFile(secretKeyFile)));
|
||||
BinaryCacheStore::BinaryCacheStore(const Params& params) : Store(params) {
|
||||
if (secretKeyFile != "")
|
||||
secretKey =
|
||||
std::unique_ptr<SecretKey>(new SecretKey(readFile(secretKeyFile)));
|
||||
|
||||
StringSink sink;
|
||||
sink << narVersionMagic1;
|
||||
narMagic = *sink.s;
|
||||
StringSink sink;
|
||||
sink << narVersionMagic1;
|
||||
narMagic = *sink.s;
|
||||
}
|
||||
|
||||
void BinaryCacheStore::init()
|
||||
{
|
||||
std::string cacheInfoFile = "nix-cache-info";
|
||||
void BinaryCacheStore::init() {
|
||||
std::string cacheInfoFile = "nix-cache-info";
|
||||
|
||||
auto cacheInfo = getFile(cacheInfoFile);
|
||||
if (!cacheInfo) {
|
||||
upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n", "text/x-nix-cache-info");
|
||||
} else {
|
||||
for (auto & line : tokenizeString<Strings>(*cacheInfo, "\n")) {
|
||||
size_t colon = line.find(':');
|
||||
if (colon == std::string::npos) continue;
|
||||
auto name = line.substr(0, colon);
|
||||
auto value = trim(line.substr(colon + 1, std::string::npos));
|
||||
if (name == "StoreDir") {
|
||||
if (value != storeDir)
|
||||
throw Error(format("binary cache '%s' is for Nix stores with prefix '%s', not '%s'")
|
||||
% getUri() % value % storeDir);
|
||||
} else if (name == "WantMassQuery") {
|
||||
wantMassQuery_ = value == "1";
|
||||
} else if (name == "Priority") {
|
||||
string2Int(value, priority);
|
||||
}
|
||||
}
|
||||
auto cacheInfo = getFile(cacheInfoFile);
|
||||
if (!cacheInfo) {
|
||||
upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n",
|
||||
"text/x-nix-cache-info");
|
||||
} else {
|
||||
for (auto& line : tokenizeString<Strings>(*cacheInfo, "\n")) {
|
||||
size_t colon = line.find(':');
|
||||
if (colon == std::string::npos) continue;
|
||||
auto name = line.substr(0, colon);
|
||||
auto value = trim(line.substr(colon + 1, std::string::npos));
|
||||
if (name == "StoreDir") {
|
||||
if (value != storeDir)
|
||||
throw Error(format("binary cache '%s' is for Nix stores with prefix "
|
||||
"'%s', not '%s'") %
|
||||
getUri() % value % storeDir);
|
||||
} else if (name == "WantMassQuery") {
|
||||
wantMassQuery_ = value == "1";
|
||||
} else if (name == "Priority") {
|
||||
string2Int(value, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryCacheStore::getFile(const std::string & path,
|
||||
Callback<std::shared_ptr<std::string>> callback) noexcept
|
||||
{
|
||||
try {
|
||||
callback(getFile(path));
|
||||
} catch (...) { callback.rethrow(); }
|
||||
void BinaryCacheStore::getFile(
|
||||
const std::string& path,
|
||||
Callback<std::shared_ptr<std::string>> callback) noexcept {
|
||||
try {
|
||||
callback(getFile(path));
|
||||
} catch (...) {
|
||||
callback.rethrow();
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryCacheStore::getFile(const std::string & path, Sink & sink)
|
||||
{
|
||||
std::promise<std::shared_ptr<std::string>> promise;
|
||||
getFile(path,
|
||||
{[&](std::future<std::shared_ptr<std::string>> result) {
|
||||
void BinaryCacheStore::getFile(const std::string& path, Sink& sink) {
|
||||
std::promise<std::shared_ptr<std::string>> promise;
|
||||
getFile(path, {[&](std::future<std::shared_ptr<std::string>> result) {
|
||||
try {
|
||||
promise.set_value(result.get());
|
||||
promise.set_value(result.get());
|
||||
} catch (...) {
|
||||
promise.set_exception(std::current_exception());
|
||||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
}});
|
||||
auto data = promise.get_future().get();
|
||||
sink((unsigned char *) data->data(), data->size());
|
||||
}});
|
||||
auto data = promise.get_future().get();
|
||||
sink((unsigned char*)data->data(), data->size());
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> BinaryCacheStore::getFile(const std::string & path)
|
||||
{
|
||||
StringSink sink;
|
||||
try {
|
||||
getFile(path, sink);
|
||||
} catch (NoSuchBinaryCacheFile &) {
|
||||
return nullptr;
|
||||
std::shared_ptr<std::string> BinaryCacheStore::getFile(
|
||||
const std::string& path) {
|
||||
StringSink sink;
|
||||
try {
|
||||
getFile(path, sink);
|
||||
} catch (NoSuchBinaryCacheFile&) {
|
||||
return nullptr;
|
||||
}
|
||||
return sink.s;
|
||||
}
|
||||
|
||||
Path BinaryCacheStore::narInfoFileFor(const Path& storePath) {
|
||||
assertStorePath(storePath);
|
||||
return storePathToHash(storePath) + ".narinfo";
|
||||
}
|
||||
|
||||
void BinaryCacheStore::writeNarInfo(ref<NarInfo> narInfo) {
|
||||
auto narInfoFile = narInfoFileFor(narInfo->path);
|
||||
|
||||
upsertFile(narInfoFile, narInfo->to_string(), "text/x-nix-narinfo");
|
||||
|
||||
auto hashPart = storePathToHash(narInfo->path);
|
||||
|
||||
{
|
||||
auto state_(state.lock());
|
||||
state_->pathInfoCache.upsert(hashPart, std::shared_ptr<NarInfo>(narInfo));
|
||||
}
|
||||
|
||||
if (diskCache)
|
||||
diskCache->upsertNarInfo(getUri(), hashPart,
|
||||
std::shared_ptr<NarInfo>(narInfo));
|
||||
}
|
||||
|
||||
void BinaryCacheStore::addToStore(const ValidPathInfo& info,
|
||||
const ref<std::string>& nar,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) {
|
||||
if (!repair && isValidPath(info.path)) return;
|
||||
|
||||
/* Verify that all references are valid. This may do some .narinfo
|
||||
reads, but typically they'll already be cached. */
|
||||
for (auto& ref : info.references) try {
|
||||
if (ref != info.path) queryPathInfo(ref);
|
||||
} catch (InvalidPath&) {
|
||||
throw Error(format("cannot add '%s' to the binary cache because the "
|
||||
"reference '%s' is not valid") %
|
||||
info.path % ref);
|
||||
}
|
||||
return sink.s;
|
||||
}
|
||||
|
||||
Path BinaryCacheStore::narInfoFileFor(const Path & storePath)
|
||||
{
|
||||
assertStorePath(storePath);
|
||||
return storePathToHash(storePath) + ".narinfo";
|
||||
}
|
||||
assert(nar->compare(0, narMagic.size(), narMagic) == 0);
|
||||
|
||||
void BinaryCacheStore::writeNarInfo(ref<NarInfo> narInfo)
|
||||
{
|
||||
auto narInfoFile = narInfoFileFor(narInfo->path);
|
||||
auto narInfo = make_ref<NarInfo>(info);
|
||||
|
||||
upsertFile(narInfoFile, narInfo->to_string(), "text/x-nix-narinfo");
|
||||
narInfo->narSize = nar->size();
|
||||
narInfo->narHash = hashString(htSHA256, *nar);
|
||||
|
||||
auto hashPart = storePathToHash(narInfo->path);
|
||||
if (info.narHash && info.narHash != narInfo->narHash)
|
||||
throw Error(
|
||||
format("refusing to copy corrupted path '%1%' to binary cache") %
|
||||
info.path);
|
||||
|
||||
auto accessor_ = std::dynamic_pointer_cast<RemoteFSAccessor>(accessor);
|
||||
|
||||
/* Optionally write a JSON file containing a listing of the
|
||||
contents of the NAR. */
|
||||
if (writeNARListing) {
|
||||
std::ostringstream jsonOut;
|
||||
|
||||
{
|
||||
auto state_(state.lock());
|
||||
state_->pathInfoCache.upsert(hashPart, std::shared_ptr<NarInfo>(narInfo));
|
||||
JSONObject jsonRoot(jsonOut);
|
||||
jsonRoot.attr("version", 1);
|
||||
|
||||
auto narAccessor = makeNarAccessor(nar);
|
||||
|
||||
if (accessor_) accessor_->addToCache(info.path, *nar, narAccessor);
|
||||
|
||||
{
|
||||
auto res = jsonRoot.placeholder("root");
|
||||
listNar(res, narAccessor, "", true);
|
||||
}
|
||||
}
|
||||
|
||||
if (diskCache)
|
||||
diskCache->upsertNarInfo(getUri(), hashPart, std::shared_ptr<NarInfo>(narInfo));
|
||||
upsertFile(storePathToHash(info.path) + ".ls", jsonOut.str(),
|
||||
"application/json");
|
||||
}
|
||||
|
||||
else {
|
||||
if (accessor_) accessor_->addToCache(info.path, *nar, makeNarAccessor(nar));
|
||||
}
|
||||
|
||||
/* Compress the NAR. */
|
||||
narInfo->compression = compression;
|
||||
auto now1 = std::chrono::steady_clock::now();
|
||||
auto narCompressed = compress(compression, *nar, parallelCompression);
|
||||
auto now2 = std::chrono::steady_clock::now();
|
||||
narInfo->fileHash = hashString(htSHA256, *narCompressed);
|
||||
narInfo->fileSize = narCompressed->size();
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1)
|
||||
.count();
|
||||
printMsg(lvlTalkative,
|
||||
format("copying path '%1%' (%2% bytes, compressed %3$.1f%% in %4% "
|
||||
"ms) to binary cache") %
|
||||
narInfo->path % narInfo->narSize %
|
||||
((1.0 - (double)narCompressed->size() / nar->size()) * 100.0) %
|
||||
duration);
|
||||
|
||||
/* Atomically write the NAR file. */
|
||||
narInfo->url = "nar/" + narInfo->fileHash.to_string(Base32, false) + ".nar" +
|
||||
(compression == "xz" ? ".xz"
|
||||
: compression == "bzip2"
|
||||
? ".bz2"
|
||||
: compression == "br" ? ".br" : "");
|
||||
if (repair || !fileExists(narInfo->url)) {
|
||||
stats.narWrite++;
|
||||
upsertFile(narInfo->url, *narCompressed, "application/x-nix-nar");
|
||||
} else
|
||||
stats.narWriteAverted++;
|
||||
|
||||
stats.narWriteBytes += nar->size();
|
||||
stats.narWriteCompressedBytes += narCompressed->size();
|
||||
stats.narWriteCompressionTimeMs += duration;
|
||||
|
||||
/* Atomically write the NAR info file.*/
|
||||
if (secretKey) narInfo->sign(*secretKey);
|
||||
|
||||
writeNarInfo(narInfo);
|
||||
|
||||
stats.narInfoWrite++;
|
||||
}
|
||||
|
||||
void BinaryCacheStore::addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs, std::shared_ptr<FSAccessor> accessor)
|
||||
{
|
||||
if (!repair && isValidPath(info.path)) return;
|
||||
bool BinaryCacheStore::isValidPathUncached(const Path& storePath) {
|
||||
// FIXME: this only checks whether a .narinfo with a matching hash
|
||||
// part exists. So ‘f4kb...-foo’ matches ‘f4kb...-bar’, even
|
||||
// though they shouldn't. Not easily fixed.
|
||||
return fileExists(narInfoFileFor(storePath));
|
||||
}
|
||||
|
||||
/* Verify that all references are valid. This may do some .narinfo
|
||||
reads, but typically they'll already be cached. */
|
||||
for (auto & ref : info.references)
|
||||
void BinaryCacheStore::narFromPath(const Path& storePath, Sink& sink) {
|
||||
auto info = queryPathInfo(storePath).cast<const NarInfo>();
|
||||
|
||||
uint64_t narSize = 0;
|
||||
|
||||
LambdaSink wrapperSink([&](const unsigned char* data, size_t len) {
|
||||
sink(data, len);
|
||||
narSize += len;
|
||||
});
|
||||
|
||||
auto decompressor = makeDecompressionSink(info->compression, wrapperSink);
|
||||
|
||||
try {
|
||||
getFile(info->url, *decompressor);
|
||||
} catch (NoSuchBinaryCacheFile& e) {
|
||||
throw SubstituteGone(e.what());
|
||||
}
|
||||
|
||||
decompressor->finish();
|
||||
|
||||
stats.narRead++;
|
||||
// stats.narReadCompressedBytes += nar->size(); // FIXME
|
||||
stats.narReadBytes += narSize;
|
||||
}
|
||||
|
||||
void BinaryCacheStore::queryPathInfoUncached(
|
||||
const Path& storePath,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept {
|
||||
auto uri = getUri();
|
||||
auto act = std::make_shared<Activity>(
|
||||
*logger, lvlTalkative, actQueryPathInfo,
|
||||
fmt("querying info about '%s' on '%s'", storePath, uri),
|
||||
Logger::Fields{storePath, uri});
|
||||
PushActivity pact(act->id);
|
||||
|
||||
auto narInfoFile = narInfoFileFor(storePath);
|
||||
|
||||
auto callbackPtr = std::make_shared<decltype(callback)>(std::move(callback));
|
||||
|
||||
getFile(
|
||||
narInfoFile, {[=](std::future<std::shared_ptr<std::string>> fut) {
|
||||
try {
|
||||
if (ref != info.path)
|
||||
queryPathInfo(ref);
|
||||
} catch (InvalidPath &) {
|
||||
throw Error(format("cannot add '%s' to the binary cache because the reference '%s' is not valid")
|
||||
% info.path % ref);
|
||||
auto data = fut.get();
|
||||
|
||||
if (!data) return (*callbackPtr)(nullptr);
|
||||
|
||||
stats.narInfoRead++;
|
||||
|
||||
(*callbackPtr)(
|
||||
(std::shared_ptr<ValidPathInfo>)std::make_shared<NarInfo>(
|
||||
*this, *data, narInfoFile));
|
||||
|
||||
(void)
|
||||
act; // force Activity into this lambda to ensure it stays alive
|
||||
} catch (...) {
|
||||
callbackPtr->rethrow();
|
||||
}
|
||||
|
||||
assert(nar->compare(0, narMagic.size(), narMagic) == 0);
|
||||
|
||||
auto narInfo = make_ref<NarInfo>(info);
|
||||
|
||||
narInfo->narSize = nar->size();
|
||||
narInfo->narHash = hashString(htSHA256, *nar);
|
||||
|
||||
if (info.narHash && info.narHash != narInfo->narHash)
|
||||
throw Error(format("refusing to copy corrupted path '%1%' to binary cache") % info.path);
|
||||
|
||||
auto accessor_ = std::dynamic_pointer_cast<RemoteFSAccessor>(accessor);
|
||||
|
||||
/* Optionally write a JSON file containing a listing of the
|
||||
contents of the NAR. */
|
||||
if (writeNARListing) {
|
||||
std::ostringstream jsonOut;
|
||||
|
||||
{
|
||||
JSONObject jsonRoot(jsonOut);
|
||||
jsonRoot.attr("version", 1);
|
||||
|
||||
auto narAccessor = makeNarAccessor(nar);
|
||||
|
||||
if (accessor_)
|
||||
accessor_->addToCache(info.path, *nar, narAccessor);
|
||||
|
||||
{
|
||||
auto res = jsonRoot.placeholder("root");
|
||||
listNar(res, narAccessor, "", true);
|
||||
}
|
||||
}
|
||||
|
||||
upsertFile(storePathToHash(info.path) + ".ls", jsonOut.str(), "application/json");
|
||||
}
|
||||
|
||||
else {
|
||||
if (accessor_)
|
||||
accessor_->addToCache(info.path, *nar, makeNarAccessor(nar));
|
||||
}
|
||||
|
||||
/* Compress the NAR. */
|
||||
narInfo->compression = compression;
|
||||
auto now1 = std::chrono::steady_clock::now();
|
||||
auto narCompressed = compress(compression, *nar, parallelCompression);
|
||||
auto now2 = std::chrono::steady_clock::now();
|
||||
narInfo->fileHash = hashString(htSHA256, *narCompressed);
|
||||
narInfo->fileSize = narCompressed->size();
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1).count();
|
||||
printMsg(lvlTalkative, format("copying path '%1%' (%2% bytes, compressed %3$.1f%% in %4% ms) to binary cache")
|
||||
% narInfo->path % narInfo->narSize
|
||||
% ((1.0 - (double) narCompressed->size() / nar->size()) * 100.0)
|
||||
% duration);
|
||||
|
||||
/* Atomically write the NAR file. */
|
||||
narInfo->url = "nar/" + narInfo->fileHash.to_string(Base32, false) + ".nar"
|
||||
+ (compression == "xz" ? ".xz" :
|
||||
compression == "bzip2" ? ".bz2" :
|
||||
compression == "br" ? ".br" :
|
||||
"");
|
||||
if (repair || !fileExists(narInfo->url)) {
|
||||
stats.narWrite++;
|
||||
upsertFile(narInfo->url, *narCompressed, "application/x-nix-nar");
|
||||
} else
|
||||
stats.narWriteAverted++;
|
||||
|
||||
stats.narWriteBytes += nar->size();
|
||||
stats.narWriteCompressedBytes += narCompressed->size();
|
||||
stats.narWriteCompressionTimeMs += duration;
|
||||
|
||||
/* Atomically write the NAR info file.*/
|
||||
if (secretKey) narInfo->sign(*secretKey);
|
||||
|
||||
writeNarInfo(narInfo);
|
||||
|
||||
stats.narInfoWrite++;
|
||||
}});
|
||||
}
|
||||
|
||||
bool BinaryCacheStore::isValidPathUncached(const Path & storePath)
|
||||
{
|
||||
// FIXME: this only checks whether a .narinfo with a matching hash
|
||||
// part exists. So ‘f4kb...-foo’ matches ‘f4kb...-bar’, even
|
||||
// though they shouldn't. Not easily fixed.
|
||||
return fileExists(narInfoFileFor(storePath));
|
||||
Path BinaryCacheStore::addToStore(const string& name, const Path& srcPath,
|
||||
bool recursive, HashType hashAlgo,
|
||||
PathFilter& filter, RepairFlag repair) {
|
||||
// FIXME: some cut&paste from LocalStore::addToStore().
|
||||
|
||||
/* Read the whole path into memory. This is not a very scalable
|
||||
method for very large paths, but `copyPath' is mainly used for
|
||||
small files. */
|
||||
StringSink sink;
|
||||
Hash h;
|
||||
if (recursive) {
|
||||
dumpPath(srcPath, sink, filter);
|
||||
h = hashString(hashAlgo, *sink.s);
|
||||
} else {
|
||||
auto s = readFile(srcPath);
|
||||
dumpString(s, sink);
|
||||
h = hashString(hashAlgo, s);
|
||||
}
|
||||
|
||||
ValidPathInfo info;
|
||||
info.path = makeFixedOutputPath(recursive, h, name);
|
||||
|
||||
addToStore(info, sink.s, repair, CheckSigs, nullptr);
|
||||
|
||||
return info.path;
|
||||
}
|
||||
|
||||
void BinaryCacheStore::narFromPath(const Path & storePath, Sink & sink)
|
||||
{
|
||||
auto info = queryPathInfo(storePath).cast<const NarInfo>();
|
||||
Path BinaryCacheStore::addTextToStore(const string& name, const string& s,
|
||||
const PathSet& references,
|
||||
RepairFlag repair) {
|
||||
ValidPathInfo info;
|
||||
info.path = computeStorePathForText(name, s, references);
|
||||
info.references = references;
|
||||
|
||||
uint64_t narSize = 0;
|
||||
|
||||
LambdaSink wrapperSink([&](const unsigned char * data, size_t len) {
|
||||
sink(data, len);
|
||||
narSize += len;
|
||||
});
|
||||
|
||||
auto decompressor = makeDecompressionSink(info->compression, wrapperSink);
|
||||
|
||||
try {
|
||||
getFile(info->url, *decompressor);
|
||||
} catch (NoSuchBinaryCacheFile & e) {
|
||||
throw SubstituteGone(e.what());
|
||||
}
|
||||
|
||||
decompressor->finish();
|
||||
|
||||
stats.narRead++;
|
||||
//stats.narReadCompressedBytes += nar->size(); // FIXME
|
||||
stats.narReadBytes += narSize;
|
||||
}
|
||||
|
||||
void BinaryCacheStore::queryPathInfoUncached(const Path & storePath,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept
|
||||
{
|
||||
auto uri = getUri();
|
||||
auto act = std::make_shared<Activity>(*logger, lvlTalkative, actQueryPathInfo,
|
||||
fmt("querying info about '%s' on '%s'", storePath, uri), Logger::Fields{storePath, uri});
|
||||
PushActivity pact(act->id);
|
||||
|
||||
auto narInfoFile = narInfoFileFor(storePath);
|
||||
|
||||
auto callbackPtr = std::make_shared<decltype(callback)>(std::move(callback));
|
||||
|
||||
getFile(narInfoFile,
|
||||
{[=](std::future<std::shared_ptr<std::string>> fut) {
|
||||
try {
|
||||
auto data = fut.get();
|
||||
|
||||
if (!data) return (*callbackPtr)(nullptr);
|
||||
|
||||
stats.narInfoRead++;
|
||||
|
||||
(*callbackPtr)((std::shared_ptr<ValidPathInfo>)
|
||||
std::make_shared<NarInfo>(*this, *data, narInfoFile));
|
||||
|
||||
(void) act; // force Activity into this lambda to ensure it stays alive
|
||||
} catch (...) {
|
||||
callbackPtr->rethrow();
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
Path BinaryCacheStore::addToStore(const string & name, const Path & srcPath,
|
||||
bool recursive, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
|
||||
{
|
||||
// FIXME: some cut&paste from LocalStore::addToStore().
|
||||
|
||||
/* Read the whole path into memory. This is not a very scalable
|
||||
method for very large paths, but `copyPath' is mainly used for
|
||||
small files. */
|
||||
if (repair || !isValidPath(info.path)) {
|
||||
StringSink sink;
|
||||
Hash h;
|
||||
if (recursive) {
|
||||
dumpPath(srcPath, sink, filter);
|
||||
h = hashString(hashAlgo, *sink.s);
|
||||
} else {
|
||||
auto s = readFile(srcPath);
|
||||
dumpString(s, sink);
|
||||
h = hashString(hashAlgo, s);
|
||||
}
|
||||
|
||||
ValidPathInfo info;
|
||||
info.path = makeFixedOutputPath(recursive, h, name);
|
||||
|
||||
dumpString(s, sink);
|
||||
addToStore(info, sink.s, repair, CheckSigs, nullptr);
|
||||
}
|
||||
|
||||
return info.path;
|
||||
return info.path;
|
||||
}
|
||||
|
||||
Path BinaryCacheStore::addTextToStore(const string & name, const string & s,
|
||||
const PathSet & references, RepairFlag repair)
|
||||
{
|
||||
ValidPathInfo info;
|
||||
info.path = computeStorePathForText(name, s, references);
|
||||
info.references = references;
|
||||
ref<FSAccessor> BinaryCacheStore::getFSAccessor() {
|
||||
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()),
|
||||
localNarCache);
|
||||
}
|
||||
|
||||
if (repair || !isValidPath(info.path)) {
|
||||
StringSink sink;
|
||||
dumpString(s, sink);
|
||||
addToStore(info, sink.s, repair, CheckSigs, nullptr);
|
||||
void BinaryCacheStore::addSignatures(const Path& storePath,
|
||||
const StringSet& sigs) {
|
||||
/* Note: this is inherently racy since there is no locking on
|
||||
binary caches. In particular, with S3 this unreliable, even
|
||||
when addSignatures() is called sequentially on a path, because
|
||||
S3 might return an outdated cached version. */
|
||||
|
||||
auto narInfo = make_ref<NarInfo>((NarInfo&)*queryPathInfo(storePath));
|
||||
|
||||
narInfo->sigs.insert(sigs.begin(), sigs.end());
|
||||
|
||||
auto narInfoFile = narInfoFileFor(narInfo->path);
|
||||
|
||||
writeNarInfo(narInfo);
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> BinaryCacheStore::getBuildLog(const Path& path) {
|
||||
Path drvPath;
|
||||
|
||||
if (isDerivation(path))
|
||||
drvPath = path;
|
||||
else {
|
||||
try {
|
||||
auto info = queryPathInfo(path);
|
||||
// FIXME: add a "Log" field to .narinfo
|
||||
if (info->deriver == "") return nullptr;
|
||||
drvPath = info->deriver;
|
||||
} catch (InvalidPath&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return info.path;
|
||||
auto logPath = "log/" + baseNameOf(drvPath);
|
||||
|
||||
debug("fetching build log from binary cache '%s/%s'", getUri(), logPath);
|
||||
|
||||
return getFile(logPath);
|
||||
}
|
||||
|
||||
ref<FSAccessor> BinaryCacheStore::getFSAccessor()
|
||||
{
|
||||
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()), localNarCache);
|
||||
}
|
||||
|
||||
void BinaryCacheStore::addSignatures(const Path & storePath, const StringSet & sigs)
|
||||
{
|
||||
/* Note: this is inherently racy since there is no locking on
|
||||
binary caches. In particular, with S3 this unreliable, even
|
||||
when addSignatures() is called sequentially on a path, because
|
||||
S3 might return an outdated cached version. */
|
||||
|
||||
auto narInfo = make_ref<NarInfo>((NarInfo &) *queryPathInfo(storePath));
|
||||
|
||||
narInfo->sigs.insert(sigs.begin(), sigs.end());
|
||||
|
||||
auto narInfoFile = narInfoFileFor(narInfo->path);
|
||||
|
||||
writeNarInfo(narInfo);
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> BinaryCacheStore::getBuildLog(const Path & path)
|
||||
{
|
||||
Path drvPath;
|
||||
|
||||
if (isDerivation(path))
|
||||
drvPath = path;
|
||||
else {
|
||||
try {
|
||||
auto info = queryPathInfo(path);
|
||||
// FIXME: add a "Log" field to .narinfo
|
||||
if (info->deriver == "") return nullptr;
|
||||
drvPath = info->deriver;
|
||||
} catch (InvalidPath &) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto logPath = "log/" + baseNameOf(drvPath);
|
||||
|
||||
debug("fetching build log from binary cache '%s/%s'", getUri(), logPath);
|
||||
|
||||
return getFile(logPath);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
150
third_party/nix/src/libstore/binary-cache-store.hh
vendored
150
third_party/nix/src/libstore/binary-cache-store.hh
vendored
|
|
@ -1,115 +1,113 @@
|
|||
#pragma once
|
||||
|
||||
#include "crypto.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include "pool.hh"
|
||||
|
||||
#include <atomic>
|
||||
#include "crypto.hh"
|
||||
#include "pool.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct NarInfo;
|
||||
|
||||
class BinaryCacheStore : public Store
|
||||
{
|
||||
public:
|
||||
class BinaryCacheStore : public Store {
|
||||
public:
|
||||
const Setting<std::string> compression{
|
||||
this, "xz", "compression",
|
||||
"NAR compression method ('xz', 'bzip2', or 'none')"};
|
||||
const Setting<bool> writeNARListing{
|
||||
this, false, "write-nar-listing",
|
||||
"whether to write a JSON file listing the files in each NAR"};
|
||||
const Setting<Path> secretKeyFile{
|
||||
this, "", "secret-key",
|
||||
"path to secret key used to sign the binary cache"};
|
||||
const Setting<Path> localNarCache{this, "", "local-nar-cache",
|
||||
"path to a local cache of NARs"};
|
||||
const Setting<bool> parallelCompression{
|
||||
this, false, "parallel-compression",
|
||||
"enable multi-threading compression, available for xz only currently"};
|
||||
|
||||
const Setting<std::string> compression{this, "xz", "compression", "NAR compression method ('xz', 'bzip2', or 'none')"};
|
||||
const Setting<bool> writeNARListing{this, false, "write-nar-listing", "whether to write a JSON file listing the files in each NAR"};
|
||||
const Setting<Path> secretKeyFile{this, "", "secret-key", "path to secret key used to sign the binary cache"};
|
||||
const Setting<Path> localNarCache{this, "", "local-nar-cache", "path to a local cache of NARs"};
|
||||
const Setting<bool> parallelCompression{this, false, "parallel-compression",
|
||||
"enable multi-threading compression, available for xz only currently"};
|
||||
private:
|
||||
std::unique_ptr<SecretKey> secretKey;
|
||||
|
||||
private:
|
||||
protected:
|
||||
BinaryCacheStore(const Params& params);
|
||||
|
||||
std::unique_ptr<SecretKey> secretKey;
|
||||
public:
|
||||
virtual bool fileExists(const std::string& path) = 0;
|
||||
|
||||
protected:
|
||||
virtual void upsertFile(const std::string& path, const std::string& data,
|
||||
const std::string& mimeType) = 0;
|
||||
|
||||
BinaryCacheStore(const Params & params);
|
||||
/* Note: subclasses must implement at least one of the two
|
||||
following getFile() methods. */
|
||||
|
||||
public:
|
||||
/* Dump the contents of the specified file to a sink. */
|
||||
virtual void getFile(const std::string& path, Sink& sink);
|
||||
|
||||
virtual bool fileExists(const std::string & path) = 0;
|
||||
/* Fetch the specified file and call the specified callback with
|
||||
the result. A subclass may implement this asynchronously. */
|
||||
virtual void getFile(
|
||||
const std::string& path,
|
||||
Callback<std::shared_ptr<std::string>> callback) noexcept;
|
||||
|
||||
virtual void upsertFile(const std::string & path,
|
||||
const std::string & data,
|
||||
const std::string & mimeType) = 0;
|
||||
std::shared_ptr<std::string> getFile(const std::string& path);
|
||||
|
||||
/* Note: subclasses must implement at least one of the two
|
||||
following getFile() methods. */
|
||||
protected:
|
||||
bool wantMassQuery_ = false;
|
||||
int priority = 50;
|
||||
|
||||
/* Dump the contents of the specified file to a sink. */
|
||||
virtual void getFile(const std::string & path, Sink & sink);
|
||||
public:
|
||||
virtual void init();
|
||||
|
||||
/* Fetch the specified file and call the specified callback with
|
||||
the result. A subclass may implement this asynchronously. */
|
||||
virtual void getFile(const std::string & path,
|
||||
Callback<std::shared_ptr<std::string>> callback) noexcept;
|
||||
private:
|
||||
std::string narMagic;
|
||||
|
||||
std::shared_ptr<std::string> getFile(const std::string & path);
|
||||
std::string narInfoFileFor(const Path& storePath);
|
||||
|
||||
protected:
|
||||
void writeNarInfo(ref<NarInfo> narInfo);
|
||||
|
||||
bool wantMassQuery_ = false;
|
||||
int priority = 50;
|
||||
public:
|
||||
bool isValidPathUncached(const Path& path) override;
|
||||
|
||||
public:
|
||||
void queryPathInfoUncached(
|
||||
const Path& path,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override;
|
||||
|
||||
virtual void init();
|
||||
Path queryPathFromHashPart(const string& hashPart) override {
|
||||
unsupported("queryPathFromHashPart");
|
||||
}
|
||||
|
||||
private:
|
||||
bool wantMassQuery() override { return wantMassQuery_; }
|
||||
|
||||
std::string narMagic;
|
||||
void addToStore(const ValidPathInfo& info, const ref<std::string>& nar,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override;
|
||||
|
||||
std::string narInfoFileFor(const Path & storePath);
|
||||
Path addToStore(const string& name, const Path& srcPath, bool recursive,
|
||||
HashType hashAlgo, PathFilter& filter,
|
||||
RepairFlag repair) override;
|
||||
|
||||
void writeNarInfo(ref<NarInfo> narInfo);
|
||||
Path addTextToStore(const string& name, const string& s,
|
||||
const PathSet& references, RepairFlag repair) override;
|
||||
|
||||
public:
|
||||
void narFromPath(const Path& path, Sink& sink) override;
|
||||
|
||||
bool isValidPathUncached(const Path & path) override;
|
||||
BuildResult buildDerivation(const Path& drvPath, const BasicDerivation& drv,
|
||||
BuildMode buildMode) override {
|
||||
unsupported("buildDerivation");
|
||||
}
|
||||
|
||||
void queryPathInfoUncached(const Path & path,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override;
|
||||
void ensurePath(const Path& path) override { unsupported("ensurePath"); }
|
||||
|
||||
Path queryPathFromHashPart(const string & hashPart) override
|
||||
{ unsupported("queryPathFromHashPart"); }
|
||||
ref<FSAccessor> getFSAccessor() override;
|
||||
|
||||
bool wantMassQuery() override { return wantMassQuery_; }
|
||||
void addSignatures(const Path& storePath, const StringSet& sigs) override;
|
||||
|
||||
void addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override;
|
||||
|
||||
Path addToStore(const string & name, const Path & srcPath,
|
||||
bool recursive, HashType hashAlgo,
|
||||
PathFilter & filter, RepairFlag repair) override;
|
||||
|
||||
Path addTextToStore(const string & name, const string & s,
|
||||
const PathSet & references, RepairFlag repair) override;
|
||||
|
||||
void narFromPath(const Path & path, Sink & sink) override;
|
||||
|
||||
BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode) override
|
||||
{ unsupported("buildDerivation"); }
|
||||
|
||||
void ensurePath(const Path & path) override
|
||||
{ unsupported("ensurePath"); }
|
||||
|
||||
ref<FSAccessor> getFSAccessor() override;
|
||||
|
||||
void addSignatures(const Path & storePath, const StringSet & sigs) override;
|
||||
|
||||
std::shared_ptr<std::string> getBuildLog(const Path & path) override;
|
||||
|
||||
int getPriority() override { return priority; }
|
||||
std::shared_ptr<std::string> getBuildLog(const Path& path) override;
|
||||
|
||||
int getPriority() override { return priority; }
|
||||
};
|
||||
|
||||
MakeError(NoSuchBinaryCacheFile, Error);
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
7791
third_party/nix/src/libstore/build.cc
vendored
7791
third_party/nix/src/libstore/build.cc
vendored
File diff suppressed because it is too large
Load diff
6
third_party/nix/src/libstore/builtins.hh
vendored
6
third_party/nix/src/libstore/builtins.hh
vendored
|
|
@ -5,7 +5,7 @@
|
|||
namespace nix {
|
||||
|
||||
// TODO: make pluggable.
|
||||
void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData);
|
||||
void builtinBuildenv(const BasicDerivation & drv);
|
||||
void builtinFetchurl(const BasicDerivation& drv, const std::string& netrcData);
|
||||
void builtinBuildenv(const BasicDerivation& drv);
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
320
third_party/nix/src/libstore/builtins/buildenv.cc
vendored
320
third_party/nix/src/libstore/builtins/buildenv.cc
vendored
|
|
@ -1,13 +1,12 @@
|
|||
#include "builtins.hh"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <algorithm>
|
||||
#include "builtins.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
typedef std::map<Path,int> Priorities;
|
||||
typedef std::map<Path, int> Priorities;
|
||||
|
||||
// FIXME: change into local variables.
|
||||
|
||||
|
|
@ -16,102 +15,104 @@ static Priorities priorities;
|
|||
static unsigned long symlinks;
|
||||
|
||||
/* For each activated package, create symlinks */
|
||||
static void createLinks(const Path & srcDir, const Path & dstDir, int priority)
|
||||
{
|
||||
DirEntries srcFiles;
|
||||
static void createLinks(const Path& srcDir, const Path& dstDir, int priority) {
|
||||
DirEntries srcFiles;
|
||||
|
||||
try {
|
||||
srcFiles = readDirectory(srcDir);
|
||||
} catch (SysError& e) {
|
||||
if (e.errNo == ENOTDIR) {
|
||||
printError(
|
||||
"warning: not including '%s' in the user environment because it's "
|
||||
"not a directory",
|
||||
srcDir);
|
||||
return;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
for (const auto& ent : srcFiles) {
|
||||
if (ent.name[0] == '.') /* not matched by glob */
|
||||
continue;
|
||||
auto srcFile = srcDir + "/" + ent.name;
|
||||
auto dstFile = dstDir + "/" + ent.name;
|
||||
|
||||
struct stat srcSt;
|
||||
try {
|
||||
srcFiles = readDirectory(srcDir);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == ENOTDIR) {
|
||||
printError("warning: not including '%s' in the user environment because it's not a directory", srcDir);
|
||||
return;
|
||||
}
|
||||
throw;
|
||||
if (stat(srcFile.c_str(), &srcSt) == -1)
|
||||
throw SysError("getting status of '%1%'", srcFile);
|
||||
} catch (SysError& e) {
|
||||
if (e.errNo == ENOENT || e.errNo == ENOTDIR) {
|
||||
printError("warning: skipping dangling symlink '%s'", dstFile);
|
||||
continue;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
for (const auto & ent : srcFiles) {
|
||||
if (ent.name[0] == '.')
|
||||
/* not matched by glob */
|
||||
continue;
|
||||
auto srcFile = srcDir + "/" + ent.name;
|
||||
auto dstFile = dstDir + "/" + ent.name;
|
||||
/* The files below are special-cased to that they don't show up
|
||||
* in user profiles, either because they are useless, or
|
||||
* because they would cauase pointless collisions (e.g., each
|
||||
* Python package brings its own
|
||||
* `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
|
||||
*/
|
||||
if (hasSuffix(srcFile, "/propagated-build-inputs") ||
|
||||
hasSuffix(srcFile, "/nix-support") ||
|
||||
hasSuffix(srcFile, "/perllocal.pod") ||
|
||||
hasSuffix(srcFile, "/info/dir") || hasSuffix(srcFile, "/log"))
|
||||
continue;
|
||||
|
||||
struct stat srcSt;
|
||||
try {
|
||||
if (stat(srcFile.c_str(), &srcSt) == -1)
|
||||
throw SysError("getting status of '%1%'", srcFile);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == ENOENT || e.errNo == ENOTDIR) {
|
||||
printError("warning: skipping dangling symlink '%s'", dstFile);
|
||||
continue;
|
||||
}
|
||||
throw;
|
||||
else if (S_ISDIR(srcSt.st_mode)) {
|
||||
struct stat dstSt;
|
||||
auto res = lstat(dstFile.c_str(), &dstSt);
|
||||
if (res == 0) {
|
||||
if (S_ISDIR(dstSt.st_mode)) {
|
||||
createLinks(srcFile, dstFile, priority);
|
||||
continue;
|
||||
} else if (S_ISLNK(dstSt.st_mode)) {
|
||||
auto target = canonPath(dstFile, true);
|
||||
if (!S_ISDIR(lstat(target).st_mode))
|
||||
throw Error("collision between '%1%' and non-directory '%2%'",
|
||||
srcFile, target);
|
||||
if (unlink(dstFile.c_str()) == -1)
|
||||
throw SysError(format("unlinking '%1%'") % dstFile);
|
||||
if (mkdir(dstFile.c_str(), 0755) == -1)
|
||||
throw SysError(format("creating directory '%1%'"));
|
||||
createLinks(target, dstFile, priorities[dstFile]);
|
||||
createLinks(srcFile, dstFile, priority);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The files below are special-cased to that they don't show up
|
||||
* in user profiles, either because they are useless, or
|
||||
* because they would cauase pointless collisions (e.g., each
|
||||
* Python package brings its own
|
||||
* `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
|
||||
*/
|
||||
if (hasSuffix(srcFile, "/propagated-build-inputs") ||
|
||||
hasSuffix(srcFile, "/nix-support") ||
|
||||
hasSuffix(srcFile, "/perllocal.pod") ||
|
||||
hasSuffix(srcFile, "/info/dir") ||
|
||||
hasSuffix(srcFile, "/log"))
|
||||
continue;
|
||||
|
||||
else if (S_ISDIR(srcSt.st_mode)) {
|
||||
struct stat dstSt;
|
||||
auto res = lstat(dstFile.c_str(), &dstSt);
|
||||
if (res == 0) {
|
||||
if (S_ISDIR(dstSt.st_mode)) {
|
||||
createLinks(srcFile, dstFile, priority);
|
||||
continue;
|
||||
} else if (S_ISLNK(dstSt.st_mode)) {
|
||||
auto target = canonPath(dstFile, true);
|
||||
if (!S_ISDIR(lstat(target).st_mode))
|
||||
throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
|
||||
if (unlink(dstFile.c_str()) == -1)
|
||||
throw SysError(format("unlinking '%1%'") % dstFile);
|
||||
if (mkdir(dstFile.c_str(), 0755) == -1)
|
||||
throw SysError(format("creating directory '%1%'"));
|
||||
createLinks(target, dstFile, priorities[dstFile]);
|
||||
createLinks(srcFile, dstFile, priority);
|
||||
continue;
|
||||
}
|
||||
} else if (errno != ENOENT)
|
||||
throw SysError(format("getting status of '%1%'") % dstFile);
|
||||
}
|
||||
|
||||
else {
|
||||
struct stat dstSt;
|
||||
auto res = lstat(dstFile.c_str(), &dstSt);
|
||||
if (res == 0) {
|
||||
if (S_ISLNK(dstSt.st_mode)) {
|
||||
auto prevPriority = priorities[dstFile];
|
||||
if (prevPriority == priority)
|
||||
throw Error(
|
||||
"packages '%1%' and '%2%' have the same priority %3%; "
|
||||
"use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
|
||||
"to change the priority of one of the conflicting packages"
|
||||
" (0 being the highest priority)",
|
||||
srcFile, readLink(dstFile), priority);
|
||||
if (prevPriority < priority)
|
||||
continue;
|
||||
if (unlink(dstFile.c_str()) == -1)
|
||||
throw SysError(format("unlinking '%1%'") % dstFile);
|
||||
} else if (S_ISDIR(dstSt.st_mode))
|
||||
throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile);
|
||||
} else if (errno != ENOENT)
|
||||
throw SysError(format("getting status of '%1%'") % dstFile);
|
||||
}
|
||||
|
||||
createSymlink(srcFile, dstFile);
|
||||
priorities[dstFile] = priority;
|
||||
symlinks++;
|
||||
} else if (errno != ENOENT)
|
||||
throw SysError(format("getting status of '%1%'") % dstFile);
|
||||
}
|
||||
|
||||
else {
|
||||
struct stat dstSt;
|
||||
auto res = lstat(dstFile.c_str(), &dstSt);
|
||||
if (res == 0) {
|
||||
if (S_ISLNK(dstSt.st_mode)) {
|
||||
auto prevPriority = priorities[dstFile];
|
||||
if (prevPriority == priority)
|
||||
throw Error(
|
||||
"packages '%1%' and '%2%' have the same priority %3%; "
|
||||
"use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
|
||||
"to change the priority of one of the conflicting packages"
|
||||
" (0 being the highest priority)",
|
||||
srcFile, readLink(dstFile), priority);
|
||||
if (prevPriority < priority) continue;
|
||||
if (unlink(dstFile.c_str()) == -1)
|
||||
throw SysError(format("unlinking '%1%'") % dstFile);
|
||||
} else if (S_ISDIR(dstSt.st_mode))
|
||||
throw Error(
|
||||
"collision between non-directory '%1%' and directory '%2%'",
|
||||
srcFile, dstFile);
|
||||
} else if (errno != ENOENT)
|
||||
throw SysError(format("getting status of '%1%'") % dstFile);
|
||||
}
|
||||
|
||||
createSymlink(srcFile, dstFile);
|
||||
priorities[dstFile] = priority;
|
||||
symlinks++;
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::set<Path> FileProp;
|
||||
|
|
@ -121,84 +122,87 @@ static FileProp postponed = FileProp{};
|
|||
|
||||
static Path out;
|
||||
|
||||
static void addPkg(const Path & pkgDir, int priority)
|
||||
{
|
||||
if (done.count(pkgDir)) return;
|
||||
done.insert(pkgDir);
|
||||
createLinks(pkgDir, out, priority);
|
||||
static void addPkg(const Path& pkgDir, int priority) {
|
||||
if (done.count(pkgDir)) return;
|
||||
done.insert(pkgDir);
|
||||
createLinks(pkgDir, out, priority);
|
||||
|
||||
try {
|
||||
for (const auto & p : tokenizeString<std::vector<string>>(
|
||||
readFile(pkgDir + "/nix-support/propagated-user-env-packages"), " \n"))
|
||||
if (!done.count(p))
|
||||
postponed.insert(p);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo != ENOENT && e.errNo != ENOTDIR) throw;
|
||||
}
|
||||
try {
|
||||
for (const auto& p : tokenizeString<std::vector<string>>(
|
||||
readFile(pkgDir + "/nix-support/propagated-user-env-packages"),
|
||||
" \n"))
|
||||
if (!done.count(p)) postponed.insert(p);
|
||||
} catch (SysError& e) {
|
||||
if (e.errNo != ENOENT && e.errNo != ENOTDIR) throw;
|
||||
}
|
||||
}
|
||||
|
||||
struct Package {
|
||||
Path path;
|
||||
bool active;
|
||||
int priority;
|
||||
Package(Path path, bool active, int priority) : path{path}, active{active}, priority{priority} {}
|
||||
Path path;
|
||||
bool active;
|
||||
int priority;
|
||||
Package(Path path, bool active, int priority)
|
||||
: path{path}, active{active}, priority{priority} {}
|
||||
};
|
||||
|
||||
typedef std::vector<Package> Packages;
|
||||
|
||||
void builtinBuildenv(const BasicDerivation & drv)
|
||||
{
|
||||
auto getAttr = [&](const string & name) {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
||||
return i->second;
|
||||
};
|
||||
void builtinBuildenv(const BasicDerivation& drv) {
|
||||
auto getAttr = [&](const string& name) {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
||||
return i->second;
|
||||
};
|
||||
|
||||
out = getAttr("out");
|
||||
createDirs(out);
|
||||
out = getAttr("out");
|
||||
createDirs(out);
|
||||
|
||||
/* Convert the stuff we get from the environment back into a
|
||||
* coherent data type. */
|
||||
Packages pkgs;
|
||||
auto derivations = tokenizeString<Strings>(getAttr("derivations"));
|
||||
while (!derivations.empty()) {
|
||||
/* !!! We're trusting the caller to structure derivations env var correctly */
|
||||
auto active = derivations.front(); derivations.pop_front();
|
||||
auto priority = stoi(derivations.front()); derivations.pop_front();
|
||||
auto outputs = stoi(derivations.front()); derivations.pop_front();
|
||||
for (auto n = 0; n < outputs; n++) {
|
||||
auto path = derivations.front(); derivations.pop_front();
|
||||
pkgs.emplace_back(path, active != "false", priority);
|
||||
}
|
||||
}
|
||||
|
||||
/* Symlink to the packages that have been installed explicitly by the
|
||||
* user. Process in priority order to reduce unnecessary
|
||||
* symlink/unlink steps.
|
||||
/* Convert the stuff we get from the environment back into a
|
||||
* coherent data type. */
|
||||
Packages pkgs;
|
||||
auto derivations = tokenizeString<Strings>(getAttr("derivations"));
|
||||
while (!derivations.empty()) {
|
||||
/* !!! We're trusting the caller to structure derivations env var correctly
|
||||
*/
|
||||
std::sort(pkgs.begin(), pkgs.end(), [](const Package & a, const Package & b) {
|
||||
return a.priority < b.priority || (a.priority == b.priority && a.path < b.path);
|
||||
});
|
||||
for (const auto & pkg : pkgs)
|
||||
if (pkg.active)
|
||||
addPkg(pkg.path, pkg.priority);
|
||||
|
||||
/* Symlink to the packages that have been "propagated" by packages
|
||||
* installed by the user (i.e., package X declares that it wants Y
|
||||
* installed as well). We do these later because they have a lower
|
||||
* priority in case of collisions.
|
||||
*/
|
||||
auto priorityCounter = 1000;
|
||||
while (!postponed.empty()) {
|
||||
auto pkgDirs = postponed;
|
||||
postponed = FileProp{};
|
||||
for (const auto & pkgDir : pkgDirs)
|
||||
addPkg(pkgDir, priorityCounter++);
|
||||
auto active = derivations.front();
|
||||
derivations.pop_front();
|
||||
auto priority = stoi(derivations.front());
|
||||
derivations.pop_front();
|
||||
auto outputs = stoi(derivations.front());
|
||||
derivations.pop_front();
|
||||
for (auto n = 0; n < outputs; n++) {
|
||||
auto path = derivations.front();
|
||||
derivations.pop_front();
|
||||
pkgs.emplace_back(path, active != "false", priority);
|
||||
}
|
||||
}
|
||||
|
||||
printError("created %d symlinks in user environment", symlinks);
|
||||
/* Symlink to the packages that have been installed explicitly by the
|
||||
* user. Process in priority order to reduce unnecessary
|
||||
* symlink/unlink steps.
|
||||
*/
|
||||
std::sort(pkgs.begin(), pkgs.end(), [](const Package& a, const Package& b) {
|
||||
return a.priority < b.priority ||
|
||||
(a.priority == b.priority && a.path < b.path);
|
||||
});
|
||||
for (const auto& pkg : pkgs)
|
||||
if (pkg.active) addPkg(pkg.path, pkg.priority);
|
||||
|
||||
createSymlink(getAttr("manifest"), out + "/manifest.nix");
|
||||
/* Symlink to the packages that have been "propagated" by packages
|
||||
* installed by the user (i.e., package X declares that it wants Y
|
||||
* installed as well). We do these later because they have a lower
|
||||
* priority in case of collisions.
|
||||
*/
|
||||
auto priorityCounter = 1000;
|
||||
while (!postponed.empty()) {
|
||||
auto pkgDirs = postponed;
|
||||
postponed = FileProp{};
|
||||
for (const auto& pkgDir : pkgDirs) addPkg(pkgDir, priorityCounter++);
|
||||
}
|
||||
|
||||
printError("created %d symlinks in user environment", symlinks);
|
||||
|
||||
createSymlink(getAttr("manifest"), out + "/manifest.nix");
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
132
third_party/nix/src/libstore/builtins/fetchurl.cc
vendored
132
third_party/nix/src/libstore/builtins/fetchurl.cc
vendored
|
|
@ -1,78 +1,76 @@
|
|||
#include "archive.hh"
|
||||
#include "builtins.hh"
|
||||
#include "compression.hh"
|
||||
#include "download.hh"
|
||||
#include "store-api.hh"
|
||||
#include "archive.hh"
|
||||
#include "compression.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
||||
{
|
||||
/* Make the host's netrc data available. Too bad curl requires
|
||||
this to be stored in a file. It would be nice if we could just
|
||||
pass a pointer to the data. */
|
||||
if (netrcData != "") {
|
||||
settings.netrcFile = "netrc";
|
||||
writeFile(settings.netrcFile, netrcData, 0600);
|
||||
void builtinFetchurl(const BasicDerivation& drv, const std::string& netrcData) {
|
||||
/* Make the host's netrc data available. Too bad curl requires
|
||||
this to be stored in a file. It would be nice if we could just
|
||||
pass a pointer to the data. */
|
||||
if (netrcData != "") {
|
||||
settings.netrcFile = "netrc";
|
||||
writeFile(settings.netrcFile, netrcData, 0600);
|
||||
}
|
||||
|
||||
auto getAttr = [&](const string& name) {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end())
|
||||
throw Error(format("attribute '%s' missing") % name);
|
||||
return i->second;
|
||||
};
|
||||
|
||||
Path storePath = getAttr("out");
|
||||
auto mainUrl = getAttr("url");
|
||||
bool unpack = get(drv.env, "unpack", "") == "1";
|
||||
|
||||
/* Note: have to use a fresh downloader here because we're in
|
||||
a forked process. */
|
||||
auto downloader = makeDownloader();
|
||||
|
||||
auto fetch = [&](const std::string& url) {
|
||||
auto source = sinkToSource([&](Sink& sink) {
|
||||
/* No need to do TLS verification, because we check the hash of
|
||||
the result anyway. */
|
||||
DownloadRequest request(url);
|
||||
request.verifyTLS = false;
|
||||
request.decompress = false;
|
||||
|
||||
auto decompressor = makeDecompressionSink(
|
||||
unpack && hasSuffix(mainUrl, ".xz") ? "xz" : "none", sink);
|
||||
downloader->download(std::move(request), *decompressor);
|
||||
decompressor->finish();
|
||||
});
|
||||
|
||||
if (unpack)
|
||||
restorePath(storePath, *source);
|
||||
else
|
||||
writeFile(storePath, *source);
|
||||
|
||||
auto executable = drv.env.find("executable");
|
||||
if (executable != drv.env.end() && executable->second == "1") {
|
||||
if (chmod(storePath.c_str(), 0755) == -1)
|
||||
throw SysError(format("making '%1%' executable") % storePath);
|
||||
}
|
||||
};
|
||||
|
||||
auto getAttr = [&](const string & name) {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end()) throw Error(format("attribute '%s' missing") % name);
|
||||
return i->second;
|
||||
};
|
||||
/* Try the hashed mirrors first. */
|
||||
if (getAttr("outputHashMode") == "flat")
|
||||
for (auto hashedMirror : settings.hashedMirrors.get()) try {
|
||||
if (!hasSuffix(hashedMirror, "/")) hashedMirror += '/';
|
||||
auto ht = parseHashType(getAttr("outputHashAlgo"));
|
||||
auto h = Hash(getAttr("outputHash"), ht);
|
||||
fetch(hashedMirror + printHashType(h.type) + "/" +
|
||||
h.to_string(Base16, false));
|
||||
return;
|
||||
} catch (Error& e) {
|
||||
debug(e.what());
|
||||
}
|
||||
|
||||
Path storePath = getAttr("out");
|
||||
auto mainUrl = getAttr("url");
|
||||
bool unpack = get(drv.env, "unpack", "") == "1";
|
||||
|
||||
/* Note: have to use a fresh downloader here because we're in
|
||||
a forked process. */
|
||||
auto downloader = makeDownloader();
|
||||
|
||||
auto fetch = [&](const std::string & url) {
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
|
||||
/* No need to do TLS verification, because we check the hash of
|
||||
the result anyway. */
|
||||
DownloadRequest request(url);
|
||||
request.verifyTLS = false;
|
||||
request.decompress = false;
|
||||
|
||||
auto decompressor = makeDecompressionSink(
|
||||
unpack && hasSuffix(mainUrl, ".xz") ? "xz" : "none", sink);
|
||||
downloader->download(std::move(request), *decompressor);
|
||||
decompressor->finish();
|
||||
});
|
||||
|
||||
if (unpack)
|
||||
restorePath(storePath, *source);
|
||||
else
|
||||
writeFile(storePath, *source);
|
||||
|
||||
auto executable = drv.env.find("executable");
|
||||
if (executable != drv.env.end() && executable->second == "1") {
|
||||
if (chmod(storePath.c_str(), 0755) == -1)
|
||||
throw SysError(format("making '%1%' executable") % storePath);
|
||||
}
|
||||
};
|
||||
|
||||
/* Try the hashed mirrors first. */
|
||||
if (getAttr("outputHashMode") == "flat")
|
||||
for (auto hashedMirror : settings.hashedMirrors.get())
|
||||
try {
|
||||
if (!hasSuffix(hashedMirror, "/")) hashedMirror += '/';
|
||||
auto ht = parseHashType(getAttr("outputHashAlgo"));
|
||||
auto h = Hash(getAttr("outputHash"), ht);
|
||||
fetch(hashedMirror + printHashType(h.type) + "/" + h.to_string(Base16, false));
|
||||
return;
|
||||
} catch (Error & e) {
|
||||
debug(e.what());
|
||||
}
|
||||
|
||||
/* Otherwise try the specified URL. */
|
||||
fetch(mainUrl);
|
||||
/* Otherwise try the specified URL. */
|
||||
fetch(mainUrl);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
136
third_party/nix/src/libstore/crypto.cc
vendored
136
third_party/nix/src/libstore/crypto.cc
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#include "crypto.hh"
|
||||
#include "util.hh"
|
||||
#include "globals.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#if HAVE_SODIUM
|
||||
#include <sodium.h>
|
||||
|
|
@ -8,119 +8,107 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
static std::pair<std::string, std::string> split(const string & s)
|
||||
{
|
||||
size_t colon = s.find(':');
|
||||
if (colon == std::string::npos || colon == 0)
|
||||
return {"", ""};
|
||||
return {std::string(s, 0, colon), std::string(s, colon + 1)};
|
||||
static std::pair<std::string, std::string> split(const string& s) {
|
||||
size_t colon = s.find(':');
|
||||
if (colon == std::string::npos || colon == 0) return {"", ""};
|
||||
return {std::string(s, 0, colon), std::string(s, colon + 1)};
|
||||
}
|
||||
|
||||
Key::Key(const string & s)
|
||||
{
|
||||
auto ss = split(s);
|
||||
Key::Key(const string& s) {
|
||||
auto ss = split(s);
|
||||
|
||||
name = ss.first;
|
||||
key = ss.second;
|
||||
name = ss.first;
|
||||
key = ss.second;
|
||||
|
||||
if (name == "" || key == "")
|
||||
throw Error("secret key is corrupt");
|
||||
if (name == "" || key == "") throw Error("secret key is corrupt");
|
||||
|
||||
key = base64Decode(key);
|
||||
key = base64Decode(key);
|
||||
}
|
||||
|
||||
SecretKey::SecretKey(const string & s)
|
||||
: Key(s)
|
||||
{
|
||||
SecretKey::SecretKey(const string& s) : Key(s) {
|
||||
#if HAVE_SODIUM
|
||||
if (key.size() != crypto_sign_SECRETKEYBYTES)
|
||||
throw Error("secret key is not valid");
|
||||
if (key.size() != crypto_sign_SECRETKEYBYTES)
|
||||
throw Error("secret key is not valid");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !HAVE_SODIUM
|
||||
[[noreturn]] static void noSodium()
|
||||
{
|
||||
throw Error("Nix was not compiled with libsodium, required for signed binary cache support");
|
||||
[[noreturn]] static void noSodium() {
|
||||
throw Error(
|
||||
"Nix was not compiled with libsodium, required for signed binary cache "
|
||||
"support");
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string SecretKey::signDetached(const std::string & data) const
|
||||
{
|
||||
std::string SecretKey::signDetached(const std::string& data) const {
|
||||
#if HAVE_SODIUM
|
||||
unsigned char sig[crypto_sign_BYTES];
|
||||
unsigned long long sigLen;
|
||||
crypto_sign_detached(sig, &sigLen, (unsigned char *) data.data(), data.size(),
|
||||
(unsigned char *) key.data());
|
||||
return name + ":" + base64Encode(std::string((char *) sig, sigLen));
|
||||
unsigned char sig[crypto_sign_BYTES];
|
||||
unsigned long long sigLen;
|
||||
crypto_sign_detached(sig, &sigLen, (unsigned char*)data.data(), data.size(),
|
||||
(unsigned char*)key.data());
|
||||
return name + ":" + base64Encode(std::string((char*)sig, sigLen));
|
||||
#else
|
||||
noSodium();
|
||||
noSodium();
|
||||
#endif
|
||||
}
|
||||
|
||||
PublicKey SecretKey::toPublicKey() const
|
||||
{
|
||||
PublicKey SecretKey::toPublicKey() const {
|
||||
#if HAVE_SODIUM
|
||||
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
|
||||
crypto_sign_ed25519_sk_to_pk(pk, (unsigned char *) key.data());
|
||||
return PublicKey(name, std::string((char *) pk, crypto_sign_PUBLICKEYBYTES));
|
||||
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
|
||||
crypto_sign_ed25519_sk_to_pk(pk, (unsigned char*)key.data());
|
||||
return PublicKey(name, std::string((char*)pk, crypto_sign_PUBLICKEYBYTES));
|
||||
#else
|
||||
noSodium();
|
||||
noSodium();
|
||||
#endif
|
||||
}
|
||||
|
||||
PublicKey::PublicKey(const string & s)
|
||||
: Key(s)
|
||||
{
|
||||
PublicKey::PublicKey(const string& s) : Key(s) {
|
||||
#if HAVE_SODIUM
|
||||
if (key.size() != crypto_sign_PUBLICKEYBYTES)
|
||||
throw Error("public key is not valid");
|
||||
if (key.size() != crypto_sign_PUBLICKEYBYTES)
|
||||
throw Error("public key is not valid");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool verifyDetached(const std::string & data, const std::string & sig,
|
||||
const PublicKeys & publicKeys)
|
||||
{
|
||||
bool verifyDetached(const std::string& data, const std::string& sig,
|
||||
const PublicKeys& publicKeys) {
|
||||
#if HAVE_SODIUM
|
||||
auto ss = split(sig);
|
||||
auto ss = split(sig);
|
||||
|
||||
auto key = publicKeys.find(ss.first);
|
||||
if (key == publicKeys.end()) return false;
|
||||
auto key = publicKeys.find(ss.first);
|
||||
if (key == publicKeys.end()) return false;
|
||||
|
||||
auto sig2 = base64Decode(ss.second);
|
||||
if (sig2.size() != crypto_sign_BYTES)
|
||||
throw Error("signature is not valid");
|
||||
auto sig2 = base64Decode(ss.second);
|
||||
if (sig2.size() != crypto_sign_BYTES) throw Error("signature is not valid");
|
||||
|
||||
return crypto_sign_verify_detached((unsigned char *) sig2.data(),
|
||||
(unsigned char *) data.data(), data.size(),
|
||||
(unsigned char *) key->second.key.data()) == 0;
|
||||
return crypto_sign_verify_detached(
|
||||
(unsigned char*)sig2.data(), (unsigned char*)data.data(),
|
||||
data.size(), (unsigned char*)key->second.key.data()) == 0;
|
||||
#else
|
||||
noSodium();
|
||||
noSodium();
|
||||
#endif
|
||||
}
|
||||
|
||||
PublicKeys getDefaultPublicKeys()
|
||||
{
|
||||
PublicKeys publicKeys;
|
||||
PublicKeys getDefaultPublicKeys() {
|
||||
PublicKeys publicKeys;
|
||||
|
||||
// FIXME: filter duplicates
|
||||
// FIXME: filter duplicates
|
||||
|
||||
for (auto s : settings.trustedPublicKeys.get()) {
|
||||
PublicKey key(s);
|
||||
publicKeys.emplace(key.name, key);
|
||||
for (auto s : settings.trustedPublicKeys.get()) {
|
||||
PublicKey key(s);
|
||||
publicKeys.emplace(key.name, key);
|
||||
}
|
||||
|
||||
for (auto secretKeyFile : settings.secretKeyFiles.get()) {
|
||||
try {
|
||||
SecretKey secretKey(readFile(secretKeyFile));
|
||||
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
|
||||
} catch (SysError& e) {
|
||||
/* Ignore unreadable key files. That's normal in a
|
||||
multi-user installation. */
|
||||
}
|
||||
}
|
||||
|
||||
for (auto secretKeyFile : settings.secretKeyFiles.get()) {
|
||||
try {
|
||||
SecretKey secretKey(readFile(secretKeyFile));
|
||||
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
|
||||
} catch (SysError & e) {
|
||||
/* Ignore unreadable key files. That's normal in a
|
||||
multi-user installation. */
|
||||
}
|
||||
}
|
||||
|
||||
return publicKeys;
|
||||
return publicKeys;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
50
third_party/nix/src/libstore/crypto.hh
vendored
50
third_party/nix/src/libstore/crypto.hh
vendored
|
|
@ -1,54 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
#include <map>
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct Key
|
||||
{
|
||||
std::string name;
|
||||
std::string key;
|
||||
struct Key {
|
||||
std::string name;
|
||||
std::string key;
|
||||
|
||||
/* Construct Key from a string in the format
|
||||
‘<name>:<key-in-base64>’. */
|
||||
Key(const std::string & s);
|
||||
/* Construct Key from a string in the format
|
||||
‘<name>:<key-in-base64>’. */
|
||||
Key(const std::string& s);
|
||||
|
||||
protected:
|
||||
Key(const std::string & name, const std::string & key)
|
||||
: name(name), key(key) { }
|
||||
protected:
|
||||
Key(const std::string& name, const std::string& key) : name(name), key(key) {}
|
||||
};
|
||||
|
||||
struct PublicKey;
|
||||
|
||||
struct SecretKey : Key
|
||||
{
|
||||
SecretKey(const std::string & s);
|
||||
struct SecretKey : Key {
|
||||
SecretKey(const std::string& s);
|
||||
|
||||
/* Return a detached signature of the given string. */
|
||||
std::string signDetached(const std::string & s) const;
|
||||
/* Return a detached signature of the given string. */
|
||||
std::string signDetached(const std::string& s) const;
|
||||
|
||||
PublicKey toPublicKey() const;
|
||||
PublicKey toPublicKey() const;
|
||||
};
|
||||
|
||||
struct PublicKey : Key
|
||||
{
|
||||
PublicKey(const std::string & data);
|
||||
struct PublicKey : Key {
|
||||
PublicKey(const std::string& data);
|
||||
|
||||
private:
|
||||
PublicKey(const std::string & name, const std::string & key)
|
||||
: Key(name, key) { }
|
||||
friend struct SecretKey;
|
||||
private:
|
||||
PublicKey(const std::string& name, const std::string& key) : Key(name, key) {}
|
||||
friend struct SecretKey;
|
||||
};
|
||||
|
||||
typedef std::map<std::string, PublicKey> PublicKeys;
|
||||
|
||||
/* Return true iff ‘sig’ is a correct signature over ‘data’ using one
|
||||
of the given public keys. */
|
||||
bool verifyDetached(const std::string & data, const std::string & sig,
|
||||
const PublicKeys & publicKeys);
|
||||
bool verifyDetached(const std::string& data, const std::string& sig,
|
||||
const PublicKeys& publicKeys);
|
||||
|
||||
PublicKeys getDefaultPublicKeys();
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
621
third_party/nix/src/libstore/derivations.cc
vendored
621
third_party/nix/src/libstore/derivations.cc
vendored
|
|
@ -1,289 +1,294 @@
|
|||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
#include "fs-accessor.hh"
|
||||
#include "globals.hh"
|
||||
#include "istringstream_nocopy.hh"
|
||||
#include "store-api.hh"
|
||||
#include "util.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "fs-accessor.hh"
|
||||
#include "istringstream_nocopy.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
void DerivationOutput::parseHashInfo(bool& recursive, Hash& hash) const {
|
||||
recursive = false;
|
||||
string algo = hashAlgo;
|
||||
|
||||
void DerivationOutput::parseHashInfo(bool & recursive, Hash & hash) const
|
||||
{
|
||||
recursive = false;
|
||||
string algo = hashAlgo;
|
||||
if (string(algo, 0, 2) == "r:") {
|
||||
recursive = true;
|
||||
algo = string(algo, 2);
|
||||
}
|
||||
|
||||
if (string(algo, 0, 2) == "r:") {
|
||||
recursive = true;
|
||||
algo = string(algo, 2);
|
||||
}
|
||||
HashType hashType = parseHashType(algo);
|
||||
if (hashType == htUnknown)
|
||||
throw Error(format("unknown hash algorithm '%1%'") % algo);
|
||||
|
||||
HashType hashType = parseHashType(algo);
|
||||
if (hashType == htUnknown)
|
||||
throw Error(format("unknown hash algorithm '%1%'") % algo);
|
||||
|
||||
hash = Hash(this->hash, hashType);
|
||||
hash = Hash(this->hash, hashType);
|
||||
}
|
||||
|
||||
|
||||
Path BasicDerivation::findOutput(const string & id) const
|
||||
{
|
||||
auto i = outputs.find(id);
|
||||
if (i == outputs.end())
|
||||
throw Error(format("derivation has no output '%1%'") % id);
|
||||
return i->second.path;
|
||||
Path BasicDerivation::findOutput(const string& id) const {
|
||||
auto i = outputs.find(id);
|
||||
if (i == outputs.end())
|
||||
throw Error(format("derivation has no output '%1%'") % id);
|
||||
return i->second.path;
|
||||
}
|
||||
|
||||
|
||||
bool BasicDerivation::isBuiltin() const
|
||||
{
|
||||
return string(builder, 0, 8) == "builtin:";
|
||||
bool BasicDerivation::isBuiltin() const {
|
||||
return string(builder, 0, 8) == "builtin:";
|
||||
}
|
||||
|
||||
|
||||
Path writeDerivation(ref<Store> store,
|
||||
const Derivation & drv, const string & name, RepairFlag repair)
|
||||
{
|
||||
PathSet references;
|
||||
references.insert(drv.inputSrcs.begin(), drv.inputSrcs.end());
|
||||
for (auto & i : drv.inputDrvs)
|
||||
references.insert(i.first);
|
||||
/* Note that the outputs of a derivation are *not* references
|
||||
(that can be missing (of course) and should not necessarily be
|
||||
held during a garbage collection). */
|
||||
string suffix = name + drvExtension;
|
||||
string contents = drv.unparse();
|
||||
return settings.readOnlyMode
|
||||
? store->computeStorePathForText(suffix, contents, references)
|
||||
: store->addTextToStore(suffix, contents, references, repair);
|
||||
Path writeDerivation(ref<Store> store, const Derivation& drv,
|
||||
const string& name, RepairFlag repair) {
|
||||
PathSet references;
|
||||
references.insert(drv.inputSrcs.begin(), drv.inputSrcs.end());
|
||||
for (auto& i : drv.inputDrvs) references.insert(i.first);
|
||||
/* Note that the outputs of a derivation are *not* references
|
||||
(that can be missing (of course) and should not necessarily be
|
||||
held during a garbage collection). */
|
||||
string suffix = name + drvExtension;
|
||||
string contents = drv.unparse();
|
||||
return settings.readOnlyMode
|
||||
? store->computeStorePathForText(suffix, contents, references)
|
||||
: store->addTextToStore(suffix, contents, references, repair);
|
||||
}
|
||||
|
||||
|
||||
/* Read string `s' from stream `str'. */
|
||||
static void expect(std::istream & str, const string & s)
|
||||
{
|
||||
char s2[s.size()];
|
||||
str.read(s2, s.size());
|
||||
if (string(s2, s.size()) != s)
|
||||
throw FormatError(format("expected string '%1%'") % s);
|
||||
static void expect(std::istream& str, const string& s) {
|
||||
char s2[s.size()];
|
||||
str.read(s2, s.size());
|
||||
if (string(s2, s.size()) != s)
|
||||
throw FormatError(format("expected string '%1%'") % s);
|
||||
}
|
||||
|
||||
|
||||
/* Read a C-style string from stream `str'. */
|
||||
static string parseString(std::istream & str)
|
||||
{
|
||||
string res;
|
||||
expect(str, "\"");
|
||||
int c;
|
||||
while ((c = str.get()) != '"')
|
||||
if (c == '\\') {
|
||||
c = str.get();
|
||||
if (c == 'n') res += '\n';
|
||||
else if (c == 'r') res += '\r';
|
||||
else if (c == 't') res += '\t';
|
||||
else res += c;
|
||||
}
|
||||
else res += c;
|
||||
return res;
|
||||
static string parseString(std::istream& str) {
|
||||
string res;
|
||||
expect(str, "\"");
|
||||
int c;
|
||||
while ((c = str.get()) != '"')
|
||||
if (c == '\\') {
|
||||
c = str.get();
|
||||
if (c == 'n')
|
||||
res += '\n';
|
||||
else if (c == 'r')
|
||||
res += '\r';
|
||||
else if (c == 't')
|
||||
res += '\t';
|
||||
else
|
||||
res += c;
|
||||
} else
|
||||
res += c;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static Path parsePath(std::istream & str)
|
||||
{
|
||||
string s = parseString(str);
|
||||
if (s.size() == 0 || s[0] != '/')
|
||||
throw FormatError(format("bad path '%1%' in derivation") % s);
|
||||
return s;
|
||||
static Path parsePath(std::istream& str) {
|
||||
string s = parseString(str);
|
||||
if (s.size() == 0 || s[0] != '/')
|
||||
throw FormatError(format("bad path '%1%' in derivation") % s);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
static bool endOfList(std::istream & str)
|
||||
{
|
||||
if (str.peek() == ',') {
|
||||
str.get();
|
||||
return false;
|
||||
}
|
||||
if (str.peek() == ']') {
|
||||
str.get();
|
||||
return true;
|
||||
}
|
||||
static bool endOfList(std::istream& str) {
|
||||
if (str.peek() == ',') {
|
||||
str.get();
|
||||
return false;
|
||||
}
|
||||
if (str.peek() == ']') {
|
||||
str.get();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static StringSet parseStrings(std::istream & str, bool arePaths)
|
||||
{
|
||||
StringSet res;
|
||||
while (!endOfList(str))
|
||||
res.insert(arePaths ? parsePath(str) : parseString(str));
|
||||
return res;
|
||||
static StringSet parseStrings(std::istream& str, bool arePaths) {
|
||||
StringSet res;
|
||||
while (!endOfList(str))
|
||||
res.insert(arePaths ? parsePath(str) : parseString(str));
|
||||
return res;
|
||||
}
|
||||
|
||||
static Derivation parseDerivation(const string& s) {
|
||||
Derivation drv;
|
||||
istringstream_nocopy str(s);
|
||||
expect(str, "Derive([");
|
||||
|
||||
static Derivation parseDerivation(const string & s)
|
||||
{
|
||||
Derivation drv;
|
||||
istringstream_nocopy str(s);
|
||||
expect(str, "Derive([");
|
||||
|
||||
/* Parse the list of outputs. */
|
||||
while (!endOfList(str)) {
|
||||
DerivationOutput out;
|
||||
expect(str, "("); string id = parseString(str);
|
||||
expect(str, ","); out.path = parsePath(str);
|
||||
expect(str, ","); out.hashAlgo = parseString(str);
|
||||
expect(str, ","); out.hash = parseString(str);
|
||||
expect(str, ")");
|
||||
drv.outputs[id] = out;
|
||||
}
|
||||
|
||||
/* Parse the list of input derivations. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str)) {
|
||||
expect(str, "(");
|
||||
Path drvPath = parsePath(str);
|
||||
expect(str, ",[");
|
||||
drv.inputDrvs[drvPath] = parseStrings(str, false);
|
||||
expect(str, ")");
|
||||
}
|
||||
|
||||
expect(str, ",["); drv.inputSrcs = parseStrings(str, true);
|
||||
expect(str, ","); drv.platform = parseString(str);
|
||||
expect(str, ","); drv.builder = parseString(str);
|
||||
|
||||
/* Parse the builder arguments. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str))
|
||||
drv.args.push_back(parseString(str));
|
||||
|
||||
/* Parse the environment variables. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str)) {
|
||||
expect(str, "("); string name = parseString(str);
|
||||
expect(str, ","); string value = parseString(str);
|
||||
expect(str, ")");
|
||||
drv.env[name] = value;
|
||||
}
|
||||
|
||||
/* Parse the list of outputs. */
|
||||
while (!endOfList(str)) {
|
||||
DerivationOutput out;
|
||||
expect(str, "(");
|
||||
string id = parseString(str);
|
||||
expect(str, ",");
|
||||
out.path = parsePath(str);
|
||||
expect(str, ",");
|
||||
out.hashAlgo = parseString(str);
|
||||
expect(str, ",");
|
||||
out.hash = parseString(str);
|
||||
expect(str, ")");
|
||||
return drv;
|
||||
drv.outputs[id] = out;
|
||||
}
|
||||
|
||||
/* Parse the list of input derivations. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str)) {
|
||||
expect(str, "(");
|
||||
Path drvPath = parsePath(str);
|
||||
expect(str, ",[");
|
||||
drv.inputDrvs[drvPath] = parseStrings(str, false);
|
||||
expect(str, ")");
|
||||
}
|
||||
|
||||
expect(str, ",[");
|
||||
drv.inputSrcs = parseStrings(str, true);
|
||||
expect(str, ",");
|
||||
drv.platform = parseString(str);
|
||||
expect(str, ",");
|
||||
drv.builder = parseString(str);
|
||||
|
||||
/* Parse the builder arguments. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str)) drv.args.push_back(parseString(str));
|
||||
|
||||
/* Parse the environment variables. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str)) {
|
||||
expect(str, "(");
|
||||
string name = parseString(str);
|
||||
expect(str, ",");
|
||||
string value = parseString(str);
|
||||
expect(str, ")");
|
||||
drv.env[name] = value;
|
||||
}
|
||||
|
||||
expect(str, ")");
|
||||
return drv;
|
||||
}
|
||||
|
||||
|
||||
Derivation readDerivation(const Path & drvPath)
|
||||
{
|
||||
try {
|
||||
return parseDerivation(readFile(drvPath));
|
||||
} catch (FormatError & e) {
|
||||
throw Error(format("error parsing derivation '%1%': %2%") % drvPath % e.msg());
|
||||
}
|
||||
Derivation readDerivation(const Path& drvPath) {
|
||||
try {
|
||||
return parseDerivation(readFile(drvPath));
|
||||
} catch (FormatError& e) {
|
||||
throw Error(format("error parsing derivation '%1%': %2%") % drvPath %
|
||||
e.msg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Derivation Store::derivationFromPath(const Path & drvPath)
|
||||
{
|
||||
assertStorePath(drvPath);
|
||||
ensurePath(drvPath);
|
||||
auto accessor = getFSAccessor();
|
||||
try {
|
||||
return parseDerivation(accessor->readFile(drvPath));
|
||||
} catch (FormatError & e) {
|
||||
throw Error(format("error parsing derivation '%1%': %2%") % drvPath % e.msg());
|
||||
}
|
||||
Derivation Store::derivationFromPath(const Path& drvPath) {
|
||||
assertStorePath(drvPath);
|
||||
ensurePath(drvPath);
|
||||
auto accessor = getFSAccessor();
|
||||
try {
|
||||
return parseDerivation(accessor->readFile(drvPath));
|
||||
} catch (FormatError& e) {
|
||||
throw Error(format("error parsing derivation '%1%': %2%") % drvPath %
|
||||
e.msg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void printString(string & res, const string & s)
|
||||
{
|
||||
res += '"';
|
||||
for (const char * i = s.c_str(); *i; i++)
|
||||
if (*i == '\"' || *i == '\\') { res += "\\"; res += *i; }
|
||||
else if (*i == '\n') res += "\\n";
|
||||
else if (*i == '\r') res += "\\r";
|
||||
else if (*i == '\t') res += "\\t";
|
||||
else res += *i;
|
||||
res += '"';
|
||||
static void printString(string& res, const string& s) {
|
||||
res += '"';
|
||||
for (const char* i = s.c_str(); *i; i++)
|
||||
if (*i == '\"' || *i == '\\') {
|
||||
res += "\\";
|
||||
res += *i;
|
||||
} else if (*i == '\n')
|
||||
res += "\\n";
|
||||
else if (*i == '\r')
|
||||
res += "\\r";
|
||||
else if (*i == '\t')
|
||||
res += "\\t";
|
||||
else
|
||||
res += *i;
|
||||
res += '"';
|
||||
}
|
||||
|
||||
|
||||
template<class ForwardIterator>
|
||||
static void printStrings(string & res, ForwardIterator i, ForwardIterator j)
|
||||
{
|
||||
res += '[';
|
||||
bool first = true;
|
||||
for ( ; i != j; ++i) {
|
||||
if (first) first = false; else res += ',';
|
||||
printString(res, *i);
|
||||
}
|
||||
res += ']';
|
||||
template <class ForwardIterator>
|
||||
static void printStrings(string& res, ForwardIterator i, ForwardIterator j) {
|
||||
res += '[';
|
||||
bool first = true;
|
||||
for (; i != j; ++i) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
res += ',';
|
||||
printString(res, *i);
|
||||
}
|
||||
res += ']';
|
||||
}
|
||||
|
||||
string Derivation::unparse() const {
|
||||
string s;
|
||||
s.reserve(65536);
|
||||
s += "Derive([";
|
||||
|
||||
string Derivation::unparse() const
|
||||
{
|
||||
string s;
|
||||
s.reserve(65536);
|
||||
s += "Derive([";
|
||||
bool first = true;
|
||||
for (auto& i : outputs) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
s += ',';
|
||||
s += '(';
|
||||
printString(s, i.first);
|
||||
s += ',';
|
||||
printString(s, i.second.path);
|
||||
s += ',';
|
||||
printString(s, i.second.hashAlgo);
|
||||
s += ',';
|
||||
printString(s, i.second.hash);
|
||||
s += ')';
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
for (auto & i : outputs) {
|
||||
if (first) first = false; else s += ',';
|
||||
s += '('; printString(s, i.first);
|
||||
s += ','; printString(s, i.second.path);
|
||||
s += ','; printString(s, i.second.hashAlgo);
|
||||
s += ','; printString(s, i.second.hash);
|
||||
s += ')';
|
||||
}
|
||||
s += "],[";
|
||||
first = true;
|
||||
for (auto& i : inputDrvs) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
s += ',';
|
||||
s += '(';
|
||||
printString(s, i.first);
|
||||
s += ',';
|
||||
printStrings(s, i.second.begin(), i.second.end());
|
||||
s += ')';
|
||||
}
|
||||
|
||||
s += "],[";
|
||||
first = true;
|
||||
for (auto & i : inputDrvs) {
|
||||
if (first) first = false; else s += ',';
|
||||
s += '('; printString(s, i.first);
|
||||
s += ','; printStrings(s, i.second.begin(), i.second.end());
|
||||
s += ')';
|
||||
}
|
||||
s += "],";
|
||||
printStrings(s, inputSrcs.begin(), inputSrcs.end());
|
||||
|
||||
s += "],";
|
||||
printStrings(s, inputSrcs.begin(), inputSrcs.end());
|
||||
s += ',';
|
||||
printString(s, platform);
|
||||
s += ',';
|
||||
printString(s, builder);
|
||||
s += ',';
|
||||
printStrings(s, args.begin(), args.end());
|
||||
|
||||
s += ','; printString(s, platform);
|
||||
s += ','; printString(s, builder);
|
||||
s += ','; printStrings(s, args.begin(), args.end());
|
||||
s += ",[";
|
||||
first = true;
|
||||
for (auto& i : env) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
s += ',';
|
||||
s += '(';
|
||||
printString(s, i.first);
|
||||
s += ',';
|
||||
printString(s, i.second);
|
||||
s += ')';
|
||||
}
|
||||
|
||||
s += ",[";
|
||||
first = true;
|
||||
for (auto & i : env) {
|
||||
if (first) first = false; else s += ',';
|
||||
s += '('; printString(s, i.first);
|
||||
s += ','; printString(s, i.second);
|
||||
s += ')';
|
||||
}
|
||||
s += "])";
|
||||
|
||||
s += "])";
|
||||
|
||||
return s;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
bool isDerivation(const string & fileName)
|
||||
{
|
||||
return hasSuffix(fileName, drvExtension);
|
||||
bool isDerivation(const string& fileName) {
|
||||
return hasSuffix(fileName, drvExtension);
|
||||
}
|
||||
|
||||
|
||||
bool BasicDerivation::isFixedOutput() const
|
||||
{
|
||||
return outputs.size() == 1 &&
|
||||
outputs.begin()->first == "out" &&
|
||||
outputs.begin()->second.hash != "";
|
||||
bool BasicDerivation::isFixedOutput() const {
|
||||
return outputs.size() == 1 && outputs.begin()->first == "out" &&
|
||||
outputs.begin()->second.hash != "";
|
||||
}
|
||||
|
||||
|
||||
DrvHashes drvHashes;
|
||||
|
||||
|
||||
/* Returns the hash of a derivation modulo fixed-output
|
||||
subderivations. A fixed-output derivation is a derivation with one
|
||||
output (`out') for which an expected hash and hash algorithm are
|
||||
|
|
@ -304,113 +309,95 @@ DrvHashes drvHashes;
|
|||
paths have been replaced by the result of a recursive call to this
|
||||
function, and that for fixed-output derivations we return a hash of
|
||||
its output path. */
|
||||
Hash hashDerivationModulo(Store & store, Derivation drv)
|
||||
{
|
||||
/* Return a fixed hash for fixed-output derivations. */
|
||||
if (drv.isFixedOutput()) {
|
||||
DerivationOutputs::const_iterator i = drv.outputs.begin();
|
||||
return hashString(htSHA256, "fixed:out:"
|
||||
+ i->second.hashAlgo + ":"
|
||||
+ i->second.hash + ":"
|
||||
+ i->second.path);
|
||||
Hash hashDerivationModulo(Store& store, Derivation drv) {
|
||||
/* Return a fixed hash for fixed-output derivations. */
|
||||
if (drv.isFixedOutput()) {
|
||||
DerivationOutputs::const_iterator i = drv.outputs.begin();
|
||||
return hashString(htSHA256, "fixed:out:" + i->second.hashAlgo + ":" +
|
||||
i->second.hash + ":" + i->second.path);
|
||||
}
|
||||
|
||||
/* For other derivations, replace the inputs paths with recursive
|
||||
calls to this function.*/
|
||||
DerivationInputs inputs2;
|
||||
for (auto& i : drv.inputDrvs) {
|
||||
Hash h = drvHashes[i.first];
|
||||
if (!h) {
|
||||
assert(store.isValidPath(i.first));
|
||||
Derivation drv2 = readDerivation(store.toRealPath(i.first));
|
||||
h = hashDerivationModulo(store, drv2);
|
||||
drvHashes[i.first] = h;
|
||||
}
|
||||
inputs2[h.to_string(Base16, false)] = i.second;
|
||||
}
|
||||
drv.inputDrvs = inputs2;
|
||||
|
||||
/* For other derivations, replace the inputs paths with recursive
|
||||
calls to this function.*/
|
||||
DerivationInputs inputs2;
|
||||
for (auto & i : drv.inputDrvs) {
|
||||
Hash h = drvHashes[i.first];
|
||||
if (!h) {
|
||||
assert(store.isValidPath(i.first));
|
||||
Derivation drv2 = readDerivation(store.toRealPath(i.first));
|
||||
h = hashDerivationModulo(store, drv2);
|
||||
drvHashes[i.first] = h;
|
||||
}
|
||||
inputs2[h.to_string(Base16, false)] = i.second;
|
||||
}
|
||||
drv.inputDrvs = inputs2;
|
||||
|
||||
return hashString(htSHA256, drv.unparse());
|
||||
return hashString(htSHA256, drv.unparse());
|
||||
}
|
||||
|
||||
|
||||
DrvPathWithOutputs parseDrvPathWithOutputs(const string & s)
|
||||
{
|
||||
size_t n = s.find("!");
|
||||
return n == s.npos
|
||||
? DrvPathWithOutputs(s, std::set<string>())
|
||||
: DrvPathWithOutputs(string(s, 0, n), tokenizeString<std::set<string> >(string(s, n + 1), ","));
|
||||
DrvPathWithOutputs parseDrvPathWithOutputs(const string& s) {
|
||||
size_t n = s.find("!");
|
||||
return n == s.npos ? DrvPathWithOutputs(s, std::set<string>())
|
||||
: DrvPathWithOutputs(string(s, 0, n),
|
||||
tokenizeString<std::set<string> >(
|
||||
string(s, n + 1), ","));
|
||||
}
|
||||
|
||||
|
||||
Path makeDrvPathWithOutputs(const Path & drvPath, const std::set<string> & outputs)
|
||||
{
|
||||
return outputs.empty()
|
||||
? drvPath
|
||||
: drvPath + "!" + concatStringsSep(",", outputs);
|
||||
Path makeDrvPathWithOutputs(const Path& drvPath,
|
||||
const std::set<string>& outputs) {
|
||||
return outputs.empty() ? drvPath
|
||||
: drvPath + "!" + concatStringsSep(",", outputs);
|
||||
}
|
||||
|
||||
|
||||
bool wantOutput(const string & output, const std::set<string> & wanted)
|
||||
{
|
||||
return wanted.empty() || wanted.find(output) != wanted.end();
|
||||
bool wantOutput(const string& output, const std::set<string>& wanted) {
|
||||
return wanted.empty() || wanted.find(output) != wanted.end();
|
||||
}
|
||||
|
||||
|
||||
PathSet BasicDerivation::outputPaths() const
|
||||
{
|
||||
PathSet paths;
|
||||
for (auto & i : outputs)
|
||||
paths.insert(i.second.path);
|
||||
return paths;
|
||||
PathSet BasicDerivation::outputPaths() const {
|
||||
PathSet paths;
|
||||
for (auto& i : outputs) paths.insert(i.second.path);
|
||||
return paths;
|
||||
}
|
||||
|
||||
Source& readDerivation(Source& in, Store& store, BasicDerivation& drv) {
|
||||
drv.outputs.clear();
|
||||
auto nr = readNum<size_t>(in);
|
||||
for (size_t n = 0; n < nr; n++) {
|
||||
auto name = readString(in);
|
||||
DerivationOutput o;
|
||||
in >> o.path >> o.hashAlgo >> o.hash;
|
||||
store.assertStorePath(o.path);
|
||||
drv.outputs[name] = o;
|
||||
}
|
||||
|
||||
Source & readDerivation(Source & in, Store & store, BasicDerivation & drv)
|
||||
{
|
||||
drv.outputs.clear();
|
||||
auto nr = readNum<size_t>(in);
|
||||
for (size_t n = 0; n < nr; n++) {
|
||||
auto name = readString(in);
|
||||
DerivationOutput o;
|
||||
in >> o.path >> o.hashAlgo >> o.hash;
|
||||
store.assertStorePath(o.path);
|
||||
drv.outputs[name] = o;
|
||||
}
|
||||
drv.inputSrcs = readStorePaths<PathSet>(store, in);
|
||||
in >> drv.platform >> drv.builder;
|
||||
drv.args = readStrings<Strings>(in);
|
||||
|
||||
drv.inputSrcs = readStorePaths<PathSet>(store, in);
|
||||
in >> drv.platform >> drv.builder;
|
||||
drv.args = readStrings<Strings>(in);
|
||||
nr = readNum<size_t>(in);
|
||||
for (size_t n = 0; n < nr; n++) {
|
||||
auto key = readString(in);
|
||||
auto value = readString(in);
|
||||
drv.env[key] = value;
|
||||
}
|
||||
|
||||
nr = readNum<size_t>(in);
|
||||
for (size_t n = 0; n < nr; n++) {
|
||||
auto key = readString(in);
|
||||
auto value = readString(in);
|
||||
drv.env[key] = value;
|
||||
}
|
||||
|
||||
return in;
|
||||
return in;
|
||||
}
|
||||
|
||||
|
||||
Sink & operator << (Sink & out, const BasicDerivation & drv)
|
||||
{
|
||||
out << drv.outputs.size();
|
||||
for (auto & i : drv.outputs)
|
||||
out << i.first << i.second.path << i.second.hashAlgo << i.second.hash;
|
||||
out << drv.inputSrcs << drv.platform << drv.builder << drv.args;
|
||||
out << drv.env.size();
|
||||
for (auto & i : drv.env)
|
||||
out << i.first << i.second;
|
||||
return out;
|
||||
Sink& operator<<(Sink& out, const BasicDerivation& drv) {
|
||||
out << drv.outputs.size();
|
||||
for (auto& i : drv.outputs)
|
||||
out << i.first << i.second.path << i.second.hashAlgo << i.second.hash;
|
||||
out << drv.inputSrcs << drv.platform << drv.builder << drv.args;
|
||||
out << drv.env.size();
|
||||
for (auto& i : drv.env) out << i.first << i.second;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
std::string hashPlaceholder(const std::string & outputName)
|
||||
{
|
||||
// FIXME: memoize?
|
||||
return "/" + hashString(htSHA256, "nix-output:" + outputName).to_string(Base32, false);
|
||||
std::string hashPlaceholder(const std::string& outputName) {
|
||||
// FIXME: memoize?
|
||||
return "/" + hashString(htSHA256, "nix-output:" + outputName)
|
||||
.to_string(Base32, false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
106
third_party/nix/src/libstore/derivations.hh
vendored
106
third_party/nix/src/libstore/derivations.hh
vendored
|
|
@ -1,36 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include <map>
|
||||
#include "hash.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Extension of derivations in the Nix store. */
|
||||
const string drvExtension = ".drv";
|
||||
|
||||
|
||||
/* Abstract syntax of derivations. */
|
||||
|
||||
struct DerivationOutput
|
||||
{
|
||||
Path path;
|
||||
string hashAlgo; /* hash used for expected hash computation */
|
||||
string hash; /* expected hash, may be null */
|
||||
DerivationOutput()
|
||||
{
|
||||
}
|
||||
DerivationOutput(Path path, string hashAlgo, string hash)
|
||||
{
|
||||
this->path = path;
|
||||
this->hashAlgo = hashAlgo;
|
||||
this->hash = hash;
|
||||
}
|
||||
void parseHashInfo(bool & recursive, Hash & hash) const;
|
||||
struct DerivationOutput {
|
||||
Path path;
|
||||
string hashAlgo; /* hash used for expected hash computation */
|
||||
string hash; /* expected hash, may be null */
|
||||
DerivationOutput() {}
|
||||
DerivationOutput(Path path, string hashAlgo, string hash) {
|
||||
this->path = path;
|
||||
this->hashAlgo = hashAlgo;
|
||||
this->hash = hash;
|
||||
}
|
||||
void parseHashInfo(bool& recursive, Hash& hash) const;
|
||||
};
|
||||
|
||||
typedef std::map<string, DerivationOutput> DerivationOutputs;
|
||||
|
|
@ -41,77 +33,73 @@ typedef std::map<Path, StringSet> DerivationInputs;
|
|||
|
||||
typedef std::map<string, string> StringPairs;
|
||||
|
||||
struct BasicDerivation
|
||||
{
|
||||
DerivationOutputs outputs; /* keyed on symbolic IDs */
|
||||
PathSet inputSrcs; /* inputs that are sources */
|
||||
string platform;
|
||||
Path builder;
|
||||
Strings args;
|
||||
StringPairs env;
|
||||
struct BasicDerivation {
|
||||
DerivationOutputs outputs; /* keyed on symbolic IDs */
|
||||
PathSet inputSrcs; /* inputs that are sources */
|
||||
string platform;
|
||||
Path builder;
|
||||
Strings args;
|
||||
StringPairs env;
|
||||
|
||||
virtual ~BasicDerivation() { };
|
||||
virtual ~BasicDerivation(){};
|
||||
|
||||
/* Return the path corresponding to the output identifier `id' in
|
||||
the given derivation. */
|
||||
Path findOutput(const string & id) const;
|
||||
/* Return the path corresponding to the output identifier `id' in
|
||||
the given derivation. */
|
||||
Path findOutput(const string& id) const;
|
||||
|
||||
bool isBuiltin() const;
|
||||
bool isBuiltin() const;
|
||||
|
||||
/* Return true iff this is a fixed-output derivation. */
|
||||
bool isFixedOutput() const;
|
||||
|
||||
/* Return the output paths of a derivation. */
|
||||
PathSet outputPaths() const;
|
||||
/* Return true iff this is a fixed-output derivation. */
|
||||
bool isFixedOutput() const;
|
||||
|
||||
/* Return the output paths of a derivation. */
|
||||
PathSet outputPaths() const;
|
||||
};
|
||||
|
||||
struct Derivation : BasicDerivation
|
||||
{
|
||||
DerivationInputs inputDrvs; /* inputs that are sub-derivations */
|
||||
struct Derivation : BasicDerivation {
|
||||
DerivationInputs inputDrvs; /* inputs that are sub-derivations */
|
||||
|
||||
/* Print a derivation. */
|
||||
std::string unparse() const;
|
||||
/* Print a derivation. */
|
||||
std::string unparse() const;
|
||||
};
|
||||
|
||||
|
||||
class Store;
|
||||
|
||||
|
||||
/* Write a derivation to the Nix store, and return its path. */
|
||||
Path writeDerivation(ref<Store> store,
|
||||
const Derivation & drv, const string & name, RepairFlag repair = NoRepair);
|
||||
Path writeDerivation(ref<Store> store, const Derivation& drv,
|
||||
const string& name, RepairFlag repair = NoRepair);
|
||||
|
||||
/* Read a derivation from a file. */
|
||||
Derivation readDerivation(const Path & drvPath);
|
||||
Derivation readDerivation(const Path& drvPath);
|
||||
|
||||
/* Check whether a file name ends with the extension for
|
||||
derivations. */
|
||||
bool isDerivation(const string & fileName);
|
||||
bool isDerivation(const string& fileName);
|
||||
|
||||
Hash hashDerivationModulo(Store & store, Derivation drv);
|
||||
Hash hashDerivationModulo(Store& store, Derivation drv);
|
||||
|
||||
/* Memoisation of hashDerivationModulo(). */
|
||||
typedef std::map<Path, Hash> DrvHashes;
|
||||
|
||||
extern DrvHashes drvHashes; // FIXME: global, not thread-safe
|
||||
extern DrvHashes drvHashes; // FIXME: global, not thread-safe
|
||||
|
||||
/* Split a string specifying a derivation and a set of outputs
|
||||
(/nix/store/hash-foo!out1,out2,...) into the derivation path and
|
||||
the outputs. */
|
||||
typedef std::pair<string, std::set<string> > DrvPathWithOutputs;
|
||||
DrvPathWithOutputs parseDrvPathWithOutputs(const string & s);
|
||||
DrvPathWithOutputs parseDrvPathWithOutputs(const string& s);
|
||||
|
||||
Path makeDrvPathWithOutputs(const Path & drvPath, const std::set<string> & outputs);
|
||||
Path makeDrvPathWithOutputs(const Path& drvPath,
|
||||
const std::set<string>& outputs);
|
||||
|
||||
bool wantOutput(const string & output, const std::set<string> & wanted);
|
||||
bool wantOutput(const string& output, const std::set<string>& wanted);
|
||||
|
||||
struct Source;
|
||||
struct Sink;
|
||||
|
||||
Source & readDerivation(Source & in, Store & store, BasicDerivation & drv);
|
||||
Sink & operator << (Sink & out, const BasicDerivation & drv);
|
||||
Source& readDerivation(Source& in, Store& store, BasicDerivation& drv);
|
||||
Sink& operator<<(Sink& out, const BasicDerivation& drv);
|
||||
|
||||
std::string hashPlaceholder(const std::string & outputName);
|
||||
std::string hashPlaceholder(const std::string& outputName);
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
1716
third_party/nix/src/libstore/download.cc
vendored
1716
third_party/nix/src/libstore/download.cc
vendored
File diff suppressed because it is too large
Load diff
180
third_party/nix/src/libstore/download.hh
vendored
180
third_party/nix/src/libstore/download.hh
vendored
|
|
@ -1,120 +1,118 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
#include <string>
|
||||
#include <future>
|
||||
#include <string>
|
||||
#include "globals.hh"
|
||||
#include "hash.hh"
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct DownloadSettings : Config
|
||||
{
|
||||
Setting<bool> enableHttp2{this, true, "http2",
|
||||
"Whether to enable HTTP/2 support."};
|
||||
struct DownloadSettings : Config {
|
||||
Setting<bool> enableHttp2{this, true, "http2",
|
||||
"Whether to enable HTTP/2 support."};
|
||||
|
||||
Setting<std::string> userAgentSuffix{this, "", "user-agent-suffix",
|
||||
"String appended to the user agent in HTTP requests."};
|
||||
Setting<std::string> userAgentSuffix{
|
||||
this, "", "user-agent-suffix",
|
||||
"String appended to the user agent in HTTP requests."};
|
||||
|
||||
Setting<size_t> httpConnections{this, 25, "http-connections",
|
||||
"Number of parallel HTTP connections.",
|
||||
{"binary-caches-parallel-connections"}};
|
||||
Setting<size_t> httpConnections{this,
|
||||
25,
|
||||
"http-connections",
|
||||
"Number of parallel HTTP connections.",
|
||||
{"binary-caches-parallel-connections"}};
|
||||
|
||||
Setting<unsigned long> connectTimeout{this, 0, "connect-timeout",
|
||||
"Timeout for connecting to servers during downloads. 0 means use curl's builtin default."};
|
||||
Setting<unsigned long> connectTimeout{
|
||||
this, 0, "connect-timeout",
|
||||
"Timeout for connecting to servers during downloads. 0 means use curl's "
|
||||
"builtin default."};
|
||||
|
||||
Setting<unsigned long> stalledDownloadTimeout{this, 300, "stalled-download-timeout",
|
||||
"Timeout (in seconds) for receiving data from servers during download. Nix cancels idle downloads after this timeout's duration."};
|
||||
Setting<unsigned long> stalledDownloadTimeout{
|
||||
this, 300, "stalled-download-timeout",
|
||||
"Timeout (in seconds) for receiving data from servers during download. "
|
||||
"Nix cancels idle downloads after this timeout's duration."};
|
||||
|
||||
Setting<unsigned int> tries{this, 5, "download-attempts",
|
||||
"How often Nix will attempt to download a file before giving up."};
|
||||
Setting<unsigned int> tries{
|
||||
this, 5, "download-attempts",
|
||||
"How often Nix will attempt to download a file before giving up."};
|
||||
};
|
||||
|
||||
extern DownloadSettings downloadSettings;
|
||||
|
||||
struct DownloadRequest
|
||||
{
|
||||
std::string uri;
|
||||
std::string expectedETag;
|
||||
bool verifyTLS = true;
|
||||
bool head = false;
|
||||
size_t tries = downloadSettings.tries;
|
||||
unsigned int baseRetryTimeMs = 250;
|
||||
ActivityId parentAct;
|
||||
bool decompress = true;
|
||||
std::shared_ptr<std::string> data;
|
||||
std::string mimeType;
|
||||
std::function<void(char *, size_t)> dataCallback;
|
||||
struct DownloadRequest {
|
||||
std::string uri;
|
||||
std::string expectedETag;
|
||||
bool verifyTLS = true;
|
||||
bool head = false;
|
||||
size_t tries = downloadSettings.tries;
|
||||
unsigned int baseRetryTimeMs = 250;
|
||||
ActivityId parentAct;
|
||||
bool decompress = true;
|
||||
std::shared_ptr<std::string> data;
|
||||
std::string mimeType;
|
||||
std::function<void(char*, size_t)> dataCallback;
|
||||
|
||||
DownloadRequest(const std::string & uri)
|
||||
: uri(uri), parentAct(getCurActivity()) { }
|
||||
DownloadRequest(const std::string& uri)
|
||||
: uri(uri), parentAct(getCurActivity()) {}
|
||||
|
||||
std::string verb()
|
||||
{
|
||||
return data ? "upload" : "download";
|
||||
}
|
||||
std::string verb() { return data ? "upload" : "download"; }
|
||||
};
|
||||
|
||||
struct DownloadResult
|
||||
{
|
||||
bool cached = false;
|
||||
std::string etag;
|
||||
std::string effectiveUri;
|
||||
std::shared_ptr<std::string> data;
|
||||
uint64_t bodySize = 0;
|
||||
struct DownloadResult {
|
||||
bool cached = false;
|
||||
std::string etag;
|
||||
std::string effectiveUri;
|
||||
std::shared_ptr<std::string> data;
|
||||
uint64_t bodySize = 0;
|
||||
};
|
||||
|
||||
struct CachedDownloadRequest
|
||||
{
|
||||
std::string uri;
|
||||
bool unpack = false;
|
||||
std::string name;
|
||||
Hash expectedHash;
|
||||
unsigned int ttl = settings.tarballTtl;
|
||||
struct CachedDownloadRequest {
|
||||
std::string uri;
|
||||
bool unpack = false;
|
||||
std::string name;
|
||||
Hash expectedHash;
|
||||
unsigned int ttl = settings.tarballTtl;
|
||||
|
||||
CachedDownloadRequest(const std::string & uri)
|
||||
: uri(uri) { }
|
||||
CachedDownloadRequest(const std::string& uri) : uri(uri) {}
|
||||
};
|
||||
|
||||
struct CachedDownloadResult
|
||||
{
|
||||
// Note: 'storePath' may be different from 'path' when using a
|
||||
// chroot store.
|
||||
Path storePath;
|
||||
Path path;
|
||||
std::optional<std::string> etag;
|
||||
std::string effectiveUri;
|
||||
struct CachedDownloadResult {
|
||||
// Note: 'storePath' may be different from 'path' when using a
|
||||
// chroot store.
|
||||
Path storePath;
|
||||
Path path;
|
||||
std::optional<std::string> etag;
|
||||
std::string effectiveUri;
|
||||
};
|
||||
|
||||
class Store;
|
||||
|
||||
struct Downloader
|
||||
{
|
||||
virtual ~Downloader() { }
|
||||
struct Downloader {
|
||||
virtual ~Downloader() {}
|
||||
|
||||
/* Enqueue a download request, returning a future to the result of
|
||||
the download. The future may throw a DownloadError
|
||||
exception. */
|
||||
virtual void enqueueDownload(const DownloadRequest & request,
|
||||
Callback<DownloadResult> callback) = 0;
|
||||
/* Enqueue a download request, returning a future to the result of
|
||||
the download. The future may throw a DownloadError
|
||||
exception. */
|
||||
virtual void enqueueDownload(const DownloadRequest& request,
|
||||
Callback<DownloadResult> callback) = 0;
|
||||
|
||||
std::future<DownloadResult> enqueueDownload(const DownloadRequest & request);
|
||||
std::future<DownloadResult> enqueueDownload(const DownloadRequest& request);
|
||||
|
||||
/* Synchronously download a file. */
|
||||
DownloadResult download(const DownloadRequest & request);
|
||||
/* Synchronously download a file. */
|
||||
DownloadResult download(const DownloadRequest& request);
|
||||
|
||||
/* Download a file, writing its data to a sink. The sink will be
|
||||
invoked on the thread of the caller. */
|
||||
void download(DownloadRequest && request, Sink & sink);
|
||||
/* Download a file, writing its data to a sink. The sink will be
|
||||
invoked on the thread of the caller. */
|
||||
void download(DownloadRequest&& request, Sink& sink);
|
||||
|
||||
/* Check if the specified file is already in ~/.cache/nix/tarballs
|
||||
and is more recent than ‘tarball-ttl’ seconds. Otherwise,
|
||||
use the recorded ETag to verify if the server has a more
|
||||
recent version, and if so, download it to the Nix store. */
|
||||
CachedDownloadResult downloadCached(ref<Store> store, const CachedDownloadRequest & request);
|
||||
/* Check if the specified file is already in ~/.cache/nix/tarballs
|
||||
and is more recent than ‘tarball-ttl’ seconds. Otherwise,
|
||||
use the recorded ETag to verify if the server has a more
|
||||
recent version, and if so, download it to the Nix store. */
|
||||
CachedDownloadResult downloadCached(ref<Store> store,
|
||||
const CachedDownloadRequest& request);
|
||||
|
||||
enum Error { NotFound, Forbidden, Misc, Transient, Interrupted };
|
||||
enum Error { NotFound, Forbidden, Misc, Transient, Interrupted };
|
||||
};
|
||||
|
||||
/* Return a shared Downloader object. Using this object is preferred
|
||||
|
|
@ -124,15 +122,13 @@ ref<Downloader> getDownloader();
|
|||
/* Return a new Downloader object. */
|
||||
ref<Downloader> makeDownloader();
|
||||
|
||||
class DownloadError : public Error
|
||||
{
|
||||
public:
|
||||
Downloader::Error error;
|
||||
DownloadError(Downloader::Error error, const FormatOrString & fs)
|
||||
: Error(fs), error(error)
|
||||
{ }
|
||||
class DownloadError : public Error {
|
||||
public:
|
||||
Downloader::Error error;
|
||||
DownloadError(Downloader::Error error, const FormatOrString& fs)
|
||||
: Error(fs), error(error) {}
|
||||
};
|
||||
|
||||
bool isUri(const string & s);
|
||||
bool isUri(const string& s);
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
144
third_party/nix/src/libstore/export-import.cc
vendored
144
third_party/nix/src/libstore/export-import.cc
vendored
|
|
@ -1,106 +1,100 @@
|
|||
#include "store-api.hh"
|
||||
#include "archive.hh"
|
||||
#include "worker-protocol.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include "archive.hh"
|
||||
#include "store-api.hh"
|
||||
#include "worker-protocol.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct HashAndWriteSink : Sink
|
||||
{
|
||||
Sink & writeSink;
|
||||
HashSink hashSink;
|
||||
HashAndWriteSink(Sink & writeSink) : writeSink(writeSink), hashSink(htSHA256)
|
||||
{
|
||||
}
|
||||
virtual void operator () (const unsigned char * data, size_t len)
|
||||
{
|
||||
writeSink(data, len);
|
||||
hashSink(data, len);
|
||||
}
|
||||
Hash currentHash()
|
||||
{
|
||||
return hashSink.currentHash().first;
|
||||
}
|
||||
struct HashAndWriteSink : Sink {
|
||||
Sink& writeSink;
|
||||
HashSink hashSink;
|
||||
HashAndWriteSink(Sink& writeSink)
|
||||
: writeSink(writeSink), hashSink(htSHA256) {}
|
||||
virtual void operator()(const unsigned char* data, size_t len) {
|
||||
writeSink(data, len);
|
||||
hashSink(data, len);
|
||||
}
|
||||
Hash currentHash() { return hashSink.currentHash().first; }
|
||||
};
|
||||
|
||||
void Store::exportPaths(const Paths & paths, Sink & sink)
|
||||
{
|
||||
Paths sorted = topoSortPaths(PathSet(paths.begin(), paths.end()));
|
||||
std::reverse(sorted.begin(), sorted.end());
|
||||
void Store::exportPaths(const Paths& paths, Sink& sink) {
|
||||
Paths sorted = topoSortPaths(PathSet(paths.begin(), paths.end()));
|
||||
std::reverse(sorted.begin(), sorted.end());
|
||||
|
||||
std::string doneLabel("paths exported");
|
||||
//logger->incExpected(doneLabel, sorted.size());
|
||||
std::string doneLabel("paths exported");
|
||||
// logger->incExpected(doneLabel, sorted.size());
|
||||
|
||||
for (auto & path : sorted) {
|
||||
//Activity act(*logger, lvlInfo, format("exporting path '%s'") % path);
|
||||
sink << 1;
|
||||
exportPath(path, sink);
|
||||
//logger->incProgress(doneLabel);
|
||||
}
|
||||
for (auto& path : sorted) {
|
||||
// Activity act(*logger, lvlInfo, format("exporting path '%s'") % path);
|
||||
sink << 1;
|
||||
exportPath(path, sink);
|
||||
// logger->incProgress(doneLabel);
|
||||
}
|
||||
|
||||
sink << 0;
|
||||
sink << 0;
|
||||
}
|
||||
|
||||
void Store::exportPath(const Path & path, Sink & sink)
|
||||
{
|
||||
auto info = queryPathInfo(path);
|
||||
void Store::exportPath(const Path& path, Sink& sink) {
|
||||
auto info = queryPathInfo(path);
|
||||
|
||||
HashAndWriteSink hashAndWriteSink(sink);
|
||||
HashAndWriteSink hashAndWriteSink(sink);
|
||||
|
||||
narFromPath(path, hashAndWriteSink);
|
||||
narFromPath(path, hashAndWriteSink);
|
||||
|
||||
/* Refuse to export paths that have changed. This prevents
|
||||
filesystem corruption from spreading to other machines.
|
||||
Don't complain if the stored hash is zero (unknown). */
|
||||
Hash hash = hashAndWriteSink.currentHash();
|
||||
if (hash != info->narHash && info->narHash != Hash(info->narHash.type))
|
||||
throw Error(format("hash of path '%1%' has changed from '%2%' to '%3%'!") % path
|
||||
% info->narHash.to_string() % hash.to_string());
|
||||
/* Refuse to export paths that have changed. This prevents
|
||||
filesystem corruption from spreading to other machines.
|
||||
Don't complain if the stored hash is zero (unknown). */
|
||||
Hash hash = hashAndWriteSink.currentHash();
|
||||
if (hash != info->narHash && info->narHash != Hash(info->narHash.type))
|
||||
throw Error(format("hash of path '%1%' has changed from '%2%' to '%3%'!") %
|
||||
path % info->narHash.to_string() % hash.to_string());
|
||||
|
||||
hashAndWriteSink << exportMagic << path << info->references << info->deriver << 0;
|
||||
hashAndWriteSink << exportMagic << path << info->references << info->deriver
|
||||
<< 0;
|
||||
}
|
||||
|
||||
Paths Store::importPaths(Source & source, std::shared_ptr<FSAccessor> accessor, CheckSigsFlag checkSigs)
|
||||
{
|
||||
Paths res;
|
||||
while (true) {
|
||||
auto n = readNum<uint64_t>(source);
|
||||
if (n == 0) break;
|
||||
if (n != 1) throw Error("input doesn't look like something created by 'nix-store --export'");
|
||||
Paths Store::importPaths(Source& source, std::shared_ptr<FSAccessor> accessor,
|
||||
CheckSigsFlag checkSigs) {
|
||||
Paths res;
|
||||
while (true) {
|
||||
auto n = readNum<uint64_t>(source);
|
||||
if (n == 0) break;
|
||||
if (n != 1)
|
||||
throw Error(
|
||||
"input doesn't look like something created by 'nix-store --export'");
|
||||
|
||||
/* Extract the NAR from the source. */
|
||||
TeeSink tee(source);
|
||||
parseDump(tee, tee.source);
|
||||
/* Extract the NAR from the source. */
|
||||
TeeSink tee(source);
|
||||
parseDump(tee, tee.source);
|
||||
|
||||
uint32_t magic = readInt(source);
|
||||
if (magic != exportMagic)
|
||||
throw Error("Nix archive cannot be imported; wrong format");
|
||||
uint32_t magic = readInt(source);
|
||||
if (magic != exportMagic)
|
||||
throw Error("Nix archive cannot be imported; wrong format");
|
||||
|
||||
ValidPathInfo info;
|
||||
ValidPathInfo info;
|
||||
|
||||
info.path = readStorePath(*this, source);
|
||||
info.path = readStorePath(*this, source);
|
||||
|
||||
//Activity act(*logger, lvlInfo, format("importing path '%s'") % info.path);
|
||||
// Activity act(*logger, lvlInfo, format("importing path '%s'") %
|
||||
// info.path);
|
||||
|
||||
info.references = readStorePaths<PathSet>(*this, source);
|
||||
info.references = readStorePaths<PathSet>(*this, source);
|
||||
|
||||
info.deriver = readString(source);
|
||||
if (info.deriver != "") assertStorePath(info.deriver);
|
||||
info.deriver = readString(source);
|
||||
if (info.deriver != "") assertStorePath(info.deriver);
|
||||
|
||||
info.narHash = hashString(htSHA256, *tee.source.data);
|
||||
info.narSize = tee.source.data->size();
|
||||
info.narHash = hashString(htSHA256, *tee.source.data);
|
||||
info.narSize = tee.source.data->size();
|
||||
|
||||
// Ignore optional legacy signature.
|
||||
if (readInt(source) == 1)
|
||||
readString(source);
|
||||
// Ignore optional legacy signature.
|
||||
if (readInt(source) == 1) readString(source);
|
||||
|
||||
addToStore(info, tee.source.data, NoRepair, checkSigs, accessor);
|
||||
addToStore(info, tee.source.data, NoRepair, checkSigs, accessor);
|
||||
|
||||
res.push_back(info.path);
|
||||
}
|
||||
res.push_back(info.path);
|
||||
}
|
||||
|
||||
return res;
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
32
third_party/nix/src/libstore/fs-accessor.hh
vendored
32
third_party/nix/src/libstore/fs-accessor.hh
vendored
|
|
@ -6,28 +6,26 @@ namespace nix {
|
|||
|
||||
/* An abstract class for accessing a filesystem-like structure, such
|
||||
as a (possibly remote) Nix store or the contents of a NAR file. */
|
||||
class FSAccessor
|
||||
{
|
||||
public:
|
||||
enum Type { tMissing, tRegular, tSymlink, tDirectory };
|
||||
class FSAccessor {
|
||||
public:
|
||||
enum Type { tMissing, tRegular, tSymlink, tDirectory };
|
||||
|
||||
struct Stat
|
||||
{
|
||||
Type type = tMissing;
|
||||
uint64_t fileSize = 0; // regular files only
|
||||
bool isExecutable = false; // regular files only
|
||||
uint64_t narOffset = 0; // regular files only
|
||||
};
|
||||
struct Stat {
|
||||
Type type = tMissing;
|
||||
uint64_t fileSize = 0; // regular files only
|
||||
bool isExecutable = false; // regular files only
|
||||
uint64_t narOffset = 0; // regular files only
|
||||
};
|
||||
|
||||
virtual ~FSAccessor() { }
|
||||
virtual ~FSAccessor() {}
|
||||
|
||||
virtual Stat stat(const Path & path) = 0;
|
||||
virtual Stat stat(const Path& path) = 0;
|
||||
|
||||
virtual StringSet readDirectory(const Path & path) = 0;
|
||||
virtual StringSet readDirectory(const Path& path) = 0;
|
||||
|
||||
virtual std::string readFile(const Path & path) = 0;
|
||||
virtual std::string readFile(const Path& path) = 0;
|
||||
|
||||
virtual std::string readLink(const Path & path) = 0;
|
||||
virtual std::string readLink(const Path& path) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
1544
third_party/nix/src/libstore/gc.cc
vendored
1544
third_party/nix/src/libstore/gc.cc
vendored
File diff suppressed because it is too large
Load diff
261
third_party/nix/src/libstore/globals.cc
vendored
261
third_party/nix/src/libstore/globals.cc
vendored
|
|
@ -1,17 +1,14 @@
|
|||
#include "globals.hh"
|
||||
#include "util.hh"
|
||||
#include "archive.hh"
|
||||
#include "args.hh"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "archive.hh"
|
||||
#include "args.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* The default location of the daemon socket, relative to nixStateDir.
|
||||
The socket is in a directory to allow you to control access to the
|
||||
Nix daemon by setting the mode/ownership of the directory
|
||||
|
|
@ -21,9 +18,9 @@ namespace nix {
|
|||
|
||||
/* chroot-like behavior from Apple's sandbox */
|
||||
#if __APPLE__
|
||||
#define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library /usr/lib /dev /bin/sh"
|
||||
#define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library /usr/lib /dev /bin/sh"
|
||||
#else
|
||||
#define DEFAULT_ALLOWED_IMPURE_PREFIXES ""
|
||||
#define DEFAULT_ALLOWED_IMPURE_PREFIXES ""
|
||||
#endif
|
||||
|
||||
Settings settings;
|
||||
|
|
@ -31,157 +28,163 @@ Settings settings;
|
|||
static GlobalConfig::Register r1(&settings);
|
||||
|
||||
Settings::Settings()
|
||||
: nixPrefix(NIX_PREFIX)
|
||||
, nixStore(canonPath(getEnv("NIX_STORE_DIR", getEnv("NIX_STORE", NIX_STORE_DIR))))
|
||||
, nixDataDir(canonPath(getEnv("NIX_DATA_DIR", NIX_DATA_DIR)))
|
||||
, nixLogDir(canonPath(getEnv("NIX_LOG_DIR", NIX_LOG_DIR)))
|
||||
, nixStateDir(canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR)))
|
||||
, nixConfDir(canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR)))
|
||||
, nixLibexecDir(canonPath(getEnv("NIX_LIBEXEC_DIR", NIX_LIBEXEC_DIR)))
|
||||
, nixBinDir(canonPath(getEnv("NIX_BIN_DIR", NIX_BIN_DIR)))
|
||||
, nixManDir(canonPath(NIX_MAN_DIR))
|
||||
, nixDaemonSocketFile(canonPath(nixStateDir + DEFAULT_SOCKET_PATH))
|
||||
{
|
||||
buildUsersGroup = getuid() == 0 ? "nixbld" : "";
|
||||
lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1";
|
||||
: nixPrefix(NIX_PREFIX),
|
||||
nixStore(canonPath(
|
||||
getEnv("NIX_STORE_DIR", getEnv("NIX_STORE", NIX_STORE_DIR)))),
|
||||
nixDataDir(canonPath(getEnv("NIX_DATA_DIR", NIX_DATA_DIR))),
|
||||
nixLogDir(canonPath(getEnv("NIX_LOG_DIR", NIX_LOG_DIR))),
|
||||
nixStateDir(canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR))),
|
||||
nixConfDir(canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR))),
|
||||
nixLibexecDir(canonPath(getEnv("NIX_LIBEXEC_DIR", NIX_LIBEXEC_DIR))),
|
||||
nixBinDir(canonPath(getEnv("NIX_BIN_DIR", NIX_BIN_DIR))),
|
||||
nixManDir(canonPath(NIX_MAN_DIR)),
|
||||
nixDaemonSocketFile(canonPath(nixStateDir + DEFAULT_SOCKET_PATH)) {
|
||||
buildUsersGroup = getuid() == 0 ? "nixbld" : "";
|
||||
lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1";
|
||||
|
||||
caFile = getEnv("NIX_SSL_CERT_FILE", getEnv("SSL_CERT_FILE", ""));
|
||||
if (caFile == "") {
|
||||
for (auto & fn : {"/etc/ssl/certs/ca-certificates.crt", "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"})
|
||||
if (pathExists(fn)) {
|
||||
caFile = fn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
caFile = getEnv("NIX_SSL_CERT_FILE", getEnv("SSL_CERT_FILE", ""));
|
||||
if (caFile == "") {
|
||||
for (auto& fn :
|
||||
{"/etc/ssl/certs/ca-certificates.crt",
|
||||
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"})
|
||||
if (pathExists(fn)) {
|
||||
caFile = fn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Backwards compatibility. */
|
||||
auto s = getEnv("NIX_REMOTE_SYSTEMS");
|
||||
if (s != "") {
|
||||
Strings ss;
|
||||
for (auto & p : tokenizeString<Strings>(s, ":"))
|
||||
ss.push_back("@" + p);
|
||||
builders = concatStringsSep(" ", ss);
|
||||
}
|
||||
/* Backwards compatibility. */
|
||||
auto s = getEnv("NIX_REMOTE_SYSTEMS");
|
||||
if (s != "") {
|
||||
Strings ss;
|
||||
for (auto& p : tokenizeString<Strings>(s, ":")) ss.push_back("@" + p);
|
||||
builders = concatStringsSep(" ", ss);
|
||||
}
|
||||
|
||||
#if defined(__linux__) && defined(SANDBOX_SHELL)
|
||||
sandboxPaths = tokenizeString<StringSet>("/bin/sh=" SANDBOX_SHELL);
|
||||
sandboxPaths = tokenizeString<StringSet>("/bin/sh=" SANDBOX_SHELL);
|
||||
#endif
|
||||
|
||||
allowedImpureHostPrefixes = tokenizeString<StringSet>(DEFAULT_ALLOWED_IMPURE_PREFIXES);
|
||||
allowedImpureHostPrefixes =
|
||||
tokenizeString<StringSet>(DEFAULT_ALLOWED_IMPURE_PREFIXES);
|
||||
}
|
||||
|
||||
void loadConfFile()
|
||||
{
|
||||
globalConfig.applyConfigFile(settings.nixConfDir + "/nix.conf");
|
||||
void loadConfFile() {
|
||||
globalConfig.applyConfigFile(settings.nixConfDir + "/nix.conf");
|
||||
|
||||
/* We only want to send overrides to the daemon, i.e. stuff from
|
||||
~/.nix/nix.conf or the command line. */
|
||||
globalConfig.resetOverriden();
|
||||
/* We only want to send overrides to the daemon, i.e. stuff from
|
||||
~/.nix/nix.conf or the command line. */
|
||||
globalConfig.resetOverriden();
|
||||
|
||||
auto dirs = getConfigDirs();
|
||||
// Iterate over them in reverse so that the ones appearing first in the path take priority
|
||||
for (auto dir = dirs.rbegin(); dir != dirs.rend(); dir++) {
|
||||
globalConfig.applyConfigFile(*dir + "/nix/nix.conf");
|
||||
}
|
||||
auto dirs = getConfigDirs();
|
||||
// Iterate over them in reverse so that the ones appearing first in the path
|
||||
// take priority
|
||||
for (auto dir = dirs.rbegin(); dir != dirs.rend(); dir++) {
|
||||
globalConfig.applyConfigFile(*dir + "/nix/nix.conf");
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Settings::getDefaultCores()
|
||||
{
|
||||
return std::max(1U, std::thread::hardware_concurrency());
|
||||
unsigned int Settings::getDefaultCores() {
|
||||
return std::max(1U, std::thread::hardware_concurrency());
|
||||
}
|
||||
|
||||
StringSet Settings::getDefaultSystemFeatures()
|
||||
{
|
||||
/* For backwards compatibility, accept some "features" that are
|
||||
used in Nixpkgs to route builds to certain machines but don't
|
||||
actually require anything special on the machines. */
|
||||
StringSet features{"nixos-test", "benchmark", "big-parallel"};
|
||||
StringSet Settings::getDefaultSystemFeatures() {
|
||||
/* For backwards compatibility, accept some "features" that are
|
||||
used in Nixpkgs to route builds to certain machines but don't
|
||||
actually require anything special on the machines. */
|
||||
StringSet features{"nixos-test", "benchmark", "big-parallel"};
|
||||
|
||||
#if __linux__
|
||||
if (access("/dev/kvm", R_OK | W_OK) == 0)
|
||||
features.insert("kvm");
|
||||
#endif
|
||||
#if __linux__
|
||||
if (access("/dev/kvm", R_OK | W_OK) == 0) features.insert("kvm");
|
||||
#endif
|
||||
|
||||
return features;
|
||||
return features;
|
||||
}
|
||||
|
||||
const string nixVersion = PACKAGE_VERSION;
|
||||
|
||||
template<> void BaseSetting<SandboxMode>::set(const std::string & str)
|
||||
{
|
||||
if (str == "true") value = smEnabled;
|
||||
else if (str == "relaxed") value = smRelaxed;
|
||||
else if (str == "false") value = smDisabled;
|
||||
else throw UsageError("option '%s' has invalid value '%s'", name, str);
|
||||
template <>
|
||||
void BaseSetting<SandboxMode>::set(const std::string& str) {
|
||||
if (str == "true")
|
||||
value = smEnabled;
|
||||
else if (str == "relaxed")
|
||||
value = smRelaxed;
|
||||
else if (str == "false")
|
||||
value = smDisabled;
|
||||
else
|
||||
throw UsageError("option '%s' has invalid value '%s'", name, str);
|
||||
}
|
||||
|
||||
template<> std::string BaseSetting<SandboxMode>::to_string()
|
||||
{
|
||||
if (value == smEnabled) return "true";
|
||||
else if (value == smRelaxed) return "relaxed";
|
||||
else if (value == smDisabled) return "false";
|
||||
else abort();
|
||||
template <>
|
||||
std::string BaseSetting<SandboxMode>::to_string() {
|
||||
if (value == smEnabled)
|
||||
return "true";
|
||||
else if (value == smRelaxed)
|
||||
return "relaxed";
|
||||
else if (value == smDisabled)
|
||||
return "false";
|
||||
else
|
||||
abort();
|
||||
}
|
||||
|
||||
template<> void BaseSetting<SandboxMode>::toJSON(JSONPlaceholder & out)
|
||||
{
|
||||
AbstractSetting::toJSON(out);
|
||||
template <>
|
||||
void BaseSetting<SandboxMode>::toJSON(JSONPlaceholder& out) {
|
||||
AbstractSetting::toJSON(out);
|
||||
}
|
||||
|
||||
template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::string & category)
|
||||
{
|
||||
args.mkFlag()
|
||||
.longName(name)
|
||||
.description("Enable sandboxing.")
|
||||
.handler([=](std::vector<std::string> ss) { override(smEnabled); })
|
||||
.category(category);
|
||||
args.mkFlag()
|
||||
.longName("no-" + name)
|
||||
.description("Disable sandboxing.")
|
||||
.handler([=](std::vector<std::string> ss) { override(smDisabled); })
|
||||
.category(category);
|
||||
args.mkFlag()
|
||||
.longName("relaxed-" + name)
|
||||
.description("Enable sandboxing, but allow builds to disable it.")
|
||||
.handler([=](std::vector<std::string> ss) { override(smRelaxed); })
|
||||
.category(category);
|
||||
template <>
|
||||
void BaseSetting<SandboxMode>::convertToArg(Args& args,
|
||||
const std::string& category) {
|
||||
args.mkFlag()
|
||||
.longName(name)
|
||||
.description("Enable sandboxing.")
|
||||
.handler([=](std::vector<std::string> ss) { override(smEnabled); })
|
||||
.category(category);
|
||||
args.mkFlag()
|
||||
.longName("no-" + name)
|
||||
.description("Disable sandboxing.")
|
||||
.handler([=](std::vector<std::string> ss) { override(smDisabled); })
|
||||
.category(category);
|
||||
args.mkFlag()
|
||||
.longName("relaxed-" + name)
|
||||
.description("Enable sandboxing, but allow builds to disable it.")
|
||||
.handler([=](std::vector<std::string> ss) { override(smRelaxed); })
|
||||
.category(category);
|
||||
}
|
||||
|
||||
void MaxBuildJobsSetting::set(const std::string & str)
|
||||
{
|
||||
if (str == "auto") value = std::max(1U, std::thread::hardware_concurrency());
|
||||
else if (!string2Int(str, value))
|
||||
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
|
||||
void MaxBuildJobsSetting::set(const std::string& str) {
|
||||
if (str == "auto")
|
||||
value = std::max(1U, std::thread::hardware_concurrency());
|
||||
else if (!string2Int(str, value))
|
||||
throw UsageError(
|
||||
"configuration setting '%s' should be 'auto' or an integer", name);
|
||||
}
|
||||
|
||||
|
||||
void initPlugins()
|
||||
{
|
||||
for (const auto & pluginFile : settings.pluginFiles.get()) {
|
||||
Paths pluginFiles;
|
||||
try {
|
||||
auto ents = readDirectory(pluginFile);
|
||||
for (const auto & ent : ents)
|
||||
pluginFiles.emplace_back(pluginFile + "/" + ent.name);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo != ENOTDIR)
|
||||
throw;
|
||||
pluginFiles.emplace_back(pluginFile);
|
||||
}
|
||||
for (const auto & file : pluginFiles) {
|
||||
/* handle is purposefully leaked as there may be state in the
|
||||
DSO needed by the action of the plugin. */
|
||||
void *handle =
|
||||
dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!handle)
|
||||
throw Error("could not dynamically open plugin file '%s': %s", file, dlerror());
|
||||
}
|
||||
void initPlugins() {
|
||||
for (const auto& pluginFile : settings.pluginFiles.get()) {
|
||||
Paths pluginFiles;
|
||||
try {
|
||||
auto ents = readDirectory(pluginFile);
|
||||
for (const auto& ent : ents)
|
||||
pluginFiles.emplace_back(pluginFile + "/" + ent.name);
|
||||
} catch (SysError& e) {
|
||||
if (e.errNo != ENOTDIR) throw;
|
||||
pluginFiles.emplace_back(pluginFile);
|
||||
}
|
||||
for (const auto& file : pluginFiles) {
|
||||
/* handle is purposefully leaked as there may be state in the
|
||||
DSO needed by the action of the plugin. */
|
||||
void* handle = dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!handle)
|
||||
throw Error("could not dynamically open plugin file '%s': %s", file,
|
||||
dlerror());
|
||||
}
|
||||
}
|
||||
|
||||
/* Since plugins can add settings, try to re-apply previously
|
||||
unknown settings. */
|
||||
globalConfig.reapplyUnknownSettings();
|
||||
globalConfig.warnUnknownSettings();
|
||||
/* Since plugins can add settings, try to re-apply previously
|
||||
unknown settings. */
|
||||
globalConfig.reapplyUnknownSettings();
|
||||
globalConfig.warnUnknownSettings();
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
614
third_party/nix/src/libstore/globals.hh
vendored
614
third_party/nix/src/libstore/globals.hh
vendored
|
|
@ -1,361 +1,475 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "config.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <map>
|
||||
#include <limits>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include "config.hh"
|
||||
#include "types.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
|
||||
|
||||
struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
|
||||
{
|
||||
MaxBuildJobsSetting(Config * options,
|
||||
unsigned int def,
|
||||
const std::string & name,
|
||||
const std::string & description,
|
||||
const std::set<std::string> & aliases = {})
|
||||
: BaseSetting<unsigned int>(def, name, description, aliases)
|
||||
{
|
||||
options->addSetting(this);
|
||||
}
|
||||
struct MaxBuildJobsSetting : public BaseSetting<unsigned int> {
|
||||
MaxBuildJobsSetting(Config* options, unsigned int def,
|
||||
const std::string& name, const std::string& description,
|
||||
const std::set<std::string>& aliases = {})
|
||||
: BaseSetting<unsigned int>(def, name, description, aliases) {
|
||||
options->addSetting(this);
|
||||
}
|
||||
|
||||
void set(const std::string & str) override;
|
||||
void set(const std::string& str) override;
|
||||
};
|
||||
|
||||
class Settings : public Config {
|
||||
unsigned int getDefaultCores();
|
||||
|
||||
unsigned int getDefaultCores();
|
||||
StringSet getDefaultSystemFeatures();
|
||||
|
||||
StringSet getDefaultSystemFeatures();
|
||||
public:
|
||||
Settings();
|
||||
|
||||
public:
|
||||
Path nixPrefix;
|
||||
|
||||
Settings();
|
||||
/* The directory where we store sources and derived files. */
|
||||
Path nixStore;
|
||||
|
||||
Path nixPrefix;
|
||||
Path nixDataDir; /* !!! fix */
|
||||
|
||||
/* The directory where we store sources and derived files. */
|
||||
Path nixStore;
|
||||
/* The directory where we log various operations. */
|
||||
Path nixLogDir;
|
||||
|
||||
Path nixDataDir; /* !!! fix */
|
||||
/* The directory where state is stored. */
|
||||
Path nixStateDir;
|
||||
|
||||
/* The directory where we log various operations. */
|
||||
Path nixLogDir;
|
||||
/* The directory where configuration files are stored. */
|
||||
Path nixConfDir;
|
||||
|
||||
/* The directory where state is stored. */
|
||||
Path nixStateDir;
|
||||
/* The directory where internal helper programs are stored. */
|
||||
Path nixLibexecDir;
|
||||
|
||||
/* The directory where configuration files are stored. */
|
||||
Path nixConfDir;
|
||||
/* The directory where the main programs are stored. */
|
||||
Path nixBinDir;
|
||||
|
||||
/* The directory where internal helper programs are stored. */
|
||||
Path nixLibexecDir;
|
||||
/* The directory where the man pages are stored. */
|
||||
Path nixManDir;
|
||||
|
||||
/* The directory where the main programs are stored. */
|
||||
Path nixBinDir;
|
||||
/* File name of the socket the daemon listens to. */
|
||||
Path nixDaemonSocketFile;
|
||||
|
||||
/* The directory where the man pages are stored. */
|
||||
Path nixManDir;
|
||||
Setting<std::string> storeUri{this, getEnv("NIX_REMOTE", "auto"), "store",
|
||||
"The default Nix store to use."};
|
||||
|
||||
/* File name of the socket the daemon listens to. */
|
||||
Path nixDaemonSocketFile;
|
||||
Setting<bool> keepFailed{
|
||||
this, false, "keep-failed",
|
||||
"Whether to keep temporary directories of failed builds."};
|
||||
|
||||
Setting<std::string> storeUri{this, getEnv("NIX_REMOTE", "auto"), "store",
|
||||
"The default Nix store to use."};
|
||||
Setting<bool> keepGoing{
|
||||
this, false, "keep-going",
|
||||
"Whether to keep building derivations when another build fails."};
|
||||
|
||||
Setting<bool> keepFailed{this, false, "keep-failed",
|
||||
"Whether to keep temporary directories of failed builds."};
|
||||
Setting<bool> tryFallback{
|
||||
this,
|
||||
false,
|
||||
"fallback",
|
||||
"Whether to fall back to building when substitution fails.",
|
||||
{"build-fallback"}};
|
||||
|
||||
Setting<bool> keepGoing{this, false, "keep-going",
|
||||
"Whether to keep building derivations when another build fails."};
|
||||
/* Whether to show build log output in real time. */
|
||||
bool verboseBuild = true;
|
||||
|
||||
Setting<bool> tryFallback{this, false, "fallback",
|
||||
"Whether to fall back to building when substitution fails.",
|
||||
{"build-fallback"}};
|
||||
Setting<size_t> logLines{
|
||||
this, 10, "log-lines",
|
||||
"If verbose-build is false, the number of lines of the tail of "
|
||||
"the log to show if a build fails."};
|
||||
|
||||
/* Whether to show build log output in real time. */
|
||||
bool verboseBuild = true;
|
||||
MaxBuildJobsSetting maxBuildJobs{this,
|
||||
1,
|
||||
"max-jobs",
|
||||
"Maximum number of parallel build jobs. "
|
||||
"\"auto\" means use number of cores.",
|
||||
{"build-max-jobs"}};
|
||||
|
||||
Setting<size_t> logLines{this, 10, "log-lines",
|
||||
"If verbose-build is false, the number of lines of the tail of "
|
||||
"the log to show if a build fails."};
|
||||
Setting<unsigned int> buildCores{
|
||||
this,
|
||||
getDefaultCores(),
|
||||
"cores",
|
||||
"Number of CPU cores to utilize in parallel within a build, "
|
||||
"i.e. by passing this number to Make via '-j'. 0 means that the "
|
||||
"number of actual CPU cores on the local host ought to be "
|
||||
"auto-detected.",
|
||||
{"build-cores"}};
|
||||
|
||||
MaxBuildJobsSetting maxBuildJobs{this, 1, "max-jobs",
|
||||
"Maximum number of parallel build jobs. \"auto\" means use number of cores.",
|
||||
{"build-max-jobs"}};
|
||||
/* Read-only mode. Don't copy stuff to the store, don't change
|
||||
the database. */
|
||||
bool readOnlyMode = false;
|
||||
|
||||
Setting<unsigned int> buildCores{this, getDefaultCores(), "cores",
|
||||
"Number of CPU cores to utilize in parallel within a build, "
|
||||
"i.e. by passing this number to Make via '-j'. 0 means that the "
|
||||
"number of actual CPU cores on the local host ought to be "
|
||||
"auto-detected.", {"build-cores"}};
|
||||
Setting<std::string> thisSystem{this, SYSTEM, "system",
|
||||
"The canonical Nix system name."};
|
||||
|
||||
/* Read-only mode. Don't copy stuff to the store, don't change
|
||||
the database. */
|
||||
bool readOnlyMode = false;
|
||||
Setting<time_t> maxSilentTime{
|
||||
this,
|
||||
0,
|
||||
"max-silent-time",
|
||||
"The maximum time in seconds that a builer can go without "
|
||||
"producing any output on stdout/stderr before it is killed. "
|
||||
"0 means infinity.",
|
||||
{"build-max-silent-time"}};
|
||||
|
||||
Setting<std::string> thisSystem{this, SYSTEM, "system",
|
||||
"The canonical Nix system name."};
|
||||
Setting<time_t> buildTimeout{
|
||||
this,
|
||||
0,
|
||||
"timeout",
|
||||
"The maximum duration in seconds that a builder can run. "
|
||||
"0 means infinity.",
|
||||
{"build-timeout"}};
|
||||
|
||||
Setting<time_t> maxSilentTime{this, 0, "max-silent-time",
|
||||
"The maximum time in seconds that a builer can go without "
|
||||
"producing any output on stdout/stderr before it is killed. "
|
||||
"0 means infinity.",
|
||||
{"build-max-silent-time"}};
|
||||
PathSetting buildHook{this, true, nixLibexecDir + "/nix/build-remote",
|
||||
"build-hook",
|
||||
"The path of the helper program that executes builds "
|
||||
"to remote machines."};
|
||||
|
||||
Setting<time_t> buildTimeout{this, 0, "timeout",
|
||||
"The maximum duration in seconds that a builder can run. "
|
||||
"0 means infinity.", {"build-timeout"}};
|
||||
Setting<std::string> builders{this, "@" + nixConfDir + "/machines",
|
||||
"builders",
|
||||
"A semicolon-separated list of build machines, "
|
||||
"in the format of nix.machines."};
|
||||
|
||||
PathSetting buildHook{this, true, nixLibexecDir + "/nix/build-remote", "build-hook",
|
||||
"The path of the helper program that executes builds to remote machines."};
|
||||
Setting<bool> buildersUseSubstitutes{
|
||||
this, false, "builders-use-substitutes",
|
||||
"Whether build machines should use their own substitutes for obtaining "
|
||||
"build dependencies if possible, rather than waiting for this host to "
|
||||
"upload them."};
|
||||
|
||||
Setting<std::string> builders{this, "@" + nixConfDir + "/machines", "builders",
|
||||
"A semicolon-separated list of build machines, in the format of nix.machines."};
|
||||
Setting<off_t> reservedSize{
|
||||
this, 8 * 1024 * 1024, "gc-reserved-space",
|
||||
"Amount of reserved disk space for the garbage collector."};
|
||||
|
||||
Setting<bool> buildersUseSubstitutes{this, false, "builders-use-substitutes",
|
||||
"Whether build machines should use their own substitutes for obtaining "
|
||||
"build dependencies if possible, rather than waiting for this host to "
|
||||
"upload them."};
|
||||
Setting<bool> fsyncMetadata{this, true, "fsync-metadata",
|
||||
"Whether SQLite should use fsync()."};
|
||||
|
||||
Setting<off_t> reservedSize{this, 8 * 1024 * 1024, "gc-reserved-space",
|
||||
"Amount of reserved disk space for the garbage collector."};
|
||||
Setting<bool> useSQLiteWAL{this, true, "use-sqlite-wal",
|
||||
"Whether SQLite should use WAL mode."};
|
||||
|
||||
Setting<bool> fsyncMetadata{this, true, "fsync-metadata",
|
||||
"Whether SQLite should use fsync()."};
|
||||
Setting<bool> syncBeforeRegistering{
|
||||
this, false, "sync-before-registering",
|
||||
"Whether to call sync() before registering a path as valid."};
|
||||
|
||||
Setting<bool> useSQLiteWAL{this, true, "use-sqlite-wal",
|
||||
"Whether SQLite should use WAL mode."};
|
||||
Setting<bool> useSubstitutes{this,
|
||||
true,
|
||||
"substitute",
|
||||
"Whether to use substitutes.",
|
||||
{"build-use-substitutes"}};
|
||||
|
||||
Setting<bool> syncBeforeRegistering{this, false, "sync-before-registering",
|
||||
"Whether to call sync() before registering a path as valid."};
|
||||
Setting<std::string> buildUsersGroup{
|
||||
this, "", "build-users-group",
|
||||
"The Unix group that contains the build users."};
|
||||
|
||||
Setting<bool> useSubstitutes{this, true, "substitute",
|
||||
"Whether to use substitutes.",
|
||||
{"build-use-substitutes"}};
|
||||
Setting<bool> impersonateLinux26{
|
||||
this,
|
||||
false,
|
||||
"impersonate-linux-26",
|
||||
"Whether to impersonate a Linux 2.6 machine on newer kernels.",
|
||||
{"build-impersonate-linux-26"}};
|
||||
|
||||
Setting<std::string> buildUsersGroup{this, "", "build-users-group",
|
||||
"The Unix group that contains the build users."};
|
||||
Setting<bool> keepLog{this,
|
||||
true,
|
||||
"keep-build-log",
|
||||
"Whether to store build logs.",
|
||||
{"build-keep-log"}};
|
||||
|
||||
Setting<bool> impersonateLinux26{this, false, "impersonate-linux-26",
|
||||
"Whether to impersonate a Linux 2.6 machine on newer kernels.",
|
||||
{"build-impersonate-linux-26"}};
|
||||
Setting<bool> compressLog{this,
|
||||
true,
|
||||
"compress-build-log",
|
||||
"Whether to compress logs.",
|
||||
{"build-compress-log"}};
|
||||
|
||||
Setting<bool> keepLog{this, true, "keep-build-log",
|
||||
"Whether to store build logs.",
|
||||
{"build-keep-log"}};
|
||||
Setting<unsigned long> maxLogSize{
|
||||
this,
|
||||
0,
|
||||
"max-build-log-size",
|
||||
"Maximum number of bytes a builder can write to stdout/stderr "
|
||||
"before being killed (0 means no limit).",
|
||||
{"build-max-log-size"}};
|
||||
|
||||
Setting<bool> compressLog{this, true, "compress-build-log",
|
||||
"Whether to compress logs.",
|
||||
{"build-compress-log"}};
|
||||
/* When buildRepeat > 0 and verboseBuild == true, whether to print
|
||||
repeated builds (i.e. builds other than the first one) to
|
||||
stderr. Hack to prevent Hydra logs from being polluted. */
|
||||
bool printRepeatedBuilds = true;
|
||||
|
||||
Setting<unsigned long> maxLogSize{this, 0, "max-build-log-size",
|
||||
"Maximum number of bytes a builder can write to stdout/stderr "
|
||||
"before being killed (0 means no limit).",
|
||||
{"build-max-log-size"}};
|
||||
Setting<unsigned int> pollInterval{
|
||||
this, 5, "build-poll-interval",
|
||||
"How often (in seconds) to poll for locks."};
|
||||
|
||||
/* When buildRepeat > 0 and verboseBuild == true, whether to print
|
||||
repeated builds (i.e. builds other than the first one) to
|
||||
stderr. Hack to prevent Hydra logs from being polluted. */
|
||||
bool printRepeatedBuilds = true;
|
||||
Setting<bool> checkRootReachability{
|
||||
this, false, "gc-check-reachability",
|
||||
"Whether to check if new GC roots can in fact be found by the "
|
||||
"garbage collector."};
|
||||
|
||||
Setting<unsigned int> pollInterval{this, 5, "build-poll-interval",
|
||||
"How often (in seconds) to poll for locks."};
|
||||
Setting<bool> gcKeepOutputs{
|
||||
this,
|
||||
false,
|
||||
"keep-outputs",
|
||||
"Whether the garbage collector should keep outputs of live derivations.",
|
||||
{"gc-keep-outputs"}};
|
||||
|
||||
Setting<bool> checkRootReachability{this, false, "gc-check-reachability",
|
||||
"Whether to check if new GC roots can in fact be found by the "
|
||||
"garbage collector."};
|
||||
Setting<bool> gcKeepDerivations{
|
||||
this,
|
||||
true,
|
||||
"keep-derivations",
|
||||
"Whether the garbage collector should keep derivers of live paths.",
|
||||
{"gc-keep-derivations"}};
|
||||
|
||||
Setting<bool> gcKeepOutputs{this, false, "keep-outputs",
|
||||
"Whether the garbage collector should keep outputs of live derivations.",
|
||||
{"gc-keep-outputs"}};
|
||||
Setting<bool> autoOptimiseStore{this, false, "auto-optimise-store",
|
||||
"Whether to automatically replace files with "
|
||||
"identical contents with hard links."};
|
||||
|
||||
Setting<bool> gcKeepDerivations{this, true, "keep-derivations",
|
||||
"Whether the garbage collector should keep derivers of live paths.",
|
||||
{"gc-keep-derivations"}};
|
||||
Setting<bool> envKeepDerivations{
|
||||
this,
|
||||
false,
|
||||
"keep-env-derivations",
|
||||
"Whether to add derivations as a dependency of user environments "
|
||||
"(to prevent them from being GCed).",
|
||||
{"env-keep-derivations"}};
|
||||
|
||||
Setting<bool> autoOptimiseStore{this, false, "auto-optimise-store",
|
||||
"Whether to automatically replace files with identical contents with hard links."};
|
||||
/* Whether to lock the Nix client and worker to the same CPU. */
|
||||
bool lockCPU;
|
||||
|
||||
Setting<bool> envKeepDerivations{this, false, "keep-env-derivations",
|
||||
"Whether to add derivations as a dependency of user environments "
|
||||
"(to prevent them from being GCed).",
|
||||
{"env-keep-derivations"}};
|
||||
/* Whether to show a stack trace if Nix evaluation fails. */
|
||||
Setting<bool> showTrace{
|
||||
this, false, "show-trace",
|
||||
"Whether to show a stack trace on evaluation errors."};
|
||||
|
||||
/* Whether to lock the Nix client and worker to the same CPU. */
|
||||
bool lockCPU;
|
||||
Setting<SandboxMode> sandboxMode {
|
||||
this,
|
||||
#if __linux__
|
||||
smEnabled
|
||||
#else
|
||||
smDisabled
|
||||
#endif
|
||||
,
|
||||
"sandbox",
|
||||
"Whether to enable sandboxed builds. Can be \"true\", \"false\" or "
|
||||
"\"relaxed\".",
|
||||
{
|
||||
"build-use-chroot", "build-use-sandbox"
|
||||
}
|
||||
};
|
||||
|
||||
/* Whether to show a stack trace if Nix evaluation fails. */
|
||||
Setting<bool> showTrace{this, false, "show-trace",
|
||||
"Whether to show a stack trace on evaluation errors."};
|
||||
Setting<PathSet> sandboxPaths{
|
||||
this,
|
||||
{},
|
||||
"sandbox-paths",
|
||||
"The paths to make available inside the build sandbox.",
|
||||
{"build-chroot-dirs", "build-sandbox-paths"}};
|
||||
|
||||
Setting<SandboxMode> sandboxMode{this,
|
||||
#if __linux__
|
||||
smEnabled
|
||||
#else
|
||||
smDisabled
|
||||
#endif
|
||||
, "sandbox",
|
||||
"Whether to enable sandboxed builds. Can be \"true\", \"false\" or \"relaxed\".",
|
||||
{"build-use-chroot", "build-use-sandbox"}};
|
||||
Setting<bool> sandboxFallback{
|
||||
this, true, "sandbox-fallback",
|
||||
"Whether to disable sandboxing when the kernel doesn't allow it."};
|
||||
|
||||
Setting<PathSet> sandboxPaths{this, {}, "sandbox-paths",
|
||||
"The paths to make available inside the build sandbox.",
|
||||
{"build-chroot-dirs", "build-sandbox-paths"}};
|
||||
Setting<PathSet> extraSandboxPaths{
|
||||
this,
|
||||
{},
|
||||
"extra-sandbox-paths",
|
||||
"Additional paths to make available inside the build sandbox.",
|
||||
{"build-extra-chroot-dirs", "build-extra-sandbox-paths"}};
|
||||
|
||||
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
|
||||
"Whether to disable sandboxing when the kernel doesn't allow it."};
|
||||
|
||||
Setting<PathSet> extraSandboxPaths{this, {}, "extra-sandbox-paths",
|
||||
"Additional paths to make available inside the build sandbox.",
|
||||
{"build-extra-chroot-dirs", "build-extra-sandbox-paths"}};
|
||||
|
||||
Setting<size_t> buildRepeat{this, 0, "repeat",
|
||||
"The number of times to repeat a build in order to verify determinism.",
|
||||
{"build-repeat"}};
|
||||
Setting<size_t> buildRepeat{
|
||||
this,
|
||||
0,
|
||||
"repeat",
|
||||
"The number of times to repeat a build in order to verify determinism.",
|
||||
{"build-repeat"}};
|
||||
|
||||
#if __linux__
|
||||
Setting<std::string> sandboxShmSize{this, "50%", "sandbox-dev-shm-size",
|
||||
"The size of /dev/shm in the build sandbox."};
|
||||
Setting<std::string> sandboxShmSize{
|
||||
this, "50%", "sandbox-dev-shm-size",
|
||||
"The size of /dev/shm in the build sandbox."};
|
||||
|
||||
Setting<Path> sandboxBuildDir{this, "/build", "sandbox-build-dir",
|
||||
"The build directory inside the sandbox."};
|
||||
Setting<Path> sandboxBuildDir{this, "/build", "sandbox-build-dir",
|
||||
"The build directory inside the sandbox."};
|
||||
#endif
|
||||
|
||||
Setting<PathSet> allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps",
|
||||
"Which prefixes to allow derivations to ask for access to (primarily for Darwin)."};
|
||||
Setting<PathSet> allowedImpureHostPrefixes{
|
||||
this,
|
||||
{},
|
||||
"allowed-impure-host-deps",
|
||||
"Which prefixes to allow derivations to ask for access to (primarily for "
|
||||
"Darwin)."};
|
||||
|
||||
#if __APPLE__
|
||||
Setting<bool> darwinLogSandboxViolations{this, false, "darwin-log-sandbox-violations",
|
||||
"Whether to log Darwin sandbox access violations to the system log."};
|
||||
Setting<bool> darwinLogSandboxViolations{
|
||||
this, false, "darwin-log-sandbox-violations",
|
||||
"Whether to log Darwin sandbox access violations to the system log."};
|
||||
#endif
|
||||
|
||||
Setting<bool> runDiffHook{this, false, "run-diff-hook",
|
||||
"Whether to run the program specified by the diff-hook setting "
|
||||
"repeated builds produce a different result. Typically used to "
|
||||
"plug in diffoscope."};
|
||||
Setting<bool> runDiffHook{
|
||||
this, false, "run-diff-hook",
|
||||
"Whether to run the program specified by the diff-hook setting "
|
||||
"repeated builds produce a different result. Typically used to "
|
||||
"plug in diffoscope."};
|
||||
|
||||
PathSetting diffHook{this, true, "", "diff-hook",
|
||||
"A program that prints out the differences between the two paths "
|
||||
"specified on its command line."};
|
||||
PathSetting diffHook{
|
||||
this, true, "", "diff-hook",
|
||||
"A program that prints out the differences between the two paths "
|
||||
"specified on its command line."};
|
||||
|
||||
Setting<bool> enforceDeterminism{this, true, "enforce-determinism",
|
||||
"Whether to fail if repeated builds produce different output."};
|
||||
Setting<bool> enforceDeterminism{
|
||||
this, true, "enforce-determinism",
|
||||
"Whether to fail if repeated builds produce different output."};
|
||||
|
||||
Setting<Strings> trustedPublicKeys{this,
|
||||
{"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="},
|
||||
"trusted-public-keys",
|
||||
"Trusted public keys for secure substitution.",
|
||||
{"binary-cache-public-keys"}};
|
||||
Setting<Strings> trustedPublicKeys{
|
||||
this,
|
||||
{"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="},
|
||||
"trusted-public-keys",
|
||||
"Trusted public keys for secure substitution.",
|
||||
{"binary-cache-public-keys"}};
|
||||
|
||||
Setting<Strings> secretKeyFiles{this, {}, "secret-key-files",
|
||||
"Secret keys with which to sign local builds."};
|
||||
Setting<Strings> secretKeyFiles{
|
||||
this,
|
||||
{},
|
||||
"secret-key-files",
|
||||
"Secret keys with which to sign local builds."};
|
||||
|
||||
Setting<unsigned int> tarballTtl{this, 60 * 60, "tarball-ttl",
|
||||
"How long downloaded files are considered up-to-date."};
|
||||
Setting<unsigned int> tarballTtl{
|
||||
this, 60 * 60, "tarball-ttl",
|
||||
"How long downloaded files are considered up-to-date."};
|
||||
|
||||
Setting<bool> requireSigs{this, true, "require-sigs",
|
||||
"Whether to check that any non-content-addressed path added to the "
|
||||
"Nix store has a valid signature (that is, one signed using a key "
|
||||
"listed in 'trusted-public-keys'."};
|
||||
Setting<bool> requireSigs{
|
||||
this, true, "require-sigs",
|
||||
"Whether to check that any non-content-addressed path added to the "
|
||||
"Nix store has a valid signature (that is, one signed using a key "
|
||||
"listed in 'trusted-public-keys'."};
|
||||
|
||||
Setting<StringSet> extraPlatforms{this,
|
||||
std::string{SYSTEM} == "x86_64-linux" ? StringSet{"i686-linux"} : StringSet{},
|
||||
"extra-platforms",
|
||||
"Additional platforms that can be built on the local system. "
|
||||
"These may be supported natively (e.g. armv7 on some aarch64 CPUs "
|
||||
"or using hacks like qemu-user."};
|
||||
Setting<StringSet> extraPlatforms{
|
||||
this,
|
||||
std::string{SYSTEM} == "x86_64-linux" ? StringSet{"i686-linux"}
|
||||
: StringSet{},
|
||||
"extra-platforms",
|
||||
"Additional platforms that can be built on the local system. "
|
||||
"These may be supported natively (e.g. armv7 on some aarch64 CPUs "
|
||||
"or using hacks like qemu-user."};
|
||||
|
||||
Setting<StringSet> systemFeatures{this, getDefaultSystemFeatures(),
|
||||
"system-features",
|
||||
"Optional features that this system implements (like \"kvm\")."};
|
||||
Setting<StringSet> systemFeatures{
|
||||
this, getDefaultSystemFeatures(), "system-features",
|
||||
"Optional features that this system implements (like \"kvm\")."};
|
||||
|
||||
Setting<Strings> substituters{this,
|
||||
nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(),
|
||||
"substituters",
|
||||
"The URIs of substituters (such as https://cache.nixos.org/).",
|
||||
{"binary-caches"}};
|
||||
Setting<Strings> substituters{
|
||||
this,
|
||||
nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"}
|
||||
: Strings(),
|
||||
"substituters",
|
||||
"The URIs of substituters (such as https://cache.nixos.org/).",
|
||||
{"binary-caches"}};
|
||||
|
||||
// FIXME: provide a way to add to option values.
|
||||
Setting<Strings> extraSubstituters{this, {}, "extra-substituters",
|
||||
"Additional URIs of substituters.",
|
||||
{"extra-binary-caches"}};
|
||||
// FIXME: provide a way to add to option values.
|
||||
Setting<Strings> extraSubstituters{this,
|
||||
{},
|
||||
"extra-substituters",
|
||||
"Additional URIs of substituters.",
|
||||
{"extra-binary-caches"}};
|
||||
|
||||
Setting<StringSet> trustedSubstituters{this, {}, "trusted-substituters",
|
||||
"Disabled substituters that may be enabled via the substituters option by untrusted users.",
|
||||
{"trusted-binary-caches"}};
|
||||
Setting<StringSet> trustedSubstituters{
|
||||
this,
|
||||
{},
|
||||
"trusted-substituters",
|
||||
"Disabled substituters that may be enabled via the substituters option "
|
||||
"by untrusted users.",
|
||||
{"trusted-binary-caches"}};
|
||||
|
||||
Setting<Strings> trustedUsers{this, {"root"}, "trusted-users",
|
||||
"Which users or groups are trusted to ask the daemon to do unsafe things."};
|
||||
Setting<Strings> trustedUsers{this,
|
||||
{"root"},
|
||||
"trusted-users",
|
||||
"Which users or groups are trusted to ask the "
|
||||
"daemon to do unsafe things."};
|
||||
|
||||
Setting<unsigned int> ttlNegativeNarInfoCache{this, 3600, "narinfo-cache-negative-ttl",
|
||||
"The TTL in seconds for negative lookups in the disk cache i.e binary cache lookups that "
|
||||
"return an invalid path result"};
|
||||
Setting<unsigned int> ttlNegativeNarInfoCache{
|
||||
this, 3600, "narinfo-cache-negative-ttl",
|
||||
"The TTL in seconds for negative lookups in the disk cache i.e binary "
|
||||
"cache lookups that "
|
||||
"return an invalid path result"};
|
||||
|
||||
Setting<unsigned int> ttlPositiveNarInfoCache{this, 30 * 24 * 3600, "narinfo-cache-positive-ttl",
|
||||
"The TTL in seconds for positive lookups in the disk cache i.e binary cache lookups that "
|
||||
"return a valid path result."};
|
||||
Setting<unsigned int> ttlPositiveNarInfoCache{
|
||||
this, 30 * 24 * 3600, "narinfo-cache-positive-ttl",
|
||||
"The TTL in seconds for positive lookups in the disk cache i.e binary "
|
||||
"cache lookups that "
|
||||
"return a valid path result."};
|
||||
|
||||
/* ?Who we trust to use the daemon in safe ways */
|
||||
Setting<Strings> allowedUsers{this, {"*"}, "allowed-users",
|
||||
"Which users or groups are allowed to connect to the daemon."};
|
||||
/* ?Who we trust to use the daemon in safe ways */
|
||||
Setting<Strings> allowedUsers{
|
||||
this,
|
||||
{"*"},
|
||||
"allowed-users",
|
||||
"Which users or groups are allowed to connect to the daemon."};
|
||||
|
||||
Setting<bool> printMissing{this, true, "print-missing",
|
||||
"Whether to print what paths need to be built or downloaded."};
|
||||
Setting<bool> printMissing{
|
||||
this, true, "print-missing",
|
||||
"Whether to print what paths need to be built or downloaded."};
|
||||
|
||||
Setting<std::string> preBuildHook{this,
|
||||
Setting<std::string> preBuildHook {
|
||||
this,
|
||||
#if __APPLE__
|
||||
nixLibexecDir + "/nix/resolve-system-dependencies",
|
||||
#else
|
||||
"",
|
||||
#endif
|
||||
"pre-build-hook",
|
||||
"A program to run just before a build to set derivation-specific build settings."};
|
||||
"A program to run just before a build to set derivation-specific build "
|
||||
"settings."
|
||||
};
|
||||
|
||||
Setting<std::string> postBuildHook{this, "", "post-build-hook",
|
||||
"A program to run just after each successful build."};
|
||||
Setting<std::string> postBuildHook{
|
||||
this, "", "post-build-hook",
|
||||
"A program to run just after each successful build."};
|
||||
|
||||
Setting<std::string> netrcFile{this, fmt("%s/%s", nixConfDir, "netrc"), "netrc-file",
|
||||
"Path to the netrc file used to obtain usernames/passwords for downloads."};
|
||||
Setting<std::string> netrcFile{this, fmt("%s/%s", nixConfDir, "netrc"),
|
||||
"netrc-file",
|
||||
"Path to the netrc file used to obtain "
|
||||
"usernames/passwords for downloads."};
|
||||
|
||||
/* Path to the SSL CA file used */
|
||||
Path caFile;
|
||||
/* Path to the SSL CA file used */
|
||||
Path caFile;
|
||||
|
||||
#if __linux__
|
||||
Setting<bool> filterSyscalls{this, true, "filter-syscalls",
|
||||
"Whether to prevent certain dangerous system calls, such as "
|
||||
"creation of setuid/setgid files or adding ACLs or extended "
|
||||
"attributes. Only disable this if you're aware of the "
|
||||
"security implications."};
|
||||
Setting<bool> filterSyscalls{
|
||||
this, true, "filter-syscalls",
|
||||
"Whether to prevent certain dangerous system calls, such as "
|
||||
"creation of setuid/setgid files or adding ACLs or extended "
|
||||
"attributes. Only disable this if you're aware of the "
|
||||
"security implications."};
|
||||
|
||||
Setting<bool> allowNewPrivileges{this, false, "allow-new-privileges",
|
||||
"Whether builders can acquire new privileges by calling programs with "
|
||||
"setuid/setgid bits or with file capabilities."};
|
||||
Setting<bool> allowNewPrivileges{
|
||||
this, false, "allow-new-privileges",
|
||||
"Whether builders can acquire new privileges by calling programs with "
|
||||
"setuid/setgid bits or with file capabilities."};
|
||||
#endif
|
||||
|
||||
Setting<Strings> hashedMirrors{this, {"http://tarballs.nixos.org/"}, "hashed-mirrors",
|
||||
"A list of servers used by builtins.fetchurl to fetch files by hash."};
|
||||
Setting<Strings> hashedMirrors{
|
||||
this,
|
||||
{"http://tarballs.nixos.org/"},
|
||||
"hashed-mirrors",
|
||||
"A list of servers used by builtins.fetchurl to fetch files by hash."};
|
||||
|
||||
Setting<uint64_t> minFree{this, 0, "min-free",
|
||||
"Automatically run the garbage collector when free disk space drops below the specified amount."};
|
||||
Setting<uint64_t> minFree{this, 0, "min-free",
|
||||
"Automatically run the garbage collector when free "
|
||||
"disk space drops below the specified amount."};
|
||||
|
||||
Setting<uint64_t> maxFree{this, std::numeric_limits<uint64_t>::max(), "max-free",
|
||||
"Stop deleting garbage when free disk space is above the specified amount."};
|
||||
Setting<uint64_t> maxFree{this, std::numeric_limits<uint64_t>::max(),
|
||||
"max-free",
|
||||
"Stop deleting garbage when free disk space is "
|
||||
"above the specified amount."};
|
||||
|
||||
Setting<uint64_t> minFreeCheckInterval{this, 5, "min-free-check-interval",
|
||||
"Number of seconds between checking free disk space."};
|
||||
Setting<uint64_t> minFreeCheckInterval{
|
||||
this, 5, "min-free-check-interval",
|
||||
"Number of seconds between checking free disk space."};
|
||||
|
||||
Setting<Paths> pluginFiles{this, {}, "plugin-files",
|
||||
"Plugins to dynamically load at nix initialization time."};
|
||||
Setting<Paths> pluginFiles{
|
||||
this,
|
||||
{},
|
||||
"plugin-files",
|
||||
"Plugins to dynamically load at nix initialization time."};
|
||||
};
|
||||
|
||||
|
||||
// FIXME: don't use a global variable.
|
||||
extern Settings settings;
|
||||
|
||||
|
|
@ -367,4 +481,4 @@ void loadConfFile();
|
|||
|
||||
extern const string nixVersion;
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -7,167 +7,150 @@ namespace nix {
|
|||
|
||||
MakeError(UploadToHTTP, Error);
|
||||
|
||||
class HttpBinaryCacheStore : public BinaryCacheStore
|
||||
{
|
||||
private:
|
||||
class HttpBinaryCacheStore : public BinaryCacheStore {
|
||||
private:
|
||||
Path cacheUri;
|
||||
|
||||
Path cacheUri;
|
||||
struct State {
|
||||
bool enabled = true;
|
||||
std::chrono::steady_clock::time_point disabledUntil;
|
||||
};
|
||||
|
||||
struct State
|
||||
{
|
||||
bool enabled = true;
|
||||
std::chrono::steady_clock::time_point disabledUntil;
|
||||
};
|
||||
Sync<State> _state;
|
||||
|
||||
Sync<State> _state;
|
||||
public:
|
||||
HttpBinaryCacheStore(const Params& params, const Path& _cacheUri)
|
||||
: BinaryCacheStore(params), cacheUri(_cacheUri) {
|
||||
if (cacheUri.back() == '/') cacheUri.pop_back();
|
||||
|
||||
public:
|
||||
diskCache = getNarInfoDiskCache();
|
||||
}
|
||||
|
||||
HttpBinaryCacheStore(
|
||||
const Params & params, const Path & _cacheUri)
|
||||
: BinaryCacheStore(params)
|
||||
, cacheUri(_cacheUri)
|
||||
{
|
||||
if (cacheUri.back() == '/')
|
||||
cacheUri.pop_back();
|
||||
std::string getUri() override { return cacheUri; }
|
||||
|
||||
diskCache = getNarInfoDiskCache();
|
||||
void init() override {
|
||||
// FIXME: do this lazily?
|
||||
if (!diskCache->cacheExists(cacheUri, wantMassQuery_, priority)) {
|
||||
try {
|
||||
BinaryCacheStore::init();
|
||||
} catch (UploadToHTTP&) {
|
||||
throw Error("'%s' does not appear to be a binary cache", cacheUri);
|
||||
}
|
||||
diskCache->createCache(cacheUri, storeDir, wantMassQuery_, priority);
|
||||
}
|
||||
}
|
||||
|
||||
std::string getUri() override
|
||||
{
|
||||
return cacheUri;
|
||||
protected:
|
||||
void maybeDisable() {
|
||||
auto state(_state.lock());
|
||||
if (state->enabled && settings.tryFallback) {
|
||||
int t = 60;
|
||||
printError("disabling binary cache '%s' for %s seconds", getUri(), t);
|
||||
state->enabled = false;
|
||||
state->disabledUntil =
|
||||
std::chrono::steady_clock::now() + std::chrono::seconds(t);
|
||||
}
|
||||
}
|
||||
|
||||
void init() override
|
||||
{
|
||||
// FIXME: do this lazily?
|
||||
if (!diskCache->cacheExists(cacheUri, wantMassQuery_, priority)) {
|
||||
try {
|
||||
BinaryCacheStore::init();
|
||||
} catch (UploadToHTTP &) {
|
||||
throw Error("'%s' does not appear to be a binary cache", cacheUri);
|
||||
}
|
||||
diskCache->createCache(cacheUri, storeDir, wantMassQuery_, priority);
|
||||
}
|
||||
void checkEnabled() {
|
||||
auto state(_state.lock());
|
||||
if (state->enabled) return;
|
||||
if (std::chrono::steady_clock::now() > state->disabledUntil) {
|
||||
state->enabled = true;
|
||||
debug("re-enabling binary cache '%s'", getUri());
|
||||
return;
|
||||
}
|
||||
throw SubstituterDisabled("substituter '%s' is disabled", getUri());
|
||||
}
|
||||
|
||||
protected:
|
||||
bool fileExists(const std::string& path) override {
|
||||
checkEnabled();
|
||||
|
||||
void maybeDisable()
|
||||
{
|
||||
auto state(_state.lock());
|
||||
if (state->enabled && settings.tryFallback) {
|
||||
int t = 60;
|
||||
printError("disabling binary cache '%s' for %s seconds", getUri(), t);
|
||||
state->enabled = false;
|
||||
state->disabledUntil = std::chrono::steady_clock::now() + std::chrono::seconds(t);
|
||||
}
|
||||
try {
|
||||
DownloadRequest request(cacheUri + "/" + path);
|
||||
request.head = true;
|
||||
getDownloader()->download(request);
|
||||
return true;
|
||||
} catch (DownloadError& e) {
|
||||
/* S3 buckets return 403 if a file doesn't exist and the
|
||||
bucket is unlistable, so treat 403 as 404. */
|
||||
if (e.error == Downloader::NotFound || e.error == Downloader::Forbidden)
|
||||
return false;
|
||||
maybeDisable();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void checkEnabled()
|
||||
{
|
||||
auto state(_state.lock());
|
||||
if (state->enabled) return;
|
||||
if (std::chrono::steady_clock::now() > state->disabledUntil) {
|
||||
state->enabled = true;
|
||||
debug("re-enabling binary cache '%s'", getUri());
|
||||
return;
|
||||
}
|
||||
throw SubstituterDisabled("substituter '%s' is disabled", getUri());
|
||||
void upsertFile(const std::string& path, const std::string& data,
|
||||
const std::string& mimeType) override {
|
||||
auto req = DownloadRequest(cacheUri + "/" + path);
|
||||
req.data = std::make_shared<string>(data); // FIXME: inefficient
|
||||
req.mimeType = mimeType;
|
||||
try {
|
||||
getDownloader()->download(req);
|
||||
} catch (DownloadError& e) {
|
||||
throw UploadToHTTP("while uploading to HTTP binary cache at '%s': %s",
|
||||
cacheUri, e.msg());
|
||||
}
|
||||
}
|
||||
|
||||
bool fileExists(const std::string & path) override
|
||||
{
|
||||
checkEnabled();
|
||||
DownloadRequest makeRequest(const std::string& path) {
|
||||
DownloadRequest request(cacheUri + "/" + path);
|
||||
return request;
|
||||
}
|
||||
|
||||
try {
|
||||
DownloadRequest request(cacheUri + "/" + path);
|
||||
request.head = true;
|
||||
getDownloader()->download(request);
|
||||
return true;
|
||||
} catch (DownloadError & e) {
|
||||
/* S3 buckets return 403 if a file doesn't exist and the
|
||||
bucket is unlistable, so treat 403 as 404. */
|
||||
if (e.error == Downloader::NotFound || e.error == Downloader::Forbidden)
|
||||
return false;
|
||||
void getFile(const std::string& path, Sink& sink) override {
|
||||
checkEnabled();
|
||||
auto request(makeRequest(path));
|
||||
try {
|
||||
getDownloader()->download(std::move(request), sink);
|
||||
} catch (DownloadError& e) {
|
||||
if (e.error == Downloader::NotFound || e.error == Downloader::Forbidden)
|
||||
throw NoSuchBinaryCacheFile(
|
||||
"file '%s' does not exist in binary cache '%s'", path, getUri());
|
||||
maybeDisable();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void getFile(
|
||||
const std::string& path,
|
||||
Callback<std::shared_ptr<std::string>> callback) noexcept override {
|
||||
checkEnabled();
|
||||
|
||||
auto request(makeRequest(path));
|
||||
|
||||
auto callbackPtr =
|
||||
std::make_shared<decltype(callback)>(std::move(callback));
|
||||
|
||||
getDownloader()->enqueueDownload(
|
||||
request, {[callbackPtr, this](std::future<DownloadResult> result) {
|
||||
try {
|
||||
(*callbackPtr)(result.get().data);
|
||||
} catch (DownloadError& e) {
|
||||
if (e.error == Downloader::NotFound ||
|
||||
e.error == Downloader::Forbidden)
|
||||
return (*callbackPtr)(std::shared_ptr<std::string>());
|
||||
maybeDisable();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void upsertFile(const std::string & path,
|
||||
const std::string & data,
|
||||
const std::string & mimeType) override
|
||||
{
|
||||
auto req = DownloadRequest(cacheUri + "/" + path);
|
||||
req.data = std::make_shared<string>(data); // FIXME: inefficient
|
||||
req.mimeType = mimeType;
|
||||
try {
|
||||
getDownloader()->download(req);
|
||||
} catch (DownloadError & e) {
|
||||
throw UploadToHTTP("while uploading to HTTP binary cache at '%s': %s", cacheUri, e.msg());
|
||||
}
|
||||
}
|
||||
|
||||
DownloadRequest makeRequest(const std::string & path)
|
||||
{
|
||||
DownloadRequest request(cacheUri + "/" + path);
|
||||
return request;
|
||||
}
|
||||
|
||||
void getFile(const std::string & path, Sink & sink) override
|
||||
{
|
||||
checkEnabled();
|
||||
auto request(makeRequest(path));
|
||||
try {
|
||||
getDownloader()->download(std::move(request), sink);
|
||||
} catch (DownloadError & e) {
|
||||
if (e.error == Downloader::NotFound || e.error == Downloader::Forbidden)
|
||||
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache '%s'", path, getUri());
|
||||
maybeDisable();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void getFile(const std::string & path,
|
||||
Callback<std::shared_ptr<std::string>> callback) noexcept override
|
||||
{
|
||||
checkEnabled();
|
||||
|
||||
auto request(makeRequest(path));
|
||||
|
||||
auto callbackPtr = std::make_shared<decltype(callback)>(std::move(callback));
|
||||
|
||||
getDownloader()->enqueueDownload(request,
|
||||
{[callbackPtr, this](std::future<DownloadResult> result) {
|
||||
try {
|
||||
(*callbackPtr)(result.get().data);
|
||||
} catch (DownloadError & e) {
|
||||
if (e.error == Downloader::NotFound || e.error == Downloader::Forbidden)
|
||||
return (*callbackPtr)(std::shared_ptr<std::string>());
|
||||
maybeDisable();
|
||||
callbackPtr->rethrow();
|
||||
} catch (...) {
|
||||
callbackPtr->rethrow();
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
callbackPtr->rethrow();
|
||||
} catch (...) {
|
||||
callbackPtr->rethrow();
|
||||
}
|
||||
}});
|
||||
}
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation regStore([](
|
||||
const std::string & uri, const Store::Params & params)
|
||||
-> std::shared_ptr<Store>
|
||||
{
|
||||
if (std::string(uri, 0, 7) != "http://" &&
|
||||
std::string(uri, 0, 8) != "https://" &&
|
||||
(getEnv("_NIX_FORCE_HTTP_BINARY_CACHE_STORE") != "1" || std::string(uri, 0, 7) != "file://")
|
||||
) return 0;
|
||||
auto store = std::make_shared<HttpBinaryCacheStore>(params, uri);
|
||||
store->init();
|
||||
return store;
|
||||
});
|
||||
|
||||
}
|
||||
static RegisterStoreImplementation regStore(
|
||||
[](const std::string& uri,
|
||||
const Store::Params& params) -> std::shared_ptr<Store> {
|
||||
if (std::string(uri, 0, 7) != "http://" &&
|
||||
std::string(uri, 0, 8) != "https://" &&
|
||||
(getEnv("_NIX_FORCE_HTTP_BINARY_CACHE_STORE") != "1" ||
|
||||
std::string(uri, 0, 7) != "file://"))
|
||||
return 0;
|
||||
auto store = std::make_shared<HttpBinaryCacheStore>(params, uri);
|
||||
store->init();
|
||||
return store;
|
||||
});
|
||||
|
||||
} // namespace nix
|
||||
|
|
|
|||
457
third_party/nix/src/libstore/legacy-ssh-store.cc
vendored
457
third_party/nix/src/libstore/legacy-ssh-store.cc
vendored
|
|
@ -1,293 +1,258 @@
|
|||
#include "archive.hh"
|
||||
#include "derivations.hh"
|
||||
#include "pool.hh"
|
||||
#include "remote-store.hh"
|
||||
#include "serve-protocol.hh"
|
||||
#include "ssh.hh"
|
||||
#include "store-api.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "ssh.hh"
|
||||
#include "derivations.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static std::string uriScheme = "ssh://";
|
||||
|
||||
struct LegacySSHStore : public Store
|
||||
{
|
||||
const Setting<int> maxConnections{this, 1, "max-connections", "maximum number of concurrent SSH connections"};
|
||||
const Setting<Path> sshKey{this, "", "ssh-key", "path to an SSH private key"};
|
||||
const Setting<bool> compress{this, false, "compress", "whether to compress the connection"};
|
||||
const Setting<Path> remoteProgram{this, "nix-store", "remote-program", "path to the nix-store executable on the remote system"};
|
||||
const Setting<std::string> remoteStore{this, "", "remote-store", "URI of the store on the remote system"};
|
||||
struct LegacySSHStore : public Store {
|
||||
const Setting<int> maxConnections{
|
||||
this, 1, "max-connections",
|
||||
"maximum number of concurrent SSH connections"};
|
||||
const Setting<Path> sshKey{this, "", "ssh-key", "path to an SSH private key"};
|
||||
const Setting<bool> compress{this, false, "compress",
|
||||
"whether to compress the connection"};
|
||||
const Setting<Path> remoteProgram{
|
||||
this, "nix-store", "remote-program",
|
||||
"path to the nix-store executable on the remote system"};
|
||||
const Setting<std::string> remoteStore{
|
||||
this, "", "remote-store", "URI of the store on the remote system"};
|
||||
|
||||
// Hack for getting remote build log output.
|
||||
const Setting<int> logFD{this, -1, "log-fd", "file descriptor to which SSH's stderr is connected"};
|
||||
// Hack for getting remote build log output.
|
||||
const Setting<int> logFD{
|
||||
this, -1, "log-fd", "file descriptor to which SSH's stderr is connected"};
|
||||
|
||||
struct Connection
|
||||
{
|
||||
std::unique_ptr<SSHMaster::Connection> sshConn;
|
||||
FdSink to;
|
||||
FdSource from;
|
||||
int remoteVersion;
|
||||
bool good = true;
|
||||
};
|
||||
struct Connection {
|
||||
std::unique_ptr<SSHMaster::Connection> sshConn;
|
||||
FdSink to;
|
||||
FdSource from;
|
||||
int remoteVersion;
|
||||
bool good = true;
|
||||
};
|
||||
|
||||
std::string host;
|
||||
std::string host;
|
||||
|
||||
ref<Pool<Connection>> connections;
|
||||
ref<Pool<Connection>> connections;
|
||||
|
||||
SSHMaster master;
|
||||
SSHMaster master;
|
||||
|
||||
LegacySSHStore(const string & host, const Params & params)
|
||||
: Store(params)
|
||||
, host(host)
|
||||
, connections(make_ref<Pool<Connection>>(
|
||||
std::max(1, (int) maxConnections),
|
||||
LegacySSHStore(const string& host, const Params& params)
|
||||
: Store(params),
|
||||
host(host),
|
||||
connections(make_ref<Pool<Connection>>(
|
||||
std::max(1, (int)maxConnections),
|
||||
[this]() { return openConnection(); },
|
||||
[](const ref<Connection> & r) { return r->good; }
|
||||
))
|
||||
, master(
|
||||
host,
|
||||
sshKey,
|
||||
// Use SSH master only if using more than 1 connection.
|
||||
connections->capacity() > 1,
|
||||
compress,
|
||||
logFD)
|
||||
{
|
||||
[](const ref<Connection>& r) { return r->good; })),
|
||||
master(host, sshKey,
|
||||
// Use SSH master only if using more than 1 connection.
|
||||
connections->capacity() > 1, compress, logFD) {}
|
||||
|
||||
ref<Connection> openConnection() {
|
||||
auto conn = make_ref<Connection>();
|
||||
conn->sshConn = master.startCommand(
|
||||
fmt("%s --serve --write", remoteProgram) +
|
||||
(remoteStore.get() == ""
|
||||
? ""
|
||||
: " --store " + shellEscape(remoteStore.get())));
|
||||
conn->to = FdSink(conn->sshConn->in.get());
|
||||
conn->from = FdSource(conn->sshConn->out.get());
|
||||
|
||||
try {
|
||||
conn->to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
|
||||
conn->to.flush();
|
||||
|
||||
unsigned int magic = readInt(conn->from);
|
||||
if (magic != SERVE_MAGIC_2)
|
||||
throw Error("protocol mismatch with 'nix-store --serve' on '%s'", host);
|
||||
conn->remoteVersion = readInt(conn->from);
|
||||
if (GET_PROTOCOL_MAJOR(conn->remoteVersion) != 0x200)
|
||||
throw Error("unsupported 'nix-store --serve' protocol version on '%s'",
|
||||
host);
|
||||
|
||||
} catch (EndOfFile& e) {
|
||||
throw Error("cannot connect to '%1%'", host);
|
||||
}
|
||||
|
||||
ref<Connection> openConnection()
|
||||
{
|
||||
auto conn = make_ref<Connection>();
|
||||
conn->sshConn = master.startCommand(
|
||||
fmt("%s --serve --write", remoteProgram)
|
||||
+ (remoteStore.get() == "" ? "" : " --store " + shellEscape(remoteStore.get())));
|
||||
conn->to = FdSink(conn->sshConn->in.get());
|
||||
conn->from = FdSource(conn->sshConn->out.get());
|
||||
return conn;
|
||||
};
|
||||
|
||||
try {
|
||||
conn->to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
|
||||
conn->to.flush();
|
||||
string getUri() override { return uriScheme + host; }
|
||||
|
||||
unsigned int magic = readInt(conn->from);
|
||||
if (magic != SERVE_MAGIC_2)
|
||||
throw Error("protocol mismatch with 'nix-store --serve' on '%s'", host);
|
||||
conn->remoteVersion = readInt(conn->from);
|
||||
if (GET_PROTOCOL_MAJOR(conn->remoteVersion) != 0x200)
|
||||
throw Error("unsupported 'nix-store --serve' protocol version on '%s'", host);
|
||||
void queryPathInfoUncached(
|
||||
const Path& path,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override {
|
||||
try {
|
||||
auto conn(connections->get());
|
||||
|
||||
} catch (EndOfFile & e) {
|
||||
throw Error("cannot connect to '%1%'", host);
|
||||
}
|
||||
debug("querying remote host '%s' for info on '%s'", host, path);
|
||||
|
||||
return conn;
|
||||
};
|
||||
conn->to << cmdQueryPathInfos << PathSet{path};
|
||||
conn->to.flush();
|
||||
|
||||
string getUri() override
|
||||
{
|
||||
return uriScheme + host;
|
||||
auto info = std::make_shared<ValidPathInfo>();
|
||||
conn->from >> info->path;
|
||||
if (info->path.empty()) return callback(nullptr);
|
||||
assert(path == info->path);
|
||||
|
||||
PathSet references;
|
||||
conn->from >> info->deriver;
|
||||
info->references = readStorePaths<PathSet>(*this, conn->from);
|
||||
readLongLong(conn->from); // download size
|
||||
info->narSize = readLongLong(conn->from);
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 4) {
|
||||
auto s = readString(conn->from);
|
||||
info->narHash = s.empty() ? Hash() : Hash(s);
|
||||
conn->from >> info->ca;
|
||||
info->sigs = readStrings<StringSet>(conn->from);
|
||||
}
|
||||
|
||||
auto s = readString(conn->from);
|
||||
assert(s == "");
|
||||
|
||||
callback(std::move(info));
|
||||
} catch (...) {
|
||||
callback.rethrow();
|
||||
}
|
||||
}
|
||||
|
||||
void addToStore(const ValidPathInfo& info, Source& source, RepairFlag repair,
|
||||
CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override {
|
||||
debug("adding path '%s' to remote host '%s'", info.path, host);
|
||||
|
||||
auto conn(connections->get());
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
|
||||
conn->to << cmdAddToStoreNar << info.path << info.deriver
|
||||
<< info.narHash.to_string(Base16, false) << info.references
|
||||
<< info.registrationTime << info.narSize << info.ultimate
|
||||
<< info.sigs << info.ca;
|
||||
try {
|
||||
copyNAR(source, conn->to);
|
||||
} catch (...) {
|
||||
conn->good = false;
|
||||
throw;
|
||||
}
|
||||
conn->to.flush();
|
||||
|
||||
} else {
|
||||
conn->to << cmdImportPaths << 1;
|
||||
try {
|
||||
copyNAR(source, conn->to);
|
||||
} catch (...) {
|
||||
conn->good = false;
|
||||
throw;
|
||||
}
|
||||
conn->to << exportMagic << info.path << info.references << info.deriver
|
||||
<< 0 << 0;
|
||||
conn->to.flush();
|
||||
}
|
||||
|
||||
void queryPathInfoUncached(const Path & path,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override
|
||||
{
|
||||
try {
|
||||
auto conn(connections->get());
|
||||
if (readInt(conn->from) != 1)
|
||||
throw Error(
|
||||
"failed to add path '%s' to remote host '%s', info.path, host");
|
||||
}
|
||||
|
||||
debug("querying remote host '%s' for info on '%s'", host, path);
|
||||
void narFromPath(const Path& path, Sink& sink) override {
|
||||
auto conn(connections->get());
|
||||
|
||||
conn->to << cmdQueryPathInfos << PathSet{path};
|
||||
conn->to.flush();
|
||||
conn->to << cmdDumpStorePath << path;
|
||||
conn->to.flush();
|
||||
copyNAR(conn->from, sink);
|
||||
}
|
||||
|
||||
auto info = std::make_shared<ValidPathInfo>();
|
||||
conn->from >> info->path;
|
||||
if (info->path.empty()) return callback(nullptr);
|
||||
assert(path == info->path);
|
||||
Path queryPathFromHashPart(const string& hashPart) override {
|
||||
unsupported("queryPathFromHashPart");
|
||||
}
|
||||
|
||||
PathSet references;
|
||||
conn->from >> info->deriver;
|
||||
info->references = readStorePaths<PathSet>(*this, conn->from);
|
||||
readLongLong(conn->from); // download size
|
||||
info->narSize = readLongLong(conn->from);
|
||||
Path addToStore(const string& name, const Path& srcPath, bool recursive,
|
||||
HashType hashAlgo, PathFilter& filter,
|
||||
RepairFlag repair) override {
|
||||
unsupported("addToStore");
|
||||
}
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 4) {
|
||||
auto s = readString(conn->from);
|
||||
info->narHash = s.empty() ? Hash() : Hash(s);
|
||||
conn->from >> info->ca;
|
||||
info->sigs = readStrings<StringSet>(conn->from);
|
||||
}
|
||||
Path addTextToStore(const string& name, const string& s,
|
||||
const PathSet& references, RepairFlag repair) override {
|
||||
unsupported("addTextToStore");
|
||||
}
|
||||
|
||||
auto s = readString(conn->from);
|
||||
assert(s == "");
|
||||
BuildResult buildDerivation(const Path& drvPath, const BasicDerivation& drv,
|
||||
BuildMode buildMode) override {
|
||||
auto conn(connections->get());
|
||||
|
||||
callback(std::move(info));
|
||||
} catch (...) { callback.rethrow(); }
|
||||
conn->to << cmdBuildDerivation << drvPath << drv << settings.maxSilentTime
|
||||
<< settings.buildTimeout;
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 2)
|
||||
conn->to << settings.maxLogSize;
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
|
||||
conn->to << settings.buildRepeat << settings.enforceDeterminism;
|
||||
|
||||
conn->to.flush();
|
||||
|
||||
BuildResult status;
|
||||
status.status = (BuildResult::Status)readInt(conn->from);
|
||||
conn->from >> status.errorMsg;
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
|
||||
conn->from >> status.timesBuilt >> status.isNonDeterministic >>
|
||||
status.startTime >> status.stopTime;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void ensurePath(const Path& path) override { unsupported("ensurePath"); }
|
||||
|
||||
void computeFSClosure(const PathSet& paths, PathSet& out,
|
||||
bool flipDirection = false, bool includeOutputs = false,
|
||||
bool includeDerivers = false) override {
|
||||
if (flipDirection || includeDerivers) {
|
||||
Store::computeFSClosure(paths, out, flipDirection, includeOutputs,
|
||||
includeDerivers);
|
||||
return;
|
||||
}
|
||||
|
||||
void addToStore(const ValidPathInfo & info, Source & source,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override
|
||||
{
|
||||
debug("adding path '%s' to remote host '%s'", info.path, host);
|
||||
auto conn(connections->get());
|
||||
|
||||
auto conn(connections->get());
|
||||
conn->to << cmdQueryClosure << includeOutputs << paths;
|
||||
conn->to.flush();
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
|
||||
auto res = readStorePaths<PathSet>(*this, conn->from);
|
||||
|
||||
conn->to
|
||||
<< cmdAddToStoreNar
|
||||
<< info.path
|
||||
<< info.deriver
|
||||
<< info.narHash.to_string(Base16, false)
|
||||
<< info.references
|
||||
<< info.registrationTime
|
||||
<< info.narSize
|
||||
<< info.ultimate
|
||||
<< info.sigs
|
||||
<< info.ca;
|
||||
try {
|
||||
copyNAR(source, conn->to);
|
||||
} catch (...) {
|
||||
conn->good = false;
|
||||
throw;
|
||||
}
|
||||
conn->to.flush();
|
||||
out.insert(res.begin(), res.end());
|
||||
}
|
||||
|
||||
} else {
|
||||
PathSet queryValidPaths(const PathSet& paths, SubstituteFlag maybeSubstitute =
|
||||
NoSubstitute) override {
|
||||
auto conn(connections->get());
|
||||
|
||||
conn->to
|
||||
<< cmdImportPaths
|
||||
<< 1;
|
||||
try {
|
||||
copyNAR(source, conn->to);
|
||||
} catch (...) {
|
||||
conn->good = false;
|
||||
throw;
|
||||
}
|
||||
conn->to
|
||||
<< exportMagic
|
||||
<< info.path
|
||||
<< info.references
|
||||
<< info.deriver
|
||||
<< 0
|
||||
<< 0;
|
||||
conn->to.flush();
|
||||
conn->to << cmdQueryValidPaths << false // lock
|
||||
<< maybeSubstitute << paths;
|
||||
conn->to.flush();
|
||||
|
||||
}
|
||||
return readStorePaths<PathSet>(*this, conn->from);
|
||||
}
|
||||
|
||||
if (readInt(conn->from) != 1)
|
||||
throw Error("failed to add path '%s' to remote host '%s', info.path, host");
|
||||
}
|
||||
void connect() override { auto conn(connections->get()); }
|
||||
|
||||
void narFromPath(const Path & path, Sink & sink) override
|
||||
{
|
||||
auto conn(connections->get());
|
||||
|
||||
conn->to << cmdDumpStorePath << path;
|
||||
conn->to.flush();
|
||||
copyNAR(conn->from, sink);
|
||||
}
|
||||
|
||||
Path queryPathFromHashPart(const string & hashPart) override
|
||||
{ unsupported("queryPathFromHashPart"); }
|
||||
|
||||
Path addToStore(const string & name, const Path & srcPath,
|
||||
bool recursive, HashType hashAlgo,
|
||||
PathFilter & filter, RepairFlag repair) override
|
||||
{ unsupported("addToStore"); }
|
||||
|
||||
Path addTextToStore(const string & name, const string & s,
|
||||
const PathSet & references, RepairFlag repair) override
|
||||
{ unsupported("addTextToStore"); }
|
||||
|
||||
BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode) override
|
||||
{
|
||||
auto conn(connections->get());
|
||||
|
||||
conn->to
|
||||
<< cmdBuildDerivation
|
||||
<< drvPath
|
||||
<< drv
|
||||
<< settings.maxSilentTime
|
||||
<< settings.buildTimeout;
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 2)
|
||||
conn->to
|
||||
<< settings.maxLogSize;
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
|
||||
conn->to
|
||||
<< settings.buildRepeat
|
||||
<< settings.enforceDeterminism;
|
||||
|
||||
conn->to.flush();
|
||||
|
||||
BuildResult status;
|
||||
status.status = (BuildResult::Status) readInt(conn->from);
|
||||
conn->from >> status.errorMsg;
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
|
||||
conn->from >> status.timesBuilt >> status.isNonDeterministic >> status.startTime >> status.stopTime;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void ensurePath(const Path & path) override
|
||||
{ unsupported("ensurePath"); }
|
||||
|
||||
void computeFSClosure(const PathSet & paths,
|
||||
PathSet & out, bool flipDirection = false,
|
||||
bool includeOutputs = false, bool includeDerivers = false) override
|
||||
{
|
||||
if (flipDirection || includeDerivers) {
|
||||
Store::computeFSClosure(paths, out, flipDirection, includeOutputs, includeDerivers);
|
||||
return;
|
||||
}
|
||||
|
||||
auto conn(connections->get());
|
||||
|
||||
conn->to
|
||||
<< cmdQueryClosure
|
||||
<< includeOutputs
|
||||
<< paths;
|
||||
conn->to.flush();
|
||||
|
||||
auto res = readStorePaths<PathSet>(*this, conn->from);
|
||||
|
||||
out.insert(res.begin(), res.end());
|
||||
}
|
||||
|
||||
PathSet queryValidPaths(const PathSet & paths,
|
||||
SubstituteFlag maybeSubstitute = NoSubstitute) override
|
||||
{
|
||||
auto conn(connections->get());
|
||||
|
||||
conn->to
|
||||
<< cmdQueryValidPaths
|
||||
<< false // lock
|
||||
<< maybeSubstitute
|
||||
<< paths;
|
||||
conn->to.flush();
|
||||
|
||||
return readStorePaths<PathSet>(*this, conn->from);
|
||||
}
|
||||
|
||||
void connect() override
|
||||
{
|
||||
auto conn(connections->get());
|
||||
}
|
||||
|
||||
unsigned int getProtocol() override
|
||||
{
|
||||
auto conn(connections->get());
|
||||
return conn->remoteVersion;
|
||||
}
|
||||
unsigned int getProtocol() override {
|
||||
auto conn(connections->get());
|
||||
return conn->remoteVersion;
|
||||
}
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation regStore([](
|
||||
const std::string & uri, const Store::Params & params)
|
||||
-> std::shared_ptr<Store>
|
||||
{
|
||||
if (std::string(uri, 0, uriScheme.size()) != uriScheme) return 0;
|
||||
return std::make_shared<LegacySSHStore>(std::string(uri, uriScheme.size()), params);
|
||||
});
|
||||
static RegisterStoreImplementation regStore(
|
||||
[](const std::string& uri,
|
||||
const Store::Params& params) -> std::shared_ptr<Store> {
|
||||
if (std::string(uri, 0, uriScheme.size()) != uriScheme) return 0;
|
||||
return std::make_shared<LegacySSHStore>(
|
||||
std::string(uri, uriScheme.size()), params);
|
||||
});
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -4,100 +4,82 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
class LocalBinaryCacheStore : public BinaryCacheStore
|
||||
{
|
||||
private:
|
||||
class LocalBinaryCacheStore : public BinaryCacheStore {
|
||||
private:
|
||||
Path binaryCacheDir;
|
||||
|
||||
Path binaryCacheDir;
|
||||
public:
|
||||
LocalBinaryCacheStore(const Params& params, const Path& binaryCacheDir)
|
||||
: BinaryCacheStore(params), binaryCacheDir(binaryCacheDir) {}
|
||||
|
||||
public:
|
||||
void init() override;
|
||||
|
||||
LocalBinaryCacheStore(
|
||||
const Params & params, const Path & binaryCacheDir)
|
||||
: BinaryCacheStore(params)
|
||||
, binaryCacheDir(binaryCacheDir)
|
||||
{
|
||||
}
|
||||
|
||||
void init() override;
|
||||
|
||||
std::string getUri() override
|
||||
{
|
||||
return "file://" + binaryCacheDir;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
bool fileExists(const std::string & path) override;
|
||||
|
||||
void upsertFile(const std::string & path,
|
||||
const std::string & data,
|
||||
const std::string & mimeType) override;
|
||||
|
||||
void getFile(const std::string & path, Sink & sink) override
|
||||
{
|
||||
try {
|
||||
readFile(binaryCacheDir + "/" + path, sink);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == ENOENT)
|
||||
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache", path);
|
||||
}
|
||||
}
|
||||
|
||||
PathSet queryAllValidPaths() override
|
||||
{
|
||||
PathSet paths;
|
||||
|
||||
for (auto & entry : readDirectory(binaryCacheDir)) {
|
||||
if (entry.name.size() != 40 ||
|
||||
!hasSuffix(entry.name, ".narinfo"))
|
||||
continue;
|
||||
paths.insert(storeDir + "/" + entry.name.substr(0, entry.name.size() - 8));
|
||||
}
|
||||
|
||||
return paths;
|
||||
std::string getUri() override { return "file://" + binaryCacheDir; }
|
||||
|
||||
protected:
|
||||
bool fileExists(const std::string& path) override;
|
||||
|
||||
void upsertFile(const std::string& path, const std::string& data,
|
||||
const std::string& mimeType) override;
|
||||
|
||||
void getFile(const std::string& path, Sink& sink) override {
|
||||
try {
|
||||
readFile(binaryCacheDir + "/" + path, sink);
|
||||
} catch (SysError& e) {
|
||||
if (e.errNo == ENOENT)
|
||||
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache",
|
||||
path);
|
||||
}
|
||||
}
|
||||
|
||||
PathSet queryAllValidPaths() override {
|
||||
PathSet paths;
|
||||
|
||||
for (auto& entry : readDirectory(binaryCacheDir)) {
|
||||
if (entry.name.size() != 40 || !hasSuffix(entry.name, ".narinfo"))
|
||||
continue;
|
||||
paths.insert(storeDir + "/" +
|
||||
entry.name.substr(0, entry.name.size() - 8));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
};
|
||||
|
||||
void LocalBinaryCacheStore::init()
|
||||
{
|
||||
createDirs(binaryCacheDir + "/nar");
|
||||
BinaryCacheStore::init();
|
||||
void LocalBinaryCacheStore::init() {
|
||||
createDirs(binaryCacheDir + "/nar");
|
||||
BinaryCacheStore::init();
|
||||
}
|
||||
|
||||
static void atomicWrite(const Path & path, const std::string & s)
|
||||
{
|
||||
Path tmp = path + ".tmp." + std::to_string(getpid());
|
||||
AutoDelete del(tmp, false);
|
||||
writeFile(tmp, s);
|
||||
if (rename(tmp.c_str(), path.c_str()))
|
||||
throw SysError(format("renaming '%1%' to '%2%'") % tmp % path);
|
||||
del.cancel();
|
||||
static void atomicWrite(const Path& path, const std::string& s) {
|
||||
Path tmp = path + ".tmp." + std::to_string(getpid());
|
||||
AutoDelete del(tmp, false);
|
||||
writeFile(tmp, s);
|
||||
if (rename(tmp.c_str(), path.c_str()))
|
||||
throw SysError(format("renaming '%1%' to '%2%'") % tmp % path);
|
||||
del.cancel();
|
||||
}
|
||||
|
||||
bool LocalBinaryCacheStore::fileExists(const std::string & path)
|
||||
{
|
||||
return pathExists(binaryCacheDir + "/" + path);
|
||||
bool LocalBinaryCacheStore::fileExists(const std::string& path) {
|
||||
return pathExists(binaryCacheDir + "/" + path);
|
||||
}
|
||||
|
||||
void LocalBinaryCacheStore::upsertFile(const std::string & path,
|
||||
const std::string & data,
|
||||
const std::string & mimeType)
|
||||
{
|
||||
atomicWrite(binaryCacheDir + "/" + path, data);
|
||||
void LocalBinaryCacheStore::upsertFile(const std::string& path,
|
||||
const std::string& data,
|
||||
const std::string& mimeType) {
|
||||
atomicWrite(binaryCacheDir + "/" + path, data);
|
||||
}
|
||||
|
||||
static RegisterStoreImplementation regStore([](
|
||||
const std::string & uri, const Store::Params & params)
|
||||
-> std::shared_ptr<Store>
|
||||
{
|
||||
if (getEnv("_NIX_FORCE_HTTP_BINARY_CACHE_STORE") == "1" ||
|
||||
std::string(uri, 0, 7) != "file://")
|
||||
static RegisterStoreImplementation regStore(
|
||||
[](const std::string& uri,
|
||||
const Store::Params& params) -> std::shared_ptr<Store> {
|
||||
if (getEnv("_NIX_FORCE_HTTP_BINARY_CACHE_STORE") == "1" ||
|
||||
std::string(uri, 0, 7) != "file://")
|
||||
return 0;
|
||||
auto store = std::make_shared<LocalBinaryCacheStore>(params, std::string(uri, 7));
|
||||
store->init();
|
||||
return store;
|
||||
});
|
||||
auto store =
|
||||
std::make_shared<LocalBinaryCacheStore>(params, std::string(uri, 7));
|
||||
store->init();
|
||||
return store;
|
||||
});
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
168
third_party/nix/src/libstore/local-fs-store.cc
vendored
168
third_party/nix/src/libstore/local-fs-store.cc
vendored
|
|
@ -1,131 +1,113 @@
|
|||
#include "archive.hh"
|
||||
#include "fs-accessor.hh"
|
||||
#include "store-api.hh"
|
||||
#include "globals.hh"
|
||||
#include "compression.hh"
|
||||
#include "derivations.hh"
|
||||
#include "fs-accessor.hh"
|
||||
#include "globals.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
LocalFSStore::LocalFSStore(const Params & params)
|
||||
: Store(params)
|
||||
{
|
||||
}
|
||||
LocalFSStore::LocalFSStore(const Params& params) : Store(params) {}
|
||||
|
||||
struct LocalStoreAccessor : public FSAccessor
|
||||
{
|
||||
ref<LocalFSStore> store;
|
||||
struct LocalStoreAccessor : public FSAccessor {
|
||||
ref<LocalFSStore> store;
|
||||
|
||||
LocalStoreAccessor(ref<LocalFSStore> store) : store(store) { }
|
||||
LocalStoreAccessor(ref<LocalFSStore> store) : store(store) {}
|
||||
|
||||
Path toRealPath(const Path & path)
|
||||
{
|
||||
Path storePath = store->toStorePath(path);
|
||||
if (!store->isValidPath(storePath))
|
||||
throw InvalidPath(format("path '%1%' is not a valid store path") % storePath);
|
||||
return store->getRealStoreDir() + std::string(path, store->storeDir.size());
|
||||
Path toRealPath(const Path& path) {
|
||||
Path storePath = store->toStorePath(path);
|
||||
if (!store->isValidPath(storePath))
|
||||
throw InvalidPath(format("path '%1%' is not a valid store path") %
|
||||
storePath);
|
||||
return store->getRealStoreDir() + std::string(path, store->storeDir.size());
|
||||
}
|
||||
|
||||
FSAccessor::Stat stat(const Path& path) override {
|
||||
auto realPath = toRealPath(path);
|
||||
|
||||
struct stat st;
|
||||
if (lstat(realPath.c_str(), &st)) {
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
return {Type::tMissing, 0, false};
|
||||
throw SysError(format("getting status of '%1%'") % path);
|
||||
}
|
||||
|
||||
FSAccessor::Stat stat(const Path & path) override
|
||||
{
|
||||
auto realPath = toRealPath(path);
|
||||
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode))
|
||||
throw Error(format("file '%1%' has unsupported type") % path);
|
||||
|
||||
struct stat st;
|
||||
if (lstat(realPath.c_str(), &st)) {
|
||||
if (errno == ENOENT || errno == ENOTDIR) return {Type::tMissing, 0, false};
|
||||
throw SysError(format("getting status of '%1%'") % path);
|
||||
}
|
||||
|
||||
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode))
|
||||
throw Error(format("file '%1%' has unsupported type") % path);
|
||||
|
||||
return {
|
||||
S_ISREG(st.st_mode) ? Type::tRegular :
|
||||
S_ISLNK(st.st_mode) ? Type::tSymlink :
|
||||
Type::tDirectory,
|
||||
S_ISREG(st.st_mode) ? (uint64_t) st.st_size : 0,
|
||||
return {S_ISREG(st.st_mode)
|
||||
? Type::tRegular
|
||||
: S_ISLNK(st.st_mode) ? Type::tSymlink : Type::tDirectory,
|
||||
S_ISREG(st.st_mode) ? (uint64_t)st.st_size : 0,
|
||||
S_ISREG(st.st_mode) && st.st_mode & S_IXUSR};
|
||||
}
|
||||
}
|
||||
|
||||
StringSet readDirectory(const Path & path) override
|
||||
{
|
||||
auto realPath = toRealPath(path);
|
||||
StringSet readDirectory(const Path& path) override {
|
||||
auto realPath = toRealPath(path);
|
||||
|
||||
auto entries = nix::readDirectory(realPath);
|
||||
auto entries = nix::readDirectory(realPath);
|
||||
|
||||
StringSet res;
|
||||
for (auto & entry : entries)
|
||||
res.insert(entry.name);
|
||||
StringSet res;
|
||||
for (auto& entry : entries) res.insert(entry.name);
|
||||
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string readFile(const Path & path) override
|
||||
{
|
||||
return nix::readFile(toRealPath(path));
|
||||
}
|
||||
std::string readFile(const Path& path) override {
|
||||
return nix::readFile(toRealPath(path));
|
||||
}
|
||||
|
||||
std::string readLink(const Path & path) override
|
||||
{
|
||||
return nix::readLink(toRealPath(path));
|
||||
}
|
||||
std::string readLink(const Path& path) override {
|
||||
return nix::readLink(toRealPath(path));
|
||||
}
|
||||
};
|
||||
|
||||
ref<FSAccessor> LocalFSStore::getFSAccessor()
|
||||
{
|
||||
return make_ref<LocalStoreAccessor>(ref<LocalFSStore>(
|
||||
std::dynamic_pointer_cast<LocalFSStore>(shared_from_this())));
|
||||
ref<FSAccessor> LocalFSStore::getFSAccessor() {
|
||||
return make_ref<LocalStoreAccessor>(ref<LocalFSStore>(
|
||||
std::dynamic_pointer_cast<LocalFSStore>(shared_from_this())));
|
||||
}
|
||||
|
||||
void LocalFSStore::narFromPath(const Path & path, Sink & sink)
|
||||
{
|
||||
if (!isValidPath(path))
|
||||
throw Error(format("path '%s' is not valid") % path);
|
||||
dumpPath(getRealStoreDir() + std::string(path, storeDir.size()), sink);
|
||||
void LocalFSStore::narFromPath(const Path& path, Sink& sink) {
|
||||
if (!isValidPath(path)) throw Error(format("path '%s' is not valid") % path);
|
||||
dumpPath(getRealStoreDir() + std::string(path, storeDir.size()), sink);
|
||||
}
|
||||
|
||||
const string LocalFSStore::drvsLogDir = "drvs";
|
||||
|
||||
std::shared_ptr<std::string> LocalFSStore::getBuildLog(const Path& path_) {
|
||||
auto path(path_);
|
||||
|
||||
assertStorePath(path);
|
||||
|
||||
std::shared_ptr<std::string> LocalFSStore::getBuildLog(const Path & path_)
|
||||
{
|
||||
auto path(path_);
|
||||
|
||||
assertStorePath(path);
|
||||
|
||||
|
||||
if (!isDerivation(path)) {
|
||||
try {
|
||||
path = queryPathInfo(path)->deriver;
|
||||
} catch (InvalidPath &) {
|
||||
return nullptr;
|
||||
}
|
||||
if (path == "") return nullptr;
|
||||
if (!isDerivation(path)) {
|
||||
try {
|
||||
path = queryPathInfo(path)->deriver;
|
||||
} catch (InvalidPath&) {
|
||||
return nullptr;
|
||||
}
|
||||
if (path == "") return nullptr;
|
||||
}
|
||||
|
||||
string baseName = baseNameOf(path);
|
||||
string baseName = baseNameOf(path);
|
||||
|
||||
for (int j = 0; j < 2; j++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
Path logPath = j == 0 ? fmt("%s/%s/%s/%s", logDir, drvsLogDir,
|
||||
string(baseName, 0, 2), string(baseName, 2))
|
||||
: fmt("%s/%s/%s", logDir, drvsLogDir, baseName);
|
||||
Path logBz2Path = logPath + ".bz2";
|
||||
|
||||
Path logPath =
|
||||
j == 0
|
||||
? fmt("%s/%s/%s/%s", logDir, drvsLogDir, string(baseName, 0, 2), string(baseName, 2))
|
||||
: fmt("%s/%s/%s", logDir, drvsLogDir, baseName);
|
||||
Path logBz2Path = logPath + ".bz2";
|
||||
|
||||
if (pathExists(logPath))
|
||||
return std::make_shared<std::string>(readFile(logPath));
|
||||
|
||||
else if (pathExists(logBz2Path)) {
|
||||
try {
|
||||
return decompress("bzip2", readFile(logBz2Path));
|
||||
} catch (Error &) { }
|
||||
}
|
||||
if (pathExists(logPath))
|
||||
return std::make_shared<std::string>(readFile(logPath));
|
||||
|
||||
else if (pathExists(logBz2Path)) {
|
||||
try {
|
||||
return decompress("bzip2", readFile(logBz2Path));
|
||||
} catch (Error&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
2219
third_party/nix/src/libstore/local-store.cc
vendored
2219
third_party/nix/src/libstore/local-store.cc
vendored
File diff suppressed because it is too large
Load diff
393
third_party/nix/src/libstore/local-store.hh
vendored
393
third_party/nix/src/libstore/local-store.hh
vendored
|
|
@ -1,309 +1,295 @@
|
|||
#pragma once
|
||||
|
||||
#include "sqlite.hh"
|
||||
|
||||
#include "pathlocks.hh"
|
||||
#include "store-api.hh"
|
||||
#include "sync.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "pathlocks.hh"
|
||||
#include "sqlite.hh"
|
||||
#include "store-api.hh"
|
||||
#include "sync.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Nix store and database schema version. Version 1 (or 0) was Nix <=
|
||||
0.7. Version 2 was Nix 0.8 and 0.9. Version 3 is Nix 0.10.
|
||||
Version 4 is Nix 0.11. Version 5 is Nix 0.12-0.16. Version 6 is
|
||||
Nix 1.0. Version 7 is Nix 1.3. Version 10 is 2.0. */
|
||||
const int nixSchemaVersion = 10;
|
||||
|
||||
|
||||
struct Derivation;
|
||||
|
||||
|
||||
struct OptimiseStats
|
||||
{
|
||||
unsigned long filesLinked = 0;
|
||||
unsigned long long bytesFreed = 0;
|
||||
unsigned long long blocksFreed = 0;
|
||||
struct OptimiseStats {
|
||||
unsigned long filesLinked = 0;
|
||||
unsigned long long bytesFreed = 0;
|
||||
unsigned long long blocksFreed = 0;
|
||||
};
|
||||
|
||||
class LocalStore : public LocalFSStore {
|
||||
private:
|
||||
/* Lock file used for upgrading. */
|
||||
AutoCloseFD globalLock;
|
||||
|
||||
class LocalStore : public LocalFSStore
|
||||
{
|
||||
private:
|
||||
struct State {
|
||||
/* The SQLite database object. */
|
||||
SQLite db;
|
||||
|
||||
/* Lock file used for upgrading. */
|
||||
AutoCloseFD globalLock;
|
||||
/* Some precompiled SQLite statements. */
|
||||
SQLiteStmt stmtRegisterValidPath;
|
||||
SQLiteStmt stmtUpdatePathInfo;
|
||||
SQLiteStmt stmtAddReference;
|
||||
SQLiteStmt stmtQueryPathInfo;
|
||||
SQLiteStmt stmtQueryReferences;
|
||||
SQLiteStmt stmtQueryReferrers;
|
||||
SQLiteStmt stmtInvalidatePath;
|
||||
SQLiteStmt stmtAddDerivationOutput;
|
||||
SQLiteStmt stmtQueryValidDerivers;
|
||||
SQLiteStmt stmtQueryDerivationOutputs;
|
||||
SQLiteStmt stmtQueryPathFromHashPart;
|
||||
SQLiteStmt stmtQueryValidPaths;
|
||||
|
||||
struct State
|
||||
{
|
||||
/* The SQLite database object. */
|
||||
SQLite db;
|
||||
/* The file to which we write our temporary roots. */
|
||||
AutoCloseFD fdTempRoots;
|
||||
|
||||
/* Some precompiled SQLite statements. */
|
||||
SQLiteStmt stmtRegisterValidPath;
|
||||
SQLiteStmt stmtUpdatePathInfo;
|
||||
SQLiteStmt stmtAddReference;
|
||||
SQLiteStmt stmtQueryPathInfo;
|
||||
SQLiteStmt stmtQueryReferences;
|
||||
SQLiteStmt stmtQueryReferrers;
|
||||
SQLiteStmt stmtInvalidatePath;
|
||||
SQLiteStmt stmtAddDerivationOutput;
|
||||
SQLiteStmt stmtQueryValidDerivers;
|
||||
SQLiteStmt stmtQueryDerivationOutputs;
|
||||
SQLiteStmt stmtQueryPathFromHashPart;
|
||||
SQLiteStmt stmtQueryValidPaths;
|
||||
/* The last time we checked whether to do an auto-GC, or an
|
||||
auto-GC finished. */
|
||||
std::chrono::time_point<std::chrono::steady_clock> lastGCCheck;
|
||||
|
||||
/* The file to which we write our temporary roots. */
|
||||
AutoCloseFD fdTempRoots;
|
||||
/* Whether auto-GC is running. If so, get gcFuture to wait for
|
||||
the GC to finish. */
|
||||
bool gcRunning = false;
|
||||
std::shared_future<void> gcFuture;
|
||||
|
||||
/* The last time we checked whether to do an auto-GC, or an
|
||||
auto-GC finished. */
|
||||
std::chrono::time_point<std::chrono::steady_clock> lastGCCheck;
|
||||
/* How much disk space was available after the previous
|
||||
auto-GC. If the current available disk space is below
|
||||
minFree but not much below availAfterGC, then there is no
|
||||
point in starting a new GC. */
|
||||
uint64_t availAfterGC = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
/* Whether auto-GC is running. If so, get gcFuture to wait for
|
||||
the GC to finish. */
|
||||
bool gcRunning = false;
|
||||
std::shared_future<void> gcFuture;
|
||||
std::unique_ptr<PublicKeys> publicKeys;
|
||||
};
|
||||
|
||||
/* How much disk space was available after the previous
|
||||
auto-GC. If the current available disk space is below
|
||||
minFree but not much below availAfterGC, then there is no
|
||||
point in starting a new GC. */
|
||||
uint64_t availAfterGC = std::numeric_limits<uint64_t>::max();
|
||||
Sync<State, std::recursive_mutex> _state;
|
||||
|
||||
std::unique_ptr<PublicKeys> publicKeys;
|
||||
};
|
||||
public:
|
||||
PathSetting realStoreDir_;
|
||||
|
||||
Sync<State, std::recursive_mutex> _state;
|
||||
const Path realStoreDir;
|
||||
const Path dbDir;
|
||||
const Path linksDir;
|
||||
const Path reservedPath;
|
||||
const Path schemaPath;
|
||||
const Path trashDir;
|
||||
const Path tempRootsDir;
|
||||
const Path fnTempRoots;
|
||||
|
||||
public:
|
||||
private:
|
||||
Setting<bool> requireSigs{
|
||||
(Store*)this, settings.requireSigs, "require-sigs",
|
||||
"whether store paths should have a trusted signature on import"};
|
||||
|
||||
PathSetting realStoreDir_;
|
||||
const PublicKeys& getPublicKeys();
|
||||
|
||||
const Path realStoreDir;
|
||||
const Path dbDir;
|
||||
const Path linksDir;
|
||||
const Path reservedPath;
|
||||
const Path schemaPath;
|
||||
const Path trashDir;
|
||||
const Path tempRootsDir;
|
||||
const Path fnTempRoots;
|
||||
public:
|
||||
// Hack for build-remote.cc.
|
||||
PathSet locksHeld = tokenizeString<PathSet>(getEnv("NIX_HELD_LOCKS"));
|
||||
|
||||
private:
|
||||
/* Initialise the local store, upgrading the schema if
|
||||
necessary. */
|
||||
LocalStore(const Params& params);
|
||||
|
||||
Setting<bool> requireSigs{(Store*) this,
|
||||
settings.requireSigs,
|
||||
"require-sigs", "whether store paths should have a trusted signature on import"};
|
||||
~LocalStore();
|
||||
|
||||
const PublicKeys & getPublicKeys();
|
||||
/* Implementations of abstract store API methods. */
|
||||
|
||||
public:
|
||||
std::string getUri() override;
|
||||
|
||||
// Hack for build-remote.cc.
|
||||
PathSet locksHeld = tokenizeString<PathSet>(getEnv("NIX_HELD_LOCKS"));
|
||||
bool isValidPathUncached(const Path& path) override;
|
||||
|
||||
/* Initialise the local store, upgrading the schema if
|
||||
necessary. */
|
||||
LocalStore(const Params & params);
|
||||
PathSet queryValidPaths(const PathSet& paths, SubstituteFlag maybeSubstitute =
|
||||
NoSubstitute) override;
|
||||
|
||||
~LocalStore();
|
||||
PathSet queryAllValidPaths() override;
|
||||
|
||||
/* Implementations of abstract store API methods. */
|
||||
void queryPathInfoUncached(
|
||||
const Path& path,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override;
|
||||
|
||||
std::string getUri() override;
|
||||
void queryReferrers(const Path& path, PathSet& referrers) override;
|
||||
|
||||
bool isValidPathUncached(const Path & path) override;
|
||||
PathSet queryValidDerivers(const Path& path) override;
|
||||
|
||||
PathSet queryValidPaths(const PathSet & paths,
|
||||
SubstituteFlag maybeSubstitute = NoSubstitute) override;
|
||||
PathSet queryDerivationOutputs(const Path& path) override;
|
||||
|
||||
PathSet queryAllValidPaths() override;
|
||||
StringSet queryDerivationOutputNames(const Path& path) override;
|
||||
|
||||
void queryPathInfoUncached(const Path & path,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override;
|
||||
Path queryPathFromHashPart(const string& hashPart) override;
|
||||
|
||||
void queryReferrers(const Path & path, PathSet & referrers) override;
|
||||
PathSet querySubstitutablePaths(const PathSet& paths) override;
|
||||
|
||||
PathSet queryValidDerivers(const Path & path) override;
|
||||
void querySubstitutablePathInfos(const PathSet& paths,
|
||||
SubstitutablePathInfos& infos) override;
|
||||
|
||||
PathSet queryDerivationOutputs(const Path & path) override;
|
||||
void addToStore(const ValidPathInfo& info, Source& source, RepairFlag repair,
|
||||
CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override;
|
||||
|
||||
StringSet queryDerivationOutputNames(const Path & path) override;
|
||||
Path addToStore(const string& name, const Path& srcPath, bool recursive,
|
||||
HashType hashAlgo, PathFilter& filter,
|
||||
RepairFlag repair) override;
|
||||
|
||||
Path queryPathFromHashPart(const string & hashPart) override;
|
||||
/* Like addToStore(), but the contents of the path are contained
|
||||
in `dump', which is either a NAR serialisation (if recursive ==
|
||||
true) or simply the contents of a regular file (if recursive ==
|
||||
false). */
|
||||
Path addToStoreFromDump(const string& dump, const string& name,
|
||||
bool recursive = true, HashType hashAlgo = htSHA256,
|
||||
RepairFlag repair = NoRepair);
|
||||
|
||||
PathSet querySubstitutablePaths(const PathSet & paths) override;
|
||||
Path addTextToStore(const string& name, const string& s,
|
||||
const PathSet& references, RepairFlag repair) override;
|
||||
|
||||
void querySubstitutablePathInfos(const PathSet & paths,
|
||||
SubstitutablePathInfos & infos) override;
|
||||
void buildPaths(const PathSet& paths, BuildMode buildMode) override;
|
||||
|
||||
void addToStore(const ValidPathInfo & info, Source & source,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override;
|
||||
BuildResult buildDerivation(const Path& drvPath, const BasicDerivation& drv,
|
||||
BuildMode buildMode) override;
|
||||
|
||||
Path addToStore(const string & name, const Path & srcPath,
|
||||
bool recursive, HashType hashAlgo,
|
||||
PathFilter & filter, RepairFlag repair) override;
|
||||
void ensurePath(const Path& path) override;
|
||||
|
||||
/* Like addToStore(), but the contents of the path are contained
|
||||
in `dump', which is either a NAR serialisation (if recursive ==
|
||||
true) or simply the contents of a regular file (if recursive ==
|
||||
false). */
|
||||
Path addToStoreFromDump(const string & dump, const string & name,
|
||||
bool recursive = true, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair);
|
||||
void addTempRoot(const Path& path) override;
|
||||
|
||||
Path addTextToStore(const string & name, const string & s,
|
||||
const PathSet & references, RepairFlag repair) override;
|
||||
void addIndirectRoot(const Path& path) override;
|
||||
|
||||
void buildPaths(const PathSet & paths, BuildMode buildMode) override;
|
||||
void syncWithGC() override;
|
||||
|
||||
BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode) override;
|
||||
private:
|
||||
typedef std::shared_ptr<AutoCloseFD> FDPtr;
|
||||
typedef list<FDPtr> FDs;
|
||||
|
||||
void ensurePath(const Path & path) override;
|
||||
void findTempRoots(FDs& fds, Roots& roots, bool censor);
|
||||
|
||||
void addTempRoot(const Path & path) override;
|
||||
public:
|
||||
Roots findRoots(bool censor) override;
|
||||
|
||||
void addIndirectRoot(const Path & path) override;
|
||||
void collectGarbage(const GCOptions& options, GCResults& results) override;
|
||||
|
||||
void syncWithGC() override;
|
||||
/* Optimise the disk space usage of the Nix store by hard-linking
|
||||
files with the same contents. */
|
||||
void optimiseStore(OptimiseStats& stats);
|
||||
|
||||
private:
|
||||
void optimiseStore() override;
|
||||
|
||||
typedef std::shared_ptr<AutoCloseFD> FDPtr;
|
||||
typedef list<FDPtr> FDs;
|
||||
/* Optimise a single store path. */
|
||||
void optimisePath(const Path& path);
|
||||
|
||||
void findTempRoots(FDs & fds, Roots & roots, bool censor);
|
||||
bool verifyStore(bool checkContents, RepairFlag repair) override;
|
||||
|
||||
public:
|
||||
/* Register the validity of a path, i.e., that `path' exists, that
|
||||
the paths referenced by it exists, and in the case of an output
|
||||
path of a derivation, that it has been produced by a successful
|
||||
execution of the derivation (or something equivalent). Also
|
||||
register the hash of the file system contents of the path. The
|
||||
hash must be a SHA-256 hash. */
|
||||
void registerValidPath(const ValidPathInfo& info);
|
||||
|
||||
Roots findRoots(bool censor) override;
|
||||
void registerValidPaths(const ValidPathInfos& infos);
|
||||
|
||||
void collectGarbage(const GCOptions & options, GCResults & results) override;
|
||||
unsigned int getProtocol() override;
|
||||
|
||||
/* Optimise the disk space usage of the Nix store by hard-linking
|
||||
files with the same contents. */
|
||||
void optimiseStore(OptimiseStats & stats);
|
||||
void vacuumDB();
|
||||
|
||||
void optimiseStore() override;
|
||||
/* Repair the contents of the given path by redownloading it using
|
||||
a substituter (if available). */
|
||||
void repairPath(const Path& path);
|
||||
|
||||
/* Optimise a single store path. */
|
||||
void optimisePath(const Path & path);
|
||||
void addSignatures(const Path& storePath, const StringSet& sigs) override;
|
||||
|
||||
bool verifyStore(bool checkContents, RepairFlag repair) override;
|
||||
/* If free disk space in /nix/store if below minFree, delete
|
||||
garbage until it exceeds maxFree. */
|
||||
void autoGC(bool sync = true);
|
||||
|
||||
/* Register the validity of a path, i.e., that `path' exists, that
|
||||
the paths referenced by it exists, and in the case of an output
|
||||
path of a derivation, that it has been produced by a successful
|
||||
execution of the derivation (or something equivalent). Also
|
||||
register the hash of the file system contents of the path. The
|
||||
hash must be a SHA-256 hash. */
|
||||
void registerValidPath(const ValidPathInfo & info);
|
||||
private:
|
||||
int getSchema();
|
||||
|
||||
void registerValidPaths(const ValidPathInfos & infos);
|
||||
void openDB(State& state, bool create);
|
||||
|
||||
unsigned int getProtocol() override;
|
||||
void makeStoreWritable();
|
||||
|
||||
void vacuumDB();
|
||||
uint64_t queryValidPathId(State& state, const Path& path);
|
||||
|
||||
/* Repair the contents of the given path by redownloading it using
|
||||
a substituter (if available). */
|
||||
void repairPath(const Path & path);
|
||||
uint64_t addValidPath(State& state, const ValidPathInfo& info,
|
||||
bool checkOutputs = true);
|
||||
|
||||
void addSignatures(const Path & storePath, const StringSet & sigs) override;
|
||||
void invalidatePath(State& state, const Path& path);
|
||||
|
||||
/* If free disk space in /nix/store if below minFree, delete
|
||||
garbage until it exceeds maxFree. */
|
||||
void autoGC(bool sync = true);
|
||||
/* Delete a path from the Nix store. */
|
||||
void invalidatePathChecked(const Path& path);
|
||||
|
||||
private:
|
||||
void verifyPath(const Path& path, const PathSet& store, PathSet& done,
|
||||
PathSet& validPaths, RepairFlag repair, bool& errors);
|
||||
|
||||
int getSchema();
|
||||
void updatePathInfo(State& state, const ValidPathInfo& info);
|
||||
|
||||
void openDB(State & state, bool create);
|
||||
void upgradeStore6();
|
||||
void upgradeStore7();
|
||||
PathSet queryValidPathsOld();
|
||||
ValidPathInfo queryPathInfoOld(const Path& path);
|
||||
|
||||
void makeStoreWritable();
|
||||
struct GCState;
|
||||
|
||||
uint64_t queryValidPathId(State & state, const Path & path);
|
||||
void deleteGarbage(GCState& state, const Path& path);
|
||||
|
||||
uint64_t addValidPath(State & state, const ValidPathInfo & info, bool checkOutputs = true);
|
||||
void tryToDelete(GCState& state, const Path& path);
|
||||
|
||||
void invalidatePath(State & state, const Path & path);
|
||||
bool canReachRoot(GCState& state, PathSet& visited, const Path& path);
|
||||
|
||||
/* Delete a path from the Nix store. */
|
||||
void invalidatePathChecked(const Path & path);
|
||||
void deletePathRecursive(GCState& state, const Path& path);
|
||||
|
||||
void verifyPath(const Path & path, const PathSet & store,
|
||||
PathSet & done, PathSet & validPaths, RepairFlag repair, bool & errors);
|
||||
bool isActiveTempFile(const GCState& state, const Path& path,
|
||||
const string& suffix);
|
||||
|
||||
void updatePathInfo(State & state, const ValidPathInfo & info);
|
||||
AutoCloseFD openGCLock(LockType lockType);
|
||||
|
||||
void upgradeStore6();
|
||||
void upgradeStore7();
|
||||
PathSet queryValidPathsOld();
|
||||
ValidPathInfo queryPathInfoOld(const Path & path);
|
||||
void findRoots(const Path& path, unsigned char type, Roots& roots);
|
||||
|
||||
struct GCState;
|
||||
void findRootsNoTemp(Roots& roots, bool censor);
|
||||
|
||||
void deleteGarbage(GCState & state, const Path & path);
|
||||
void findRuntimeRoots(Roots& roots, bool censor);
|
||||
|
||||
void tryToDelete(GCState & state, const Path & path);
|
||||
void removeUnusedLinks(const GCState& state);
|
||||
|
||||
bool canReachRoot(GCState & state, PathSet & visited, const Path & path);
|
||||
Path createTempDirInStore();
|
||||
|
||||
void deletePathRecursive(GCState & state, const Path & path);
|
||||
void checkDerivationOutputs(const Path& drvPath, const Derivation& drv);
|
||||
|
||||
bool isActiveTempFile(const GCState & state,
|
||||
const Path & path, const string & suffix);
|
||||
typedef std::unordered_set<ino_t> InodeHash;
|
||||
|
||||
AutoCloseFD openGCLock(LockType lockType);
|
||||
InodeHash loadInodeHash();
|
||||
Strings readDirectoryIgnoringInodes(const Path& path,
|
||||
const InodeHash& inodeHash);
|
||||
void optimisePath_(Activity* act, OptimiseStats& stats, const Path& path,
|
||||
InodeHash& inodeHash);
|
||||
|
||||
void findRoots(const Path & path, unsigned char type, Roots & roots);
|
||||
// Internal versions that are not wrapped in retry_sqlite.
|
||||
bool isValidPath_(State& state, const Path& path);
|
||||
void queryReferrers(State& state, const Path& path, PathSet& referrers);
|
||||
|
||||
void findRootsNoTemp(Roots & roots, bool censor);
|
||||
/* Add signatures to a ValidPathInfo using the secret keys
|
||||
specified by the ‘secret-key-files’ option. */
|
||||
void signPathInfo(ValidPathInfo& info);
|
||||
|
||||
void findRuntimeRoots(Roots & roots, bool censor);
|
||||
Path getRealStoreDir() override { return realStoreDir; }
|
||||
|
||||
void removeUnusedLinks(const GCState & state);
|
||||
void createUser(const std::string& userName, uid_t userId) override;
|
||||
|
||||
Path createTempDirInStore();
|
||||
|
||||
void checkDerivationOutputs(const Path & drvPath, const Derivation & drv);
|
||||
|
||||
typedef std::unordered_set<ino_t> InodeHash;
|
||||
|
||||
InodeHash loadInodeHash();
|
||||
Strings readDirectoryIgnoringInodes(const Path & path, const InodeHash & inodeHash);
|
||||
void optimisePath_(Activity * act, OptimiseStats & stats, const Path & path, InodeHash & inodeHash);
|
||||
|
||||
// Internal versions that are not wrapped in retry_sqlite.
|
||||
bool isValidPath_(State & state, const Path & path);
|
||||
void queryReferrers(State & state, const Path & path, PathSet & referrers);
|
||||
|
||||
/* Add signatures to a ValidPathInfo using the secret keys
|
||||
specified by the ‘secret-key-files’ option. */
|
||||
void signPathInfo(ValidPathInfo & info);
|
||||
|
||||
Path getRealStoreDir() override { return realStoreDir; }
|
||||
|
||||
void createUser(const std::string & userName, uid_t userId) override;
|
||||
|
||||
friend class DerivationGoal;
|
||||
friend class SubstitutionGoal;
|
||||
friend class DerivationGoal;
|
||||
friend class SubstitutionGoal;
|
||||
};
|
||||
|
||||
|
||||
typedef std::pair<dev_t, ino_t> Inode;
|
||||
typedef set<Inode> InodesSeen;
|
||||
|
||||
|
||||
/* "Fix", or canonicalise, the meta-data of the files in a store path
|
||||
after it has been built. In particular:
|
||||
- the last modification date on each file is set to 1 (i.e.,
|
||||
|
|
@ -312,11 +298,12 @@ typedef set<Inode> InodesSeen;
|
|||
without execute permission; setuid bits etc. are cleared)
|
||||
- the owner and group are set to the Nix user and group, if we're
|
||||
running as root. */
|
||||
void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & inodesSeen);
|
||||
void canonicalisePathMetaData(const Path & path, uid_t fromUid);
|
||||
void canonicalisePathMetaData(const Path& path, uid_t fromUid,
|
||||
InodesSeen& inodesSeen);
|
||||
void canonicalisePathMetaData(const Path& path, uid_t fromUid);
|
||||
|
||||
void canonicaliseTimestampAndPermissions(const Path & path);
|
||||
void canonicaliseTimestampAndPermissions(const Path& path);
|
||||
|
||||
MakeError(PathInUse, Error);
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
159
third_party/nix/src/libstore/machines.cc
vendored
159
third_party/nix/src/libstore/machines.cc
vendored
|
|
@ -1,100 +1,93 @@
|
|||
#include "machines.hh"
|
||||
#include "util.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include "globals.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
Machine::Machine(decltype(storeUri) storeUri,
|
||||
decltype(systemTypes) systemTypes,
|
||||
decltype(sshKey) sshKey,
|
||||
decltype(maxJobs) maxJobs,
|
||||
decltype(speedFactor) speedFactor,
|
||||
decltype(supportedFeatures) supportedFeatures,
|
||||
decltype(mandatoryFeatures) mandatoryFeatures,
|
||||
decltype(sshPublicHostKey) sshPublicHostKey) :
|
||||
storeUri(
|
||||
// Backwards compatibility: if the URI is a hostname,
|
||||
// prepend ssh://.
|
||||
storeUri.find("://") != std::string::npos
|
||||
|| hasPrefix(storeUri, "local")
|
||||
|| hasPrefix(storeUri, "remote")
|
||||
|| hasPrefix(storeUri, "auto")
|
||||
|| hasPrefix(storeUri, "/")
|
||||
? storeUri
|
||||
: "ssh://" + storeUri),
|
||||
systemTypes(systemTypes),
|
||||
sshKey(sshKey),
|
||||
maxJobs(maxJobs),
|
||||
speedFactor(std::max(1U, speedFactor)),
|
||||
supportedFeatures(supportedFeatures),
|
||||
mandatoryFeatures(mandatoryFeatures),
|
||||
sshPublicHostKey(sshPublicHostKey)
|
||||
{}
|
||||
Machine::Machine(decltype(storeUri) storeUri, decltype(systemTypes) systemTypes,
|
||||
decltype(sshKey) sshKey, decltype(maxJobs) maxJobs,
|
||||
decltype(speedFactor) speedFactor,
|
||||
decltype(supportedFeatures) supportedFeatures,
|
||||
decltype(mandatoryFeatures) mandatoryFeatures,
|
||||
decltype(sshPublicHostKey) sshPublicHostKey)
|
||||
: storeUri(
|
||||
// Backwards compatibility: if the URI is a hostname,
|
||||
// prepend ssh://.
|
||||
storeUri.find("://") != std::string::npos ||
|
||||
hasPrefix(storeUri, "local") ||
|
||||
hasPrefix(storeUri, "remote") ||
|
||||
hasPrefix(storeUri, "auto") || hasPrefix(storeUri, "/")
|
||||
? storeUri
|
||||
: "ssh://" + storeUri),
|
||||
systemTypes(systemTypes),
|
||||
sshKey(sshKey),
|
||||
maxJobs(maxJobs),
|
||||
speedFactor(std::max(1U, speedFactor)),
|
||||
supportedFeatures(supportedFeatures),
|
||||
mandatoryFeatures(mandatoryFeatures),
|
||||
sshPublicHostKey(sshPublicHostKey) {}
|
||||
|
||||
bool Machine::allSupported(const std::set<string> & features) const {
|
||||
return std::all_of(features.begin(), features.end(),
|
||||
[&](const string & feature) {
|
||||
return supportedFeatures.count(feature) ||
|
||||
mandatoryFeatures.count(feature);
|
||||
});
|
||||
bool Machine::allSupported(const std::set<string>& features) const {
|
||||
return std::all_of(features.begin(), features.end(),
|
||||
[&](const string& feature) {
|
||||
return supportedFeatures.count(feature) ||
|
||||
mandatoryFeatures.count(feature);
|
||||
});
|
||||
}
|
||||
|
||||
bool Machine::mandatoryMet(const std::set<string> & features) const {
|
||||
return std::all_of(mandatoryFeatures.begin(), mandatoryFeatures.end(),
|
||||
[&](const string & feature) {
|
||||
return features.count(feature);
|
||||
});
|
||||
bool Machine::mandatoryMet(const std::set<string>& features) const {
|
||||
return std::all_of(
|
||||
mandatoryFeatures.begin(), mandatoryFeatures.end(),
|
||||
[&](const string& feature) { return features.count(feature); });
|
||||
}
|
||||
|
||||
void parseMachines(const std::string & s, Machines & machines)
|
||||
{
|
||||
for (auto line : tokenizeString<std::vector<string>>(s, "\n;")) {
|
||||
trim(line);
|
||||
line.erase(std::find(line.begin(), line.end(), '#'), line.end());
|
||||
if (line.empty()) continue;
|
||||
void parseMachines(const std::string& s, Machines& machines) {
|
||||
for (auto line : tokenizeString<std::vector<string>>(s, "\n;")) {
|
||||
trim(line);
|
||||
line.erase(std::find(line.begin(), line.end(), '#'), line.end());
|
||||
if (line.empty()) continue;
|
||||
|
||||
if (line[0] == '@') {
|
||||
auto file = trim(std::string(line, 1));
|
||||
try {
|
||||
parseMachines(readFile(file), machines);
|
||||
} catch (const SysError & e) {
|
||||
if (e.errNo != ENOENT)
|
||||
throw;
|
||||
debug("cannot find machines file '%s'", file);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto tokens = tokenizeString<std::vector<string>>(line);
|
||||
auto sz = tokens.size();
|
||||
if (sz < 1)
|
||||
throw FormatError("bad machine specification '%s'", line);
|
||||
|
||||
auto isSet = [&](size_t n) {
|
||||
return tokens.size() > n && tokens[n] != "" && tokens[n] != "-";
|
||||
};
|
||||
|
||||
machines.emplace_back(tokens[0],
|
||||
isSet(1) ? tokenizeString<std::vector<string>>(tokens[1], ",") : std::vector<string>{settings.thisSystem},
|
||||
isSet(2) ? tokens[2] : "",
|
||||
isSet(3) ? std::stoull(tokens[3]) : 1LL,
|
||||
isSet(4) ? std::stoull(tokens[4]) : 1LL,
|
||||
isSet(5) ? tokenizeString<std::set<string>>(tokens[5], ",") : std::set<string>{},
|
||||
isSet(6) ? tokenizeString<std::set<string>>(tokens[6], ",") : std::set<string>{},
|
||||
isSet(7) ? tokens[7] : "");
|
||||
if (line[0] == '@') {
|
||||
auto file = trim(std::string(line, 1));
|
||||
try {
|
||||
parseMachines(readFile(file), machines);
|
||||
} catch (const SysError& e) {
|
||||
if (e.errNo != ENOENT) throw;
|
||||
debug("cannot find machines file '%s'", file);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto tokens = tokenizeString<std::vector<string>>(line);
|
||||
auto sz = tokens.size();
|
||||
if (sz < 1) throw FormatError("bad machine specification '%s'", line);
|
||||
|
||||
auto isSet = [&](size_t n) {
|
||||
return tokens.size() > n && tokens[n] != "" && tokens[n] != "-";
|
||||
};
|
||||
|
||||
machines.emplace_back(
|
||||
tokens[0],
|
||||
isSet(1) ? tokenizeString<std::vector<string>>(tokens[1], ",")
|
||||
: std::vector<string>{settings.thisSystem},
|
||||
isSet(2) ? tokens[2] : "", isSet(3) ? std::stoull(tokens[3]) : 1LL,
|
||||
isSet(4) ? std::stoull(tokens[4]) : 1LL,
|
||||
isSet(5) ? tokenizeString<std::set<string>>(tokens[5], ",")
|
||||
: std::set<string>{},
|
||||
isSet(6) ? tokenizeString<std::set<string>>(tokens[6], ",")
|
||||
: std::set<string>{},
|
||||
isSet(7) ? tokens[7] : "");
|
||||
}
|
||||
}
|
||||
|
||||
Machines getMachines()
|
||||
{
|
||||
static auto machines = [&]() {
|
||||
Machines machines;
|
||||
parseMachines(settings.builders, machines);
|
||||
return machines;
|
||||
}();
|
||||
Machines getMachines() {
|
||||
static auto machines = [&]() {
|
||||
Machines machines;
|
||||
parseMachines(settings.builders, machines);
|
||||
return machines;
|
||||
}();
|
||||
return machines;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
41
third_party/nix/src/libstore/machines.hh
vendored
41
third_party/nix/src/libstore/machines.hh
vendored
|
|
@ -5,35 +5,32 @@
|
|||
namespace nix {
|
||||
|
||||
struct Machine {
|
||||
const string storeUri;
|
||||
const std::vector<string> systemTypes;
|
||||
const string sshKey;
|
||||
const unsigned int maxJobs;
|
||||
const unsigned int speedFactor;
|
||||
const std::set<string> supportedFeatures;
|
||||
const std::set<string> mandatoryFeatures;
|
||||
const std::string sshPublicHostKey;
|
||||
bool enabled = true;
|
||||
|
||||
const string storeUri;
|
||||
const std::vector<string> systemTypes;
|
||||
const string sshKey;
|
||||
const unsigned int maxJobs;
|
||||
const unsigned int speedFactor;
|
||||
const std::set<string> supportedFeatures;
|
||||
const std::set<string> mandatoryFeatures;
|
||||
const std::string sshPublicHostKey;
|
||||
bool enabled = true;
|
||||
bool allSupported(const std::set<string>& features) const;
|
||||
|
||||
bool allSupported(const std::set<string> & features) const;
|
||||
bool mandatoryMet(const std::set<string>& features) const;
|
||||
|
||||
bool mandatoryMet(const std::set<string> & features) const;
|
||||
|
||||
Machine(decltype(storeUri) storeUri,
|
||||
decltype(systemTypes) systemTypes,
|
||||
decltype(sshKey) sshKey,
|
||||
decltype(maxJobs) maxJobs,
|
||||
decltype(speedFactor) speedFactor,
|
||||
decltype(supportedFeatures) supportedFeatures,
|
||||
decltype(mandatoryFeatures) mandatoryFeatures,
|
||||
decltype(sshPublicHostKey) sshPublicHostKey);
|
||||
Machine(decltype(storeUri) storeUri, decltype(systemTypes) systemTypes,
|
||||
decltype(sshKey) sshKey, decltype(maxJobs) maxJobs,
|
||||
decltype(speedFactor) speedFactor,
|
||||
decltype(supportedFeatures) supportedFeatures,
|
||||
decltype(mandatoryFeatures) mandatoryFeatures,
|
||||
decltype(sshPublicHostKey) sshPublicHostKey);
|
||||
};
|
||||
|
||||
typedef std::vector<Machine> Machines;
|
||||
|
||||
void parseMachines(const std::string & s, Machines & machines);
|
||||
void parseMachines(const std::string& s, Machines& machines);
|
||||
|
||||
Machines getMachines();
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
470
third_party/nix/src/libstore/misc.cc
vendored
470
third_party/nix/src/libstore/misc.cc
vendored
|
|
@ -1,282 +1,266 @@
|
|||
#include "derivations.hh"
|
||||
#include "parsed-derivations.hh"
|
||||
#include "globals.hh"
|
||||
#include "local-store.hh"
|
||||
#include "parsed-derivations.hh"
|
||||
#include "store-api.hh"
|
||||
#include "thread-pool.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
void Store::computeFSClosure(const PathSet& startPaths, PathSet& paths_,
|
||||
bool flipDirection, bool includeOutputs,
|
||||
bool includeDerivers) {
|
||||
struct State {
|
||||
size_t pending;
|
||||
PathSet& paths;
|
||||
std::exception_ptr exc;
|
||||
};
|
||||
|
||||
void Store::computeFSClosure(const PathSet & startPaths,
|
||||
PathSet & paths_, bool flipDirection, bool includeOutputs, bool includeDerivers)
|
||||
{
|
||||
struct State
|
||||
Sync<State> state_(State{0, paths_, 0});
|
||||
|
||||
std::function<void(const Path&)> enqueue;
|
||||
|
||||
std::condition_variable done;
|
||||
|
||||
enqueue = [&](const Path& path) -> void {
|
||||
{
|
||||
size_t pending;
|
||||
PathSet & paths;
|
||||
std::exception_ptr exc;
|
||||
};
|
||||
|
||||
Sync<State> state_(State{0, paths_, 0});
|
||||
|
||||
std::function<void(const Path &)> enqueue;
|
||||
|
||||
std::condition_variable done;
|
||||
|
||||
enqueue = [&](const Path & path) -> void {
|
||||
{
|
||||
auto state(state_.lock());
|
||||
if (state->exc) return;
|
||||
if (state->paths.count(path)) return;
|
||||
state->paths.insert(path);
|
||||
state->pending++;
|
||||
}
|
||||
|
||||
queryPathInfo(path, {[&, path](std::future<ref<ValidPathInfo>> fut) {
|
||||
// FIXME: calls to isValidPath() should be async
|
||||
|
||||
try {
|
||||
auto info = fut.get();
|
||||
|
||||
if (flipDirection) {
|
||||
|
||||
PathSet referrers;
|
||||
queryReferrers(path, referrers);
|
||||
for (auto & ref : referrers)
|
||||
if (ref != path)
|
||||
enqueue(ref);
|
||||
|
||||
if (includeOutputs)
|
||||
for (auto & i : queryValidDerivers(path))
|
||||
enqueue(i);
|
||||
|
||||
if (includeDerivers && isDerivation(path))
|
||||
for (auto & i : queryDerivationOutputs(path))
|
||||
if (isValidPath(i) && queryPathInfo(i)->deriver == path)
|
||||
enqueue(i);
|
||||
|
||||
} else {
|
||||
|
||||
for (auto & ref : info->references)
|
||||
if (ref != path)
|
||||
enqueue(ref);
|
||||
|
||||
if (includeOutputs && isDerivation(path))
|
||||
for (auto & i : queryDerivationOutputs(path))
|
||||
if (isValidPath(i)) enqueue(i);
|
||||
|
||||
if (includeDerivers && isValidPath(info->deriver))
|
||||
enqueue(info->deriver);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
auto state(state_.lock());
|
||||
assert(state->pending);
|
||||
if (!--state->pending) done.notify_one();
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
auto state(state_.lock());
|
||||
if (!state->exc) state->exc = std::current_exception();
|
||||
assert(state->pending);
|
||||
if (!--state->pending) done.notify_one();
|
||||
};
|
||||
}});
|
||||
};
|
||||
|
||||
for (auto & startPath : startPaths)
|
||||
enqueue(startPath);
|
||||
|
||||
{
|
||||
auto state(state_.lock());
|
||||
while (state->pending) state.wait(done);
|
||||
if (state->exc) std::rethrow_exception(state->exc);
|
||||
auto state(state_.lock());
|
||||
if (state->exc) return;
|
||||
if (state->paths.count(path)) return;
|
||||
state->paths.insert(path);
|
||||
state->pending++;
|
||||
}
|
||||
}
|
||||
|
||||
queryPathInfo(
|
||||
path, {[&, path](std::future<ref<ValidPathInfo>> fut) {
|
||||
// FIXME: calls to isValidPath() should be async
|
||||
|
||||
void Store::computeFSClosure(const Path & startPath,
|
||||
PathSet & paths_, bool flipDirection, bool includeOutputs, bool includeDerivers)
|
||||
{
|
||||
computeFSClosure(PathSet{startPath}, paths_, flipDirection, includeOutputs, includeDerivers);
|
||||
}
|
||||
try {
|
||||
auto info = fut.get();
|
||||
|
||||
if (flipDirection) {
|
||||
PathSet referrers;
|
||||
queryReferrers(path, referrers);
|
||||
for (auto& ref : referrers)
|
||||
if (ref != path) enqueue(ref);
|
||||
|
||||
void Store::queryMissing(const PathSet & targets,
|
||||
PathSet & willBuild_, PathSet & willSubstitute_, PathSet & unknown_,
|
||||
unsigned long long & downloadSize_, unsigned long long & narSize_)
|
||||
{
|
||||
Activity act(*logger, lvlDebug, actUnknown, "querying info about missing paths");
|
||||
if (includeOutputs)
|
||||
for (auto& i : queryValidDerivers(path)) enqueue(i);
|
||||
|
||||
downloadSize_ = narSize_ = 0;
|
||||
if (includeDerivers && isDerivation(path))
|
||||
for (auto& i : queryDerivationOutputs(path))
|
||||
if (isValidPath(i) && queryPathInfo(i)->deriver == path)
|
||||
enqueue(i);
|
||||
|
||||
ThreadPool pool;
|
||||
} else {
|
||||
for (auto& ref : info->references)
|
||||
if (ref != path) enqueue(ref);
|
||||
|
||||
struct State
|
||||
{
|
||||
PathSet done;
|
||||
PathSet & unknown, & willSubstitute, & willBuild;
|
||||
unsigned long long & downloadSize;
|
||||
unsigned long long & narSize;
|
||||
};
|
||||
if (includeOutputs && isDerivation(path))
|
||||
for (auto& i : queryDerivationOutputs(path))
|
||||
if (isValidPath(i)) enqueue(i);
|
||||
|
||||
struct DrvState
|
||||
{
|
||||
size_t left;
|
||||
bool done = false;
|
||||
PathSet outPaths;
|
||||
DrvState(size_t left) : left(left) { }
|
||||
};
|
||||
|
||||
Sync<State> state_(State{PathSet(), unknown_, willSubstitute_, willBuild_, downloadSize_, narSize_});
|
||||
|
||||
std::function<void(Path)> doPath;
|
||||
|
||||
auto mustBuildDrv = [&](const Path & drvPath, const Derivation & drv) {
|
||||
{
|
||||
auto state(state_.lock());
|
||||
state->willBuild.insert(drvPath);
|
||||
}
|
||||
|
||||
for (auto & i : drv.inputDrvs)
|
||||
pool.enqueue(std::bind(doPath, makeDrvPathWithOutputs(i.first, i.second)));
|
||||
};
|
||||
|
||||
auto checkOutput = [&](
|
||||
const Path & drvPath, ref<Derivation> drv, const Path & outPath, ref<Sync<DrvState>> drvState_)
|
||||
{
|
||||
if (drvState_->lock()->done) return;
|
||||
|
||||
SubstitutablePathInfos infos;
|
||||
querySubstitutablePathInfos({outPath}, infos);
|
||||
|
||||
if (infos.empty()) {
|
||||
drvState_->lock()->done = true;
|
||||
mustBuildDrv(drvPath, *drv);
|
||||
} else {
|
||||
{
|
||||
auto drvState(drvState_->lock());
|
||||
if (drvState->done) return;
|
||||
assert(drvState->left);
|
||||
drvState->left--;
|
||||
drvState->outPaths.insert(outPath);
|
||||
if (!drvState->left) {
|
||||
for (auto & path : drvState->outPaths)
|
||||
pool.enqueue(std::bind(doPath, path));
|
||||
}
|
||||
if (includeDerivers && isValidPath(info->deriver))
|
||||
enqueue(info->deriver);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
doPath = [&](const Path & path) {
|
||||
|
||||
{
|
||||
auto state(state_.lock());
|
||||
if (state->done.count(path)) return;
|
||||
state->done.insert(path);
|
||||
}
|
||||
|
||||
DrvPathWithOutputs i2 = parseDrvPathWithOutputs(path);
|
||||
|
||||
if (isDerivation(i2.first)) {
|
||||
if (!isValidPath(i2.first)) {
|
||||
// FIXME: we could try to substitute the derivation.
|
||||
auto state(state_.lock());
|
||||
state->unknown.insert(path);
|
||||
return;
|
||||
}
|
||||
|
||||
Derivation drv = derivationFromPath(i2.first);
|
||||
ParsedDerivation parsedDrv(i2.first, drv);
|
||||
|
||||
PathSet invalid;
|
||||
for (auto & j : drv.outputs)
|
||||
if (wantOutput(j.first, i2.second)
|
||||
&& !isValidPath(j.second.path))
|
||||
invalid.insert(j.second.path);
|
||||
if (invalid.empty()) return;
|
||||
|
||||
if (settings.useSubstitutes && parsedDrv.substitutesAllowed()) {
|
||||
auto drvState = make_ref<Sync<DrvState>>(DrvState(invalid.size()));
|
||||
for (auto & output : invalid)
|
||||
pool.enqueue(std::bind(checkOutput, i2.first, make_ref<Derivation>(drv), output, drvState));
|
||||
} else
|
||||
mustBuildDrv(i2.first, drv);
|
||||
|
||||
} else {
|
||||
|
||||
if (isValidPath(path)) return;
|
||||
|
||||
SubstitutablePathInfos infos;
|
||||
querySubstitutablePathInfos({path}, infos);
|
||||
|
||||
if (infos.empty()) {
|
||||
auto state(state_.lock());
|
||||
state->unknown.insert(path);
|
||||
return;
|
||||
}
|
||||
|
||||
auto info = infos.find(path);
|
||||
assert(info != infos.end());
|
||||
|
||||
{
|
||||
auto state(state_.lock());
|
||||
state->willSubstitute.insert(path);
|
||||
state->downloadSize += info->second.downloadSize;
|
||||
state->narSize += info->second.narSize;
|
||||
auto state(state_.lock());
|
||||
assert(state->pending);
|
||||
if (!--state->pending) done.notify_one();
|
||||
}
|
||||
|
||||
for (auto & ref : info->second.references)
|
||||
pool.enqueue(std::bind(doPath, ref));
|
||||
} catch (...) {
|
||||
auto state(state_.lock());
|
||||
if (!state->exc) state->exc = std::current_exception();
|
||||
assert(state->pending);
|
||||
if (!--state->pending) done.notify_one();
|
||||
};
|
||||
}});
|
||||
};
|
||||
|
||||
for (auto& startPath : startPaths) enqueue(startPath);
|
||||
|
||||
{
|
||||
auto state(state_.lock());
|
||||
while (state->pending) state.wait(done);
|
||||
if (state->exc) std::rethrow_exception(state->exc);
|
||||
}
|
||||
}
|
||||
|
||||
void Store::computeFSClosure(const Path& startPath, PathSet& paths_,
|
||||
bool flipDirection, bool includeOutputs,
|
||||
bool includeDerivers) {
|
||||
computeFSClosure(PathSet{startPath}, paths_, flipDirection, includeOutputs,
|
||||
includeDerivers);
|
||||
}
|
||||
|
||||
void Store::queryMissing(const PathSet& targets, PathSet& willBuild_,
|
||||
PathSet& willSubstitute_, PathSet& unknown_,
|
||||
unsigned long long& downloadSize_,
|
||||
unsigned long long& narSize_) {
|
||||
Activity act(*logger, lvlDebug, actUnknown,
|
||||
"querying info about missing paths");
|
||||
|
||||
downloadSize_ = narSize_ = 0;
|
||||
|
||||
ThreadPool pool;
|
||||
|
||||
struct State {
|
||||
PathSet done;
|
||||
PathSet &unknown, &willSubstitute, &willBuild;
|
||||
unsigned long long& downloadSize;
|
||||
unsigned long long& narSize;
|
||||
};
|
||||
|
||||
struct DrvState {
|
||||
size_t left;
|
||||
bool done = false;
|
||||
PathSet outPaths;
|
||||
DrvState(size_t left) : left(left) {}
|
||||
};
|
||||
|
||||
Sync<State> state_(State{PathSet(), unknown_, willSubstitute_, willBuild_,
|
||||
downloadSize_, narSize_});
|
||||
|
||||
std::function<void(Path)> doPath;
|
||||
|
||||
auto mustBuildDrv = [&](const Path& drvPath, const Derivation& drv) {
|
||||
{
|
||||
auto state(state_.lock());
|
||||
state->willBuild.insert(drvPath);
|
||||
}
|
||||
|
||||
for (auto& i : drv.inputDrvs)
|
||||
pool.enqueue(
|
||||
std::bind(doPath, makeDrvPathWithOutputs(i.first, i.second)));
|
||||
};
|
||||
|
||||
auto checkOutput = [&](const Path& drvPath, ref<Derivation> drv,
|
||||
const Path& outPath, ref<Sync<DrvState>> drvState_) {
|
||||
if (drvState_->lock()->done) return;
|
||||
|
||||
SubstitutablePathInfos infos;
|
||||
querySubstitutablePathInfos({outPath}, infos);
|
||||
|
||||
if (infos.empty()) {
|
||||
drvState_->lock()->done = true;
|
||||
mustBuildDrv(drvPath, *drv);
|
||||
} else {
|
||||
{
|
||||
auto drvState(drvState_->lock());
|
||||
if (drvState->done) return;
|
||||
assert(drvState->left);
|
||||
drvState->left--;
|
||||
drvState->outPaths.insert(outPath);
|
||||
if (!drvState->left) {
|
||||
for (auto& path : drvState->outPaths)
|
||||
pool.enqueue(std::bind(doPath, path));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (auto & path : targets)
|
||||
pool.enqueue(std::bind(doPath, path));
|
||||
doPath = [&](const Path& path) {
|
||||
{
|
||||
auto state(state_.lock());
|
||||
if (state->done.count(path)) return;
|
||||
state->done.insert(path);
|
||||
}
|
||||
|
||||
pool.process();
|
||||
DrvPathWithOutputs i2 = parseDrvPathWithOutputs(path);
|
||||
|
||||
if (isDerivation(i2.first)) {
|
||||
if (!isValidPath(i2.first)) {
|
||||
// FIXME: we could try to substitute the derivation.
|
||||
auto state(state_.lock());
|
||||
state->unknown.insert(path);
|
||||
return;
|
||||
}
|
||||
|
||||
Derivation drv = derivationFromPath(i2.first);
|
||||
ParsedDerivation parsedDrv(i2.first, drv);
|
||||
|
||||
PathSet invalid;
|
||||
for (auto& j : drv.outputs)
|
||||
if (wantOutput(j.first, i2.second) && !isValidPath(j.second.path))
|
||||
invalid.insert(j.second.path);
|
||||
if (invalid.empty()) return;
|
||||
|
||||
if (settings.useSubstitutes && parsedDrv.substitutesAllowed()) {
|
||||
auto drvState = make_ref<Sync<DrvState>>(DrvState(invalid.size()));
|
||||
for (auto& output : invalid)
|
||||
pool.enqueue(std::bind(checkOutput, i2.first,
|
||||
make_ref<Derivation>(drv), output, drvState));
|
||||
} else
|
||||
mustBuildDrv(i2.first, drv);
|
||||
|
||||
} else {
|
||||
if (isValidPath(path)) return;
|
||||
|
||||
SubstitutablePathInfos infos;
|
||||
querySubstitutablePathInfos({path}, infos);
|
||||
|
||||
if (infos.empty()) {
|
||||
auto state(state_.lock());
|
||||
state->unknown.insert(path);
|
||||
return;
|
||||
}
|
||||
|
||||
auto info = infos.find(path);
|
||||
assert(info != infos.end());
|
||||
|
||||
{
|
||||
auto state(state_.lock());
|
||||
state->willSubstitute.insert(path);
|
||||
state->downloadSize += info->second.downloadSize;
|
||||
state->narSize += info->second.narSize;
|
||||
}
|
||||
|
||||
for (auto& ref : info->second.references)
|
||||
pool.enqueue(std::bind(doPath, ref));
|
||||
}
|
||||
};
|
||||
|
||||
for (auto& path : targets) pool.enqueue(std::bind(doPath, path));
|
||||
|
||||
pool.process();
|
||||
}
|
||||
|
||||
Paths Store::topoSortPaths(const PathSet& paths) {
|
||||
Paths sorted;
|
||||
PathSet visited, parents;
|
||||
|
||||
Paths Store::topoSortPaths(const PathSet & paths)
|
||||
{
|
||||
Paths sorted;
|
||||
PathSet visited, parents;
|
||||
std::function<void(const Path& path, const Path* parent)> dfsVisit;
|
||||
|
||||
std::function<void(const Path & path, const Path * parent)> dfsVisit;
|
||||
dfsVisit = [&](const Path& path, const Path* parent) {
|
||||
if (parents.find(path) != parents.end())
|
||||
throw BuildError(
|
||||
format("cycle detected in the references of '%1%' from '%2%'") %
|
||||
path % *parent);
|
||||
|
||||
dfsVisit = [&](const Path & path, const Path * parent) {
|
||||
if (parents.find(path) != parents.end())
|
||||
throw BuildError(format("cycle detected in the references of '%1%' from '%2%'") % path % *parent);
|
||||
if (visited.find(path) != visited.end()) return;
|
||||
visited.insert(path);
|
||||
parents.insert(path);
|
||||
|
||||
if (visited.find(path) != visited.end()) return;
|
||||
visited.insert(path);
|
||||
parents.insert(path);
|
||||
PathSet references;
|
||||
try {
|
||||
references = queryPathInfo(path)->references;
|
||||
} catch (InvalidPath&) {
|
||||
}
|
||||
|
||||
PathSet references;
|
||||
try {
|
||||
references = queryPathInfo(path)->references;
|
||||
} catch (InvalidPath &) {
|
||||
}
|
||||
for (auto& i : references)
|
||||
/* Don't traverse into paths that don't exist. That can
|
||||
happen due to substitutes for non-existent paths. */
|
||||
if (i != path && paths.find(i) != paths.end()) dfsVisit(i, &path);
|
||||
|
||||
for (auto & i : references)
|
||||
/* Don't traverse into paths that don't exist. That can
|
||||
happen due to substitutes for non-existent paths. */
|
||||
if (i != path && paths.find(i) != paths.end())
|
||||
dfsVisit(i, &path);
|
||||
sorted.push_front(path);
|
||||
parents.erase(path);
|
||||
};
|
||||
|
||||
sorted.push_front(path);
|
||||
parents.erase(path);
|
||||
};
|
||||
for (auto& i : paths) dfsVisit(i, nullptr);
|
||||
|
||||
for (auto & i : paths)
|
||||
dfsVisit(i, nullptr);
|
||||
|
||||
return sorted;
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
408
third_party/nix/src/libstore/nar-accessor.cc
vendored
408
third_party/nix/src/libstore/nar-accessor.cc
vendored
|
|
@ -1,266 +1,242 @@
|
|||
#include "nar-accessor.hh"
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <stack>
|
||||
#include "archive.hh"
|
||||
#include "json.hh"
|
||||
|
||||
#include <map>
|
||||
#include <stack>
|
||||
#include <algorithm>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct NarMember
|
||||
{
|
||||
FSAccessor::Type type = FSAccessor::Type::tMissing;
|
||||
struct NarMember {
|
||||
FSAccessor::Type type = FSAccessor::Type::tMissing;
|
||||
|
||||
bool isExecutable = false;
|
||||
bool isExecutable = false;
|
||||
|
||||
/* If this is a regular file, position of the contents of this
|
||||
file in the NAR. */
|
||||
size_t start = 0, size = 0;
|
||||
/* If this is a regular file, position of the contents of this
|
||||
file in the NAR. */
|
||||
size_t start = 0, size = 0;
|
||||
|
||||
std::string target;
|
||||
std::string target;
|
||||
|
||||
/* If this is a directory, all the children of the directory. */
|
||||
std::map<std::string, NarMember> children;
|
||||
/* If this is a directory, all the children of the directory. */
|
||||
std::map<std::string, NarMember> children;
|
||||
};
|
||||
|
||||
struct NarAccessor : public FSAccessor
|
||||
{
|
||||
std::shared_ptr<const std::string> nar;
|
||||
struct NarAccessor : public FSAccessor {
|
||||
std::shared_ptr<const std::string> nar;
|
||||
|
||||
GetNarBytes getNarBytes;
|
||||
GetNarBytes getNarBytes;
|
||||
|
||||
NarMember root;
|
||||
NarMember root;
|
||||
|
||||
struct NarIndexer : ParseSink, StringSource
|
||||
{
|
||||
NarAccessor & acc;
|
||||
struct NarIndexer : ParseSink, StringSource {
|
||||
NarAccessor& acc;
|
||||
|
||||
std::stack<NarMember *> parents;
|
||||
std::stack<NarMember*> parents;
|
||||
|
||||
std::string currentStart;
|
||||
bool isExec = false;
|
||||
std::string currentStart;
|
||||
bool isExec = false;
|
||||
|
||||
NarIndexer(NarAccessor & acc, const std::string & nar)
|
||||
: StringSource(nar), acc(acc)
|
||||
{ }
|
||||
NarIndexer(NarAccessor& acc, const std::string& nar)
|
||||
: StringSource(nar), acc(acc) {}
|
||||
|
||||
void createMember(const Path & path, NarMember member) {
|
||||
size_t level = std::count(path.begin(), path.end(), '/');
|
||||
while (parents.size() > level) parents.pop();
|
||||
void createMember(const Path& path, NarMember member) {
|
||||
size_t level = std::count(path.begin(), path.end(), '/');
|
||||
while (parents.size() > level) parents.pop();
|
||||
|
||||
if (parents.empty()) {
|
||||
acc.root = std::move(member);
|
||||
parents.push(&acc.root);
|
||||
} else {
|
||||
if (parents.top()->type != FSAccessor::Type::tDirectory)
|
||||
throw Error("NAR file missing parent directory of path '%s'", path);
|
||||
auto result = parents.top()->children.emplace(baseNameOf(path), std::move(member));
|
||||
parents.push(&result.first->second);
|
||||
}
|
||||
}
|
||||
|
||||
void createDirectory(const Path & path) override
|
||||
{
|
||||
createMember(path, {FSAccessor::Type::tDirectory, false, 0, 0});
|
||||
}
|
||||
|
||||
void createRegularFile(const Path & path) override
|
||||
{
|
||||
createMember(path, {FSAccessor::Type::tRegular, false, 0, 0});
|
||||
}
|
||||
|
||||
void isExecutable() override
|
||||
{
|
||||
parents.top()->isExecutable = true;
|
||||
}
|
||||
|
||||
void preallocateContents(unsigned long long size) override
|
||||
{
|
||||
currentStart = string(s, pos, 16);
|
||||
assert(size <= std::numeric_limits<size_t>::max());
|
||||
parents.top()->size = (size_t)size;
|
||||
parents.top()->start = pos;
|
||||
}
|
||||
|
||||
void receiveContents(unsigned char * data, unsigned int len) override
|
||||
{
|
||||
// Sanity check
|
||||
if (!currentStart.empty()) {
|
||||
assert(len < 16 || currentStart == string((char *) data, 16));
|
||||
currentStart.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void createSymlink(const Path & path, const string & target) override
|
||||
{
|
||||
createMember(path,
|
||||
NarMember{FSAccessor::Type::tSymlink, false, 0, 0, target});
|
||||
if (parents.empty()) {
|
||||
acc.root = std::move(member);
|
||||
parents.push(&acc.root);
|
||||
} else {
|
||||
if (parents.top()->type != FSAccessor::Type::tDirectory)
|
||||
throw Error("NAR file missing parent directory of path '%s'", path);
|
||||
auto result = parents.top()->children.emplace(baseNameOf(path),
|
||||
std::move(member));
|
||||
parents.push(&result.first->second);
|
||||
}
|
||||
}
|
||||
|
||||
void createDirectory(const Path& path) override {
|
||||
createMember(path, {FSAccessor::Type::tDirectory, false, 0, 0});
|
||||
}
|
||||
|
||||
void createRegularFile(const Path& path) override {
|
||||
createMember(path, {FSAccessor::Type::tRegular, false, 0, 0});
|
||||
}
|
||||
|
||||
void isExecutable() override { parents.top()->isExecutable = true; }
|
||||
|
||||
void preallocateContents(unsigned long long size) override {
|
||||
currentStart = string(s, pos, 16);
|
||||
assert(size <= std::numeric_limits<size_t>::max());
|
||||
parents.top()->size = (size_t)size;
|
||||
parents.top()->start = pos;
|
||||
}
|
||||
|
||||
void receiveContents(unsigned char* data, unsigned int len) override {
|
||||
// Sanity check
|
||||
if (!currentStart.empty()) {
|
||||
assert(len < 16 || currentStart == string((char*)data, 16));
|
||||
currentStart.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void createSymlink(const Path& path, const string& target) override {
|
||||
createMember(path,
|
||||
NarMember{FSAccessor::Type::tSymlink, false, 0, 0, target});
|
||||
}
|
||||
};
|
||||
|
||||
NarAccessor(ref<const std::string> nar) : nar(nar) {
|
||||
NarIndexer indexer(*this, *nar);
|
||||
parseDump(indexer, indexer);
|
||||
}
|
||||
|
||||
NarAccessor(const std::string& listing, GetNarBytes getNarBytes)
|
||||
: getNarBytes(getNarBytes) {
|
||||
using json = nlohmann::json;
|
||||
|
||||
std::function<void(NarMember&, json&)> recurse;
|
||||
|
||||
recurse = [&](NarMember& member, json& v) {
|
||||
std::string type = v["type"];
|
||||
|
||||
if (type == "directory") {
|
||||
member.type = FSAccessor::Type::tDirectory;
|
||||
for (auto i = v["entries"].begin(); i != v["entries"].end(); ++i) {
|
||||
std::string name = i.key();
|
||||
recurse(member.children[name], i.value());
|
||||
}
|
||||
} else if (type == "regular") {
|
||||
member.type = FSAccessor::Type::tRegular;
|
||||
member.size = v["size"];
|
||||
member.isExecutable = v.value("executable", false);
|
||||
member.start = v["narOffset"];
|
||||
} else if (type == "symlink") {
|
||||
member.type = FSAccessor::Type::tSymlink;
|
||||
member.target = v.value("target", "");
|
||||
} else
|
||||
return;
|
||||
};
|
||||
|
||||
NarAccessor(ref<const std::string> nar) : nar(nar)
|
||||
{
|
||||
NarIndexer indexer(*this, *nar);
|
||||
parseDump(indexer, indexer);
|
||||
json v = json::parse(listing);
|
||||
recurse(root, v);
|
||||
}
|
||||
|
||||
NarMember* find(const Path& path) {
|
||||
Path canon = path == "" ? "" : canonPath(path);
|
||||
NarMember* current = &root;
|
||||
auto end = path.end();
|
||||
for (auto it = path.begin(); it != end;) {
|
||||
// because it != end, the remaining component is non-empty so we need
|
||||
// a directory
|
||||
if (current->type != FSAccessor::Type::tDirectory) return nullptr;
|
||||
|
||||
// skip slash (canonPath above ensures that this is always a slash)
|
||||
assert(*it == '/');
|
||||
it += 1;
|
||||
|
||||
// lookup current component
|
||||
auto next = std::find(it, end, '/');
|
||||
auto child = current->children.find(std::string(it, next));
|
||||
if (child == current->children.end()) return nullptr;
|
||||
current = &child->second;
|
||||
|
||||
it = next;
|
||||
}
|
||||
|
||||
NarAccessor(const std::string & listing, GetNarBytes getNarBytes)
|
||||
: getNarBytes(getNarBytes)
|
||||
{
|
||||
using json = nlohmann::json;
|
||||
return current;
|
||||
}
|
||||
|
||||
std::function<void(NarMember &, json &)> recurse;
|
||||
NarMember& get(const Path& path) {
|
||||
auto result = find(path);
|
||||
if (result == nullptr)
|
||||
throw Error("NAR file does not contain path '%1%'", path);
|
||||
return *result;
|
||||
}
|
||||
|
||||
recurse = [&](NarMember & member, json & v) {
|
||||
std::string type = v["type"];
|
||||
Stat stat(const Path& path) override {
|
||||
auto i = find(path);
|
||||
if (i == nullptr) return {FSAccessor::Type::tMissing, 0, false};
|
||||
return {i->type, i->size, i->isExecutable, i->start};
|
||||
}
|
||||
|
||||
if (type == "directory") {
|
||||
member.type = FSAccessor::Type::tDirectory;
|
||||
for (auto i = v["entries"].begin(); i != v["entries"].end(); ++i) {
|
||||
std::string name = i.key();
|
||||
recurse(member.children[name], i.value());
|
||||
}
|
||||
} else if (type == "regular") {
|
||||
member.type = FSAccessor::Type::tRegular;
|
||||
member.size = v["size"];
|
||||
member.isExecutable = v.value("executable", false);
|
||||
member.start = v["narOffset"];
|
||||
} else if (type == "symlink") {
|
||||
member.type = FSAccessor::Type::tSymlink;
|
||||
member.target = v.value("target", "");
|
||||
} else return;
|
||||
};
|
||||
StringSet readDirectory(const Path& path) override {
|
||||
auto i = get(path);
|
||||
|
||||
json v = json::parse(listing);
|
||||
recurse(root, v);
|
||||
}
|
||||
if (i.type != FSAccessor::Type::tDirectory)
|
||||
throw Error(format("path '%1%' inside NAR file is not a directory") %
|
||||
path);
|
||||
|
||||
NarMember * find(const Path & path)
|
||||
{
|
||||
Path canon = path == "" ? "" : canonPath(path);
|
||||
NarMember * current = &root;
|
||||
auto end = path.end();
|
||||
for (auto it = path.begin(); it != end; ) {
|
||||
// because it != end, the remaining component is non-empty so we need
|
||||
// a directory
|
||||
if (current->type != FSAccessor::Type::tDirectory) return nullptr;
|
||||
StringSet res;
|
||||
for (auto& child : i.children) res.insert(child.first);
|
||||
|
||||
// skip slash (canonPath above ensures that this is always a slash)
|
||||
assert(*it == '/');
|
||||
it += 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
// lookup current component
|
||||
auto next = std::find(it, end, '/');
|
||||
auto child = current->children.find(std::string(it, next));
|
||||
if (child == current->children.end()) return nullptr;
|
||||
current = &child->second;
|
||||
std::string readFile(const Path& path) override {
|
||||
auto i = get(path);
|
||||
if (i.type != FSAccessor::Type::tRegular)
|
||||
throw Error(format("path '%1%' inside NAR file is not a regular file") %
|
||||
path);
|
||||
|
||||
it = next;
|
||||
}
|
||||
if (getNarBytes) return getNarBytes(i.start, i.size);
|
||||
|
||||
return current;
|
||||
}
|
||||
assert(nar);
|
||||
return std::string(*nar, i.start, i.size);
|
||||
}
|
||||
|
||||
NarMember & get(const Path & path) {
|
||||
auto result = find(path);
|
||||
if (result == nullptr)
|
||||
throw Error("NAR file does not contain path '%1%'", path);
|
||||
return *result;
|
||||
}
|
||||
|
||||
Stat stat(const Path & path) override
|
||||
{
|
||||
auto i = find(path);
|
||||
if (i == nullptr)
|
||||
return {FSAccessor::Type::tMissing, 0, false};
|
||||
return {i->type, i->size, i->isExecutable, i->start};
|
||||
}
|
||||
|
||||
StringSet readDirectory(const Path & path) override
|
||||
{
|
||||
auto i = get(path);
|
||||
|
||||
if (i.type != FSAccessor::Type::tDirectory)
|
||||
throw Error(format("path '%1%' inside NAR file is not a directory") % path);
|
||||
|
||||
StringSet res;
|
||||
for (auto & child : i.children)
|
||||
res.insert(child.first);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string readFile(const Path & path) override
|
||||
{
|
||||
auto i = get(path);
|
||||
if (i.type != FSAccessor::Type::tRegular)
|
||||
throw Error(format("path '%1%' inside NAR file is not a regular file") % path);
|
||||
|
||||
if (getNarBytes) return getNarBytes(i.start, i.size);
|
||||
|
||||
assert(nar);
|
||||
return std::string(*nar, i.start, i.size);
|
||||
}
|
||||
|
||||
std::string readLink(const Path & path) override
|
||||
{
|
||||
auto i = get(path);
|
||||
if (i.type != FSAccessor::Type::tSymlink)
|
||||
throw Error(format("path '%1%' inside NAR file is not a symlink") % path);
|
||||
return i.target;
|
||||
}
|
||||
std::string readLink(const Path& path) override {
|
||||
auto i = get(path);
|
||||
if (i.type != FSAccessor::Type::tSymlink)
|
||||
throw Error(format("path '%1%' inside NAR file is not a symlink") % path);
|
||||
return i.target;
|
||||
}
|
||||
};
|
||||
|
||||
ref<FSAccessor> makeNarAccessor(ref<const std::string> nar)
|
||||
{
|
||||
return make_ref<NarAccessor>(nar);
|
||||
ref<FSAccessor> makeNarAccessor(ref<const std::string> nar) {
|
||||
return make_ref<NarAccessor>(nar);
|
||||
}
|
||||
|
||||
ref<FSAccessor> makeLazyNarAccessor(const std::string & listing,
|
||||
GetNarBytes getNarBytes)
|
||||
{
|
||||
return make_ref<NarAccessor>(listing, getNarBytes);
|
||||
ref<FSAccessor> makeLazyNarAccessor(const std::string& listing,
|
||||
GetNarBytes getNarBytes) {
|
||||
return make_ref<NarAccessor>(listing, getNarBytes);
|
||||
}
|
||||
|
||||
void listNar(JSONPlaceholder & res, ref<FSAccessor> accessor,
|
||||
const Path & path, bool recurse)
|
||||
{
|
||||
auto st = accessor->stat(path);
|
||||
void listNar(JSONPlaceholder& res, ref<FSAccessor> accessor, const Path& path,
|
||||
bool recurse) {
|
||||
auto st = accessor->stat(path);
|
||||
|
||||
auto obj = res.object();
|
||||
auto obj = res.object();
|
||||
|
||||
switch (st.type) {
|
||||
switch (st.type) {
|
||||
case FSAccessor::Type::tRegular:
|
||||
obj.attr("type", "regular");
|
||||
obj.attr("size", st.fileSize);
|
||||
if (st.isExecutable)
|
||||
obj.attr("executable", true);
|
||||
if (st.narOffset)
|
||||
obj.attr("narOffset", st.narOffset);
|
||||
break;
|
||||
obj.attr("type", "regular");
|
||||
obj.attr("size", st.fileSize);
|
||||
if (st.isExecutable) obj.attr("executable", true);
|
||||
if (st.narOffset) obj.attr("narOffset", st.narOffset);
|
||||
break;
|
||||
case FSAccessor::Type::tDirectory:
|
||||
obj.attr("type", "directory");
|
||||
{
|
||||
auto res2 = obj.object("entries");
|
||||
for (auto & name : accessor->readDirectory(path)) {
|
||||
if (recurse) {
|
||||
auto res3 = res2.placeholder(name);
|
||||
listNar(res3, accessor, path + "/" + name, true);
|
||||
} else
|
||||
res2.object(name);
|
||||
}
|
||||
obj.attr("type", "directory");
|
||||
{
|
||||
auto res2 = obj.object("entries");
|
||||
for (auto& name : accessor->readDirectory(path)) {
|
||||
if (recurse) {
|
||||
auto res3 = res2.placeholder(name);
|
||||
listNar(res3, accessor, path + "/" + name, true);
|
||||
} else
|
||||
res2.object(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FSAccessor::Type::tSymlink:
|
||||
obj.attr("type", "symlink");
|
||||
obj.attr("target", accessor->readLink(path));
|
||||
break;
|
||||
obj.attr("type", "symlink");
|
||||
obj.attr("target", accessor->readLink(path));
|
||||
break;
|
||||
default:
|
||||
throw Error("path '%s' does not exist in NAR", path);
|
||||
}
|
||||
throw Error("path '%s' does not exist in NAR", path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
12
third_party/nix/src/libstore/nar-accessor.hh
vendored
12
third_party/nix/src/libstore/nar-accessor.hh
vendored
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "fs-accessor.hh"
|
||||
|
||||
namespace nix {
|
||||
|
|
@ -16,15 +15,14 @@ ref<FSAccessor> makeNarAccessor(ref<const std::string> nar);
|
|||
inside the NAR. */
|
||||
typedef std::function<std::string(uint64_t, uint64_t)> GetNarBytes;
|
||||
|
||||
ref<FSAccessor> makeLazyNarAccessor(
|
||||
const std::string & listing,
|
||||
GetNarBytes getNarBytes);
|
||||
ref<FSAccessor> makeLazyNarAccessor(const std::string& listing,
|
||||
GetNarBytes getNarBytes);
|
||||
|
||||
class JSONPlaceholder;
|
||||
|
||||
/* Write a JSON representation of the contents of a NAR (except file
|
||||
contents). */
|
||||
void listNar(JSONPlaceholder & res, ref<FSAccessor> accessor,
|
||||
const Path & path, bool recurse);
|
||||
void listNar(JSONPlaceholder& res, ref<FSAccessor> accessor, const Path& path,
|
||||
bool recurse);
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
386
third_party/nix/src/libstore/nar-info-disk-cache.cc
vendored
386
third_party/nix/src/libstore/nar-info-disk-cache.cc
vendored
|
|
@ -1,13 +1,12 @@
|
|||
#include "nar-info-disk-cache.hh"
|
||||
#include "sync.hh"
|
||||
#include "sqlite.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include "globals.hh"
|
||||
#include "sqlite.hh"
|
||||
#include "sync.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static const char * schema = R"sql(
|
||||
static const char* schema = R"sql(
|
||||
|
||||
create table if not exists BinaryCaches (
|
||||
id integer primary key autoincrement not null,
|
||||
|
|
@ -45,223 +44,222 @@ create table if not exists LastPurge (
|
|||
|
||||
)sql";
|
||||
|
||||
class NarInfoDiskCacheImpl : public NarInfoDiskCache
|
||||
{
|
||||
public:
|
||||
class NarInfoDiskCacheImpl : public NarInfoDiskCache {
|
||||
public:
|
||||
/* How often to purge expired entries from the cache. */
|
||||
const int purgeInterval = 24 * 3600;
|
||||
|
||||
/* How often to purge expired entries from the cache. */
|
||||
const int purgeInterval = 24 * 3600;
|
||||
struct Cache {
|
||||
int id;
|
||||
Path storeDir;
|
||||
bool wantMassQuery;
|
||||
int priority;
|
||||
};
|
||||
|
||||
struct Cache
|
||||
{
|
||||
int id;
|
||||
Path storeDir;
|
||||
bool wantMassQuery;
|
||||
int priority;
|
||||
};
|
||||
struct State {
|
||||
SQLite db;
|
||||
SQLiteStmt insertCache, queryCache, insertNAR, insertMissingNAR, queryNAR,
|
||||
purgeCache;
|
||||
std::map<std::string, Cache> caches;
|
||||
};
|
||||
|
||||
struct State
|
||||
{
|
||||
SQLite db;
|
||||
SQLiteStmt insertCache, queryCache, insertNAR, insertMissingNAR, queryNAR, purgeCache;
|
||||
std::map<std::string, Cache> caches;
|
||||
};
|
||||
Sync<State> _state;
|
||||
|
||||
Sync<State> _state;
|
||||
NarInfoDiskCacheImpl() {
|
||||
auto state(_state.lock());
|
||||
|
||||
NarInfoDiskCacheImpl()
|
||||
{
|
||||
auto state(_state.lock());
|
||||
Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite";
|
||||
createDirs(dirOf(dbPath));
|
||||
|
||||
Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite";
|
||||
createDirs(dirOf(dbPath));
|
||||
state->db = SQLite(dbPath);
|
||||
|
||||
state->db = SQLite(dbPath);
|
||||
if (sqlite3_busy_timeout(state->db, 60 * 60 * 1000) != SQLITE_OK)
|
||||
throwSQLiteError(state->db, "setting timeout");
|
||||
|
||||
if (sqlite3_busy_timeout(state->db, 60 * 60 * 1000) != SQLITE_OK)
|
||||
throwSQLiteError(state->db, "setting timeout");
|
||||
// We can always reproduce the cache.
|
||||
state->db.exec("pragma synchronous = off");
|
||||
state->db.exec("pragma main.journal_mode = truncate");
|
||||
|
||||
// We can always reproduce the cache.
|
||||
state->db.exec("pragma synchronous = off");
|
||||
state->db.exec("pragma main.journal_mode = truncate");
|
||||
state->db.exec(schema);
|
||||
|
||||
state->db.exec(schema);
|
||||
state->insertCache.create(
|
||||
state->db,
|
||||
"insert or replace into BinaryCaches(url, timestamp, storeDir, "
|
||||
"wantMassQuery, priority) values (?, ?, ?, ?, ?)");
|
||||
|
||||
state->insertCache.create(state->db,
|
||||
"insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)");
|
||||
state->queryCache.create(state->db,
|
||||
"select id, storeDir, wantMassQuery, priority "
|
||||
"from BinaryCaches where url = ?");
|
||||
|
||||
state->queryCache.create(state->db,
|
||||
"select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ?");
|
||||
state->insertNAR.create(
|
||||
state->db,
|
||||
"insert or replace into NARs(cache, hashPart, namePart, url, "
|
||||
"compression, fileHash, fileSize, narHash, "
|
||||
"narSize, refs, deriver, sigs, ca, timestamp, present) values (?, ?, "
|
||||
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)");
|
||||
|
||||
state->insertNAR.create(state->db,
|
||||
"insert or replace into NARs(cache, hashPart, namePart, url, compression, fileHash, fileSize, narHash, "
|
||||
"narSize, refs, deriver, sigs, ca, timestamp, present) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)");
|
||||
state->insertMissingNAR.create(
|
||||
state->db,
|
||||
"insert or replace into NARs(cache, hashPart, timestamp, present) "
|
||||
"values (?, ?, ?, 0)");
|
||||
|
||||
state->insertMissingNAR.create(state->db,
|
||||
"insert or replace into NARs(cache, hashPart, timestamp, present) values (?, ?, ?, 0)");
|
||||
state->queryNAR.create(
|
||||
state->db,
|
||||
"select present, namePart, url, compression, fileHash, fileSize, "
|
||||
"narHash, narSize, refs, deriver, sigs, ca from NARs where cache = ? "
|
||||
"and hashPart = ? and ((present = 0 and timestamp > ?) or (present = 1 "
|
||||
"and timestamp > ?))");
|
||||
|
||||
state->queryNAR.create(state->db,
|
||||
"select present, namePart, url, compression, fileHash, fileSize, narHash, narSize, refs, deriver, sigs, ca from NARs where cache = ? and hashPart = ? and ((present = 0 and timestamp > ?) or (present = 1 and timestamp > ?))");
|
||||
/* Periodically purge expired entries from the database. */
|
||||
retrySQLite<void>([&]() {
|
||||
auto now = time(0);
|
||||
|
||||
/* Periodically purge expired entries from the database. */
|
||||
retrySQLite<void>([&]() {
|
||||
auto now = time(0);
|
||||
SQLiteStmt queryLastPurge(state->db, "select value from LastPurge");
|
||||
auto queryLastPurge_(queryLastPurge.use());
|
||||
|
||||
SQLiteStmt queryLastPurge(state->db, "select value from LastPurge");
|
||||
auto queryLastPurge_(queryLastPurge.use());
|
||||
if (!queryLastPurge_.next() ||
|
||||
queryLastPurge_.getInt(0) < now - purgeInterval) {
|
||||
SQLiteStmt(state->db,
|
||||
"delete from NARs where ((present = 0 and timestamp < ?) or "
|
||||
"(present = 1 and timestamp < ?))")
|
||||
.use()(now - settings.ttlNegativeNarInfoCache)(
|
||||
now - settings.ttlPositiveNarInfoCache)
|
||||
.exec();
|
||||
|
||||
if (!queryLastPurge_.next() || queryLastPurge_.getInt(0) < now - purgeInterval) {
|
||||
SQLiteStmt(state->db,
|
||||
"delete from NARs where ((present = 0 and timestamp < ?) or (present = 1 and timestamp < ?))")
|
||||
.use()
|
||||
(now - settings.ttlNegativeNarInfoCache)
|
||||
(now - settings.ttlPositiveNarInfoCache)
|
||||
.exec();
|
||||
debug("deleted %d entries from the NAR info disk cache",
|
||||
sqlite3_changes(state->db));
|
||||
|
||||
debug("deleted %d entries from the NAR info disk cache", sqlite3_changes(state->db));
|
||||
SQLiteStmt(
|
||||
state->db,
|
||||
"insert or replace into LastPurge(dummy, value) values ('', ?)")
|
||||
.use()(now)
|
||||
.exec();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SQLiteStmt(state->db,
|
||||
"insert or replace into LastPurge(dummy, value) values ('', ?)")
|
||||
.use()(now).exec();
|
||||
}
|
||||
Cache& getCache(State& state, const std::string& uri) {
|
||||
auto i = state.caches.find(uri);
|
||||
if (i == state.caches.end()) abort();
|
||||
return i->second;
|
||||
}
|
||||
|
||||
void createCache(const std::string& uri, const Path& storeDir,
|
||||
bool wantMassQuery, int priority) override {
|
||||
retrySQLite<void>([&]() {
|
||||
auto state(_state.lock());
|
||||
|
||||
// FIXME: race
|
||||
|
||||
state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority)
|
||||
.exec();
|
||||
assert(sqlite3_changes(state->db) == 1);
|
||||
state->caches[uri] = Cache{(int)sqlite3_last_insert_rowid(state->db),
|
||||
storeDir, wantMassQuery, priority};
|
||||
});
|
||||
}
|
||||
|
||||
bool cacheExists(const std::string& uri, bool& wantMassQuery,
|
||||
int& priority) override {
|
||||
return retrySQLite<bool>([&]() {
|
||||
auto state(_state.lock());
|
||||
|
||||
auto i = state->caches.find(uri);
|
||||
if (i == state->caches.end()) {
|
||||
auto queryCache(state->queryCache.use()(uri));
|
||||
if (!queryCache.next()) return false;
|
||||
state->caches.emplace(
|
||||
uri, Cache{(int)queryCache.getInt(0), queryCache.getStr(1),
|
||||
queryCache.getInt(2) != 0, (int)queryCache.getInt(3)});
|
||||
}
|
||||
|
||||
auto& cache(getCache(*state, uri));
|
||||
|
||||
wantMassQuery = cache.wantMassQuery;
|
||||
priority = cache.priority;
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
std::pair<Outcome, std::shared_ptr<NarInfo>> lookupNarInfo(
|
||||
const std::string& uri, const std::string& hashPart) override {
|
||||
return retrySQLite<std::pair<Outcome, std::shared_ptr<NarInfo>>>(
|
||||
[&]() -> std::pair<Outcome, std::shared_ptr<NarInfo>> {
|
||||
auto state(_state.lock());
|
||||
|
||||
auto& cache(getCache(*state, uri));
|
||||
|
||||
auto now = time(0);
|
||||
|
||||
auto queryNAR(state->queryNAR.use()(cache.id)(hashPart)(
|
||||
now - settings.ttlNegativeNarInfoCache)(
|
||||
now - settings.ttlPositiveNarInfoCache));
|
||||
|
||||
if (!queryNAR.next()) return {oUnknown, 0};
|
||||
|
||||
if (!queryNAR.getInt(0)) return {oInvalid, 0};
|
||||
|
||||
auto narInfo = make_ref<NarInfo>();
|
||||
|
||||
auto namePart = queryNAR.getStr(1);
|
||||
narInfo->path = cache.storeDir + "/" + hashPart +
|
||||
(namePart.empty() ? "" : "-" + namePart);
|
||||
narInfo->url = queryNAR.getStr(2);
|
||||
narInfo->compression = queryNAR.getStr(3);
|
||||
if (!queryNAR.isNull(4)) narInfo->fileHash = Hash(queryNAR.getStr(4));
|
||||
narInfo->fileSize = queryNAR.getInt(5);
|
||||
narInfo->narHash = Hash(queryNAR.getStr(6));
|
||||
narInfo->narSize = queryNAR.getInt(7);
|
||||
for (auto& r : tokenizeString<Strings>(queryNAR.getStr(8), " "))
|
||||
narInfo->references.insert(cache.storeDir + "/" + r);
|
||||
if (!queryNAR.isNull(9))
|
||||
narInfo->deriver = cache.storeDir + "/" + queryNAR.getStr(9);
|
||||
for (auto& sig : tokenizeString<Strings>(queryNAR.getStr(10), " "))
|
||||
narInfo->sigs.insert(sig);
|
||||
narInfo->ca = queryNAR.getStr(11);
|
||||
|
||||
return {oValid, narInfo};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Cache & getCache(State & state, const std::string & uri)
|
||||
{
|
||||
auto i = state.caches.find(uri);
|
||||
if (i == state.caches.end()) abort();
|
||||
return i->second;
|
||||
}
|
||||
void upsertNarInfo(const std::string& uri, const std::string& hashPart,
|
||||
std::shared_ptr<ValidPathInfo> info) override {
|
||||
retrySQLite<void>([&]() {
|
||||
auto state(_state.lock());
|
||||
|
||||
void createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override
|
||||
{
|
||||
retrySQLite<void>([&]() {
|
||||
auto state(_state.lock());
|
||||
auto& cache(getCache(*state, uri));
|
||||
|
||||
// FIXME: race
|
||||
if (info) {
|
||||
auto narInfo = std::dynamic_pointer_cast<NarInfo>(info);
|
||||
|
||||
state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority).exec();
|
||||
assert(sqlite3_changes(state->db) == 1);
|
||||
state->caches[uri] = Cache{(int) sqlite3_last_insert_rowid(state->db), storeDir, wantMassQuery, priority};
|
||||
});
|
||||
}
|
||||
assert(hashPart == storePathToHash(info->path));
|
||||
|
||||
bool cacheExists(const std::string & uri,
|
||||
bool & wantMassQuery, int & priority) override
|
||||
{
|
||||
return retrySQLite<bool>([&]() {
|
||||
auto state(_state.lock());
|
||||
state->insertNAR
|
||||
.use()(cache.id)(hashPart)(storePathToName(info->path))(
|
||||
narInfo ? narInfo->url : "", narInfo != 0)(
|
||||
narInfo ? narInfo->compression : "", narInfo != 0)(
|
||||
narInfo && narInfo->fileHash ? narInfo->fileHash.to_string()
|
||||
: "",
|
||||
narInfo && narInfo->fileHash)(
|
||||
narInfo ? narInfo->fileSize : 0,
|
||||
narInfo != 0 && narInfo->fileSize)(info->narHash.to_string())(
|
||||
info->narSize)(concatStringsSep(" ", info->shortRefs()))(
|
||||
info->deriver != "" ? baseNameOf(info->deriver) : "",
|
||||
info->deriver !=
|
||||
"")(concatStringsSep(" ", info->sigs))(info->ca)(time(0))
|
||||
.exec();
|
||||
|
||||
auto i = state->caches.find(uri);
|
||||
if (i == state->caches.end()) {
|
||||
auto queryCache(state->queryCache.use()(uri));
|
||||
if (!queryCache.next()) return false;
|
||||
state->caches.emplace(uri,
|
||||
Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)});
|
||||
}
|
||||
|
||||
auto & cache(getCache(*state, uri));
|
||||
|
||||
wantMassQuery = cache.wantMassQuery;
|
||||
priority = cache.priority;
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
std::pair<Outcome, std::shared_ptr<NarInfo>> lookupNarInfo(
|
||||
const std::string & uri, const std::string & hashPart) override
|
||||
{
|
||||
return retrySQLite<std::pair<Outcome, std::shared_ptr<NarInfo>>>(
|
||||
[&]() -> std::pair<Outcome, std::shared_ptr<NarInfo>> {
|
||||
auto state(_state.lock());
|
||||
|
||||
auto & cache(getCache(*state, uri));
|
||||
|
||||
auto now = time(0);
|
||||
|
||||
auto queryNAR(state->queryNAR.use()
|
||||
(cache.id)
|
||||
(hashPart)
|
||||
(now - settings.ttlNegativeNarInfoCache)
|
||||
(now - settings.ttlPositiveNarInfoCache));
|
||||
|
||||
if (!queryNAR.next())
|
||||
return {oUnknown, 0};
|
||||
|
||||
if (!queryNAR.getInt(0))
|
||||
return {oInvalid, 0};
|
||||
|
||||
auto narInfo = make_ref<NarInfo>();
|
||||
|
||||
auto namePart = queryNAR.getStr(1);
|
||||
narInfo->path = cache.storeDir + "/" +
|
||||
hashPart + (namePart.empty() ? "" : "-" + namePart);
|
||||
narInfo->url = queryNAR.getStr(2);
|
||||
narInfo->compression = queryNAR.getStr(3);
|
||||
if (!queryNAR.isNull(4))
|
||||
narInfo->fileHash = Hash(queryNAR.getStr(4));
|
||||
narInfo->fileSize = queryNAR.getInt(5);
|
||||
narInfo->narHash = Hash(queryNAR.getStr(6));
|
||||
narInfo->narSize = queryNAR.getInt(7);
|
||||
for (auto & r : tokenizeString<Strings>(queryNAR.getStr(8), " "))
|
||||
narInfo->references.insert(cache.storeDir + "/" + r);
|
||||
if (!queryNAR.isNull(9))
|
||||
narInfo->deriver = cache.storeDir + "/" + queryNAR.getStr(9);
|
||||
for (auto & sig : tokenizeString<Strings>(queryNAR.getStr(10), " "))
|
||||
narInfo->sigs.insert(sig);
|
||||
narInfo->ca = queryNAR.getStr(11);
|
||||
|
||||
return {oValid, narInfo};
|
||||
});
|
||||
}
|
||||
|
||||
void upsertNarInfo(
|
||||
const std::string & uri, const std::string & hashPart,
|
||||
std::shared_ptr<ValidPathInfo> info) override
|
||||
{
|
||||
retrySQLite<void>([&]() {
|
||||
auto state(_state.lock());
|
||||
|
||||
auto & cache(getCache(*state, uri));
|
||||
|
||||
if (info) {
|
||||
|
||||
auto narInfo = std::dynamic_pointer_cast<NarInfo>(info);
|
||||
|
||||
assert(hashPart == storePathToHash(info->path));
|
||||
|
||||
state->insertNAR.use()
|
||||
(cache.id)
|
||||
(hashPart)
|
||||
(storePathToName(info->path))
|
||||
(narInfo ? narInfo->url : "", narInfo != 0)
|
||||
(narInfo ? narInfo->compression : "", narInfo != 0)
|
||||
(narInfo && narInfo->fileHash ? narInfo->fileHash.to_string() : "", narInfo && narInfo->fileHash)
|
||||
(narInfo ? narInfo->fileSize : 0, narInfo != 0 && narInfo->fileSize)
|
||||
(info->narHash.to_string())
|
||||
(info->narSize)
|
||||
(concatStringsSep(" ", info->shortRefs()))
|
||||
(info->deriver != "" ? baseNameOf(info->deriver) : "", info->deriver != "")
|
||||
(concatStringsSep(" ", info->sigs))
|
||||
(info->ca)
|
||||
(time(0)).exec();
|
||||
|
||||
} else {
|
||||
state->insertMissingNAR.use()
|
||||
(cache.id)
|
||||
(hashPart)
|
||||
(time(0)).exec();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
state->insertMissingNAR.use()(cache.id)(hashPart)(time(0)).exec();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ref<NarInfoDiskCache> getNarInfoDiskCache()
|
||||
{
|
||||
static ref<NarInfoDiskCache> cache = make_ref<NarInfoDiskCacheImpl>();
|
||||
return cache;
|
||||
ref<NarInfoDiskCache> getNarInfoDiskCache() {
|
||||
static ref<NarInfoDiskCache> cache = make_ref<NarInfoDiskCacheImpl>();
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include "ref.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "ref.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class NarInfoDiskCache
|
||||
{
|
||||
public:
|
||||
typedef enum { oValid, oInvalid, oUnknown } Outcome;
|
||||
class NarInfoDiskCache {
|
||||
public:
|
||||
typedef enum { oValid, oInvalid, oUnknown } Outcome;
|
||||
|
||||
virtual void createCache(const std::string & uri, const Path & storeDir,
|
||||
bool wantMassQuery, int priority) = 0;
|
||||
virtual void createCache(const std::string& uri, const Path& storeDir,
|
||||
bool wantMassQuery, int priority) = 0;
|
||||
|
||||
virtual bool cacheExists(const std::string & uri,
|
||||
bool & wantMassQuery, int & priority) = 0;
|
||||
virtual bool cacheExists(const std::string& uri, bool& wantMassQuery,
|
||||
int& priority) = 0;
|
||||
|
||||
virtual std::pair<Outcome, std::shared_ptr<NarInfo>> lookupNarInfo(
|
||||
const std::string & uri, const std::string & hashPart) = 0;
|
||||
virtual std::pair<Outcome, std::shared_ptr<NarInfo>> lookupNarInfo(
|
||||
const std::string& uri, const std::string& hashPart) = 0;
|
||||
|
||||
virtual void upsertNarInfo(
|
||||
const std::string & uri, const std::string & hashPart,
|
||||
std::shared_ptr<ValidPathInfo> info) = 0;
|
||||
virtual void upsertNarInfo(const std::string& uri,
|
||||
const std::string& hashPart,
|
||||
std::shared_ptr<ValidPathInfo> info) = 0;
|
||||
};
|
||||
|
||||
/* Return a singleton cache object that can be used concurrently by
|
||||
multiple threads. */
|
||||
ref<NarInfoDiskCache> getNarInfoDiskCache();
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
175
third_party/nix/src/libstore/nar-info.cc
vendored
175
third_party/nix/src/libstore/nar-info.cc
vendored
|
|
@ -1,116 +1,105 @@
|
|||
#include "globals.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
NarInfo::NarInfo(const Store & store, const std::string & s, const std::string & whence)
|
||||
{
|
||||
auto corrupt = [&]() {
|
||||
throw Error(format("NAR info file '%1%' is corrupt") % whence);
|
||||
};
|
||||
NarInfo::NarInfo(const Store& store, const std::string& s,
|
||||
const std::string& whence) {
|
||||
auto corrupt = [&]() {
|
||||
throw Error(format("NAR info file '%1%' is corrupt") % whence);
|
||||
};
|
||||
|
||||
auto parseHashField = [&](const string & s) {
|
||||
try {
|
||||
return Hash(s);
|
||||
} catch (BadHash &) {
|
||||
corrupt();
|
||||
return Hash(); // never reached
|
||||
}
|
||||
};
|
||||
auto parseHashField = [&](const string& s) {
|
||||
try {
|
||||
return Hash(s);
|
||||
} catch (BadHash&) {
|
||||
corrupt();
|
||||
return Hash(); // never reached
|
||||
}
|
||||
};
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < s.size()) {
|
||||
size_t pos = 0;
|
||||
while (pos < s.size()) {
|
||||
size_t colon = s.find(':', pos);
|
||||
if (colon == std::string::npos) corrupt();
|
||||
|
||||
size_t colon = s.find(':', pos);
|
||||
if (colon == std::string::npos) corrupt();
|
||||
std::string name(s, pos, colon - pos);
|
||||
|
||||
std::string name(s, pos, colon - pos);
|
||||
size_t eol = s.find('\n', colon + 2);
|
||||
if (eol == std::string::npos) corrupt();
|
||||
|
||||
size_t eol = s.find('\n', colon + 2);
|
||||
if (eol == std::string::npos) corrupt();
|
||||
std::string value(s, colon + 2, eol - colon - 2);
|
||||
|
||||
std::string value(s, colon + 2, eol - colon - 2);
|
||||
|
||||
if (name == "StorePath") {
|
||||
if (!store.isStorePath(value)) corrupt();
|
||||
path = value;
|
||||
}
|
||||
else if (name == "URL")
|
||||
url = value;
|
||||
else if (name == "Compression")
|
||||
compression = value;
|
||||
else if (name == "FileHash")
|
||||
fileHash = parseHashField(value);
|
||||
else if (name == "FileSize") {
|
||||
if (!string2Int(value, fileSize)) corrupt();
|
||||
}
|
||||
else if (name == "NarHash")
|
||||
narHash = parseHashField(value);
|
||||
else if (name == "NarSize") {
|
||||
if (!string2Int(value, narSize)) corrupt();
|
||||
}
|
||||
else if (name == "References") {
|
||||
auto refs = tokenizeString<Strings>(value, " ");
|
||||
if (!references.empty()) corrupt();
|
||||
for (auto & r : refs) {
|
||||
auto r2 = store.storeDir + "/" + r;
|
||||
if (!store.isStorePath(r2)) corrupt();
|
||||
references.insert(r2);
|
||||
}
|
||||
}
|
||||
else if (name == "Deriver") {
|
||||
if (value != "unknown-deriver") {
|
||||
auto p = store.storeDir + "/" + value;
|
||||
if (!store.isStorePath(p)) corrupt();
|
||||
deriver = p;
|
||||
}
|
||||
}
|
||||
else if (name == "System")
|
||||
system = value;
|
||||
else if (name == "Sig")
|
||||
sigs.insert(value);
|
||||
else if (name == "CA") {
|
||||
if (!ca.empty()) corrupt();
|
||||
ca = value;
|
||||
}
|
||||
|
||||
pos = eol + 1;
|
||||
if (name == "StorePath") {
|
||||
if (!store.isStorePath(value)) corrupt();
|
||||
path = value;
|
||||
} else if (name == "URL")
|
||||
url = value;
|
||||
else if (name == "Compression")
|
||||
compression = value;
|
||||
else if (name == "FileHash")
|
||||
fileHash = parseHashField(value);
|
||||
else if (name == "FileSize") {
|
||||
if (!string2Int(value, fileSize)) corrupt();
|
||||
} else if (name == "NarHash")
|
||||
narHash = parseHashField(value);
|
||||
else if (name == "NarSize") {
|
||||
if (!string2Int(value, narSize)) corrupt();
|
||||
} else if (name == "References") {
|
||||
auto refs = tokenizeString<Strings>(value, " ");
|
||||
if (!references.empty()) corrupt();
|
||||
for (auto& r : refs) {
|
||||
auto r2 = store.storeDir + "/" + r;
|
||||
if (!store.isStorePath(r2)) corrupt();
|
||||
references.insert(r2);
|
||||
}
|
||||
} else if (name == "Deriver") {
|
||||
if (value != "unknown-deriver") {
|
||||
auto p = store.storeDir + "/" + value;
|
||||
if (!store.isStorePath(p)) corrupt();
|
||||
deriver = p;
|
||||
}
|
||||
} else if (name == "System")
|
||||
system = value;
|
||||
else if (name == "Sig")
|
||||
sigs.insert(value);
|
||||
else if (name == "CA") {
|
||||
if (!ca.empty()) corrupt();
|
||||
ca = value;
|
||||
}
|
||||
|
||||
if (compression == "") compression = "bzip2";
|
||||
pos = eol + 1;
|
||||
}
|
||||
|
||||
if (path.empty() || url.empty() || narSize == 0 || !narHash) corrupt();
|
||||
if (compression == "") compression = "bzip2";
|
||||
|
||||
if (path.empty() || url.empty() || narSize == 0 || !narHash) corrupt();
|
||||
}
|
||||
|
||||
std::string NarInfo::to_string() const
|
||||
{
|
||||
std::string res;
|
||||
res += "StorePath: " + path + "\n";
|
||||
res += "URL: " + url + "\n";
|
||||
assert(compression != "");
|
||||
res += "Compression: " + compression + "\n";
|
||||
assert(fileHash.type == htSHA256);
|
||||
res += "FileHash: " + fileHash.to_string(Base32) + "\n";
|
||||
res += "FileSize: " + std::to_string(fileSize) + "\n";
|
||||
assert(narHash.type == htSHA256);
|
||||
res += "NarHash: " + narHash.to_string(Base32) + "\n";
|
||||
res += "NarSize: " + std::to_string(narSize) + "\n";
|
||||
std::string NarInfo::to_string() const {
|
||||
std::string res;
|
||||
res += "StorePath: " + path + "\n";
|
||||
res += "URL: " + url + "\n";
|
||||
assert(compression != "");
|
||||
res += "Compression: " + compression + "\n";
|
||||
assert(fileHash.type == htSHA256);
|
||||
res += "FileHash: " + fileHash.to_string(Base32) + "\n";
|
||||
res += "FileSize: " + std::to_string(fileSize) + "\n";
|
||||
assert(narHash.type == htSHA256);
|
||||
res += "NarHash: " + narHash.to_string(Base32) + "\n";
|
||||
res += "NarSize: " + std::to_string(narSize) + "\n";
|
||||
|
||||
res += "References: " + concatStringsSep(" ", shortRefs()) + "\n";
|
||||
res += "References: " + concatStringsSep(" ", shortRefs()) + "\n";
|
||||
|
||||
if (!deriver.empty())
|
||||
res += "Deriver: " + baseNameOf(deriver) + "\n";
|
||||
if (!deriver.empty()) res += "Deriver: " + baseNameOf(deriver) + "\n";
|
||||
|
||||
if (!system.empty())
|
||||
res += "System: " + system + "\n";
|
||||
if (!system.empty()) res += "System: " + system + "\n";
|
||||
|
||||
for (auto sig : sigs)
|
||||
res += "Sig: " + sig + "\n";
|
||||
for (auto sig : sigs) res += "Sig: " + sig + "\n";
|
||||
|
||||
if (!ca.empty())
|
||||
res += "CA: " + ca + "\n";
|
||||
if (!ca.empty()) res += "CA: " + ca + "\n";
|
||||
|
||||
return res;
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
25
third_party/nix/src/libstore/nar-info.hh
vendored
25
third_party/nix/src/libstore/nar-info.hh
vendored
|
|
@ -1,24 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
#include "store-api.hh"
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct NarInfo : ValidPathInfo
|
||||
{
|
||||
std::string url;
|
||||
std::string compression;
|
||||
Hash fileHash;
|
||||
uint64_t fileSize = 0;
|
||||
std::string system;
|
||||
struct NarInfo : ValidPathInfo {
|
||||
std::string url;
|
||||
std::string compression;
|
||||
Hash fileHash;
|
||||
uint64_t fileSize = 0;
|
||||
std::string system;
|
||||
|
||||
NarInfo() { }
|
||||
NarInfo(const ValidPathInfo & info) : ValidPathInfo(info) { }
|
||||
NarInfo(const Store & store, const std::string & s, const std::string & whence);
|
||||
NarInfo() {}
|
||||
NarInfo(const ValidPathInfo& info) : ValidPathInfo(info) {}
|
||||
NarInfo(const Store& store, const std::string& s, const std::string& whence);
|
||||
|
||||
std::string to_string() const;
|
||||
std::string to_string() const;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
475
third_party/nix/src/libstore/optimise-store.cc
vendored
475
third_party/nix/src/libstore/optimise-store.cc
vendored
|
|
@ -1,302 +1,285 @@
|
|||
#include "util.hh"
|
||||
#include "local-store.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
|
||||
#include "globals.hh"
|
||||
#include "local-store.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
static void makeWritable(const Path & path)
|
||||
{
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st))
|
||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
||||
if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
|
||||
throw SysError(format("changing writability of '%1%'") % path);
|
||||
static void makeWritable(const Path& path) {
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st))
|
||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
||||
if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
|
||||
throw SysError(format("changing writability of '%1%'") % path);
|
||||
}
|
||||
|
||||
|
||||
struct MakeReadOnly
|
||||
{
|
||||
Path path;
|
||||
MakeReadOnly(const Path & path) : path(path) { }
|
||||
~MakeReadOnly()
|
||||
{
|
||||
try {
|
||||
/* This will make the path read-only. */
|
||||
if (path != "") canonicaliseTimestampAndPermissions(path);
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
struct MakeReadOnly {
|
||||
Path path;
|
||||
MakeReadOnly(const Path& path) : path(path) {}
|
||||
~MakeReadOnly() {
|
||||
try {
|
||||
/* This will make the path read-only. */
|
||||
if (path != "") canonicaliseTimestampAndPermissions(path);
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LocalStore::InodeHash LocalStore::loadInodeHash() {
|
||||
debug("loading hash inodes in memory");
|
||||
InodeHash inodeHash;
|
||||
|
||||
LocalStore::InodeHash LocalStore::loadInodeHash()
|
||||
{
|
||||
debug("loading hash inodes in memory");
|
||||
InodeHash inodeHash;
|
||||
AutoCloseDir dir(opendir(linksDir.c_str()));
|
||||
if (!dir) throw SysError(format("opening directory '%1%'") % linksDir);
|
||||
|
||||
AutoCloseDir dir(opendir(linksDir.c_str()));
|
||||
if (!dir) throw SysError(format("opening directory '%1%'") % linksDir);
|
||||
struct dirent* dirent;
|
||||
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
||||
checkInterrupt();
|
||||
// We don't care if we hit non-hash files, anything goes
|
||||
inodeHash.insert(dirent->d_ino);
|
||||
}
|
||||
if (errno) throw SysError(format("reading directory '%1%'") % linksDir);
|
||||
|
||||
struct dirent * dirent;
|
||||
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
||||
checkInterrupt();
|
||||
// We don't care if we hit non-hash files, anything goes
|
||||
inodeHash.insert(dirent->d_ino);
|
||||
}
|
||||
if (errno) throw SysError(format("reading directory '%1%'") % linksDir);
|
||||
printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size());
|
||||
|
||||
printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size());
|
||||
|
||||
return inodeHash;
|
||||
return inodeHash;
|
||||
}
|
||||
|
||||
Strings LocalStore::readDirectoryIgnoringInodes(const Path& path,
|
||||
const InodeHash& inodeHash) {
|
||||
Strings names;
|
||||
|
||||
Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHash & inodeHash)
|
||||
{
|
||||
Strings names;
|
||||
AutoCloseDir dir(opendir(path.c_str()));
|
||||
if (!dir) throw SysError(format("opening directory '%1%'") % path);
|
||||
|
||||
AutoCloseDir dir(opendir(path.c_str()));
|
||||
if (!dir) throw SysError(format("opening directory '%1%'") % path);
|
||||
|
||||
struct dirent * dirent;
|
||||
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
||||
checkInterrupt();
|
||||
|
||||
if (inodeHash.count(dirent->d_ino)) {
|
||||
debug(format("'%1%' is already linked") % dirent->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
string name = dirent->d_name;
|
||||
if (name == "." || name == "..") continue;
|
||||
names.push_back(name);
|
||||
}
|
||||
if (errno) throw SysError(format("reading directory '%1%'") % path);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
||||
const Path & path, InodeHash & inodeHash)
|
||||
{
|
||||
struct dirent* dirent;
|
||||
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
||||
checkInterrupt();
|
||||
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st))
|
||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
||||
if (inodeHash.count(dirent->d_ino)) {
|
||||
debug(format("'%1%' is already linked") % dirent->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
string name = dirent->d_name;
|
||||
if (name == "." || name == "..") continue;
|
||||
names.push_back(name);
|
||||
}
|
||||
if (errno) throw SysError(format("reading directory '%1%'") % path);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
void LocalStore::optimisePath_(Activity* act, OptimiseStats& stats,
|
||||
const Path& path, InodeHash& inodeHash) {
|
||||
checkInterrupt();
|
||||
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st))
|
||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
||||
|
||||
#if __APPLE__
|
||||
/* HFS/macOS has some undocumented security feature disabling hardlinking for
|
||||
special files within .app dirs. *.app/Contents/PkgInfo and
|
||||
*.app/Contents/Resources/\*.lproj seem to be the only paths affected. See
|
||||
https://github.com/NixOS/nix/issues/1443 for more discussion. */
|
||||
/* HFS/macOS has some undocumented security feature disabling hardlinking for
|
||||
special files within .app dirs. *.app/Contents/PkgInfo and
|
||||
*.app/Contents/Resources/\*.lproj seem to be the only paths affected. See
|
||||
https://github.com/NixOS/nix/issues/1443 for more discussion. */
|
||||
|
||||
if (std::regex_search(path, std::regex("\\.app/Contents/.+$")))
|
||||
{
|
||||
debug(format("'%1%' is not allowed to be linked in macOS") % path);
|
||||
return;
|
||||
}
|
||||
if (std::regex_search(path, std::regex("\\.app/Contents/.+$"))) {
|
||||
debug(format("'%1%' is not allowed to be linked in macOS") % path);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
Strings names = readDirectoryIgnoringInodes(path, inodeHash);
|
||||
for (auto & i : names)
|
||||
optimisePath_(act, stats, path + "/" + i, inodeHash);
|
||||
return;
|
||||
}
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
Strings names = readDirectoryIgnoringInodes(path, inodeHash);
|
||||
for (auto& i : names) optimisePath_(act, stats, path + "/" + i, inodeHash);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We can hard link regular files and maybe symlinks. */
|
||||
if (!S_ISREG(st.st_mode)
|
||||
/* We can hard link regular files and maybe symlinks. */
|
||||
if (!S_ISREG(st.st_mode)
|
||||
#if CAN_LINK_SYMLINK
|
||||
&& !S_ISLNK(st.st_mode)
|
||||
&& !S_ISLNK(st.st_mode)
|
||||
#endif
|
||||
) return;
|
||||
)
|
||||
return;
|
||||
|
||||
/* Sometimes SNAFUs can cause files in the Nix store to be
|
||||
modified, in particular when running programs as root under
|
||||
NixOS (example: $fontconfig/var/cache being modified). Skip
|
||||
those files. FIXME: check the modification time. */
|
||||
if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
|
||||
printError(format("skipping suspicious writable file '%1%'") % path);
|
||||
/* Sometimes SNAFUs can cause files in the Nix store to be
|
||||
modified, in particular when running programs as root under
|
||||
NixOS (example: $fontconfig/var/cache being modified). Skip
|
||||
those files. FIXME: check the modification time. */
|
||||
if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
|
||||
printError(format("skipping suspicious writable file '%1%'") % path);
|
||||
return;
|
||||
}
|
||||
|
||||
/* This can still happen on top-level files. */
|
||||
if (st.st_nlink > 1 && inodeHash.count(st.st_ino)) {
|
||||
debug(format("'%1%' is already linked, with %2% other file(s)") % path %
|
||||
(st.st_nlink - 2));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Hash the file. Note that hashPath() returns the hash over the
|
||||
NAR serialisation, which includes the execute bit on the file.
|
||||
Thus, executable and non-executable files with the same
|
||||
contents *won't* be linked (which is good because otherwise the
|
||||
permissions would be screwed up).
|
||||
|
||||
Also note that if `path' is a symlink, then we're hashing the
|
||||
contents of the symlink (i.e. the result of readlink()), not
|
||||
the contents of the target (which may not even exist). */
|
||||
Hash hash = hashPath(htSHA256, path).first;
|
||||
debug(format("'%1%' has hash '%2%'") % path % hash.to_string());
|
||||
|
||||
/* Check if this is a known hash. */
|
||||
Path linkPath = linksDir + "/" + hash.to_string(Base32, false);
|
||||
|
||||
retry:
|
||||
if (!pathExists(linkPath)) {
|
||||
/* Nope, create a hard link in the links directory. */
|
||||
if (link(path.c_str(), linkPath.c_str()) == 0) {
|
||||
inodeHash.insert(st.st_ino);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (errno) {
|
||||
case EEXIST:
|
||||
/* Fall through if another process created ‘linkPath’ before
|
||||
we did. */
|
||||
break;
|
||||
|
||||
case ENOSPC:
|
||||
/* On ext4, that probably means the directory index is
|
||||
full. When that happens, it's fine to ignore it: we
|
||||
just effectively disable deduplication of this
|
||||
file. */
|
||||
printInfo("cannot link '%s' to '%s': %s", linkPath, path,
|
||||
strerror(errno));
|
||||
return;
|
||||
|
||||
default:
|
||||
throw SysError("cannot link '%1%' to '%2%'", linkPath, path);
|
||||
}
|
||||
}
|
||||
|
||||
/* This can still happen on top-level files. */
|
||||
if (st.st_nlink > 1 && inodeHash.count(st.st_ino)) {
|
||||
debug(format("'%1%' is already linked, with %2% other file(s)") % path % (st.st_nlink - 2));
|
||||
return;
|
||||
/* Yes! We've seen a file with the same contents. Replace the
|
||||
current file with a hard link to that file. */
|
||||
struct stat stLink;
|
||||
if (lstat(linkPath.c_str(), &stLink))
|
||||
throw SysError(format("getting attributes of path '%1%'") % linkPath);
|
||||
|
||||
if (st.st_ino == stLink.st_ino) {
|
||||
debug(format("'%1%' is already linked to '%2%'") % path % linkPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (st.st_size != stLink.st_size) {
|
||||
printError(format("removing corrupted link '%1%'") % linkPath);
|
||||
unlink(linkPath.c_str());
|
||||
goto retry;
|
||||
}
|
||||
|
||||
printMsg(lvlTalkative, format("linking '%1%' to '%2%'") % path % linkPath);
|
||||
|
||||
/* Make the containing directory writable, but only if it's not
|
||||
the store itself (we don't want or need to mess with its
|
||||
permissions). */
|
||||
bool mustToggle = dirOf(path) != realStoreDir;
|
||||
if (mustToggle) makeWritable(dirOf(path));
|
||||
|
||||
/* When we're done, make the directory read-only again and reset
|
||||
its timestamp back to 0. */
|
||||
MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : "");
|
||||
|
||||
Path tempLink =
|
||||
(format("%1%/.tmp-link-%2%-%3%") % realStoreDir % getpid() % random())
|
||||
.str();
|
||||
|
||||
if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
|
||||
if (errno == EMLINK) {
|
||||
/* Too many links to the same file (>= 32000 on most file
|
||||
systems). This is likely to happen with empty files.
|
||||
Just shrug and ignore. */
|
||||
if (st.st_size)
|
||||
printInfo(format("'%1%' has maximum number of links") % linkPath);
|
||||
return;
|
||||
}
|
||||
throw SysError("cannot link '%1%' to '%2%'", tempLink, linkPath);
|
||||
}
|
||||
|
||||
/* Hash the file. Note that hashPath() returns the hash over the
|
||||
NAR serialisation, which includes the execute bit on the file.
|
||||
Thus, executable and non-executable files with the same
|
||||
contents *won't* be linked (which is good because otherwise the
|
||||
permissions would be screwed up).
|
||||
|
||||
Also note that if `path' is a symlink, then we're hashing the
|
||||
contents of the symlink (i.e. the result of readlink()), not
|
||||
the contents of the target (which may not even exist). */
|
||||
Hash hash = hashPath(htSHA256, path).first;
|
||||
debug(format("'%1%' has hash '%2%'") % path % hash.to_string());
|
||||
|
||||
/* Check if this is a known hash. */
|
||||
Path linkPath = linksDir + "/" + hash.to_string(Base32, false);
|
||||
|
||||
retry:
|
||||
if (!pathExists(linkPath)) {
|
||||
/* Nope, create a hard link in the links directory. */
|
||||
if (link(path.c_str(), linkPath.c_str()) == 0) {
|
||||
inodeHash.insert(st.st_ino);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (errno) {
|
||||
case EEXIST:
|
||||
/* Fall through if another process created ‘linkPath’ before
|
||||
we did. */
|
||||
break;
|
||||
|
||||
case ENOSPC:
|
||||
/* On ext4, that probably means the directory index is
|
||||
full. When that happens, it's fine to ignore it: we
|
||||
just effectively disable deduplication of this
|
||||
file. */
|
||||
printInfo("cannot link '%s' to '%s': %s", linkPath, path, strerror(errno));
|
||||
return;
|
||||
|
||||
default:
|
||||
throw SysError("cannot link '%1%' to '%2%'", linkPath, path);
|
||||
}
|
||||
/* Atomically replace the old file with the new hard link. */
|
||||
if (rename(tempLink.c_str(), path.c_str()) == -1) {
|
||||
if (unlink(tempLink.c_str()) == -1)
|
||||
printError(format("unable to unlink '%1%'") % tempLink);
|
||||
if (errno == EMLINK) {
|
||||
/* Some filesystems generate too many links on the rename,
|
||||
rather than on the original link. (Probably it
|
||||
temporarily increases the st_nlink field before
|
||||
decreasing it again.) */
|
||||
debug("'%s' has reached maximum number of links", linkPath);
|
||||
return;
|
||||
}
|
||||
throw SysError(format("cannot rename '%1%' to '%2%'") % tempLink % path);
|
||||
}
|
||||
|
||||
/* Yes! We've seen a file with the same contents. Replace the
|
||||
current file with a hard link to that file. */
|
||||
struct stat stLink;
|
||||
if (lstat(linkPath.c_str(), &stLink))
|
||||
throw SysError(format("getting attributes of path '%1%'") % linkPath);
|
||||
stats.filesLinked++;
|
||||
stats.bytesFreed += st.st_size;
|
||||
stats.blocksFreed += st.st_blocks;
|
||||
|
||||
if (st.st_ino == stLink.st_ino) {
|
||||
debug(format("'%1%' is already linked to '%2%'") % path % linkPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (st.st_size != stLink.st_size) {
|
||||
printError(format("removing corrupted link '%1%'") % linkPath);
|
||||
unlink(linkPath.c_str());
|
||||
goto retry;
|
||||
}
|
||||
|
||||
printMsg(lvlTalkative, format("linking '%1%' to '%2%'") % path % linkPath);
|
||||
|
||||
/* Make the containing directory writable, but only if it's not
|
||||
the store itself (we don't want or need to mess with its
|
||||
permissions). */
|
||||
bool mustToggle = dirOf(path) != realStoreDir;
|
||||
if (mustToggle) makeWritable(dirOf(path));
|
||||
|
||||
/* When we're done, make the directory read-only again and reset
|
||||
its timestamp back to 0. */
|
||||
MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : "");
|
||||
|
||||
Path tempLink = (format("%1%/.tmp-link-%2%-%3%")
|
||||
% realStoreDir % getpid() % random()).str();
|
||||
|
||||
if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
|
||||
if (errno == EMLINK) {
|
||||
/* Too many links to the same file (>= 32000 on most file
|
||||
systems). This is likely to happen with empty files.
|
||||
Just shrug and ignore. */
|
||||
if (st.st_size)
|
||||
printInfo(format("'%1%' has maximum number of links") % linkPath);
|
||||
return;
|
||||
}
|
||||
throw SysError("cannot link '%1%' to '%2%'", tempLink, linkPath);
|
||||
}
|
||||
|
||||
/* Atomically replace the old file with the new hard link. */
|
||||
if (rename(tempLink.c_str(), path.c_str()) == -1) {
|
||||
if (unlink(tempLink.c_str()) == -1)
|
||||
printError(format("unable to unlink '%1%'") % tempLink);
|
||||
if (errno == EMLINK) {
|
||||
/* Some filesystems generate too many links on the rename,
|
||||
rather than on the original link. (Probably it
|
||||
temporarily increases the st_nlink field before
|
||||
decreasing it again.) */
|
||||
debug("'%s' has reached maximum number of links", linkPath);
|
||||
return;
|
||||
}
|
||||
throw SysError(format("cannot rename '%1%' to '%2%'") % tempLink % path);
|
||||
}
|
||||
|
||||
stats.filesLinked++;
|
||||
stats.bytesFreed += st.st_size;
|
||||
stats.blocksFreed += st.st_blocks;
|
||||
|
||||
if (act)
|
||||
act->result(resFileLinked, st.st_size, st.st_blocks);
|
||||
if (act) act->result(resFileLinked, st.st_size, st.st_blocks);
|
||||
}
|
||||
|
||||
void LocalStore::optimiseStore(OptimiseStats& stats) {
|
||||
Activity act(*logger, actOptimiseStore);
|
||||
|
||||
void LocalStore::optimiseStore(OptimiseStats & stats)
|
||||
{
|
||||
Activity act(*logger, actOptimiseStore);
|
||||
PathSet paths = queryAllValidPaths();
|
||||
InodeHash inodeHash = loadInodeHash();
|
||||
|
||||
PathSet paths = queryAllValidPaths();
|
||||
InodeHash inodeHash = loadInodeHash();
|
||||
act.progress(0, paths.size());
|
||||
|
||||
act.progress(0, paths.size());
|
||||
uint64_t done = 0;
|
||||
|
||||
uint64_t done = 0;
|
||||
|
||||
for (auto & i : paths) {
|
||||
addTempRoot(i);
|
||||
if (!isValidPath(i)) continue; /* path was GC'ed, probably */
|
||||
{
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("optimising path '%s'", i));
|
||||
optimisePath_(&act, stats, realStoreDir + "/" + baseNameOf(i), inodeHash);
|
||||
}
|
||||
done++;
|
||||
act.progress(done, paths.size());
|
||||
for (auto& i : paths) {
|
||||
addTempRoot(i);
|
||||
if (!isValidPath(i)) continue; /* path was GC'ed, probably */
|
||||
{
|
||||
Activity act(*logger, lvlTalkative, actUnknown,
|
||||
fmt("optimising path '%s'", i));
|
||||
optimisePath_(&act, stats, realStoreDir + "/" + baseNameOf(i), inodeHash);
|
||||
}
|
||||
done++;
|
||||
act.progress(done, paths.size());
|
||||
}
|
||||
}
|
||||
|
||||
static string showBytes(unsigned long long bytes)
|
||||
{
|
||||
return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
|
||||
static string showBytes(unsigned long long bytes) {
|
||||
return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
|
||||
}
|
||||
|
||||
void LocalStore::optimiseStore()
|
||||
{
|
||||
OptimiseStats stats;
|
||||
void LocalStore::optimiseStore() {
|
||||
OptimiseStats stats;
|
||||
|
||||
optimiseStore(stats);
|
||||
optimiseStore(stats);
|
||||
|
||||
printInfo(
|
||||
format("%1% freed by hard-linking %2% files")
|
||||
% showBytes(stats.bytesFreed)
|
||||
% stats.filesLinked);
|
||||
printInfo(format("%1% freed by hard-linking %2% files") %
|
||||
showBytes(stats.bytesFreed) % stats.filesLinked);
|
||||
}
|
||||
|
||||
void LocalStore::optimisePath(const Path & path)
|
||||
{
|
||||
OptimiseStats stats;
|
||||
InodeHash inodeHash;
|
||||
void LocalStore::optimisePath(const Path& path) {
|
||||
OptimiseStats stats;
|
||||
InodeHash inodeHash;
|
||||
|
||||
if (settings.autoOptimiseStore) optimisePath_(nullptr, stats, path, inodeHash);
|
||||
if (settings.autoOptimiseStore)
|
||||
optimisePath_(nullptr, stats, path, inodeHash);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
180
third_party/nix/src/libstore/parsed-derivations.cc
vendored
180
third_party/nix/src/libstore/parsed-derivations.cc
vendored
|
|
@ -2,115 +2,115 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
ParsedDerivation::ParsedDerivation(const Path & drvPath, BasicDerivation & drv)
|
||||
: drvPath(drvPath), drv(drv)
|
||||
{
|
||||
/* Parse the __json attribute, if any. */
|
||||
auto jsonAttr = drv.env.find("__json");
|
||||
if (jsonAttr != drv.env.end()) {
|
||||
try {
|
||||
structuredAttrs = nlohmann::json::parse(jsonAttr->second);
|
||||
} catch (std::exception & e) {
|
||||
throw Error("cannot process __json attribute of '%s': %s", drvPath, e.what());
|
||||
}
|
||||
ParsedDerivation::ParsedDerivation(const Path& drvPath, BasicDerivation& drv)
|
||||
: drvPath(drvPath), drv(drv) {
|
||||
/* Parse the __json attribute, if any. */
|
||||
auto jsonAttr = drv.env.find("__json");
|
||||
if (jsonAttr != drv.env.end()) {
|
||||
try {
|
||||
structuredAttrs = nlohmann::json::parse(jsonAttr->second);
|
||||
} catch (std::exception& e) {
|
||||
throw Error("cannot process __json attribute of '%s': %s", drvPath,
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> ParsedDerivation::getStringAttr(const std::string & name) const
|
||||
{
|
||||
if (structuredAttrs) {
|
||||
auto i = structuredAttrs->find(name);
|
||||
if (i == structuredAttrs->end())
|
||||
return {};
|
||||
else {
|
||||
if (!i->is_string())
|
||||
throw Error("attribute '%s' of derivation '%s' must be a string", name, drvPath);
|
||||
return i->get<std::string>();
|
||||
}
|
||||
} else {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end())
|
||||
return {};
|
||||
else
|
||||
return i->second;
|
||||
std::optional<std::string> ParsedDerivation::getStringAttr(
|
||||
const std::string& name) const {
|
||||
if (structuredAttrs) {
|
||||
auto i = structuredAttrs->find(name);
|
||||
if (i == structuredAttrs->end())
|
||||
return {};
|
||||
else {
|
||||
if (!i->is_string())
|
||||
throw Error("attribute '%s' of derivation '%s' must be a string", name,
|
||||
drvPath);
|
||||
return i->get<std::string>();
|
||||
}
|
||||
} else {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end())
|
||||
return {};
|
||||
else
|
||||
return i->second;
|
||||
}
|
||||
}
|
||||
|
||||
bool ParsedDerivation::getBoolAttr(const std::string & name, bool def) const
|
||||
{
|
||||
if (structuredAttrs) {
|
||||
auto i = structuredAttrs->find(name);
|
||||
if (i == structuredAttrs->end())
|
||||
return def;
|
||||
else {
|
||||
if (!i->is_boolean())
|
||||
throw Error("attribute '%s' of derivation '%s' must be a Boolean", name, drvPath);
|
||||
return i->get<bool>();
|
||||
}
|
||||
} else {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end())
|
||||
return def;
|
||||
else
|
||||
return i->second == "1";
|
||||
bool ParsedDerivation::getBoolAttr(const std::string& name, bool def) const {
|
||||
if (structuredAttrs) {
|
||||
auto i = structuredAttrs->find(name);
|
||||
if (i == structuredAttrs->end())
|
||||
return def;
|
||||
else {
|
||||
if (!i->is_boolean())
|
||||
throw Error("attribute '%s' of derivation '%s' must be a Boolean", name,
|
||||
drvPath);
|
||||
return i->get<bool>();
|
||||
}
|
||||
} else {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end())
|
||||
return def;
|
||||
else
|
||||
return i->second == "1";
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Strings> ParsedDerivation::getStringsAttr(const std::string & name) const
|
||||
{
|
||||
if (structuredAttrs) {
|
||||
auto i = structuredAttrs->find(name);
|
||||
if (i == structuredAttrs->end())
|
||||
return {};
|
||||
else {
|
||||
if (!i->is_array())
|
||||
throw Error("attribute '%s' of derivation '%s' must be a list of strings", name, drvPath);
|
||||
Strings res;
|
||||
for (auto j = i->begin(); j != i->end(); ++j) {
|
||||
if (!j->is_string())
|
||||
throw Error("attribute '%s' of derivation '%s' must be a list of strings", name, drvPath);
|
||||
res.push_back(j->get<std::string>());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end())
|
||||
return {};
|
||||
else
|
||||
return tokenizeString<Strings>(i->second);
|
||||
std::optional<Strings> ParsedDerivation::getStringsAttr(
|
||||
const std::string& name) const {
|
||||
if (structuredAttrs) {
|
||||
auto i = structuredAttrs->find(name);
|
||||
if (i == structuredAttrs->end())
|
||||
return {};
|
||||
else {
|
||||
if (!i->is_array())
|
||||
throw Error(
|
||||
"attribute '%s' of derivation '%s' must be a list of strings", name,
|
||||
drvPath);
|
||||
Strings res;
|
||||
for (auto j = i->begin(); j != i->end(); ++j) {
|
||||
if (!j->is_string())
|
||||
throw Error(
|
||||
"attribute '%s' of derivation '%s' must be a list of strings",
|
||||
name, drvPath);
|
||||
res.push_back(j->get<std::string>());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
auto i = drv.env.find(name);
|
||||
if (i == drv.env.end())
|
||||
return {};
|
||||
else
|
||||
return tokenizeString<Strings>(i->second);
|
||||
}
|
||||
}
|
||||
|
||||
StringSet ParsedDerivation::getRequiredSystemFeatures() const
|
||||
{
|
||||
StringSet res;
|
||||
for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings()))
|
||||
res.insert(i);
|
||||
return res;
|
||||
StringSet ParsedDerivation::getRequiredSystemFeatures() const {
|
||||
StringSet res;
|
||||
for (auto& i : getStringsAttr("requiredSystemFeatures").value_or(Strings()))
|
||||
res.insert(i);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ParsedDerivation::canBuildLocally() const
|
||||
{
|
||||
if (drv.platform != settings.thisSystem.get()
|
||||
&& !settings.extraPlatforms.get().count(drv.platform)
|
||||
&& !drv.isBuiltin())
|
||||
return false;
|
||||
bool ParsedDerivation::canBuildLocally() const {
|
||||
if (drv.platform != settings.thisSystem.get() &&
|
||||
!settings.extraPlatforms.get().count(drv.platform) && !drv.isBuiltin())
|
||||
return false;
|
||||
|
||||
for (auto & feature : getRequiredSystemFeatures())
|
||||
if (!settings.systemFeatures.get().count(feature)) return false;
|
||||
for (auto& feature : getRequiredSystemFeatures())
|
||||
if (!settings.systemFeatures.get().count(feature)) return false;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParsedDerivation::willBuildLocally() const
|
||||
{
|
||||
return getBoolAttr("preferLocalBuild") && canBuildLocally();
|
||||
bool ParsedDerivation::willBuildLocally() const {
|
||||
return getBoolAttr("preferLocalBuild") && canBuildLocally();
|
||||
}
|
||||
|
||||
bool ParsedDerivation::substitutesAllowed() const
|
||||
{
|
||||
return getBoolAttr("allowSubstitutes", true);
|
||||
bool ParsedDerivation::substitutesAllowed() const {
|
||||
return getBoolAttr("allowSubstitutes", true);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -1,37 +1,33 @@
|
|||
#include "derivations.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "derivations.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class ParsedDerivation
|
||||
{
|
||||
Path drvPath;
|
||||
BasicDerivation & drv;
|
||||
std::optional<nlohmann::json> structuredAttrs;
|
||||
class ParsedDerivation {
|
||||
Path drvPath;
|
||||
BasicDerivation& drv;
|
||||
std::optional<nlohmann::json> structuredAttrs;
|
||||
|
||||
public:
|
||||
public:
|
||||
ParsedDerivation(const Path& drvPath, BasicDerivation& drv);
|
||||
|
||||
ParsedDerivation(const Path & drvPath, BasicDerivation & drv);
|
||||
const std::optional<nlohmann::json>& getStructuredAttrs() const {
|
||||
return structuredAttrs;
|
||||
}
|
||||
|
||||
const std::optional<nlohmann::json> & getStructuredAttrs() const
|
||||
{
|
||||
return structuredAttrs;
|
||||
}
|
||||
std::optional<std::string> getStringAttr(const std::string& name) const;
|
||||
|
||||
std::optional<std::string> getStringAttr(const std::string & name) const;
|
||||
bool getBoolAttr(const std::string& name, bool def = false) const;
|
||||
|
||||
bool getBoolAttr(const std::string & name, bool def = false) const;
|
||||
std::optional<Strings> getStringsAttr(const std::string& name) const;
|
||||
|
||||
std::optional<Strings> getStringsAttr(const std::string & name) const;
|
||||
StringSet getRequiredSystemFeatures() const;
|
||||
|
||||
StringSet getRequiredSystemFeatures() const;
|
||||
bool canBuildLocally() const;
|
||||
|
||||
bool canBuildLocally() const;
|
||||
bool willBuildLocally() const;
|
||||
|
||||
bool willBuildLocally() const;
|
||||
|
||||
bool substitutesAllowed() const;
|
||||
bool substitutesAllowed() const;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
290
third_party/nix/src/libstore/pathlocks.cc
vendored
290
third_party/nix/src/libstore/pathlocks.cc
vendored
|
|
@ -1,178 +1,156 @@
|
|||
#include "pathlocks.hh"
|
||||
#include "util.hh"
|
||||
#include "sync.hh"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
#include "sync.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
AutoCloseFD openLockFile(const Path& path, bool create) {
|
||||
AutoCloseFD fd;
|
||||
|
||||
fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600);
|
||||
if (!fd && (create || errno != ENOENT))
|
||||
throw SysError(format("opening lock file '%1%'") % path);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
void deleteLockFile(const Path& path, int fd) {
|
||||
/* Get rid of the lock file. Have to be careful not to introduce
|
||||
races. Write a (meaningless) token to the file to indicate to
|
||||
other processes waiting on this lock that the lock is stale
|
||||
(deleted). */
|
||||
unlink(path.c_str());
|
||||
writeFull(fd, "d");
|
||||
/* Note that the result of unlink() is ignored; removing the lock
|
||||
file is an optimisation, not a necessity. */
|
||||
}
|
||||
|
||||
bool lockFile(int fd, LockType lockType, bool wait) {
|
||||
int type;
|
||||
if (lockType == ltRead)
|
||||
type = LOCK_SH;
|
||||
else if (lockType == ltWrite)
|
||||
type = LOCK_EX;
|
||||
else if (lockType == ltNone)
|
||||
type = LOCK_UN;
|
||||
else
|
||||
abort();
|
||||
|
||||
if (wait) {
|
||||
while (flock(fd, type) != 0) {
|
||||
checkInterrupt();
|
||||
if (errno != EINTR)
|
||||
throw SysError(format("acquiring/releasing lock"));
|
||||
else
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
while (flock(fd, type | LOCK_NB) != 0) {
|
||||
checkInterrupt();
|
||||
if (errno == EWOULDBLOCK) return false;
|
||||
if (errno != EINTR) throw SysError(format("acquiring/releasing lock"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PathLocks::PathLocks() : deletePaths(false) {}
|
||||
|
||||
PathLocks::PathLocks(const PathSet& paths, const string& waitMsg)
|
||||
: deletePaths(false) {
|
||||
lockPaths(paths, waitMsg);
|
||||
}
|
||||
|
||||
bool PathLocks::lockPaths(const PathSet& paths, const string& waitMsg,
|
||||
bool wait) {
|
||||
assert(fds.empty());
|
||||
|
||||
/* Note that `fds' is built incrementally so that the destructor
|
||||
will only release those locks that we have already acquired. */
|
||||
|
||||
/* Acquire the lock for each path in sorted order. This ensures
|
||||
that locks are always acquired in the same order, thus
|
||||
preventing deadlocks. */
|
||||
for (auto& path : paths) {
|
||||
checkInterrupt();
|
||||
Path lockPath = path + ".lock";
|
||||
|
||||
debug(format("locking path '%1%'") % path);
|
||||
|
||||
AutoCloseFD openLockFile(const Path & path, bool create)
|
||||
{
|
||||
AutoCloseFD fd;
|
||||
|
||||
fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600);
|
||||
if (!fd && (create || errno != ENOENT))
|
||||
throw SysError(format("opening lock file '%1%'") % path);
|
||||
while (1) {
|
||||
/* Open/create the lock file. */
|
||||
fd = openLockFile(lockPath, true);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
void deleteLockFile(const Path & path, int fd)
|
||||
{
|
||||
/* Get rid of the lock file. Have to be careful not to introduce
|
||||
races. Write a (meaningless) token to the file to indicate to
|
||||
other processes waiting on this lock that the lock is stale
|
||||
(deleted). */
|
||||
unlink(path.c_str());
|
||||
writeFull(fd, "d");
|
||||
/* Note that the result of unlink() is ignored; removing the lock
|
||||
file is an optimisation, not a necessity. */
|
||||
}
|
||||
|
||||
|
||||
bool lockFile(int fd, LockType lockType, bool wait)
|
||||
{
|
||||
int type;
|
||||
if (lockType == ltRead) type = LOCK_SH;
|
||||
else if (lockType == ltWrite) type = LOCK_EX;
|
||||
else if (lockType == ltNone) type = LOCK_UN;
|
||||
else abort();
|
||||
|
||||
if (wait) {
|
||||
while (flock(fd, type) != 0) {
|
||||
checkInterrupt();
|
||||
if (errno != EINTR)
|
||||
throw SysError(format("acquiring/releasing lock"));
|
||||
else
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
while (flock(fd, type | LOCK_NB) != 0) {
|
||||
checkInterrupt();
|
||||
if (errno == EWOULDBLOCK) return false;
|
||||
if (errno != EINTR)
|
||||
throw SysError(format("acquiring/releasing lock"));
|
||||
/* Acquire an exclusive lock. */
|
||||
if (!lockFile(fd.get(), ltWrite, false)) {
|
||||
if (wait) {
|
||||
if (waitMsg != "") printError(waitMsg);
|
||||
lockFile(fd.get(), ltWrite, true);
|
||||
} else {
|
||||
/* Failed to lock this path; release all other
|
||||
locks. */
|
||||
unlock();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
debug(format("lock acquired on '%1%'") % lockPath);
|
||||
|
||||
/* Check that the lock file hasn't become stale (i.e.,
|
||||
hasn't been unlinked). */
|
||||
struct stat st;
|
||||
if (fstat(fd.get(), &st) == -1)
|
||||
throw SysError(format("statting lock file '%1%'") % lockPath);
|
||||
if (st.st_size != 0)
|
||||
/* This lock file has been unlinked, so we're holding
|
||||
a lock on a deleted file. This means that other
|
||||
processes may create and acquire a lock on
|
||||
`lockPath', and proceed. So we must retry. */
|
||||
debug(format("open lock file '%1%' has become stale") % lockPath);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
/* Use borrow so that the descriptor isn't closed. */
|
||||
fds.push_back(FDPair(fd.release(), lockPath));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
PathLocks::PathLocks()
|
||||
: deletePaths(false)
|
||||
{
|
||||
PathLocks::~PathLocks() {
|
||||
try {
|
||||
unlock();
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
void PathLocks::unlock() {
|
||||
for (auto& i : fds) {
|
||||
if (deletePaths) deleteLockFile(i.second, i.first);
|
||||
|
||||
PathLocks::PathLocks(const PathSet & paths, const string & waitMsg)
|
||||
: deletePaths(false)
|
||||
{
|
||||
lockPaths(paths, waitMsg);
|
||||
if (close(i.first) == -1)
|
||||
printError(format("error (ignored): cannot close lock file on '%1%'") %
|
||||
i.second);
|
||||
|
||||
debug(format("lock released on '%1%'") % i.second);
|
||||
}
|
||||
|
||||
fds.clear();
|
||||
}
|
||||
|
||||
|
||||
bool PathLocks::lockPaths(const PathSet & paths,
|
||||
const string & waitMsg, bool wait)
|
||||
{
|
||||
assert(fds.empty());
|
||||
|
||||
/* Note that `fds' is built incrementally so that the destructor
|
||||
will only release those locks that we have already acquired. */
|
||||
|
||||
/* Acquire the lock for each path in sorted order. This ensures
|
||||
that locks are always acquired in the same order, thus
|
||||
preventing deadlocks. */
|
||||
for (auto & path : paths) {
|
||||
checkInterrupt();
|
||||
Path lockPath = path + ".lock";
|
||||
|
||||
debug(format("locking path '%1%'") % path);
|
||||
|
||||
AutoCloseFD fd;
|
||||
|
||||
while (1) {
|
||||
|
||||
/* Open/create the lock file. */
|
||||
fd = openLockFile(lockPath, true);
|
||||
|
||||
/* Acquire an exclusive lock. */
|
||||
if (!lockFile(fd.get(), ltWrite, false)) {
|
||||
if (wait) {
|
||||
if (waitMsg != "") printError(waitMsg);
|
||||
lockFile(fd.get(), ltWrite, true);
|
||||
} else {
|
||||
/* Failed to lock this path; release all other
|
||||
locks. */
|
||||
unlock();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
debug(format("lock acquired on '%1%'") % lockPath);
|
||||
|
||||
/* Check that the lock file hasn't become stale (i.e.,
|
||||
hasn't been unlinked). */
|
||||
struct stat st;
|
||||
if (fstat(fd.get(), &st) == -1)
|
||||
throw SysError(format("statting lock file '%1%'") % lockPath);
|
||||
if (st.st_size != 0)
|
||||
/* This lock file has been unlinked, so we're holding
|
||||
a lock on a deleted file. This means that other
|
||||
processes may create and acquire a lock on
|
||||
`lockPath', and proceed. So we must retry. */
|
||||
debug(format("open lock file '%1%' has become stale") % lockPath);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
/* Use borrow so that the descriptor isn't closed. */
|
||||
fds.push_back(FDPair(fd.release(), lockPath));
|
||||
}
|
||||
|
||||
return true;
|
||||
void PathLocks::setDeletion(bool deletePaths) {
|
||||
this->deletePaths = deletePaths;
|
||||
}
|
||||
|
||||
|
||||
PathLocks::~PathLocks()
|
||||
{
|
||||
try {
|
||||
unlock();
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PathLocks::unlock()
|
||||
{
|
||||
for (auto & i : fds) {
|
||||
if (deletePaths) deleteLockFile(i.second, i.first);
|
||||
|
||||
if (close(i.first) == -1)
|
||||
printError(
|
||||
format("error (ignored): cannot close lock file on '%1%'") % i.second);
|
||||
|
||||
debug(format("lock released on '%1%'") % i.second);
|
||||
}
|
||||
|
||||
fds.clear();
|
||||
}
|
||||
|
||||
|
||||
void PathLocks::setDeletion(bool deletePaths)
|
||||
{
|
||||
this->deletePaths = deletePaths;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
35
third_party/nix/src/libstore/pathlocks.hh
vendored
35
third_party/nix/src/libstore/pathlocks.hh
vendored
|
|
@ -7,32 +7,29 @@ namespace nix {
|
|||
/* Open (possibly create) a lock file and return the file descriptor.
|
||||
-1 is returned if create is false and the lock could not be opened
|
||||
because it doesn't exist. Any other error throws an exception. */
|
||||
AutoCloseFD openLockFile(const Path & path, bool create);
|
||||
AutoCloseFD openLockFile(const Path& path, bool create);
|
||||
|
||||
/* Delete an open lock file. */
|
||||
void deleteLockFile(const Path & path, int fd);
|
||||
void deleteLockFile(const Path& path, int fd);
|
||||
|
||||
enum LockType { ltRead, ltWrite, ltNone };
|
||||
|
||||
bool lockFile(int fd, LockType lockType, bool wait);
|
||||
|
||||
class PathLocks
|
||||
{
|
||||
private:
|
||||
typedef std::pair<int, Path> FDPair;
|
||||
list<FDPair> fds;
|
||||
bool deletePaths;
|
||||
class PathLocks {
|
||||
private:
|
||||
typedef std::pair<int, Path> FDPair;
|
||||
list<FDPair> fds;
|
||||
bool deletePaths;
|
||||
|
||||
public:
|
||||
PathLocks();
|
||||
PathLocks(const PathSet & paths,
|
||||
const string & waitMsg = "");
|
||||
bool lockPaths(const PathSet & _paths,
|
||||
const string & waitMsg = "",
|
||||
bool wait = true);
|
||||
~PathLocks();
|
||||
void unlock();
|
||||
void setDeletion(bool deletePaths);
|
||||
public:
|
||||
PathLocks();
|
||||
PathLocks(const PathSet& paths, const string& waitMsg = "");
|
||||
bool lockPaths(const PathSet& _paths, const string& waitMsg = "",
|
||||
bool wait = true);
|
||||
~PathLocks();
|
||||
void unlock();
|
||||
void setDeletion(bool deletePaths);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
379
third_party/nix/src/libstore/profiles.cc
vendored
379
third_party/nix/src/libstore/profiles.cc
vendored
|
|
@ -1,259 +1,226 @@
|
|||
#include "profiles.hh"
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include "store-api.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
static bool cmpGensByNumber(const Generation & a, const Generation & b)
|
||||
{
|
||||
return a.number < b.number;
|
||||
static bool cmpGensByNumber(const Generation& a, const Generation& b) {
|
||||
return a.number < b.number;
|
||||
}
|
||||
|
||||
|
||||
/* Parse a generation name of the format
|
||||
`<profilename>-<number>-link'. */
|
||||
static int parseName(const string & profileName, const string & name)
|
||||
{
|
||||
if (string(name, 0, profileName.size() + 1) != profileName + "-") return -1;
|
||||
string s = string(name, profileName.size() + 1);
|
||||
string::size_type p = s.find("-link");
|
||||
if (p == string::npos) return -1;
|
||||
static int parseName(const string& profileName, const string& name) {
|
||||
if (string(name, 0, profileName.size() + 1) != profileName + "-") return -1;
|
||||
string s = string(name, profileName.size() + 1);
|
||||
string::size_type p = s.find("-link");
|
||||
if (p == string::npos) return -1;
|
||||
int n;
|
||||
if (string2Int(string(s, 0, p), n) && n >= 0)
|
||||
return n;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
Generations findGenerations(Path profile, int& curGen) {
|
||||
Generations gens;
|
||||
|
||||
Path profileDir = dirOf(profile);
|
||||
string profileName = baseNameOf(profile);
|
||||
|
||||
for (auto& i : readDirectory(profileDir)) {
|
||||
int n;
|
||||
if (string2Int(string(s, 0, p), n) && n >= 0)
|
||||
return n;
|
||||
else
|
||||
return -1;
|
||||
if ((n = parseName(profileName, i.name)) != -1) {
|
||||
Generation gen;
|
||||
gen.path = profileDir + "/" + i.name;
|
||||
gen.number = n;
|
||||
struct stat st;
|
||||
if (lstat(gen.path.c_str(), &st) != 0)
|
||||
throw SysError(format("statting '%1%'") % gen.path);
|
||||
gen.creationTime = st.st_mtime;
|
||||
gens.push_back(gen);
|
||||
}
|
||||
}
|
||||
|
||||
gens.sort(cmpGensByNumber);
|
||||
|
||||
curGen = pathExists(profile) ? parseName(profileName, readLink(profile)) : -1;
|
||||
|
||||
return gens;
|
||||
}
|
||||
|
||||
static void makeName(const Path& profile, unsigned int num, Path& outLink) {
|
||||
Path prefix = (format("%1%-%2%") % profile % num).str();
|
||||
outLink = prefix + "-link";
|
||||
}
|
||||
|
||||
Path createGeneration(ref<LocalFSStore> store, Path profile, Path outPath) {
|
||||
/* The new generation number should be higher than old the
|
||||
previous ones. */
|
||||
int dummy;
|
||||
Generations gens = findGenerations(profile, dummy);
|
||||
|
||||
Generations findGenerations(Path profile, int & curGen)
|
||||
{
|
||||
Generations gens;
|
||||
unsigned int num;
|
||||
if (gens.size() > 0) {
|
||||
Generation last = gens.back();
|
||||
|
||||
Path profileDir = dirOf(profile);
|
||||
string profileName = baseNameOf(profile);
|
||||
if (readLink(last.path) == outPath) {
|
||||
/* We only create a new generation symlink if it differs
|
||||
from the last one.
|
||||
|
||||
for (auto & i : readDirectory(profileDir)) {
|
||||
int n;
|
||||
if ((n = parseName(profileName, i.name)) != -1) {
|
||||
Generation gen;
|
||||
gen.path = profileDir + "/" + i.name;
|
||||
gen.number = n;
|
||||
struct stat st;
|
||||
if (lstat(gen.path.c_str(), &st) != 0)
|
||||
throw SysError(format("statting '%1%'") % gen.path);
|
||||
gen.creationTime = st.st_mtime;
|
||||
gens.push_back(gen);
|
||||
}
|
||||
This helps keeping gratuitous installs/rebuilds from piling
|
||||
up uncontrolled numbers of generations, cluttering up the
|
||||
UI like grub. */
|
||||
return last.path;
|
||||
}
|
||||
|
||||
gens.sort(cmpGensByNumber);
|
||||
num = gens.back().number;
|
||||
} else {
|
||||
num = 0;
|
||||
}
|
||||
|
||||
curGen = pathExists(profile)
|
||||
? parseName(profileName, readLink(profile))
|
||||
: -1;
|
||||
/* Create the new generation. Note that addPermRoot() blocks if
|
||||
the garbage collector is running to prevent the stuff we've
|
||||
built from moving from the temporary roots (which the GC knows)
|
||||
to the permanent roots (of which the GC would have a stale
|
||||
view). If we didn't do it this way, the GC might remove the
|
||||
user environment etc. we've just built. */
|
||||
Path generation;
|
||||
makeName(profile, num + 1, generation);
|
||||
store->addPermRoot(outPath, generation, false, true);
|
||||
|
||||
return gens;
|
||||
return generation;
|
||||
}
|
||||
|
||||
|
||||
static void makeName(const Path & profile, unsigned int num,
|
||||
Path & outLink)
|
||||
{
|
||||
Path prefix = (format("%1%-%2%") % profile % num).str();
|
||||
outLink = prefix + "-link";
|
||||
static void removeFile(const Path& path) {
|
||||
if (remove(path.c_str()) == -1)
|
||||
throw SysError(format("cannot unlink '%1%'") % path);
|
||||
}
|
||||
|
||||
void deleteGeneration(const Path& profile, unsigned int gen) {
|
||||
Path generation;
|
||||
makeName(profile, gen, generation);
|
||||
removeFile(generation);
|
||||
}
|
||||
|
||||
Path createGeneration(ref<LocalFSStore> store, Path profile, Path outPath)
|
||||
{
|
||||
/* The new generation number should be higher than old the
|
||||
previous ones. */
|
||||
int dummy;
|
||||
Generations gens = findGenerations(profile, dummy);
|
||||
static void deleteGeneration2(const Path& profile, unsigned int gen,
|
||||
bool dryRun) {
|
||||
if (dryRun)
|
||||
printInfo(format("would remove generation %1%") % gen);
|
||||
else {
|
||||
printInfo(format("removing generation %1%") % gen);
|
||||
deleteGeneration(profile, gen);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int num;
|
||||
if (gens.size() > 0) {
|
||||
Generation last = gens.back();
|
||||
void deleteGenerations(const Path& profile,
|
||||
const std::set<unsigned int>& gensToDelete,
|
||||
bool dryRun) {
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
if (readLink(last.path) == outPath) {
|
||||
/* We only create a new generation symlink if it differs
|
||||
from the last one.
|
||||
int curGen;
|
||||
Generations gens = findGenerations(profile, curGen);
|
||||
|
||||
This helps keeping gratuitous installs/rebuilds from piling
|
||||
up uncontrolled numbers of generations, cluttering up the
|
||||
UI like grub. */
|
||||
return last.path;
|
||||
}
|
||||
if (gensToDelete.find(curGen) != gensToDelete.end())
|
||||
throw Error(format("cannot delete current generation of profile %1%'") %
|
||||
profile);
|
||||
|
||||
num = gens.back().number;
|
||||
} else {
|
||||
num = 0;
|
||||
for (auto& i : gens) {
|
||||
if (gensToDelete.find(i.number) == gensToDelete.end()) continue;
|
||||
deleteGeneration2(profile, i.number, dryRun);
|
||||
}
|
||||
}
|
||||
|
||||
void deleteGenerationsGreaterThan(const Path& profile, int max, bool dryRun) {
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
int curGen;
|
||||
bool fromCurGen = false;
|
||||
Generations gens = findGenerations(profile, curGen);
|
||||
for (auto i = gens.rbegin(); i != gens.rend(); ++i) {
|
||||
if (i->number == curGen) {
|
||||
fromCurGen = true;
|
||||
max--;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Create the new generation. Note that addPermRoot() blocks if
|
||||
the garbage collector is running to prevent the stuff we've
|
||||
built from moving from the temporary roots (which the GC knows)
|
||||
to the permanent roots (of which the GC would have a stale
|
||||
view). If we didn't do it this way, the GC might remove the
|
||||
user environment etc. we've just built. */
|
||||
Path generation;
|
||||
makeName(profile, num + 1, generation);
|
||||
store->addPermRoot(outPath, generation, false, true);
|
||||
|
||||
return generation;
|
||||
if (fromCurGen) {
|
||||
if (max) {
|
||||
max--;
|
||||
continue;
|
||||
}
|
||||
deleteGeneration2(profile, i->number, dryRun);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void deleteOldGenerations(const Path& profile, bool dryRun) {
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
static void removeFile(const Path & path)
|
||||
{
|
||||
if (remove(path.c_str()) == -1)
|
||||
throw SysError(format("cannot unlink '%1%'") % path);
|
||||
int curGen;
|
||||
Generations gens = findGenerations(profile, curGen);
|
||||
|
||||
for (auto& i : gens)
|
||||
if (i.number != curGen) deleteGeneration2(profile, i.number, dryRun);
|
||||
}
|
||||
|
||||
void deleteGenerationsOlderThan(const Path& profile, time_t t, bool dryRun) {
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
void deleteGeneration(const Path & profile, unsigned int gen)
|
||||
{
|
||||
Path generation;
|
||||
makeName(profile, gen, generation);
|
||||
removeFile(generation);
|
||||
}
|
||||
int curGen;
|
||||
Generations gens = findGenerations(profile, curGen);
|
||||
|
||||
|
||||
static void deleteGeneration2(const Path & profile, unsigned int gen, bool dryRun)
|
||||
{
|
||||
if (dryRun)
|
||||
printInfo(format("would remove generation %1%") % gen);
|
||||
else {
|
||||
printInfo(format("removing generation %1%") % gen);
|
||||
deleteGeneration(profile, gen);
|
||||
bool canDelete = false;
|
||||
for (auto i = gens.rbegin(); i != gens.rend(); ++i)
|
||||
if (canDelete) {
|
||||
assert(i->creationTime < t);
|
||||
if (i->number != curGen) deleteGeneration2(profile, i->number, dryRun);
|
||||
} else if (i->creationTime < t) {
|
||||
/* We may now start deleting generations, but we don't
|
||||
delete this generation yet, because this generation was
|
||||
still the one that was active at the requested point in
|
||||
time. */
|
||||
canDelete = true;
|
||||
}
|
||||
}
|
||||
|
||||
void deleteGenerationsOlderThan(const Path& profile, const string& timeSpec,
|
||||
bool dryRun) {
|
||||
time_t curTime = time(0);
|
||||
string strDays = string(timeSpec, 0, timeSpec.size() - 1);
|
||||
int days;
|
||||
|
||||
void deleteGenerations(const Path & profile, const std::set<unsigned int> & gensToDelete, bool dryRun)
|
||||
{
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
if (!string2Int(strDays, days) || days < 1)
|
||||
throw Error(format("invalid number of days specifier '%1%'") % timeSpec);
|
||||
|
||||
int curGen;
|
||||
Generations gens = findGenerations(profile, curGen);
|
||||
time_t oldTime = curTime - days * 24 * 3600;
|
||||
|
||||
if (gensToDelete.find(curGen) != gensToDelete.end())
|
||||
throw Error(format("cannot delete current generation of profile %1%'") % profile);
|
||||
|
||||
for (auto & i : gens) {
|
||||
if (gensToDelete.find(i.number) == gensToDelete.end()) continue;
|
||||
deleteGeneration2(profile, i.number, dryRun);
|
||||
}
|
||||
deleteGenerationsOlderThan(profile, oldTime, dryRun);
|
||||
}
|
||||
|
||||
void deleteGenerationsGreaterThan(const Path & profile, int max, bool dryRun)
|
||||
{
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
void switchLink(Path link, Path target) {
|
||||
/* Hacky. */
|
||||
if (dirOf(target) == dirOf(link)) target = baseNameOf(target);
|
||||
|
||||
int curGen;
|
||||
bool fromCurGen = false;
|
||||
Generations gens = findGenerations(profile, curGen);
|
||||
for (auto i = gens.rbegin(); i != gens.rend(); ++i) {
|
||||
if (i->number == curGen) {
|
||||
fromCurGen = true;
|
||||
max--;
|
||||
continue;
|
||||
}
|
||||
if (fromCurGen) {
|
||||
if (max) {
|
||||
max--;
|
||||
continue;
|
||||
}
|
||||
deleteGeneration2(profile, i->number, dryRun);
|
||||
}
|
||||
}
|
||||
replaceSymlink(target, link);
|
||||
}
|
||||
|
||||
void deleteOldGenerations(const Path & profile, bool dryRun)
|
||||
{
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
int curGen;
|
||||
Generations gens = findGenerations(profile, curGen);
|
||||
|
||||
for (auto & i : gens)
|
||||
if (i.number != curGen)
|
||||
deleteGeneration2(profile, i.number, dryRun);
|
||||
void lockProfile(PathLocks& lock, const Path& profile) {
|
||||
lock.lockPaths({profile},
|
||||
(format("waiting for lock on profile '%1%'") % profile).str());
|
||||
lock.setDeletion(true);
|
||||
}
|
||||
|
||||
|
||||
void deleteGenerationsOlderThan(const Path & profile, time_t t, bool dryRun)
|
||||
{
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
int curGen;
|
||||
Generations gens = findGenerations(profile, curGen);
|
||||
|
||||
bool canDelete = false;
|
||||
for (auto i = gens.rbegin(); i != gens.rend(); ++i)
|
||||
if (canDelete) {
|
||||
assert(i->creationTime < t);
|
||||
if (i->number != curGen)
|
||||
deleteGeneration2(profile, i->number, dryRun);
|
||||
} else if (i->creationTime < t) {
|
||||
/* We may now start deleting generations, but we don't
|
||||
delete this generation yet, because this generation was
|
||||
still the one that was active at the requested point in
|
||||
time. */
|
||||
canDelete = true;
|
||||
}
|
||||
string optimisticLockProfile(const Path& profile) {
|
||||
return pathExists(profile) ? readLink(profile) : "";
|
||||
}
|
||||
|
||||
|
||||
void deleteGenerationsOlderThan(const Path & profile, const string & timeSpec, bool dryRun)
|
||||
{
|
||||
time_t curTime = time(0);
|
||||
string strDays = string(timeSpec, 0, timeSpec.size() - 1);
|
||||
int days;
|
||||
|
||||
if (!string2Int(strDays, days) || days < 1)
|
||||
throw Error(format("invalid number of days specifier '%1%'") % timeSpec);
|
||||
|
||||
time_t oldTime = curTime - days * 24 * 3600;
|
||||
|
||||
deleteGenerationsOlderThan(profile, oldTime, dryRun);
|
||||
}
|
||||
|
||||
|
||||
void switchLink(Path link, Path target)
|
||||
{
|
||||
/* Hacky. */
|
||||
if (dirOf(target) == dirOf(link)) target = baseNameOf(target);
|
||||
|
||||
replaceSymlink(target, link);
|
||||
}
|
||||
|
||||
|
||||
void lockProfile(PathLocks & lock, const Path & profile)
|
||||
{
|
||||
lock.lockPaths({profile}, (format("waiting for lock on profile '%1%'") % profile).str());
|
||||
lock.setDeletion(true);
|
||||
}
|
||||
|
||||
|
||||
string optimisticLockProfile(const Path & profile)
|
||||
{
|
||||
return pathExists(profile) ? readLink(profile) : "";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
50
third_party/nix/src/libstore/profiles.hh
vendored
50
third_party/nix/src/libstore/profiles.hh
vendored
|
|
@ -1,57 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "pathlocks.hh"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "pathlocks.hh"
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
struct Generation
|
||||
{
|
||||
int number;
|
||||
Path path;
|
||||
time_t creationTime;
|
||||
Generation()
|
||||
{
|
||||
number = -1;
|
||||
}
|
||||
operator bool() const
|
||||
{
|
||||
return number != -1;
|
||||
}
|
||||
struct Generation {
|
||||
int number;
|
||||
Path path;
|
||||
time_t creationTime;
|
||||
Generation() { number = -1; }
|
||||
operator bool() const { return number != -1; }
|
||||
};
|
||||
|
||||
typedef list<Generation> Generations;
|
||||
|
||||
|
||||
/* Returns the list of currently present generations for the specified
|
||||
profile, sorted by generation number. */
|
||||
Generations findGenerations(Path profile, int & curGen);
|
||||
Generations findGenerations(Path profile, int& curGen);
|
||||
|
||||
class LocalFSStore;
|
||||
|
||||
Path createGeneration(ref<LocalFSStore> store, Path profile, Path outPath);
|
||||
|
||||
void deleteGeneration(const Path & profile, unsigned int gen);
|
||||
void deleteGeneration(const Path& profile, unsigned int gen);
|
||||
|
||||
void deleteGenerations(const Path & profile, const std::set<unsigned int> & gensToDelete, bool dryRun);
|
||||
void deleteGenerations(const Path& profile,
|
||||
const std::set<unsigned int>& gensToDelete, bool dryRun);
|
||||
|
||||
void deleteGenerationsGreaterThan(const Path & profile, const int max, bool dryRun);
|
||||
void deleteGenerationsGreaterThan(const Path& profile, const int max,
|
||||
bool dryRun);
|
||||
|
||||
void deleteOldGenerations(const Path & profile, bool dryRun);
|
||||
void deleteOldGenerations(const Path& profile, bool dryRun);
|
||||
|
||||
void deleteGenerationsOlderThan(const Path & profile, time_t t, bool dryRun);
|
||||
void deleteGenerationsOlderThan(const Path& profile, time_t t, bool dryRun);
|
||||
|
||||
void deleteGenerationsOlderThan(const Path & profile, const string & timeSpec, bool dryRun);
|
||||
void deleteGenerationsOlderThan(const Path& profile, const string& timeSpec,
|
||||
bool dryRun);
|
||||
|
||||
void switchLink(Path link, Path target);
|
||||
|
||||
/* Ensure exclusive access to a profile. Any command that modifies
|
||||
the profile first acquires this lock. */
|
||||
void lockProfile(PathLocks & lock, const Path & profile);
|
||||
void lockProfile(PathLocks& lock, const Path& profile);
|
||||
|
||||
/* Optimistic locking is used by long-running operations like `nix-env
|
||||
-i'. Instead of acquiring the exclusive lock for the entire
|
||||
|
|
@ -62,6 +54,6 @@ void lockProfile(PathLocks & lock, const Path & profile);
|
|||
generally cheap, since the build results are still in the Nix
|
||||
store. Most of the time, only the user environment has to be
|
||||
rebuilt. */
|
||||
string optimisticLockProfile(const Path & profile);
|
||||
string optimisticLockProfile(const Path& profile);
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
174
third_party/nix/src/libstore/references.cc
vendored
174
third_party/nix/src/libstore/references.cc
vendored
|
|
@ -1,122 +1,110 @@
|
|||
#include "references.hh"
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include "archive.hh"
|
||||
#include "hash.hh"
|
||||
#include "util.hh"
|
||||
#include "archive.hh"
|
||||
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
static unsigned int refLength = 32; /* characters */
|
||||
|
||||
static void search(const unsigned char* s, size_t len, StringSet& hashes,
|
||||
StringSet& seen) {
|
||||
static bool initialised = false;
|
||||
static bool isBase32[256];
|
||||
if (!initialised) {
|
||||
for (unsigned int i = 0; i < 256; ++i) isBase32[i] = false;
|
||||
for (unsigned int i = 0; i < base32Chars.size(); ++i)
|
||||
isBase32[(unsigned char)base32Chars[i]] = true;
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
static void search(const unsigned char * s, size_t len,
|
||||
StringSet & hashes, StringSet & seen)
|
||||
{
|
||||
static bool initialised = false;
|
||||
static bool isBase32[256];
|
||||
if (!initialised) {
|
||||
for (unsigned int i = 0; i < 256; ++i) isBase32[i] = false;
|
||||
for (unsigned int i = 0; i < base32Chars.size(); ++i)
|
||||
isBase32[(unsigned char) base32Chars[i]] = true;
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i + refLength <= len; ) {
|
||||
int j;
|
||||
bool match = true;
|
||||
for (j = refLength - 1; j >= 0; --j)
|
||||
if (!isBase32[(unsigned char) s[i + j]]) {
|
||||
i += j + 1;
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
if (!match) continue;
|
||||
string ref((const char *) s + i, refLength);
|
||||
if (hashes.find(ref) != hashes.end()) {
|
||||
debug(format("found reference to '%1%' at offset '%2%'")
|
||||
% ref % i);
|
||||
seen.insert(ref);
|
||||
hashes.erase(ref);
|
||||
}
|
||||
++i;
|
||||
for (size_t i = 0; i + refLength <= len;) {
|
||||
int j;
|
||||
bool match = true;
|
||||
for (j = refLength - 1; j >= 0; --j)
|
||||
if (!isBase32[(unsigned char)s[i + j]]) {
|
||||
i += j + 1;
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
if (!match) continue;
|
||||
string ref((const char*)s + i, refLength);
|
||||
if (hashes.find(ref) != hashes.end()) {
|
||||
debug(format("found reference to '%1%' at offset '%2%'") % ref % i);
|
||||
seen.insert(ref);
|
||||
hashes.erase(ref);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
struct RefScanSink : Sink {
|
||||
HashSink hashSink;
|
||||
StringSet hashes;
|
||||
StringSet seen;
|
||||
|
||||
struct RefScanSink : Sink
|
||||
{
|
||||
HashSink hashSink;
|
||||
StringSet hashes;
|
||||
StringSet seen;
|
||||
string tail;
|
||||
|
||||
string tail;
|
||||
RefScanSink() : hashSink(htSHA256) {}
|
||||
|
||||
RefScanSink() : hashSink(htSHA256) { }
|
||||
|
||||
void operator () (const unsigned char * data, size_t len);
|
||||
void operator()(const unsigned char* data, size_t len);
|
||||
};
|
||||
|
||||
void RefScanSink::operator()(const unsigned char* data, size_t len) {
|
||||
hashSink(data, len);
|
||||
|
||||
void RefScanSink::operator () (const unsigned char * data, size_t len)
|
||||
{
|
||||
hashSink(data, len);
|
||||
/* It's possible that a reference spans the previous and current
|
||||
fragment, so search in the concatenation of the tail of the
|
||||
previous fragment and the start of the current fragment. */
|
||||
string s =
|
||||
tail + string((const char*)data, len > refLength ? refLength : len);
|
||||
search((const unsigned char*)s.data(), s.size(), hashes, seen);
|
||||
|
||||
/* It's possible that a reference spans the previous and current
|
||||
fragment, so search in the concatenation of the tail of the
|
||||
previous fragment and the start of the current fragment. */
|
||||
string s = tail + string((const char *) data, len > refLength ? refLength : len);
|
||||
search((const unsigned char *) s.data(), s.size(), hashes, seen);
|
||||
search(data, len, hashes, seen);
|
||||
|
||||
search(data, len, hashes, seen);
|
||||
|
||||
size_t tailLen = len <= refLength ? len : refLength;
|
||||
tail =
|
||||
string(tail, tail.size() < refLength - tailLen ? 0 : tail.size() - (refLength - tailLen)) +
|
||||
string((const char *) data + len - tailLen, tailLen);
|
||||
size_t tailLen = len <= refLength ? len : refLength;
|
||||
tail = string(tail, tail.size() < refLength - tailLen
|
||||
? 0
|
||||
: tail.size() - (refLength - tailLen)) +
|
||||
string((const char*)data + len - tailLen, tailLen);
|
||||
}
|
||||
|
||||
PathSet scanForReferences(const string& path, const PathSet& refs,
|
||||
HashResult& hash) {
|
||||
RefScanSink sink;
|
||||
std::map<string, Path> backMap;
|
||||
|
||||
PathSet scanForReferences(const string & path,
|
||||
const PathSet & refs, HashResult & hash)
|
||||
{
|
||||
RefScanSink sink;
|
||||
std::map<string, Path> backMap;
|
||||
/* For efficiency (and a higher hit rate), just search for the
|
||||
hash part of the file name. (This assumes that all references
|
||||
have the form `HASH-bla'). */
|
||||
for (auto& i : refs) {
|
||||
string baseName = baseNameOf(i);
|
||||
string::size_type pos = baseName.find('-');
|
||||
if (pos == string::npos) throw Error(format("bad reference '%1%'") % i);
|
||||
string s = string(baseName, 0, pos);
|
||||
assert(s.size() == refLength);
|
||||
assert(backMap.find(s) == backMap.end());
|
||||
// parseHash(htSHA256, s);
|
||||
sink.hashes.insert(s);
|
||||
backMap[s] = i;
|
||||
}
|
||||
|
||||
/* For efficiency (and a higher hit rate), just search for the
|
||||
hash part of the file name. (This assumes that all references
|
||||
have the form `HASH-bla'). */
|
||||
for (auto & i : refs) {
|
||||
string baseName = baseNameOf(i);
|
||||
string::size_type pos = baseName.find('-');
|
||||
if (pos == string::npos)
|
||||
throw Error(format("bad reference '%1%'") % i);
|
||||
string s = string(baseName, 0, pos);
|
||||
assert(s.size() == refLength);
|
||||
assert(backMap.find(s) == backMap.end());
|
||||
// parseHash(htSHA256, s);
|
||||
sink.hashes.insert(s);
|
||||
backMap[s] = i;
|
||||
}
|
||||
/* Look for the hashes in the NAR dump of the path. */
|
||||
dumpPath(path, sink);
|
||||
|
||||
/* Look for the hashes in the NAR dump of the path. */
|
||||
dumpPath(path, sink);
|
||||
/* Map the hashes found back to their store paths. */
|
||||
PathSet found;
|
||||
for (auto& i : sink.seen) {
|
||||
std::map<string, Path>::iterator j;
|
||||
if ((j = backMap.find(i)) == backMap.end()) abort();
|
||||
found.insert(j->second);
|
||||
}
|
||||
|
||||
/* Map the hashes found back to their store paths. */
|
||||
PathSet found;
|
||||
for (auto & i : sink.seen) {
|
||||
std::map<string, Path>::iterator j;
|
||||
if ((j = backMap.find(i)) == backMap.end()) abort();
|
||||
found.insert(j->second);
|
||||
}
|
||||
hash = sink.hashSink.finish();
|
||||
|
||||
hash = sink.hashSink.finish();
|
||||
|
||||
return found;
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
8
third_party/nix/src/libstore/references.hh
vendored
8
third_party/nix/src/libstore/references.hh
vendored
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
PathSet scanForReferences(const Path & path, const PathSet & refs,
|
||||
HashResult & hash);
|
||||
|
||||
PathSet scanForReferences(const Path& path, const PathSet& refs,
|
||||
HashResult& hash);
|
||||
|
||||
}
|
||||
|
|
|
|||
173
third_party/nix/src/libstore/remote-fs-accessor.cc
vendored
173
third_party/nix/src/libstore/remote-fs-accessor.cc
vendored
|
|
@ -1,129 +1,120 @@
|
|||
#include "remote-fs-accessor.hh"
|
||||
#include "nar-accessor.hh"
|
||||
#include "json.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include "json.hh"
|
||||
#include "nar-accessor.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
RemoteFSAccessor::RemoteFSAccessor(ref<Store> store, const Path & cacheDir)
|
||||
: store(store)
|
||||
, cacheDir(cacheDir)
|
||||
{
|
||||
if (cacheDir != "")
|
||||
createDirs(cacheDir);
|
||||
RemoteFSAccessor::RemoteFSAccessor(ref<Store> store, const Path& cacheDir)
|
||||
: store(store), cacheDir(cacheDir) {
|
||||
if (cacheDir != "") createDirs(cacheDir);
|
||||
}
|
||||
|
||||
Path RemoteFSAccessor::makeCacheFile(const Path & storePath, const std::string & ext)
|
||||
{
|
||||
assert(cacheDir != "");
|
||||
return fmt("%s/%s.%s", cacheDir, storePathToHash(storePath), ext);
|
||||
Path RemoteFSAccessor::makeCacheFile(const Path& storePath,
|
||||
const std::string& ext) {
|
||||
assert(cacheDir != "");
|
||||
return fmt("%s/%s.%s", cacheDir, storePathToHash(storePath), ext);
|
||||
}
|
||||
|
||||
void RemoteFSAccessor::addToCache(const Path & storePath, const std::string & nar,
|
||||
ref<FSAccessor> narAccessor)
|
||||
{
|
||||
nars.emplace(storePath, narAccessor);
|
||||
void RemoteFSAccessor::addToCache(const Path& storePath, const std::string& nar,
|
||||
ref<FSAccessor> narAccessor) {
|
||||
nars.emplace(storePath, narAccessor);
|
||||
|
||||
if (cacheDir != "") {
|
||||
try {
|
||||
std::ostringstream str;
|
||||
JSONPlaceholder jsonRoot(str);
|
||||
listNar(jsonRoot, narAccessor, "", true);
|
||||
writeFile(makeCacheFile(storePath, "ls"), str.str());
|
||||
if (cacheDir != "") {
|
||||
try {
|
||||
std::ostringstream str;
|
||||
JSONPlaceholder jsonRoot(str);
|
||||
listNar(jsonRoot, narAccessor, "", true);
|
||||
writeFile(makeCacheFile(storePath, "ls"), str.str());
|
||||
|
||||
/* FIXME: do this asynchronously. */
|
||||
writeFile(makeCacheFile(storePath, "nar"), nar);
|
||||
/* FIXME: do this asynchronously. */
|
||||
writeFile(makeCacheFile(storePath, "nar"), nar);
|
||||
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_)
|
||||
{
|
||||
auto path = canonPath(path_);
|
||||
std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path& path_) {
|
||||
auto path = canonPath(path_);
|
||||
|
||||
auto storePath = store->toStorePath(path);
|
||||
std::string restPath = std::string(path, storePath.size());
|
||||
auto storePath = store->toStorePath(path);
|
||||
std::string restPath = std::string(path, storePath.size());
|
||||
|
||||
if (!store->isValidPath(storePath))
|
||||
throw InvalidPath(format("path '%1%' is not a valid store path") % storePath);
|
||||
if (!store->isValidPath(storePath))
|
||||
throw InvalidPath(format("path '%1%' is not a valid store path") %
|
||||
storePath);
|
||||
|
||||
auto i = nars.find(storePath);
|
||||
if (i != nars.end()) return {i->second, restPath};
|
||||
auto i = nars.find(storePath);
|
||||
if (i != nars.end()) return {i->second, restPath};
|
||||
|
||||
StringSink sink;
|
||||
std::string listing;
|
||||
Path cacheFile;
|
||||
StringSink sink;
|
||||
std::string listing;
|
||||
Path cacheFile;
|
||||
|
||||
if (cacheDir != "" && pathExists(cacheFile = makeCacheFile(storePath, "nar"))) {
|
||||
if (cacheDir != "" &&
|
||||
pathExists(cacheFile = makeCacheFile(storePath, "nar"))) {
|
||||
try {
|
||||
listing = nix::readFile(makeCacheFile(storePath, "ls"));
|
||||
|
||||
try {
|
||||
listing = nix::readFile(makeCacheFile(storePath, "ls"));
|
||||
auto narAccessor = makeLazyNarAccessor(
|
||||
listing, [cacheFile](uint64_t offset, uint64_t length) {
|
||||
AutoCloseFD fd = open(cacheFile.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (!fd) throw SysError("opening NAR cache file '%s'", cacheFile);
|
||||
|
||||
auto narAccessor = makeLazyNarAccessor(listing,
|
||||
[cacheFile](uint64_t offset, uint64_t length) {
|
||||
if (lseek(fd.get(), offset, SEEK_SET) != (off_t)offset)
|
||||
throw SysError("seeking in '%s'", cacheFile);
|
||||
|
||||
AutoCloseFD fd = open(cacheFile.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (!fd)
|
||||
throw SysError("opening NAR cache file '%s'", cacheFile);
|
||||
std::string buf(length, 0);
|
||||
readFull(fd.get(), (unsigned char*)buf.data(), length);
|
||||
|
||||
if (lseek(fd.get(), offset, SEEK_SET) != (off_t) offset)
|
||||
throw SysError("seeking in '%s'", cacheFile);
|
||||
return buf;
|
||||
});
|
||||
|
||||
std::string buf(length, 0);
|
||||
readFull(fd.get(), (unsigned char *) buf.data(), length);
|
||||
nars.emplace(storePath, narAccessor);
|
||||
return {narAccessor, restPath};
|
||||
|
||||
return buf;
|
||||
});
|
||||
|
||||
nars.emplace(storePath, narAccessor);
|
||||
return {narAccessor, restPath};
|
||||
|
||||
} catch (SysError &) { }
|
||||
|
||||
try {
|
||||
*sink.s = nix::readFile(cacheFile);
|
||||
|
||||
auto narAccessor = makeNarAccessor(sink.s);
|
||||
nars.emplace(storePath, narAccessor);
|
||||
return {narAccessor, restPath};
|
||||
|
||||
} catch (SysError &) { }
|
||||
} catch (SysError&) {
|
||||
}
|
||||
|
||||
store->narFromPath(storePath, sink);
|
||||
auto narAccessor = makeNarAccessor(sink.s);
|
||||
addToCache(storePath, *sink.s, narAccessor);
|
||||
return {narAccessor, restPath};
|
||||
try {
|
||||
*sink.s = nix::readFile(cacheFile);
|
||||
|
||||
auto narAccessor = makeNarAccessor(sink.s);
|
||||
nars.emplace(storePath, narAccessor);
|
||||
return {narAccessor, restPath};
|
||||
|
||||
} catch (SysError&) {
|
||||
}
|
||||
}
|
||||
|
||||
store->narFromPath(storePath, sink);
|
||||
auto narAccessor = makeNarAccessor(sink.s);
|
||||
addToCache(storePath, *sink.s, narAccessor);
|
||||
return {narAccessor, restPath};
|
||||
}
|
||||
|
||||
FSAccessor::Stat RemoteFSAccessor::stat(const Path & path)
|
||||
{
|
||||
auto res = fetch(path);
|
||||
return res.first->stat(res.second);
|
||||
FSAccessor::Stat RemoteFSAccessor::stat(const Path& path) {
|
||||
auto res = fetch(path);
|
||||
return res.first->stat(res.second);
|
||||
}
|
||||
|
||||
StringSet RemoteFSAccessor::readDirectory(const Path & path)
|
||||
{
|
||||
auto res = fetch(path);
|
||||
return res.first->readDirectory(res.second);
|
||||
StringSet RemoteFSAccessor::readDirectory(const Path& path) {
|
||||
auto res = fetch(path);
|
||||
return res.first->readDirectory(res.second);
|
||||
}
|
||||
|
||||
std::string RemoteFSAccessor::readFile(const Path & path)
|
||||
{
|
||||
auto res = fetch(path);
|
||||
return res.first->readFile(res.second);
|
||||
std::string RemoteFSAccessor::readFile(const Path& path) {
|
||||
auto res = fetch(path);
|
||||
return res.first->readFile(res.second);
|
||||
}
|
||||
|
||||
std::string RemoteFSAccessor::readLink(const Path & path)
|
||||
{
|
||||
auto res = fetch(path);
|
||||
return res.first->readLink(res.second);
|
||||
std::string RemoteFSAccessor::readLink(const Path& path) {
|
||||
auto res = fetch(path);
|
||||
return res.first->readLink(res.second);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -6,35 +6,33 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
class RemoteFSAccessor : public FSAccessor
|
||||
{
|
||||
ref<Store> store;
|
||||
class RemoteFSAccessor : public FSAccessor {
|
||||
ref<Store> store;
|
||||
|
||||
std::map<Path, ref<FSAccessor>> nars;
|
||||
std::map<Path, ref<FSAccessor>> nars;
|
||||
|
||||
Path cacheDir;
|
||||
Path cacheDir;
|
||||
|
||||
std::pair<ref<FSAccessor>, Path> fetch(const Path & path_);
|
||||
std::pair<ref<FSAccessor>, Path> fetch(const Path& path_);
|
||||
|
||||
friend class BinaryCacheStore;
|
||||
friend class BinaryCacheStore;
|
||||
|
||||
Path makeCacheFile(const Path & storePath, const std::string & ext);
|
||||
Path makeCacheFile(const Path& storePath, const std::string& ext);
|
||||
|
||||
void addToCache(const Path & storePath, const std::string & nar,
|
||||
ref<FSAccessor> narAccessor);
|
||||
void addToCache(const Path& storePath, const std::string& nar,
|
||||
ref<FSAccessor> narAccessor);
|
||||
|
||||
public:
|
||||
public:
|
||||
RemoteFSAccessor(ref<Store> store,
|
||||
const /* FIXME: use std::optional */ Path& cacheDir = "");
|
||||
|
||||
RemoteFSAccessor(ref<Store> store,
|
||||
const /* FIXME: use std::optional */ Path & cacheDir = "");
|
||||
Stat stat(const Path& path) override;
|
||||
|
||||
Stat stat(const Path & path) override;
|
||||
StringSet readDirectory(const Path& path) override;
|
||||
|
||||
StringSet readDirectory(const Path & path) override;
|
||||
std::string readFile(const Path& path) override;
|
||||
|
||||
std::string readFile(const Path & path) override;
|
||||
|
||||
std::string readLink(const Path & path) override;
|
||||
std::string readLink(const Path& path) override;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
1352
third_party/nix/src/libstore/remote-store.cc
vendored
1352
third_party/nix/src/libstore/remote-store.cc
vendored
File diff suppressed because it is too large
Load diff
179
third_party/nix/src/libstore/remote-store.hh
vendored
179
third_party/nix/src/libstore/remote-store.hh
vendored
|
|
@ -2,160 +2,151 @@
|
|||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "store-api.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
class Pipe;
|
||||
class Pid;
|
||||
struct FdSink;
|
||||
struct FdSource;
|
||||
template<typename T> class Pool;
|
||||
template <typename T>
|
||||
class Pool;
|
||||
struct ConnectionHandle;
|
||||
|
||||
|
||||
/* FIXME: RemoteStore is a misnomer - should be something like
|
||||
DaemonStore. */
|
||||
class RemoteStore : public virtual Store
|
||||
{
|
||||
public:
|
||||
class RemoteStore : public virtual Store {
|
||||
public:
|
||||
const Setting<int> maxConnections{
|
||||
(Store*)this, 1, "max-connections",
|
||||
"maximum number of concurrent connections to the Nix daemon"};
|
||||
|
||||
const Setting<int> maxConnections{(Store*) this, 1,
|
||||
"max-connections", "maximum number of concurrent connections to the Nix daemon"};
|
||||
const Setting<unsigned int> maxConnectionAge{
|
||||
(Store*)this, std::numeric_limits<unsigned int>::max(),
|
||||
"max-connection-age", "number of seconds to reuse a connection"};
|
||||
|
||||
const Setting<unsigned int> maxConnectionAge{(Store*) this, std::numeric_limits<unsigned int>::max(),
|
||||
"max-connection-age", "number of seconds to reuse a connection"};
|
||||
virtual bool sameMachine() = 0;
|
||||
|
||||
virtual bool sameMachine() = 0;
|
||||
RemoteStore(const Params& params);
|
||||
|
||||
RemoteStore(const Params & params);
|
||||
/* Implementations of abstract store API methods. */
|
||||
|
||||
/* Implementations of abstract store API methods. */
|
||||
bool isValidPathUncached(const Path& path) override;
|
||||
|
||||
bool isValidPathUncached(const Path & path) override;
|
||||
PathSet queryValidPaths(const PathSet& paths, SubstituteFlag maybeSubstitute =
|
||||
NoSubstitute) override;
|
||||
|
||||
PathSet queryValidPaths(const PathSet & paths,
|
||||
SubstituteFlag maybeSubstitute = NoSubstitute) override;
|
||||
PathSet queryAllValidPaths() override;
|
||||
|
||||
PathSet queryAllValidPaths() override;
|
||||
void queryPathInfoUncached(
|
||||
const Path& path,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override;
|
||||
|
||||
void queryPathInfoUncached(const Path & path,
|
||||
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override;
|
||||
void queryReferrers(const Path& path, PathSet& referrers) override;
|
||||
|
||||
void queryReferrers(const Path & path, PathSet & referrers) override;
|
||||
PathSet queryValidDerivers(const Path& path) override;
|
||||
|
||||
PathSet queryValidDerivers(const Path & path) override;
|
||||
PathSet queryDerivationOutputs(const Path& path) override;
|
||||
|
||||
PathSet queryDerivationOutputs(const Path & path) override;
|
||||
StringSet queryDerivationOutputNames(const Path& path) override;
|
||||
|
||||
StringSet queryDerivationOutputNames(const Path & path) override;
|
||||
Path queryPathFromHashPart(const string& hashPart) override;
|
||||
|
||||
Path queryPathFromHashPart(const string & hashPart) override;
|
||||
PathSet querySubstitutablePaths(const PathSet& paths) override;
|
||||
|
||||
PathSet querySubstitutablePaths(const PathSet & paths) override;
|
||||
void querySubstitutablePathInfos(const PathSet& paths,
|
||||
SubstitutablePathInfos& infos) override;
|
||||
|
||||
void querySubstitutablePathInfos(const PathSet & paths,
|
||||
SubstitutablePathInfos & infos) override;
|
||||
void addToStore(const ValidPathInfo& info, Source& nar, RepairFlag repair,
|
||||
CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override;
|
||||
|
||||
void addToStore(const ValidPathInfo & info, Source & nar,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override;
|
||||
Path addToStore(const string& name, const Path& srcPath,
|
||||
bool recursive = true, HashType hashAlgo = htSHA256,
|
||||
PathFilter& filter = defaultPathFilter,
|
||||
RepairFlag repair = NoRepair) override;
|
||||
|
||||
Path addToStore(const string & name, const Path & srcPath,
|
||||
bool recursive = true, HashType hashAlgo = htSHA256,
|
||||
PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair) override;
|
||||
Path addTextToStore(const string& name, const string& s,
|
||||
const PathSet& references, RepairFlag repair) override;
|
||||
|
||||
Path addTextToStore(const string & name, const string & s,
|
||||
const PathSet & references, RepairFlag repair) override;
|
||||
void buildPaths(const PathSet& paths, BuildMode buildMode) override;
|
||||
|
||||
void buildPaths(const PathSet & paths, BuildMode buildMode) override;
|
||||
BuildResult buildDerivation(const Path& drvPath, const BasicDerivation& drv,
|
||||
BuildMode buildMode) override;
|
||||
|
||||
BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode) override;
|
||||
void ensurePath(const Path& path) override;
|
||||
|
||||
void ensurePath(const Path & path) override;
|
||||
void addTempRoot(const Path& path) override;
|
||||
|
||||
void addTempRoot(const Path & path) override;
|
||||
void addIndirectRoot(const Path& path) override;
|
||||
|
||||
void addIndirectRoot(const Path & path) override;
|
||||
void syncWithGC() override;
|
||||
|
||||
void syncWithGC() override;
|
||||
Roots findRoots(bool censor) override;
|
||||
|
||||
Roots findRoots(bool censor) override;
|
||||
void collectGarbage(const GCOptions& options, GCResults& results) override;
|
||||
|
||||
void collectGarbage(const GCOptions & options, GCResults & results) override;
|
||||
void optimiseStore() override;
|
||||
|
||||
void optimiseStore() override;
|
||||
bool verifyStore(bool checkContents, RepairFlag repair) override;
|
||||
|
||||
bool verifyStore(bool checkContents, RepairFlag repair) override;
|
||||
void addSignatures(const Path& storePath, const StringSet& sigs) override;
|
||||
|
||||
void addSignatures(const Path & storePath, const StringSet & sigs) override;
|
||||
void queryMissing(const PathSet& targets, PathSet& willBuild,
|
||||
PathSet& willSubstitute, PathSet& unknown,
|
||||
unsigned long long& downloadSize,
|
||||
unsigned long long& narSize) override;
|
||||
|
||||
void queryMissing(const PathSet & targets,
|
||||
PathSet & willBuild, PathSet & willSubstitute, PathSet & unknown,
|
||||
unsigned long long & downloadSize, unsigned long long & narSize) override;
|
||||
void connect() override;
|
||||
|
||||
void connect() override;
|
||||
unsigned int getProtocol() override;
|
||||
|
||||
unsigned int getProtocol() override;
|
||||
void flushBadConnections();
|
||||
|
||||
void flushBadConnections();
|
||||
protected:
|
||||
struct Connection {
|
||||
AutoCloseFD fd;
|
||||
FdSink to;
|
||||
FdSource from;
|
||||
unsigned int daemonVersion;
|
||||
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
||||
|
||||
protected:
|
||||
virtual ~Connection();
|
||||
|
||||
struct Connection
|
||||
{
|
||||
AutoCloseFD fd;
|
||||
FdSink to;
|
||||
FdSource from;
|
||||
unsigned int daemonVersion;
|
||||
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
||||
std::exception_ptr processStderr(Sink* sink = 0, Source* source = 0);
|
||||
};
|
||||
|
||||
virtual ~Connection();
|
||||
ref<Connection> openConnectionWrapper();
|
||||
|
||||
std::exception_ptr processStderr(Sink * sink = 0, Source * source = 0);
|
||||
};
|
||||
virtual ref<Connection> openConnection() = 0;
|
||||
|
||||
ref<Connection> openConnectionWrapper();
|
||||
void initConnection(Connection& conn);
|
||||
|
||||
virtual ref<Connection> openConnection() = 0;
|
||||
ref<Pool<Connection>> connections;
|
||||
|
||||
void initConnection(Connection & conn);
|
||||
virtual void setOptions(Connection& conn);
|
||||
|
||||
ref<Pool<Connection>> connections;
|
||||
ConnectionHandle getConnection();
|
||||
|
||||
virtual void setOptions(Connection & conn);
|
||||
|
||||
ConnectionHandle getConnection();
|
||||
|
||||
friend struct ConnectionHandle;
|
||||
|
||||
private:
|
||||
|
||||
std::atomic_bool failed{false};
|
||||
friend struct ConnectionHandle;
|
||||
|
||||
private:
|
||||
std::atomic_bool failed{false};
|
||||
};
|
||||
|
||||
class UDSRemoteStore : public LocalFSStore, public RemoteStore
|
||||
{
|
||||
public:
|
||||
class UDSRemoteStore : public LocalFSStore, public RemoteStore {
|
||||
public:
|
||||
UDSRemoteStore(const Params& params);
|
||||
UDSRemoteStore(std::string path, const Params& params);
|
||||
|
||||
UDSRemoteStore(const Params & params);
|
||||
UDSRemoteStore(std::string path, const Params & params);
|
||||
std::string getUri() override;
|
||||
|
||||
std::string getUri() override;
|
||||
bool sameMachine() { return true; }
|
||||
|
||||
bool sameMachine()
|
||||
{ return true; }
|
||||
|
||||
private:
|
||||
|
||||
ref<RemoteStore::Connection> openConnection() override;
|
||||
std::optional<std::string> path;
|
||||
private:
|
||||
ref<RemoteStore::Connection> openConnection() override;
|
||||
std::optional<std::string> path;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -1,14 +1,6 @@
|
|||
#if ENABLE_S3
|
||||
|
||||
#include "s3.hh"
|
||||
#include "s3-binary-cache-store.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "nar-info-disk-cache.hh"
|
||||
#include "globals.hh"
|
||||
#include "compression.hh"
|
||||
#include "download.hh"
|
||||
#include "istringstream_nocopy.hh"
|
||||
|
||||
#include <aws/core/Aws.h>
|
||||
#include <aws/core/VersionConfig.h>
|
||||
#include <aws/core/auth/AWSCredentialsProvider.h>
|
||||
|
|
@ -24,408 +16,403 @@
|
|||
#include <aws/s3/model/ListObjectsRequest.h>
|
||||
#include <aws/s3/model/PutObjectRequest.h>
|
||||
#include <aws/transfer/TransferManager.h>
|
||||
#include "compression.hh"
|
||||
#include "download.hh"
|
||||
#include "globals.hh"
|
||||
#include "istringstream_nocopy.hh"
|
||||
#include "nar-info-disk-cache.hh"
|
||||
#include "nar-info.hh"
|
||||
#include "s3.hh"
|
||||
|
||||
using namespace Aws::Transfer;
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct S3Error : public Error
|
||||
{
|
||||
Aws::S3::S3Errors err;
|
||||
S3Error(Aws::S3::S3Errors err, const FormatOrString & fs)
|
||||
: Error(fs), err(err) { };
|
||||
struct S3Error : public Error {
|
||||
Aws::S3::S3Errors err;
|
||||
S3Error(Aws::S3::S3Errors err, const FormatOrString& fs)
|
||||
: Error(fs), err(err){};
|
||||
};
|
||||
|
||||
/* Helper: given an Outcome<R, E>, return R in case of success, or
|
||||
throw an exception in case of an error. */
|
||||
template<typename R, typename E>
|
||||
R && checkAws(const FormatOrString & fs, Aws::Utils::Outcome<R, E> && outcome)
|
||||
{
|
||||
if (!outcome.IsSuccess())
|
||||
throw S3Error(
|
||||
outcome.GetError().GetErrorType(),
|
||||
fs.s + ": " + outcome.GetError().GetMessage());
|
||||
return outcome.GetResultWithOwnership();
|
||||
template <typename R, typename E>
|
||||
R&& checkAws(const FormatOrString& fs, Aws::Utils::Outcome<R, E>&& outcome) {
|
||||
if (!outcome.IsSuccess())
|
||||
throw S3Error(outcome.GetError().GetErrorType(),
|
||||
fs.s + ": " + outcome.GetError().GetMessage());
|
||||
return outcome.GetResultWithOwnership();
|
||||
}
|
||||
|
||||
class AwsLogger : public Aws::Utils::Logging::FormattedLogSystem
|
||||
{
|
||||
using Aws::Utils::Logging::FormattedLogSystem::FormattedLogSystem;
|
||||
class AwsLogger : public Aws::Utils::Logging::FormattedLogSystem {
|
||||
using Aws::Utils::Logging::FormattedLogSystem::FormattedLogSystem;
|
||||
|
||||
void ProcessFormattedStatement(Aws::String && statement) override
|
||||
{
|
||||
debug("AWS: %s", chomp(statement));
|
||||
}
|
||||
void ProcessFormattedStatement(Aws::String&& statement) override {
|
||||
debug("AWS: %s", chomp(statement));
|
||||
}
|
||||
};
|
||||
|
||||
static void initAWS()
|
||||
{
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, []() {
|
||||
Aws::SDKOptions options;
|
||||
static void initAWS() {
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, []() {
|
||||
Aws::SDKOptions options;
|
||||
|
||||
/* We install our own OpenSSL locking function (see
|
||||
shared.cc), so don't let aws-sdk-cpp override it. */
|
||||
options.cryptoOptions.initAndCleanupOpenSSL = false;
|
||||
/* We install our own OpenSSL locking function (see
|
||||
shared.cc), so don't let aws-sdk-cpp override it. */
|
||||
options.cryptoOptions.initAndCleanupOpenSSL = false;
|
||||
|
||||
if (verbosity >= lvlDebug) {
|
||||
options.loggingOptions.logLevel =
|
||||
verbosity == lvlDebug
|
||||
? Aws::Utils::Logging::LogLevel::Debug
|
||||
: Aws::Utils::Logging::LogLevel::Trace;
|
||||
options.loggingOptions.logger_create_fn = [options]() {
|
||||
return std::make_shared<AwsLogger>(options.loggingOptions.logLevel);
|
||||
};
|
||||
}
|
||||
if (verbosity >= lvlDebug) {
|
||||
options.loggingOptions.logLevel =
|
||||
verbosity == lvlDebug ? Aws::Utils::Logging::LogLevel::Debug
|
||||
: Aws::Utils::Logging::LogLevel::Trace;
|
||||
options.loggingOptions.logger_create_fn = [options]() {
|
||||
return std::make_shared<AwsLogger>(options.loggingOptions.logLevel);
|
||||
};
|
||||
}
|
||||
|
||||
Aws::InitAPI(options);
|
||||
});
|
||||
Aws::InitAPI(options);
|
||||
});
|
||||
}
|
||||
|
||||
S3Helper::S3Helper(const string & profile, const string & region, const string & scheme, const string & endpoint)
|
||||
: config(makeConfig(region, scheme, endpoint))
|
||||
, client(make_ref<Aws::S3::S3Client>(
|
||||
profile == ""
|
||||
? std::dynamic_pointer_cast<Aws::Auth::AWSCredentialsProvider>(
|
||||
std::make_shared<Aws::Auth::DefaultAWSCredentialsProviderChain>())
|
||||
: std::dynamic_pointer_cast<Aws::Auth::AWSCredentialsProvider>(
|
||||
std::make_shared<Aws::Auth::ProfileConfigFileAWSCredentialsProvider>(profile.c_str())),
|
||||
*config,
|
||||
// FIXME: https://github.com/aws/aws-sdk-cpp/issues/759
|
||||
S3Helper::S3Helper(const string& profile, const string& region,
|
||||
const string& scheme, const string& endpoint)
|
||||
: config(makeConfig(region, scheme, endpoint)),
|
||||
client(make_ref<Aws::S3::S3Client>(
|
||||
profile == ""
|
||||
? std::dynamic_pointer_cast<Aws::Auth::AWSCredentialsProvider>(
|
||||
std::make_shared<
|
||||
Aws::Auth::DefaultAWSCredentialsProviderChain>())
|
||||
: std::dynamic_pointer_cast<Aws::Auth::AWSCredentialsProvider>(
|
||||
std::make_shared<
|
||||
Aws::Auth::ProfileConfigFileAWSCredentialsProvider>(
|
||||
profile.c_str())),
|
||||
*config,
|
||||
// FIXME: https://github.com/aws/aws-sdk-cpp/issues/759
|
||||
#if AWS_VERSION_MAJOR == 1 && AWS_VERSION_MINOR < 3
|
||||
false,
|
||||
false,
|
||||
#else
|
||||
Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never,
|
||||
Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never,
|
||||
#endif
|
||||
endpoint.empty()))
|
||||
{
|
||||
endpoint.empty())) {
|
||||
}
|
||||
|
||||
/* Log AWS retries. */
|
||||
class RetryStrategy : public Aws::Client::DefaultRetryStrategy
|
||||
{
|
||||
bool ShouldRetry(const Aws::Client::AWSError<Aws::Client::CoreErrors>& error, long attemptedRetries) const override
|
||||
{
|
||||
auto retry = Aws::Client::DefaultRetryStrategy::ShouldRetry(error, attemptedRetries);
|
||||
if (retry)
|
||||
printError("AWS error '%s' (%s), will retry in %d ms",
|
||||
error.GetExceptionName(), error.GetMessage(), CalculateDelayBeforeNextRetry(error, attemptedRetries));
|
||||
return retry;
|
||||
}
|
||||
class RetryStrategy : public Aws::Client::DefaultRetryStrategy {
|
||||
bool ShouldRetry(const Aws::Client::AWSError<Aws::Client::CoreErrors>& error,
|
||||
long attemptedRetries) const override {
|
||||
auto retry =
|
||||
Aws::Client::DefaultRetryStrategy::ShouldRetry(error, attemptedRetries);
|
||||
if (retry)
|
||||
printError("AWS error '%s' (%s), will retry in %d ms",
|
||||
error.GetExceptionName(), error.GetMessage(),
|
||||
CalculateDelayBeforeNextRetry(error, attemptedRetries));
|
||||
return retry;
|
||||
}
|
||||
};
|
||||
|
||||
ref<Aws::Client::ClientConfiguration> S3Helper::makeConfig(const string & region, const string & scheme, const string & endpoint)
|
||||
{
|
||||
initAWS();
|
||||
auto res = make_ref<Aws::Client::ClientConfiguration>();
|
||||
res->region = region;
|
||||
if (!scheme.empty()) {
|
||||
res->scheme = Aws::Http::SchemeMapper::FromString(scheme.c_str());
|
||||
}
|
||||
if (!endpoint.empty()) {
|
||||
res->endpointOverride = endpoint;
|
||||
}
|
||||
res->requestTimeoutMs = 600 * 1000;
|
||||
res->connectTimeoutMs = 5 * 1000;
|
||||
res->retryStrategy = std::make_shared<RetryStrategy>();
|
||||
res->caFile = settings.caFile;
|
||||
return res;
|
||||
ref<Aws::Client::ClientConfiguration> S3Helper::makeConfig(
|
||||
const string& region, const string& scheme, const string& endpoint) {
|
||||
initAWS();
|
||||
auto res = make_ref<Aws::Client::ClientConfiguration>();
|
||||
res->region = region;
|
||||
if (!scheme.empty()) {
|
||||
res->scheme = Aws::Http::SchemeMapper::FromString(scheme.c_str());
|
||||
}
|
||||
if (!endpoint.empty()) {
|
||||
res->endpointOverride = endpoint;
|
||||
}
|
||||
res->requestTimeoutMs = 600 * 1000;
|
||||
res->connectTimeoutMs = 5 * 1000;
|
||||
res->retryStrategy = std::make_shared<RetryStrategy>();
|
||||
res->caFile = settings.caFile;
|
||||
return res;
|
||||
}
|
||||
|
||||
S3Helper::DownloadResult S3Helper::getObject(
|
||||
const std::string & bucketName, const std::string & key)
|
||||
{
|
||||
debug("fetching 's3://%s/%s'...", bucketName, key);
|
||||
S3Helper::DownloadResult S3Helper::getObject(const std::string& bucketName,
|
||||
const std::string& key) {
|
||||
debug("fetching 's3://%s/%s'...", bucketName, key);
|
||||
|
||||
auto request =
|
||||
Aws::S3::Model::GetObjectRequest()
|
||||
.WithBucket(bucketName)
|
||||
.WithKey(key);
|
||||
auto request =
|
||||
Aws::S3::Model::GetObjectRequest().WithBucket(bucketName).WithKey(key);
|
||||
|
||||
request.SetResponseStreamFactory([&]() {
|
||||
return Aws::New<std::stringstream>("STRINGSTREAM");
|
||||
request.SetResponseStreamFactory(
|
||||
[&]() { return Aws::New<std::stringstream>("STRINGSTREAM"); });
|
||||
|
||||
DownloadResult res;
|
||||
|
||||
auto now1 = std::chrono::steady_clock::now();
|
||||
|
||||
try {
|
||||
auto result = checkAws(fmt("AWS error fetching '%s'", key),
|
||||
client->GetObject(request));
|
||||
|
||||
res.data =
|
||||
decompress(result.GetContentEncoding(),
|
||||
dynamic_cast<std::stringstream&>(result.GetBody()).str());
|
||||
|
||||
} catch (S3Error& e) {
|
||||
if (e.err != Aws::S3::S3Errors::NO_SUCH_KEY) throw;
|
||||
}
|
||||
|
||||
auto now2 = std::chrono::steady_clock::now();
|
||||
|
||||
res.durationMs =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1)
|
||||
.count();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore {
|
||||
const Setting<std::string> profile{
|
||||
this, "", "profile", "The name of the AWS configuration profile to use."};
|
||||
const Setting<std::string> region{
|
||||
this, Aws::Region::US_EAST_1, "region", {"aws-region"}};
|
||||
const Setting<std::string> scheme{
|
||||
this, "", "scheme",
|
||||
"The scheme to use for S3 requests, https by default."};
|
||||
const Setting<std::string> endpoint{
|
||||
this, "", "endpoint",
|
||||
"An optional override of the endpoint to use when talking to S3."};
|
||||
const Setting<std::string> narinfoCompression{
|
||||
this, "", "narinfo-compression", "compression method for .narinfo files"};
|
||||
const Setting<std::string> lsCompression{this, "", "ls-compression",
|
||||
"compression method for .ls files"};
|
||||
const Setting<std::string> logCompression{
|
||||
this, "", "log-compression", "compression method for log/* files"};
|
||||
const Setting<bool> multipartUpload{this, false, "multipart-upload",
|
||||
"whether to use multi-part uploads"};
|
||||
const Setting<uint64_t> bufferSize{
|
||||
this, 5 * 1024 * 1024, "buffer-size",
|
||||
"size (in bytes) of each part in multi-part uploads"};
|
||||
|
||||
std::string bucketName;
|
||||
|
||||
Stats stats;
|
||||
|
||||
S3Helper s3Helper;
|
||||
|
||||
S3BinaryCacheStoreImpl(const Params& params, const std::string& bucketName)
|
||||
: S3BinaryCacheStore(params),
|
||||
bucketName(bucketName),
|
||||
s3Helper(profile, region, scheme, endpoint) {
|
||||
diskCache = getNarInfoDiskCache();
|
||||
}
|
||||
|
||||
std::string getUri() override { return "s3://" + bucketName; }
|
||||
|
||||
void init() override {
|
||||
if (!diskCache->cacheExists(getUri(), wantMassQuery_, priority)) {
|
||||
BinaryCacheStore::init();
|
||||
|
||||
diskCache->createCache(getUri(), storeDir, wantMassQuery_, priority);
|
||||
}
|
||||
}
|
||||
|
||||
const Stats& getS3Stats() override { return stats; }
|
||||
|
||||
/* This is a specialisation of isValidPath() that optimistically
|
||||
fetches the .narinfo file, rather than first checking for its
|
||||
existence via a HEAD request. Since .narinfos are small, doing
|
||||
a GET is unlikely to be slower than HEAD. */
|
||||
bool isValidPathUncached(const Path& storePath) override {
|
||||
try {
|
||||
queryPathInfo(storePath);
|
||||
return true;
|
||||
} catch (InvalidPath& e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool fileExists(const std::string& path) override {
|
||||
stats.head++;
|
||||
|
||||
auto res = s3Helper.client->HeadObject(Aws::S3::Model::HeadObjectRequest()
|
||||
.WithBucket(bucketName)
|
||||
.WithKey(path));
|
||||
|
||||
if (!res.IsSuccess()) {
|
||||
auto& error = res.GetError();
|
||||
if (error.GetErrorType() == Aws::S3::S3Errors::RESOURCE_NOT_FOUND ||
|
||||
error.GetErrorType() == Aws::S3::S3Errors::NO_SUCH_KEY
|
||||
// If bucket listing is disabled, 404s turn into 403s
|
||||
|| error.GetErrorType() == Aws::S3::S3Errors::ACCESS_DENIED)
|
||||
return false;
|
||||
throw Error(format("AWS error fetching '%s': %s") % path %
|
||||
error.GetMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<TransferManager> transferManager;
|
||||
std::once_flag transferManagerCreated;
|
||||
|
||||
void uploadFile(const std::string& path, const std::string& data,
|
||||
const std::string& mimeType,
|
||||
const std::string& contentEncoding) {
|
||||
auto stream = std::make_shared<istringstream_nocopy>(data);
|
||||
|
||||
auto maxThreads = std::thread::hardware_concurrency();
|
||||
|
||||
static std::shared_ptr<Aws::Utils::Threading::PooledThreadExecutor>
|
||||
executor =
|
||||
std::make_shared<Aws::Utils::Threading::PooledThreadExecutor>(
|
||||
maxThreads);
|
||||
|
||||
std::call_once(transferManagerCreated, [&]() {
|
||||
if (multipartUpload) {
|
||||
TransferManagerConfiguration transferConfig(executor.get());
|
||||
|
||||
transferConfig.s3Client = s3Helper.client;
|
||||
transferConfig.bufferSize = bufferSize;
|
||||
|
||||
transferConfig.uploadProgressCallback =
|
||||
[](const TransferManager* transferManager,
|
||||
const std::shared_ptr<const TransferHandle>& transferHandle) {
|
||||
// FIXME: find a way to properly abort the multipart upload.
|
||||
// checkInterrupt();
|
||||
debug("upload progress ('%s'): '%d' of '%d' bytes",
|
||||
transferHandle->GetKey(),
|
||||
transferHandle->GetBytesTransferred(),
|
||||
transferHandle->GetBytesTotalSize());
|
||||
};
|
||||
|
||||
transferManager = TransferManager::Create(transferConfig);
|
||||
}
|
||||
});
|
||||
|
||||
DownloadResult res;
|
||||
|
||||
auto now1 = std::chrono::steady_clock::now();
|
||||
|
||||
try {
|
||||
if (transferManager) {
|
||||
if (contentEncoding != "")
|
||||
throw Error(
|
||||
"setting a content encoding is not supported with S3 multi-part "
|
||||
"uploads");
|
||||
|
||||
auto result = checkAws(fmt("AWS error fetching '%s'", key),
|
||||
client->GetObject(request));
|
||||
std::shared_ptr<TransferHandle> transferHandle =
|
||||
transferManager->UploadFile(stream, bucketName, path, mimeType,
|
||||
Aws::Map<Aws::String, Aws::String>(),
|
||||
nullptr /*, contentEncoding */);
|
||||
|
||||
res.data = decompress(result.GetContentEncoding(),
|
||||
dynamic_cast<std::stringstream &>(result.GetBody()).str());
|
||||
transferHandle->WaitUntilFinished();
|
||||
|
||||
} catch (S3Error & e) {
|
||||
if (e.err != Aws::S3::S3Errors::NO_SUCH_KEY) throw;
|
||||
if (transferHandle->GetStatus() == TransferStatus::FAILED)
|
||||
throw Error("AWS error: failed to upload 's3://%s/%s': %s", bucketName,
|
||||
path, transferHandle->GetLastError().GetMessage());
|
||||
|
||||
if (transferHandle->GetStatus() != TransferStatus::COMPLETED)
|
||||
throw Error(
|
||||
"AWS error: transfer status of 's3://%s/%s' in unexpected state",
|
||||
bucketName, path);
|
||||
|
||||
} else {
|
||||
auto request = Aws::S3::Model::PutObjectRequest()
|
||||
.WithBucket(bucketName)
|
||||
.WithKey(path);
|
||||
|
||||
request.SetContentType(mimeType);
|
||||
|
||||
if (contentEncoding != "") request.SetContentEncoding(contentEncoding);
|
||||
|
||||
auto stream = std::make_shared<istringstream_nocopy>(data);
|
||||
|
||||
request.SetBody(stream);
|
||||
|
||||
auto result = checkAws(fmt("AWS error uploading '%s'", path),
|
||||
s3Helper.client->PutObject(request));
|
||||
}
|
||||
|
||||
auto now2 = std::chrono::steady_clock::now();
|
||||
|
||||
res.durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1).count();
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1)
|
||||
.count();
|
||||
|
||||
return res;
|
||||
}
|
||||
printInfo(format("uploaded 's3://%1%/%2%' (%3% bytes) in %4% ms") %
|
||||
bucketName % path % data.size() % duration);
|
||||
|
||||
struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore
|
||||
{
|
||||
const Setting<std::string> profile{this, "", "profile", "The name of the AWS configuration profile to use."};
|
||||
const Setting<std::string> region{this, Aws::Region::US_EAST_1, "region", {"aws-region"}};
|
||||
const Setting<std::string> scheme{this, "", "scheme", "The scheme to use for S3 requests, https by default."};
|
||||
const Setting<std::string> endpoint{this, "", "endpoint", "An optional override of the endpoint to use when talking to S3."};
|
||||
const Setting<std::string> narinfoCompression{this, "", "narinfo-compression", "compression method for .narinfo files"};
|
||||
const Setting<std::string> lsCompression{this, "", "ls-compression", "compression method for .ls files"};
|
||||
const Setting<std::string> logCompression{this, "", "log-compression", "compression method for log/* files"};
|
||||
const Setting<bool> multipartUpload{
|
||||
this, false, "multipart-upload", "whether to use multi-part uploads"};
|
||||
const Setting<uint64_t> bufferSize{
|
||||
this, 5 * 1024 * 1024, "buffer-size", "size (in bytes) of each part in multi-part uploads"};
|
||||
stats.putTimeMs += duration;
|
||||
stats.putBytes += data.size();
|
||||
stats.put++;
|
||||
}
|
||||
|
||||
std::string bucketName;
|
||||
void upsertFile(const std::string& path, const std::string& data,
|
||||
const std::string& mimeType) override {
|
||||
if (narinfoCompression != "" && hasSuffix(path, ".narinfo"))
|
||||
uploadFile(path, *compress(narinfoCompression, data), mimeType,
|
||||
narinfoCompression);
|
||||
else if (lsCompression != "" && hasSuffix(path, ".ls"))
|
||||
uploadFile(path, *compress(lsCompression, data), mimeType, lsCompression);
|
||||
else if (logCompression != "" && hasPrefix(path, "log/"))
|
||||
uploadFile(path, *compress(logCompression, data), mimeType,
|
||||
logCompression);
|
||||
else
|
||||
uploadFile(path, data, mimeType, "");
|
||||
}
|
||||
|
||||
Stats stats;
|
||||
void getFile(const std::string& path, Sink& sink) override {
|
||||
stats.get++;
|
||||
|
||||
S3Helper s3Helper;
|
||||
// FIXME: stream output to sink.
|
||||
auto res = s3Helper.getObject(bucketName, path);
|
||||
|
||||
S3BinaryCacheStoreImpl(
|
||||
const Params & params, const std::string & bucketName)
|
||||
: S3BinaryCacheStore(params)
|
||||
, bucketName(bucketName)
|
||||
, s3Helper(profile, region, scheme, endpoint)
|
||||
{
|
||||
diskCache = getNarInfoDiskCache();
|
||||
}
|
||||
stats.getBytes += res.data ? res.data->size() : 0;
|
||||
stats.getTimeMs += res.durationMs;
|
||||
|
||||
std::string getUri() override
|
||||
{
|
||||
return "s3://" + bucketName;
|
||||
}
|
||||
if (res.data) {
|
||||
printTalkative("downloaded 's3://%s/%s' (%d bytes) in %d ms", bucketName,
|
||||
path, res.data->size(), res.durationMs);
|
||||
|
||||
void init() override
|
||||
{
|
||||
if (!diskCache->cacheExists(getUri(), wantMassQuery_, priority)) {
|
||||
sink((unsigned char*)res.data->data(), res.data->size());
|
||||
} else
|
||||
throw NoSuchBinaryCacheFile(
|
||||
"file '%s' does not exist in binary cache '%s'", path, getUri());
|
||||
}
|
||||
|
||||
BinaryCacheStore::init();
|
||||
PathSet queryAllValidPaths() override {
|
||||
PathSet paths;
|
||||
std::string marker;
|
||||
|
||||
diskCache->createCache(getUri(), storeDir, wantMassQuery_, priority);
|
||||
}
|
||||
}
|
||||
do {
|
||||
debug(format("listing bucket 's3://%s' from key '%s'...") % bucketName %
|
||||
marker);
|
||||
|
||||
const Stats & getS3Stats() override
|
||||
{
|
||||
return stats;
|
||||
}
|
||||
auto res = checkAws(
|
||||
format("AWS error listing bucket '%s'") % bucketName,
|
||||
s3Helper.client->ListObjects(Aws::S3::Model::ListObjectsRequest()
|
||||
.WithBucket(bucketName)
|
||||
.WithDelimiter("/")
|
||||
.WithMarker(marker)));
|
||||
|
||||
/* This is a specialisation of isValidPath() that optimistically
|
||||
fetches the .narinfo file, rather than first checking for its
|
||||
existence via a HEAD request. Since .narinfos are small, doing
|
||||
a GET is unlikely to be slower than HEAD. */
|
||||
bool isValidPathUncached(const Path & storePath) override
|
||||
{
|
||||
try {
|
||||
queryPathInfo(storePath);
|
||||
return true;
|
||||
} catch (InvalidPath & e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto& contents = res.GetContents();
|
||||
|
||||
bool fileExists(const std::string & path) override
|
||||
{
|
||||
stats.head++;
|
||||
debug(format("got %d keys, next marker '%s'") % contents.size() %
|
||||
res.GetNextMarker());
|
||||
|
||||
auto res = s3Helper.client->HeadObject(
|
||||
Aws::S3::Model::HeadObjectRequest()
|
||||
.WithBucket(bucketName)
|
||||
.WithKey(path));
|
||||
for (auto object : contents) {
|
||||
auto& key = object.GetKey();
|
||||
if (key.size() != 40 || !hasSuffix(key, ".narinfo")) continue;
|
||||
paths.insert(storeDir + "/" + key.substr(0, key.size() - 8));
|
||||
}
|
||||
|
||||
if (!res.IsSuccess()) {
|
||||
auto & error = res.GetError();
|
||||
if (error.GetErrorType() == Aws::S3::S3Errors::RESOURCE_NOT_FOUND
|
||||
|| error.GetErrorType() == Aws::S3::S3Errors::NO_SUCH_KEY
|
||||
// If bucket listing is disabled, 404s turn into 403s
|
||||
|| error.GetErrorType() == Aws::S3::S3Errors::ACCESS_DENIED)
|
||||
return false;
|
||||
throw Error(format("AWS error fetching '%s': %s") % path % error.GetMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<TransferManager> transferManager;
|
||||
std::once_flag transferManagerCreated;
|
||||
|
||||
void uploadFile(const std::string & path, const std::string & data,
|
||||
const std::string & mimeType,
|
||||
const std::string & contentEncoding)
|
||||
{
|
||||
auto stream = std::make_shared<istringstream_nocopy>(data);
|
||||
|
||||
auto maxThreads = std::thread::hardware_concurrency();
|
||||
|
||||
static std::shared_ptr<Aws::Utils::Threading::PooledThreadExecutor>
|
||||
executor = std::make_shared<Aws::Utils::Threading::PooledThreadExecutor>(maxThreads);
|
||||
|
||||
std::call_once(transferManagerCreated, [&]()
|
||||
{
|
||||
if (multipartUpload) {
|
||||
TransferManagerConfiguration transferConfig(executor.get());
|
||||
|
||||
transferConfig.s3Client = s3Helper.client;
|
||||
transferConfig.bufferSize = bufferSize;
|
||||
|
||||
transferConfig.uploadProgressCallback =
|
||||
[](const TransferManager *transferManager,
|
||||
const std::shared_ptr<const TransferHandle>
|
||||
&transferHandle)
|
||||
{
|
||||
//FIXME: find a way to properly abort the multipart upload.
|
||||
//checkInterrupt();
|
||||
debug("upload progress ('%s'): '%d' of '%d' bytes",
|
||||
transferHandle->GetKey(),
|
||||
transferHandle->GetBytesTransferred(),
|
||||
transferHandle->GetBytesTotalSize());
|
||||
};
|
||||
|
||||
transferManager = TransferManager::Create(transferConfig);
|
||||
}
|
||||
});
|
||||
|
||||
auto now1 = std::chrono::steady_clock::now();
|
||||
|
||||
if (transferManager) {
|
||||
|
||||
if (contentEncoding != "")
|
||||
throw Error("setting a content encoding is not supported with S3 multi-part uploads");
|
||||
|
||||
std::shared_ptr<TransferHandle> transferHandle =
|
||||
transferManager->UploadFile(
|
||||
stream, bucketName, path, mimeType,
|
||||
Aws::Map<Aws::String, Aws::String>(),
|
||||
nullptr /*, contentEncoding */);
|
||||
|
||||
transferHandle->WaitUntilFinished();
|
||||
|
||||
if (transferHandle->GetStatus() == TransferStatus::FAILED)
|
||||
throw Error("AWS error: failed to upload 's3://%s/%s': %s",
|
||||
bucketName, path, transferHandle->GetLastError().GetMessage());
|
||||
|
||||
if (transferHandle->GetStatus() != TransferStatus::COMPLETED)
|
||||
throw Error("AWS error: transfer status of 's3://%s/%s' in unexpected state",
|
||||
bucketName, path);
|
||||
|
||||
} else {
|
||||
|
||||
auto request =
|
||||
Aws::S3::Model::PutObjectRequest()
|
||||
.WithBucket(bucketName)
|
||||
.WithKey(path);
|
||||
|
||||
request.SetContentType(mimeType);
|
||||
|
||||
if (contentEncoding != "")
|
||||
request.SetContentEncoding(contentEncoding);
|
||||
|
||||
auto stream = std::make_shared<istringstream_nocopy>(data);
|
||||
|
||||
request.SetBody(stream);
|
||||
|
||||
auto result = checkAws(fmt("AWS error uploading '%s'", path),
|
||||
s3Helper.client->PutObject(request));
|
||||
}
|
||||
|
||||
auto now2 = std::chrono::steady_clock::now();
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1)
|
||||
.count();
|
||||
|
||||
printInfo(format("uploaded 's3://%1%/%2%' (%3% bytes) in %4% ms") %
|
||||
bucketName % path % data.size() % duration);
|
||||
|
||||
stats.putTimeMs += duration;
|
||||
stats.putBytes += data.size();
|
||||
stats.put++;
|
||||
}
|
||||
|
||||
void upsertFile(const std::string & path, const std::string & data,
|
||||
const std::string & mimeType) override
|
||||
{
|
||||
if (narinfoCompression != "" && hasSuffix(path, ".narinfo"))
|
||||
uploadFile(path, *compress(narinfoCompression, data), mimeType, narinfoCompression);
|
||||
else if (lsCompression != "" && hasSuffix(path, ".ls"))
|
||||
uploadFile(path, *compress(lsCompression, data), mimeType, lsCompression);
|
||||
else if (logCompression != "" && hasPrefix(path, "log/"))
|
||||
uploadFile(path, *compress(logCompression, data), mimeType, logCompression);
|
||||
else
|
||||
uploadFile(path, data, mimeType, "");
|
||||
}
|
||||
|
||||
void getFile(const std::string & path, Sink & sink) override
|
||||
{
|
||||
stats.get++;
|
||||
|
||||
// FIXME: stream output to sink.
|
||||
auto res = s3Helper.getObject(bucketName, path);
|
||||
|
||||
stats.getBytes += res.data ? res.data->size() : 0;
|
||||
stats.getTimeMs += res.durationMs;
|
||||
|
||||
if (res.data) {
|
||||
printTalkative("downloaded 's3://%s/%s' (%d bytes) in %d ms",
|
||||
bucketName, path, res.data->size(), res.durationMs);
|
||||
|
||||
sink((unsigned char *) res.data->data(), res.data->size());
|
||||
} else
|
||||
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache '%s'", path, getUri());
|
||||
}
|
||||
|
||||
PathSet queryAllValidPaths() override
|
||||
{
|
||||
PathSet paths;
|
||||
std::string marker;
|
||||
|
||||
do {
|
||||
debug(format("listing bucket 's3://%s' from key '%s'...") % bucketName % marker);
|
||||
|
||||
auto res = checkAws(format("AWS error listing bucket '%s'") % bucketName,
|
||||
s3Helper.client->ListObjects(
|
||||
Aws::S3::Model::ListObjectsRequest()
|
||||
.WithBucket(bucketName)
|
||||
.WithDelimiter("/")
|
||||
.WithMarker(marker)));
|
||||
|
||||
auto & contents = res.GetContents();
|
||||
|
||||
debug(format("got %d keys, next marker '%s'")
|
||||
% contents.size() % res.GetNextMarker());
|
||||
|
||||
for (auto object : contents) {
|
||||
auto & key = object.GetKey();
|
||||
if (key.size() != 40 || !hasSuffix(key, ".narinfo")) continue;
|
||||
paths.insert(storeDir + "/" + key.substr(0, key.size() - 8));
|
||||
}
|
||||
|
||||
marker = res.GetNextMarker();
|
||||
} while (!marker.empty());
|
||||
|
||||
return paths;
|
||||
}
|
||||
marker = res.GetNextMarker();
|
||||
} while (!marker.empty());
|
||||
|
||||
return paths;
|
||||
}
|
||||
};
|
||||
|
||||
static RegisterStoreImplementation regStore([](
|
||||
const std::string & uri, const Store::Params & params)
|
||||
-> std::shared_ptr<Store>
|
||||
{
|
||||
if (std::string(uri, 0, 5) != "s3://") return 0;
|
||||
auto store = std::make_shared<S3BinaryCacheStoreImpl>(params, std::string(uri, 5));
|
||||
store->init();
|
||||
return store;
|
||||
});
|
||||
static RegisterStoreImplementation regStore(
|
||||
[](const std::string& uri,
|
||||
const Store::Params& params) -> std::shared_ptr<Store> {
|
||||
if (std::string(uri, 0, 5) != "s3://") return 0;
|
||||
auto store =
|
||||
std::make_shared<S3BinaryCacheStoreImpl>(params, std::string(uri, 5));
|
||||
store->init();
|
||||
return store;
|
||||
});
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,33 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "binary-cache-store.hh"
|
||||
|
||||
#include <atomic>
|
||||
#include "binary-cache-store.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class S3BinaryCacheStore : public BinaryCacheStore
|
||||
{
|
||||
protected:
|
||||
class S3BinaryCacheStore : public BinaryCacheStore {
|
||||
protected:
|
||||
S3BinaryCacheStore(const Params& params) : BinaryCacheStore(params) {}
|
||||
|
||||
S3BinaryCacheStore(const Params & params)
|
||||
: BinaryCacheStore(params)
|
||||
{ }
|
||||
public:
|
||||
struct Stats {
|
||||
std::atomic<uint64_t> put{0};
|
||||
std::atomic<uint64_t> putBytes{0};
|
||||
std::atomic<uint64_t> putTimeMs{0};
|
||||
std::atomic<uint64_t> get{0};
|
||||
std::atomic<uint64_t> getBytes{0};
|
||||
std::atomic<uint64_t> getTimeMs{0};
|
||||
std::atomic<uint64_t> head{0};
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
struct Stats
|
||||
{
|
||||
std::atomic<uint64_t> put{0};
|
||||
std::atomic<uint64_t> putBytes{0};
|
||||
std::atomic<uint64_t> putTimeMs{0};
|
||||
std::atomic<uint64_t> get{0};
|
||||
std::atomic<uint64_t> getBytes{0};
|
||||
std::atomic<uint64_t> getTimeMs{0};
|
||||
std::atomic<uint64_t> head{0};
|
||||
};
|
||||
|
||||
virtual const Stats & getS3Stats() = 0;
|
||||
virtual const Stats& getS3Stats() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
41
third_party/nix/src/libstore/s3.hh
vendored
41
third_party/nix/src/libstore/s3.hh
vendored
|
|
@ -4,30 +4,39 @@
|
|||
|
||||
#include "ref.hh"
|
||||
|
||||
namespace Aws { namespace Client { class ClientConfiguration; } }
|
||||
namespace Aws { namespace S3 { class S3Client; } }
|
||||
namespace Aws {
|
||||
namespace Client {
|
||||
class ClientConfiguration;
|
||||
}
|
||||
} // namespace Aws
|
||||
namespace Aws {
|
||||
namespace S3 {
|
||||
class S3Client;
|
||||
}
|
||||
} // namespace Aws
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct S3Helper
|
||||
{
|
||||
ref<Aws::Client::ClientConfiguration> config;
|
||||
ref<Aws::S3::S3Client> client;
|
||||
struct S3Helper {
|
||||
ref<Aws::Client::ClientConfiguration> config;
|
||||
ref<Aws::S3::S3Client> client;
|
||||
|
||||
S3Helper(const std::string & profile, const std::string & region, const std::string & scheme, const std::string & endpoint);
|
||||
S3Helper(const std::string& profile, const std::string& region,
|
||||
const std::string& scheme, const std::string& endpoint);
|
||||
|
||||
ref<Aws::Client::ClientConfiguration> makeConfig(const std::string & region, const std::string & scheme, const std::string & endpoint);
|
||||
ref<Aws::Client::ClientConfiguration> makeConfig(const std::string& region,
|
||||
const std::string& scheme,
|
||||
const std::string& endpoint);
|
||||
|
||||
struct DownloadResult
|
||||
{
|
||||
std::shared_ptr<std::string> data;
|
||||
unsigned int durationMs;
|
||||
};
|
||||
struct DownloadResult {
|
||||
std::shared_ptr<std::string> data;
|
||||
unsigned int durationMs;
|
||||
};
|
||||
|
||||
DownloadResult getObject(
|
||||
const std::string & bucketName, const std::string & key);
|
||||
DownloadResult getObject(const std::string& bucketName,
|
||||
const std::string& key);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
||||
#endif
|
||||
|
|
|
|||
24
third_party/nix/src/libstore/serve-protocol.hh
vendored
24
third_party/nix/src/libstore/serve-protocol.hh
vendored
|
|
@ -6,19 +6,19 @@ namespace nix {
|
|||
#define SERVE_MAGIC_2 0x5452eecb
|
||||
|
||||
#define SERVE_PROTOCOL_VERSION 0x205
|
||||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||
#define GET_PROTOCOL_MAJOR(x) ((x)&0xff00)
|
||||
#define GET_PROTOCOL_MINOR(x) ((x)&0x00ff)
|
||||
|
||||
typedef enum {
|
||||
cmdQueryValidPaths = 1,
|
||||
cmdQueryPathInfos = 2,
|
||||
cmdDumpStorePath = 3,
|
||||
cmdImportPaths = 4,
|
||||
cmdExportPaths = 5,
|
||||
cmdBuildPaths = 6,
|
||||
cmdQueryClosure = 7,
|
||||
cmdBuildDerivation = 8,
|
||||
cmdAddToStoreNar = 9,
|
||||
cmdQueryValidPaths = 1,
|
||||
cmdQueryPathInfos = 2,
|
||||
cmdDumpStorePath = 3,
|
||||
cmdImportPaths = 4,
|
||||
cmdExportPaths = 5,
|
||||
cmdBuildPaths = 6,
|
||||
cmdQueryClosure = 7,
|
||||
cmdBuildDerivation = 8,
|
||||
cmdAddToStoreNar = 9,
|
||||
} ServeCommand;
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
274
third_party/nix/src/libstore/sqlite.cc
vendored
274
third_party/nix/src/libstore/sqlite.cc
vendored
|
|
@ -1,198 +1,172 @@
|
|||
#include "sqlite.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <atomic>
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs)
|
||||
{
|
||||
int err = sqlite3_errcode(db);
|
||||
int exterr = sqlite3_extended_errcode(db);
|
||||
[[noreturn]] void throwSQLiteError(sqlite3* db, const FormatOrString& fs) {
|
||||
int err = sqlite3_errcode(db);
|
||||
int exterr = sqlite3_extended_errcode(db);
|
||||
|
||||
auto path = sqlite3_db_filename(db, nullptr);
|
||||
if (!path) path = "(in-memory)";
|
||||
auto path = sqlite3_db_filename(db, nullptr);
|
||||
if (!path) path = "(in-memory)";
|
||||
|
||||
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
|
||||
throw SQLiteBusy(
|
||||
err == SQLITE_PROTOCOL
|
||||
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
|
||||
throw SQLiteBusy(
|
||||
err == SQLITE_PROTOCOL
|
||||
? fmt("SQLite database '%s' is busy (SQLITE_PROTOCOL)", path)
|
||||
: fmt("SQLite database '%s' is busy", path));
|
||||
}
|
||||
else
|
||||
throw SQLiteError("%s: %s (in '%s')", fs.s, sqlite3_errstr(exterr), path);
|
||||
} else
|
||||
throw SQLiteError("%s: %s (in '%s')", fs.s, sqlite3_errstr(exterr), path);
|
||||
}
|
||||
|
||||
SQLite::SQLite(const Path & path)
|
||||
{
|
||||
if (sqlite3_open_v2(path.c_str(), &db,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0) != SQLITE_OK)
|
||||
throw Error(format("cannot open SQLite database '%s'") % path);
|
||||
SQLite::SQLite(const Path& path) {
|
||||
if (sqlite3_open_v2(path.c_str(), &db,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
|
||||
0) != SQLITE_OK)
|
||||
throw Error(format("cannot open SQLite database '%s'") % path);
|
||||
}
|
||||
|
||||
SQLite::~SQLite()
|
||||
{
|
||||
try {
|
||||
if (db && sqlite3_close(db) != SQLITE_OK)
|
||||
throwSQLiteError(db, "closing database");
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
SQLite::~SQLite() {
|
||||
try {
|
||||
if (db && sqlite3_close(db) != SQLITE_OK)
|
||||
throwSQLiteError(db, "closing database");
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
void SQLite::exec(const std::string & stmt)
|
||||
{
|
||||
retrySQLite<void>([&]() {
|
||||
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, format("executing SQLite statement '%s'") % stmt);
|
||||
});
|
||||
void SQLite::exec(const std::string& stmt) {
|
||||
retrySQLite<void>([&]() {
|
||||
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, format("executing SQLite statement '%s'") % stmt);
|
||||
});
|
||||
}
|
||||
|
||||
void SQLiteStmt::create(sqlite3 * db, const string & sql)
|
||||
{
|
||||
checkInterrupt();
|
||||
assert(!stmt);
|
||||
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, fmt("creating statement '%s'", sql));
|
||||
this->db = db;
|
||||
this->sql = sql;
|
||||
void SQLiteStmt::create(sqlite3* db, const string& sql) {
|
||||
checkInterrupt();
|
||||
assert(!stmt);
|
||||
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, fmt("creating statement '%s'", sql));
|
||||
this->db = db;
|
||||
this->sql = sql;
|
||||
}
|
||||
|
||||
SQLiteStmt::~SQLiteStmt()
|
||||
{
|
||||
try {
|
||||
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
|
||||
throwSQLiteError(db, fmt("finalizing statement '%s'", sql));
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
SQLiteStmt::~SQLiteStmt() {
|
||||
try {
|
||||
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
|
||||
throwSQLiteError(db, fmt("finalizing statement '%s'", sql));
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
SQLiteStmt::Use::Use(SQLiteStmt & stmt)
|
||||
: stmt(stmt)
|
||||
{
|
||||
assert(stmt.stmt);
|
||||
/* Note: sqlite3_reset() returns the error code for the most
|
||||
recent call to sqlite3_step(). So ignore it. */
|
||||
sqlite3_reset(stmt);
|
||||
SQLiteStmt::Use::Use(SQLiteStmt& stmt) : stmt(stmt) {
|
||||
assert(stmt.stmt);
|
||||
/* Note: sqlite3_reset() returns the error code for the most
|
||||
recent call to sqlite3_step(). So ignore it. */
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
|
||||
SQLiteStmt::Use::~Use()
|
||||
{
|
||||
sqlite3_reset(stmt);
|
||||
SQLiteStmt::Use::~Use() { sqlite3_reset(stmt); }
|
||||
|
||||
SQLiteStmt::Use& SQLiteStmt::Use::operator()(const std::string& value,
|
||||
bool notNull) {
|
||||
if (notNull) {
|
||||
if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1,
|
||||
SQLITE_TRANSIENT) != SQLITE_OK)
|
||||
throwSQLiteError(stmt.db, "binding argument");
|
||||
} else
|
||||
bind();
|
||||
return *this;
|
||||
}
|
||||
|
||||
SQLiteStmt::Use & SQLiteStmt::Use::operator () (const std::string & value, bool notNull)
|
||||
{
|
||||
if (notNull) {
|
||||
if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
|
||||
throwSQLiteError(stmt.db, "binding argument");
|
||||
} else
|
||||
bind();
|
||||
return *this;
|
||||
SQLiteStmt::Use& SQLiteStmt::Use::operator()(int64_t value, bool notNull) {
|
||||
if (notNull) {
|
||||
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
|
||||
throwSQLiteError(stmt.db, "binding argument");
|
||||
} else
|
||||
bind();
|
||||
return *this;
|
||||
}
|
||||
|
||||
SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull)
|
||||
{
|
||||
if (notNull) {
|
||||
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
|
||||
throwSQLiteError(stmt.db, "binding argument");
|
||||
} else
|
||||
bind();
|
||||
return *this;
|
||||
SQLiteStmt::Use& SQLiteStmt::Use::bind() {
|
||||
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
|
||||
throwSQLiteError(stmt.db, "binding argument");
|
||||
return *this;
|
||||
}
|
||||
|
||||
SQLiteStmt::Use & SQLiteStmt::Use::bind()
|
||||
{
|
||||
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
|
||||
throwSQLiteError(stmt.db, "binding argument");
|
||||
return *this;
|
||||
int SQLiteStmt::Use::step() { return sqlite3_step(stmt); }
|
||||
|
||||
void SQLiteStmt::Use::exec() {
|
||||
int r = step();
|
||||
assert(r != SQLITE_ROW);
|
||||
if (r != SQLITE_DONE)
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite statement '%s'", stmt.sql));
|
||||
}
|
||||
|
||||
int SQLiteStmt::Use::step()
|
||||
{
|
||||
return sqlite3_step(stmt);
|
||||
bool SQLiteStmt::Use::next() {
|
||||
int r = step();
|
||||
if (r != SQLITE_DONE && r != SQLITE_ROW)
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite query '%s'", stmt.sql));
|
||||
return r == SQLITE_ROW;
|
||||
}
|
||||
|
||||
void SQLiteStmt::Use::exec()
|
||||
{
|
||||
int r = step();
|
||||
assert(r != SQLITE_ROW);
|
||||
if (r != SQLITE_DONE)
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite statement '%s'", stmt.sql));
|
||||
std::string SQLiteStmt::Use::getStr(int col) {
|
||||
auto s = (const char*)sqlite3_column_text(stmt, col);
|
||||
assert(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool SQLiteStmt::Use::next()
|
||||
{
|
||||
int r = step();
|
||||
if (r != SQLITE_DONE && r != SQLITE_ROW)
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite query '%s'", stmt.sql));
|
||||
return r == SQLITE_ROW;
|
||||
int64_t SQLiteStmt::Use::getInt(int col) {
|
||||
// FIXME: detect nulls?
|
||||
return sqlite3_column_int64(stmt, col);
|
||||
}
|
||||
|
||||
std::string SQLiteStmt::Use::getStr(int col)
|
||||
{
|
||||
auto s = (const char *) sqlite3_column_text(stmt, col);
|
||||
assert(s);
|
||||
return s;
|
||||
bool SQLiteStmt::Use::isNull(int col) {
|
||||
return sqlite3_column_type(stmt, col) == SQLITE_NULL;
|
||||
}
|
||||
|
||||
int64_t SQLiteStmt::Use::getInt(int col)
|
||||
{
|
||||
// FIXME: detect nulls?
|
||||
return sqlite3_column_int64(stmt, col);
|
||||
SQLiteTxn::SQLiteTxn(sqlite3* db) {
|
||||
this->db = db;
|
||||
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, "starting transaction");
|
||||
active = true;
|
||||
}
|
||||
|
||||
bool SQLiteStmt::Use::isNull(int col)
|
||||
{
|
||||
return sqlite3_column_type(stmt, col) == SQLITE_NULL;
|
||||
void SQLiteTxn::commit() {
|
||||
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, "committing transaction");
|
||||
active = false;
|
||||
}
|
||||
|
||||
SQLiteTxn::SQLiteTxn(sqlite3 * db)
|
||||
{
|
||||
this->db = db;
|
||||
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, "starting transaction");
|
||||
active = true;
|
||||
SQLiteTxn::~SQLiteTxn() {
|
||||
try {
|
||||
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, "aborting transaction");
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
void SQLiteTxn::commit()
|
||||
{
|
||||
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, "committing transaction");
|
||||
active = false;
|
||||
void handleSQLiteBusy(const SQLiteBusy& e) {
|
||||
static std::atomic<time_t> lastWarned{0};
|
||||
|
||||
time_t now = time(0);
|
||||
|
||||
if (now > lastWarned + 10) {
|
||||
lastWarned = now;
|
||||
printError("warning: %s", e.what());
|
||||
}
|
||||
|
||||
/* Sleep for a while since retrying the transaction right away
|
||||
is likely to fail again. */
|
||||
checkInterrupt();
|
||||
struct timespec t;
|
||||
t.tv_sec = 0;
|
||||
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
|
||||
nanosleep(&t, 0);
|
||||
}
|
||||
|
||||
SQLiteTxn::~SQLiteTxn()
|
||||
{
|
||||
try {
|
||||
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, "aborting transaction");
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
void handleSQLiteBusy(const SQLiteBusy & e)
|
||||
{
|
||||
static std::atomic<time_t> lastWarned{0};
|
||||
|
||||
time_t now = time(0);
|
||||
|
||||
if (now > lastWarned + 10) {
|
||||
lastWarned = now;
|
||||
printError("warning: %s", e.what());
|
||||
}
|
||||
|
||||
/* Sleep for a while since retrying the transaction right away
|
||||
is likely to fail again. */
|
||||
checkInterrupt();
|
||||
struct timespec t;
|
||||
t.tv_sec = 0;
|
||||
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
|
||||
nanosleep(&t, 0);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
134
third_party/nix/src/libstore/sqlite.hh
vendored
134
third_party/nix/src/libstore/sqlite.hh
vendored
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
class sqlite3;
|
||||
|
|
@ -11,104 +10,99 @@ class sqlite3_stmt;
|
|||
namespace nix {
|
||||
|
||||
/* RAII wrapper to close a SQLite database automatically. */
|
||||
struct SQLite
|
||||
{
|
||||
sqlite3 * db = 0;
|
||||
SQLite() { }
|
||||
SQLite(const Path & path);
|
||||
SQLite(const SQLite & from) = delete;
|
||||
SQLite& operator = (const SQLite & from) = delete;
|
||||
SQLite& operator = (SQLite && from) { db = from.db; from.db = 0; return *this; }
|
||||
~SQLite();
|
||||
operator sqlite3 * () { return db; }
|
||||
struct SQLite {
|
||||
sqlite3* db = 0;
|
||||
SQLite() {}
|
||||
SQLite(const Path& path);
|
||||
SQLite(const SQLite& from) = delete;
|
||||
SQLite& operator=(const SQLite& from) = delete;
|
||||
SQLite& operator=(SQLite&& from) {
|
||||
db = from.db;
|
||||
from.db = 0;
|
||||
return *this;
|
||||
}
|
||||
~SQLite();
|
||||
operator sqlite3*() { return db; }
|
||||
|
||||
void exec(const std::string & stmt);
|
||||
void exec(const std::string& stmt);
|
||||
};
|
||||
|
||||
/* RAII wrapper to create and destroy SQLite prepared statements. */
|
||||
struct SQLiteStmt
|
||||
{
|
||||
sqlite3 * db = 0;
|
||||
sqlite3_stmt * stmt = 0;
|
||||
std::string sql;
|
||||
SQLiteStmt() { }
|
||||
SQLiteStmt(sqlite3 * db, const std::string & sql) { create(db, sql); }
|
||||
void create(sqlite3 * db, const std::string & s);
|
||||
~SQLiteStmt();
|
||||
operator sqlite3_stmt * () { return stmt; }
|
||||
struct SQLiteStmt {
|
||||
sqlite3* db = 0;
|
||||
sqlite3_stmt* stmt = 0;
|
||||
std::string sql;
|
||||
SQLiteStmt() {}
|
||||
SQLiteStmt(sqlite3* db, const std::string& sql) { create(db, sql); }
|
||||
void create(sqlite3* db, const std::string& s);
|
||||
~SQLiteStmt();
|
||||
operator sqlite3_stmt*() { return stmt; }
|
||||
|
||||
/* Helper for binding / executing statements. */
|
||||
class Use
|
||||
{
|
||||
friend struct SQLiteStmt;
|
||||
private:
|
||||
SQLiteStmt & stmt;
|
||||
unsigned int curArg = 1;
|
||||
Use(SQLiteStmt & stmt);
|
||||
/* Helper for binding / executing statements. */
|
||||
class Use {
|
||||
friend struct SQLiteStmt;
|
||||
|
||||
public:
|
||||
private:
|
||||
SQLiteStmt& stmt;
|
||||
unsigned int curArg = 1;
|
||||
Use(SQLiteStmt& stmt);
|
||||
|
||||
~Use();
|
||||
public:
|
||||
~Use();
|
||||
|
||||
/* Bind the next parameter. */
|
||||
Use & operator () (const std::string & value, bool notNull = true);
|
||||
Use & operator () (int64_t value, bool notNull = true);
|
||||
Use & bind(); // null
|
||||
/* Bind the next parameter. */
|
||||
Use& operator()(const std::string& value, bool notNull = true);
|
||||
Use& operator()(int64_t value, bool notNull = true);
|
||||
Use& bind(); // null
|
||||
|
||||
int step();
|
||||
int step();
|
||||
|
||||
/* Execute a statement that does not return rows. */
|
||||
void exec();
|
||||
/* Execute a statement that does not return rows. */
|
||||
void exec();
|
||||
|
||||
/* For statements that return 0 or more rows. Returns true iff
|
||||
a row is available. */
|
||||
bool next();
|
||||
/* For statements that return 0 or more rows. Returns true iff
|
||||
a row is available. */
|
||||
bool next();
|
||||
|
||||
std::string getStr(int col);
|
||||
int64_t getInt(int col);
|
||||
bool isNull(int col);
|
||||
};
|
||||
std::string getStr(int col);
|
||||
int64_t getInt(int col);
|
||||
bool isNull(int col);
|
||||
};
|
||||
|
||||
Use use()
|
||||
{
|
||||
return Use(*this);
|
||||
}
|
||||
Use use() { return Use(*this); }
|
||||
};
|
||||
|
||||
/* RAII helper that ensures transactions are aborted unless explicitly
|
||||
committed. */
|
||||
struct SQLiteTxn
|
||||
{
|
||||
bool active = false;
|
||||
sqlite3 * db;
|
||||
struct SQLiteTxn {
|
||||
bool active = false;
|
||||
sqlite3* db;
|
||||
|
||||
SQLiteTxn(sqlite3 * db);
|
||||
SQLiteTxn(sqlite3* db);
|
||||
|
||||
void commit();
|
||||
void commit();
|
||||
|
||||
~SQLiteTxn();
|
||||
~SQLiteTxn();
|
||||
};
|
||||
|
||||
|
||||
MakeError(SQLiteError, Error);
|
||||
MakeError(SQLiteBusy, SQLiteError);
|
||||
|
||||
[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs);
|
||||
[[noreturn]] void throwSQLiteError(sqlite3* db, const FormatOrString& fs);
|
||||
|
||||
void handleSQLiteBusy(const SQLiteBusy & e);
|
||||
void handleSQLiteBusy(const SQLiteBusy& e);
|
||||
|
||||
/* Convenience function for retrying a SQLite transaction when the
|
||||
database is busy. */
|
||||
template<typename T>
|
||||
T retrySQLite(std::function<T()> fun)
|
||||
{
|
||||
while (true) {
|
||||
try {
|
||||
return fun();
|
||||
} catch (SQLiteBusy & e) {
|
||||
handleSQLiteBusy(e);
|
||||
}
|
||||
template <typename T>
|
||||
T retrySQLite(std::function<T()> fun) {
|
||||
while (true) {
|
||||
try {
|
||||
return fun();
|
||||
} catch (SQLiteBusy& e) {
|
||||
handleSQLiteBusy(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
128
third_party/nix/src/libstore/ssh-store.cc
vendored
128
third_party/nix/src/libstore/ssh-store.cc
vendored
|
|
@ -1,100 +1,84 @@
|
|||
#include "store-api.hh"
|
||||
#include "remote-store.hh"
|
||||
#include "remote-fs-accessor.hh"
|
||||
#include "archive.hh"
|
||||
#include "worker-protocol.hh"
|
||||
#include "pool.hh"
|
||||
#include "remote-fs-accessor.hh"
|
||||
#include "remote-store.hh"
|
||||
#include "ssh.hh"
|
||||
#include "store-api.hh"
|
||||
#include "worker-protocol.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static std::string uriScheme = "ssh-ng://";
|
||||
|
||||
class SSHStore : public RemoteStore
|
||||
{
|
||||
public:
|
||||
class SSHStore : public RemoteStore {
|
||||
public:
|
||||
const Setting<Path> sshKey{(Store*)this, "", "ssh-key",
|
||||
"path to an SSH private key"};
|
||||
const Setting<bool> compress{(Store*)this, false, "compress",
|
||||
"whether to compress the connection"};
|
||||
|
||||
const Setting<Path> sshKey{(Store*) this, "", "ssh-key", "path to an SSH private key"};
|
||||
const Setting<bool> compress{(Store*) this, false, "compress", "whether to compress the connection"};
|
||||
SSHStore(const std::string& host, const Params& params)
|
||||
: Store(params),
|
||||
RemoteStore(params),
|
||||
host(host),
|
||||
master(host, sshKey,
|
||||
// Use SSH master only if using more than 1 connection.
|
||||
connections->capacity() > 1, compress) {}
|
||||
|
||||
SSHStore(const std::string & host, const Params & params)
|
||||
: Store(params)
|
||||
, RemoteStore(params)
|
||||
, host(host)
|
||||
, master(
|
||||
host,
|
||||
sshKey,
|
||||
// Use SSH master only if using more than 1 connection.
|
||||
connections->capacity() > 1,
|
||||
compress)
|
||||
{
|
||||
}
|
||||
std::string getUri() override { return uriScheme + host; }
|
||||
|
||||
std::string getUri() override
|
||||
{
|
||||
return uriScheme + host;
|
||||
}
|
||||
bool sameMachine() { return false; }
|
||||
|
||||
bool sameMachine()
|
||||
{ return false; }
|
||||
void narFromPath(const Path& path, Sink& sink) override;
|
||||
|
||||
void narFromPath(const Path & path, Sink & sink) override;
|
||||
ref<FSAccessor> getFSAccessor() override;
|
||||
|
||||
ref<FSAccessor> getFSAccessor() override;
|
||||
private:
|
||||
struct Connection : RemoteStore::Connection {
|
||||
std::unique_ptr<SSHMaster::Connection> sshConn;
|
||||
};
|
||||
|
||||
private:
|
||||
ref<RemoteStore::Connection> openConnection() override;
|
||||
|
||||
struct Connection : RemoteStore::Connection
|
||||
{
|
||||
std::unique_ptr<SSHMaster::Connection> sshConn;
|
||||
};
|
||||
std::string host;
|
||||
|
||||
ref<RemoteStore::Connection> openConnection() override;
|
||||
SSHMaster master;
|
||||
|
||||
std::string host;
|
||||
|
||||
SSHMaster master;
|
||||
|
||||
void setOptions(RemoteStore::Connection & conn) override
|
||||
{
|
||||
/* TODO Add a way to explicitly ask for some options to be
|
||||
forwarded. One option: A way to query the daemon for its
|
||||
settings, and then a series of params to SSHStore like
|
||||
forward-cores or forward-overridden-cores that only
|
||||
override the requested settings.
|
||||
*/
|
||||
};
|
||||
void setOptions(RemoteStore::Connection& conn) override{
|
||||
/* TODO Add a way to explicitly ask for some options to be
|
||||
forwarded. One option: A way to query the daemon for its
|
||||
settings, and then a series of params to SSHStore like
|
||||
forward-cores or forward-overridden-cores that only
|
||||
override the requested settings.
|
||||
*/
|
||||
};
|
||||
};
|
||||
|
||||
void SSHStore::narFromPath(const Path & path, Sink & sink)
|
||||
{
|
||||
auto conn(connections->get());
|
||||
conn->to << wopNarFromPath << path;
|
||||
conn->processStderr();
|
||||
copyNAR(conn->from, sink);
|
||||
void SSHStore::narFromPath(const Path& path, Sink& sink) {
|
||||
auto conn(connections->get());
|
||||
conn->to << wopNarFromPath << path;
|
||||
conn->processStderr();
|
||||
copyNAR(conn->from, sink);
|
||||
}
|
||||
|
||||
ref<FSAccessor> SSHStore::getFSAccessor()
|
||||
{
|
||||
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()));
|
||||
ref<FSAccessor> SSHStore::getFSAccessor() {
|
||||
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()));
|
||||
}
|
||||
|
||||
ref<RemoteStore::Connection> SSHStore::openConnection()
|
||||
{
|
||||
auto conn = make_ref<Connection>();
|
||||
conn->sshConn = master.startCommand("nix-daemon --stdio");
|
||||
conn->to = FdSink(conn->sshConn->in.get());
|
||||
conn->from = FdSource(conn->sshConn->out.get());
|
||||
initConnection(*conn);
|
||||
return conn;
|
||||
ref<RemoteStore::Connection> SSHStore::openConnection() {
|
||||
auto conn = make_ref<Connection>();
|
||||
conn->sshConn = master.startCommand("nix-daemon --stdio");
|
||||
conn->to = FdSink(conn->sshConn->in.get());
|
||||
conn->from = FdSource(conn->sshConn->out.get());
|
||||
initConnection(*conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
static RegisterStoreImplementation regStore([](
|
||||
const std::string & uri, const Store::Params & params)
|
||||
-> std::shared_ptr<Store>
|
||||
{
|
||||
if (std::string(uri, 0, uriScheme.size()) != uriScheme) return 0;
|
||||
return std::make_shared<SSHStore>(std::string(uri, uriScheme.size()), params);
|
||||
static RegisterStoreImplementation regStore([](const std::string& uri,
|
||||
const Store::Params& params)
|
||||
-> std::shared_ptr<Store> {
|
||||
if (std::string(uri, 0, uriScheme.size()) != uriScheme) return 0;
|
||||
return std::make_shared<SSHStore>(std::string(uri, uriScheme.size()), params);
|
||||
});
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
148
third_party/nix/src/libstore/ssh.cc
vendored
148
third_party/nix/src/libstore/ssh.cc
vendored
|
|
@ -2,64 +2,60 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
SSHMaster::SSHMaster(const std::string & host, const std::string & keyFile, bool useMaster, bool compress, int logFD)
|
||||
: host(host)
|
||||
, fakeSSH(host == "localhost")
|
||||
, keyFile(keyFile)
|
||||
, useMaster(useMaster && !fakeSSH)
|
||||
, compress(compress)
|
||||
, logFD(logFD)
|
||||
{
|
||||
if (host == "" || hasPrefix(host, "-"))
|
||||
throw Error("invalid SSH host name '%s'", host);
|
||||
SSHMaster::SSHMaster(const std::string& host, const std::string& keyFile,
|
||||
bool useMaster, bool compress, int logFD)
|
||||
: host(host),
|
||||
fakeSSH(host == "localhost"),
|
||||
keyFile(keyFile),
|
||||
useMaster(useMaster && !fakeSSH),
|
||||
compress(compress),
|
||||
logFD(logFD) {
|
||||
if (host == "" || hasPrefix(host, "-"))
|
||||
throw Error("invalid SSH host name '%s'", host);
|
||||
}
|
||||
|
||||
void SSHMaster::addCommonSSHOpts(Strings & args)
|
||||
{
|
||||
for (auto & i : tokenizeString<Strings>(getEnv("NIX_SSHOPTS")))
|
||||
args.push_back(i);
|
||||
if (!keyFile.empty())
|
||||
args.insert(args.end(), {"-i", keyFile});
|
||||
if (compress)
|
||||
args.push_back("-C");
|
||||
void SSHMaster::addCommonSSHOpts(Strings& args) {
|
||||
for (auto& i : tokenizeString<Strings>(getEnv("NIX_SSHOPTS")))
|
||||
args.push_back(i);
|
||||
if (!keyFile.empty()) args.insert(args.end(), {"-i", keyFile});
|
||||
if (compress) args.push_back("-C");
|
||||
}
|
||||
|
||||
std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string & command)
|
||||
{
|
||||
Path socketPath = startMaster();
|
||||
std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(
|
||||
const std::string& command) {
|
||||
Path socketPath = startMaster();
|
||||
|
||||
Pipe in, out;
|
||||
in.create();
|
||||
out.create();
|
||||
Pipe in, out;
|
||||
in.create();
|
||||
out.create();
|
||||
|
||||
auto conn = std::make_unique<Connection>();
|
||||
ProcessOptions options;
|
||||
options.dieWithParent = false;
|
||||
auto conn = std::make_unique<Connection>();
|
||||
ProcessOptions options;
|
||||
options.dieWithParent = false;
|
||||
|
||||
conn->sshPid = startProcess([&]() {
|
||||
conn->sshPid = startProcess(
|
||||
[&]() {
|
||||
restoreSignals();
|
||||
|
||||
close(in.writeSide.get());
|
||||
close(out.readSide.get());
|
||||
|
||||
if (dup2(in.readSide.get(), STDIN_FILENO) == -1)
|
||||
throw SysError("duping over stdin");
|
||||
throw SysError("duping over stdin");
|
||||
if (dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
|
||||
throw SysError("duping over stdout");
|
||||
throw SysError("duping over stdout");
|
||||
if (logFD != -1 && dup2(logFD, STDERR_FILENO) == -1)
|
||||
throw SysError("duping over stderr");
|
||||
throw SysError("duping over stderr");
|
||||
|
||||
Strings args;
|
||||
|
||||
if (fakeSSH) {
|
||||
args = { "bash", "-c" };
|
||||
args = {"bash", "-c"};
|
||||
} else {
|
||||
args = { "ssh", host.c_str(), "-x", "-a" };
|
||||
addCommonSSHOpts(args);
|
||||
if (socketPath != "")
|
||||
args.insert(args.end(), {"-S", socketPath});
|
||||
if (verbosity >= lvlChatty)
|
||||
args.push_back("-v");
|
||||
args = {"ssh", host.c_str(), "-x", "-a"};
|
||||
addCommonSSHOpts(args);
|
||||
if (socketPath != "") args.insert(args.end(), {"-S", socketPath});
|
||||
if (verbosity >= lvlChatty) args.push_back("-v");
|
||||
}
|
||||
|
||||
args.push_back(command);
|
||||
|
|
@ -67,68 +63,70 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string
|
|||
|
||||
// could not exec ssh/bash
|
||||
throw SysError("unable to execute '%s'", args.front());
|
||||
}, options);
|
||||
},
|
||||
options);
|
||||
|
||||
in.readSide = -1;
|
||||
out.writeSide = -1;
|
||||
|
||||
in.readSide = -1;
|
||||
out.writeSide = -1;
|
||||
conn->out = std::move(out.readSide);
|
||||
conn->in = std::move(in.writeSide);
|
||||
|
||||
conn->out = std::move(out.readSide);
|
||||
conn->in = std::move(in.writeSide);
|
||||
|
||||
return conn;
|
||||
return conn;
|
||||
}
|
||||
|
||||
Path SSHMaster::startMaster()
|
||||
{
|
||||
if (!useMaster) return "";
|
||||
Path SSHMaster::startMaster() {
|
||||
if (!useMaster) return "";
|
||||
|
||||
auto state(state_.lock());
|
||||
auto state(state_.lock());
|
||||
|
||||
if (state->sshMaster != -1) return state->socketPath;
|
||||
if (state->sshMaster != -1) return state->socketPath;
|
||||
|
||||
state->tmpDir = std::make_unique<AutoDelete>(createTempDir("", "nix", true, true, 0700));
|
||||
state->tmpDir =
|
||||
std::make_unique<AutoDelete>(createTempDir("", "nix", true, true, 0700));
|
||||
|
||||
state->socketPath = (Path) *state->tmpDir + "/ssh.sock";
|
||||
state->socketPath = (Path)*state->tmpDir + "/ssh.sock";
|
||||
|
||||
Pipe out;
|
||||
out.create();
|
||||
Pipe out;
|
||||
out.create();
|
||||
|
||||
ProcessOptions options;
|
||||
options.dieWithParent = false;
|
||||
ProcessOptions options;
|
||||
options.dieWithParent = false;
|
||||
|
||||
state->sshMaster = startProcess([&]() {
|
||||
state->sshMaster = startProcess(
|
||||
[&]() {
|
||||
restoreSignals();
|
||||
|
||||
close(out.readSide.get());
|
||||
|
||||
if (dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
|
||||
throw SysError("duping over stdout");
|
||||
throw SysError("duping over stdout");
|
||||
|
||||
Strings args =
|
||||
{ "ssh", host.c_str(), "-M", "-N", "-S", state->socketPath
|
||||
, "-o", "LocalCommand=echo started"
|
||||
, "-o", "PermitLocalCommand=yes"
|
||||
};
|
||||
if (verbosity >= lvlChatty)
|
||||
args.push_back("-v");
|
||||
Strings args = {"ssh", host.c_str(),
|
||||
"-M", "-N",
|
||||
"-S", state->socketPath,
|
||||
"-o", "LocalCommand=echo started",
|
||||
"-o", "PermitLocalCommand=yes"};
|
||||
if (verbosity >= lvlChatty) args.push_back("-v");
|
||||
addCommonSSHOpts(args);
|
||||
execvp(args.begin()->c_str(), stringsToCharPtrs(args).data());
|
||||
|
||||
throw SysError("unable to execute '%s'", args.front());
|
||||
}, options);
|
||||
},
|
||||
options);
|
||||
|
||||
out.writeSide = -1;
|
||||
out.writeSide = -1;
|
||||
|
||||
std::string reply;
|
||||
try {
|
||||
reply = readLine(out.readSide.get());
|
||||
} catch (EndOfFile & e) { }
|
||||
std::string reply;
|
||||
try {
|
||||
reply = readLine(out.readSide.get());
|
||||
} catch (EndOfFile& e) {
|
||||
}
|
||||
|
||||
if (reply != "started")
|
||||
throw Error("failed to start SSH master connection to '%s'", host);
|
||||
if (reply != "started")
|
||||
throw Error("failed to start SSH master connection to '%s'", host);
|
||||
|
||||
return state->socketPath;
|
||||
return state->socketPath;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
56
third_party/nix/src/libstore/ssh.hh
vendored
56
third_party/nix/src/libstore/ssh.hh
vendored
|
|
@ -1,45 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "util.hh"
|
||||
#include "sync.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class SSHMaster
|
||||
{
|
||||
private:
|
||||
class SSHMaster {
|
||||
private:
|
||||
const std::string host;
|
||||
bool fakeSSH;
|
||||
const std::string keyFile;
|
||||
const bool useMaster;
|
||||
const bool compress;
|
||||
const int logFD;
|
||||
|
||||
const std::string host;
|
||||
bool fakeSSH;
|
||||
const std::string keyFile;
|
||||
const bool useMaster;
|
||||
const bool compress;
|
||||
const int logFD;
|
||||
struct State {
|
||||
Pid sshMaster;
|
||||
std::unique_ptr<AutoDelete> tmpDir;
|
||||
Path socketPath;
|
||||
};
|
||||
|
||||
struct State
|
||||
{
|
||||
Pid sshMaster;
|
||||
std::unique_ptr<AutoDelete> tmpDir;
|
||||
Path socketPath;
|
||||
};
|
||||
Sync<State> state_;
|
||||
|
||||
Sync<State> state_;
|
||||
void addCommonSSHOpts(Strings& args);
|
||||
|
||||
void addCommonSSHOpts(Strings & args);
|
||||
public:
|
||||
SSHMaster(const std::string& host, const std::string& keyFile, bool useMaster,
|
||||
bool compress, int logFD = -1);
|
||||
|
||||
public:
|
||||
struct Connection {
|
||||
Pid sshPid;
|
||||
AutoCloseFD out, in;
|
||||
};
|
||||
|
||||
SSHMaster(const std::string & host, const std::string & keyFile, bool useMaster, bool compress, int logFD = -1);
|
||||
std::unique_ptr<Connection> startCommand(const std::string& command);
|
||||
|
||||
struct Connection
|
||||
{
|
||||
Pid sshPid;
|
||||
AutoCloseFD out, in;
|
||||
};
|
||||
|
||||
std::unique_ptr<Connection> startCommand(const std::string & command);
|
||||
|
||||
Path startMaster();
|
||||
Path startMaster();
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
1375
third_party/nix/src/libstore/store-api.cc
vendored
1375
third_party/nix/src/libstore/store-api.cc
vendored
File diff suppressed because it is too large
Load diff
1246
third_party/nix/src/libstore/store-api.hh
vendored
1246
third_party/nix/src/libstore/store-api.hh
vendored
File diff suppressed because it is too large
Load diff
102
third_party/nix/src/libstore/worker-protocol.hh
vendored
102
third_party/nix/src/libstore/worker-protocol.hh
vendored
|
|
@ -2,68 +2,64 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
|
||||
#define WORKER_MAGIC_1 0x6e697863
|
||||
#define WORKER_MAGIC_2 0x6478696f
|
||||
|
||||
#define PROTOCOL_VERSION 0x115
|
||||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||
|
||||
#define GET_PROTOCOL_MAJOR(x) ((x)&0xff00)
|
||||
#define GET_PROTOCOL_MINOR(x) ((x)&0x00ff)
|
||||
|
||||
typedef enum {
|
||||
wopIsValidPath = 1,
|
||||
wopHasSubstitutes = 3,
|
||||
wopQueryPathHash = 4, // obsolete
|
||||
wopQueryReferences = 5, // obsolete
|
||||
wopQueryReferrers = 6,
|
||||
wopAddToStore = 7,
|
||||
wopAddTextToStore = 8,
|
||||
wopBuildPaths = 9,
|
||||
wopEnsurePath = 10,
|
||||
wopAddTempRoot = 11,
|
||||
wopAddIndirectRoot = 12,
|
||||
wopSyncWithGC = 13,
|
||||
wopFindRoots = 14,
|
||||
wopExportPath = 16, // obsolete
|
||||
wopQueryDeriver = 18, // obsolete
|
||||
wopSetOptions = 19,
|
||||
wopCollectGarbage = 20,
|
||||
wopQuerySubstitutablePathInfo = 21,
|
||||
wopQueryDerivationOutputs = 22,
|
||||
wopQueryAllValidPaths = 23,
|
||||
wopQueryFailedPaths = 24,
|
||||
wopClearFailedPaths = 25,
|
||||
wopQueryPathInfo = 26,
|
||||
wopImportPaths = 27, // obsolete
|
||||
wopQueryDerivationOutputNames = 28,
|
||||
wopQueryPathFromHashPart = 29,
|
||||
wopQuerySubstitutablePathInfos = 30,
|
||||
wopQueryValidPaths = 31,
|
||||
wopQuerySubstitutablePaths = 32,
|
||||
wopQueryValidDerivers = 33,
|
||||
wopOptimiseStore = 34,
|
||||
wopVerifyStore = 35,
|
||||
wopBuildDerivation = 36,
|
||||
wopAddSignatures = 37,
|
||||
wopNarFromPath = 38,
|
||||
wopAddToStoreNar = 39,
|
||||
wopQueryMissing = 40,
|
||||
wopIsValidPath = 1,
|
||||
wopHasSubstitutes = 3,
|
||||
wopQueryPathHash = 4, // obsolete
|
||||
wopQueryReferences = 5, // obsolete
|
||||
wopQueryReferrers = 6,
|
||||
wopAddToStore = 7,
|
||||
wopAddTextToStore = 8,
|
||||
wopBuildPaths = 9,
|
||||
wopEnsurePath = 10,
|
||||
wopAddTempRoot = 11,
|
||||
wopAddIndirectRoot = 12,
|
||||
wopSyncWithGC = 13,
|
||||
wopFindRoots = 14,
|
||||
wopExportPath = 16, // obsolete
|
||||
wopQueryDeriver = 18, // obsolete
|
||||
wopSetOptions = 19,
|
||||
wopCollectGarbage = 20,
|
||||
wopQuerySubstitutablePathInfo = 21,
|
||||
wopQueryDerivationOutputs = 22,
|
||||
wopQueryAllValidPaths = 23,
|
||||
wopQueryFailedPaths = 24,
|
||||
wopClearFailedPaths = 25,
|
||||
wopQueryPathInfo = 26,
|
||||
wopImportPaths = 27, // obsolete
|
||||
wopQueryDerivationOutputNames = 28,
|
||||
wopQueryPathFromHashPart = 29,
|
||||
wopQuerySubstitutablePathInfos = 30,
|
||||
wopQueryValidPaths = 31,
|
||||
wopQuerySubstitutablePaths = 32,
|
||||
wopQueryValidDerivers = 33,
|
||||
wopOptimiseStore = 34,
|
||||
wopVerifyStore = 35,
|
||||
wopBuildDerivation = 36,
|
||||
wopAddSignatures = 37,
|
||||
wopNarFromPath = 38,
|
||||
wopAddToStoreNar = 39,
|
||||
wopQueryMissing = 40,
|
||||
} WorkerOp;
|
||||
|
||||
|
||||
#define STDERR_NEXT 0x6f6c6d67
|
||||
#define STDERR_READ 0x64617461 // data needed from source
|
||||
#define STDERR_WRITE 0x64617416 // data for sink
|
||||
#define STDERR_LAST 0x616c7473
|
||||
#define STDERR_NEXT 0x6f6c6d67
|
||||
#define STDERR_READ 0x64617461 // data needed from source
|
||||
#define STDERR_WRITE 0x64617416 // data for sink
|
||||
#define STDERR_LAST 0x616c7473
|
||||
#define STDERR_ERROR 0x63787470
|
||||
#define STDERR_START_ACTIVITY 0x53545254
|
||||
#define STDERR_STOP_ACTIVITY 0x53544f50
|
||||
#define STDERR_RESULT 0x52534c54
|
||||
#define STDERR_STOP_ACTIVITY 0x53544f50
|
||||
#define STDERR_RESULT 0x52534c54
|
||||
|
||||
Path readStorePath(Store& store, Source& from);
|
||||
template <class T>
|
||||
T readStorePaths(Store& store, Source& from);
|
||||
|
||||
Path readStorePath(Store & store, Source & from);
|
||||
template<class T> T readStorePaths(Store & store, Source & from);
|
||||
|
||||
|
||||
}
|
||||
} // namespace nix
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue