forked from lix-project/lix
parent
eecc4ff1c0
commit
71d9b64e3d
|
@ -1,11 +1,17 @@
|
||||||
|
#include "file-descriptor.hh"
|
||||||
|
#include "file-system.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
|
#include "hash.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
|
#include "path.hh"
|
||||||
#include "pathlocks.hh"
|
#include "pathlocks.hh"
|
||||||
#include "processes.hh"
|
#include "processes.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
|
#include "thread-pool.hh"
|
||||||
#include "unix-domain-socket.hh"
|
#include "unix-domain-socket.hh"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
|
@ -24,6 +30,9 @@ namespace nix {
|
||||||
|
|
||||||
constexpr static const std::string_view gcSocketPath = "/gc-socket/socket";
|
constexpr static const std::string_view gcSocketPath = "/gc-socket/socket";
|
||||||
constexpr static const std::string_view gcRootsDir = "gcroots";
|
constexpr static const std::string_view gcRootsDir = "gcroots";
|
||||||
|
/** /nix/store/.garbage-pending-deletion */
|
||||||
|
constexpr static const std::string_view gcGarbageBin = "/.garbage-pending-deletion";
|
||||||
|
constexpr static const std::string_view gcGarbageBinName = gcGarbageBin.substr(1);
|
||||||
|
|
||||||
|
|
||||||
static void makeSymlink(const Path & link, const Path & target)
|
static void makeSymlink(const Path & link, const Path & target)
|
||||||
|
@ -358,14 +367,212 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note [Deletion]
|
||||||
|
* ~~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* Lix generally tries to maintain the invariant that an extant ("valid") store
|
||||||
|
* path doesn't have any missing dependencies in the store. This means that
|
||||||
|
* when deleting the store path, it needs to be done in an appropriate
|
||||||
|
* topological order that store paths disappear before their dependencies.
|
||||||
|
*
|
||||||
|
* This, however, conflicts with the desire to delete paths faster without
|
||||||
|
* caring about ordering. However, nobody said that we have to *delete* the
|
||||||
|
* store paths to make them disappear; we do in fact only care about making
|
||||||
|
* them disappear. This leads to the following design to implement a fast GC
|
||||||
|
* without rewriting the entire thing:
|
||||||
|
* 1. Keep topologically "deleting" store paths as before.
|
||||||
|
* 2. Make the deletion operation *much* faster. In particular, we can achieve
|
||||||
|
* this by using renameat into a garbage bin instead of actually deleting
|
||||||
|
* the store path synchronously.
|
||||||
|
* 3. After renaming the paths, dispatch them to a thread pool to delete the
|
||||||
|
* garbage bin asynchronously.
|
||||||
|
*
|
||||||
|
* This effectively transitions the GC from interleaving deletion and marking
|
||||||
|
* to exclusively marking, with deletion happening asynchronously on other
|
||||||
|
* threads.
|
||||||
|
*
|
||||||
|
* There are some edge cases to a garbage bin, however:
|
||||||
|
* - How do you avoid the "begin deleting a store path, get interrupted, it
|
||||||
|
* reappears, and gets deleted again" problem? That is, the rename might fail
|
||||||
|
* on the second iteration. We can fix this by adding entropy into the file
|
||||||
|
* names in addition to the actual store paths.
|
||||||
|
* - How do you deal with getting interrupted mid way through the deletion
|
||||||
|
* after the marking is complete? The simplest solution here is to just list
|
||||||
|
* the garbage bin at startup and add all items in it to the deletion queue.
|
||||||
|
*/
|
||||||
|
|
||||||
struct GCLimitReached : std::exception { };
|
struct GCLimitReached : std::exception { };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that deletes paths from the Nix store in a fast manner.
|
||||||
|
*
|
||||||
|
* Has loose semantics for the deleted amount; will delete the amount requested
|
||||||
|
* and try to not go over by too much.
|
||||||
|
*
|
||||||
|
* See Note [Deletion] for the design of the concurrent deleter.
|
||||||
|
*/
|
||||||
|
class GCDeleter
|
||||||
|
{
|
||||||
|
std::unique_ptr<ThreadPool> pool;
|
||||||
|
AutoCloseDir storeDir;
|
||||||
|
AutoCloseDir garbageBinDir;
|
||||||
|
/** Path to the garbage bin, only used in errors */
|
||||||
|
std::string const garbageBinDirName;
|
||||||
|
|
||||||
|
std::optional<uint64_t> const deletionLimit;
|
||||||
|
|
||||||
|
struct State
|
||||||
|
{
|
||||||
|
uint64_t deletedSoFar = 0;
|
||||||
|
bool printed = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sync<State> _state;
|
||||||
|
|
||||||
|
void noteDeletionAmount(uint64_t bytesFreed)
|
||||||
|
{
|
||||||
|
auto state{_state.lock()};
|
||||||
|
state->deletedSoFar += bytesFreed;
|
||||||
|
if (deletionLimit.has_value() && state->deletedSoFar >= *deletionLimit && !state->printed) {
|
||||||
|
state->printed = true;
|
||||||
|
printInfo("deleted more than %d bytes; stopping", *deletionLimit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reachedDeletionLimit()
|
||||||
|
{
|
||||||
|
if (!deletionLimit.has_value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto state{_state.lock()};
|
||||||
|
return state->deletedSoFar >= *deletionLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
GCDeleter(Path store, uint64_t deletionLimit)
|
||||||
|
: pool{std::make_unique<ThreadPool>()}
|
||||||
|
, storeDir(store)
|
||||||
|
, garbageBinDirName(store + gcGarbageBin)
|
||||||
|
, deletionLimit(
|
||||||
|
deletionLimit == std::numeric_limits<uint64_t>::max() ? std::nullopt
|
||||||
|
: std::optional(deletionLimit)
|
||||||
|
)
|
||||||
|
, _state{State{}}
|
||||||
|
{
|
||||||
|
if (mkdir(garbageBinDirName.c_str(), 0775) == -1 && errno != EEXIST) {
|
||||||
|
throw SysError("creating directory '%1%'", garbageBinDirName);
|
||||||
|
}
|
||||||
|
garbageBinDir = AutoCloseDir(garbageBinDirName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deletes the existing set of garbage that is already in the garbage bin. */
|
||||||
|
bool deleteExistingGarbage()
|
||||||
|
{
|
||||||
|
auto entries = readDirectory(garbageBinDir.get(), garbageBinDirName);
|
||||||
|
for (auto & entry : entries) {
|
||||||
|
if (submitAlreadyBinnedPathForDeletion(entry.name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronously wait for all the deletion to finish if we care about
|
||||||
|
// deletion limits. If we don't care, we can just keep going.
|
||||||
|
if (deletionLimit.has_value()) {
|
||||||
|
pool->process();
|
||||||
|
pool = std::make_unique<ThreadPool>();
|
||||||
|
return reachedDeletionLimit();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool submitAlreadyBinnedPathForDeletion(std::string const nameInGarbageBin)
|
||||||
|
{
|
||||||
|
if (reachedDeletionLimit()) {
|
||||||
|
pool->process();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool->enqueue([this, nameInGarbageBin]() {
|
||||||
|
// This leaves paths in the garbage bin if we need to exit early.
|
||||||
|
// This is somewhat unfortunate, but it ensures that we exit
|
||||||
|
// promptly after we collect enough garbage.
|
||||||
|
if (reachedDeletionLimit()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t freed = 0;
|
||||||
|
|
||||||
|
printInfo("actually deleting path %1%/%2%", this->garbageBinDirName, nameInGarbageBin);
|
||||||
|
deletePath(garbageBinDir.dirfd(), nameInGarbageBin.c_str(), freed);
|
||||||
|
|
||||||
|
this->noteDeletionAmount(freed);
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Submit a path to be deleted. Will do nothing and return true if enough has already been
|
||||||
|
* deleted.
|
||||||
|
*
|
||||||
|
* \return whether we have already deleted enough to satisfy the target amount
|
||||||
|
*/
|
||||||
|
bool submitStorePathForDeletion(StorePath storePath)
|
||||||
|
{
|
||||||
|
return submitPathForDeletion(std::string(storePath.name()), storePath.asStringRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool submitPathForDeletion(const std::string & name, const std::string & fileNameInStore)
|
||||||
|
{
|
||||||
|
// Randomize the store path so it will never collide.
|
||||||
|
StorePath randomStorePath = StorePath::random(name);
|
||||||
|
|
||||||
|
struct stat64 st;
|
||||||
|
if (fstatat64(storeDir.dirfd(), fileNameInStore.c_str(), &st, AT_SYMLINK_NOFOLLOW) != 0) {
|
||||||
|
throw SysError("failure to fstatat '%1%' in the store", fileNameInStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
// You cannot fchmodat a symbolic link, apparently.
|
||||||
|
if (!S_ISLNK(st.st_mode)) {
|
||||||
|
if (fchmodat(storeDir.dirfd(), fileNameInStore.c_str(), 0775, 0) == -1) {
|
||||||
|
throw SysError("give write permission to put '%1%' in the garbage bin", fileNameInStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renameat(
|
||||||
|
storeDir.dirfd(),
|
||||||
|
fileNameInStore.c_str(),
|
||||||
|
garbageBinDir.dirfd(),
|
||||||
|
randomStorePath.asStringRef().c_str()
|
||||||
|
)
|
||||||
|
== -1)
|
||||||
|
{
|
||||||
|
throw SysError("putting '%1%' in the garbage bin '%2%' as '%3%'", fileNameInStore, garbageBinDirName, randomStorePath.asStringRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
return submitAlreadyBinnedPathForDeletion(randomStorePath.asStringRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t deletedSoFar()
|
||||||
|
{
|
||||||
|
auto state{_state.lock()};
|
||||||
|
return state->deletedSoFar;
|
||||||
|
}
|
||||||
|
|
||||||
|
~GCDeleter() noexcept(false)
|
||||||
|
{
|
||||||
|
pool->process();
|
||||||
|
if (unlinkat(storeDir.dirfd(), std::string(gcGarbageBinName).c_str(), AT_REMOVEDIR) == -1) {
|
||||||
|
warn("ignored an error removing the garbage bin %s: %s", garbageBinDirName, strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegate class to expose just the operations required to perform GC on a store.
|
* Delegate class to expose just the operations required to perform GC on a store.
|
||||||
*/
|
*/
|
||||||
class GCStoreDelegate {
|
class GCStoreDelegate
|
||||||
|
{
|
||||||
LocalStore const & store;
|
LocalStore const & store;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -380,7 +587,8 @@ class GCStoreDelegate {
|
||||||
/**
|
/**
|
||||||
* Class holding a server to receive new GC roots.
|
* Class holding a server to receive new GC roots.
|
||||||
*/
|
*/
|
||||||
class GCOperation {
|
class GCOperation
|
||||||
|
{
|
||||||
const GCStoreDelegate store;
|
const GCStoreDelegate store;
|
||||||
|
|
||||||
std::thread serverThread;
|
std::thread serverThread;
|
||||||
|
@ -584,6 +792,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
|
|
||||||
GCOperation gcServer {*this, stateDir.get()};
|
GCOperation gcServer {*this, stateDir.get()};
|
||||||
|
|
||||||
|
GCDeleter deleter{realStoreDir, options.maxFreed};
|
||||||
|
|
||||||
/* Find the roots. Since we've grabbed the GC lock, the set of
|
/* Find the roots. Since we've grabbed the GC lock, the set of
|
||||||
permanent roots cannot increase now. */
|
permanent roots cannot increase now. */
|
||||||
printInfo("finding garbage collector roots...");
|
printInfo("finding garbage collector roots...");
|
||||||
|
@ -608,7 +818,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
|
|
||||||
/* Helper function that deletes a path from the store and throws
|
/* Helper function that deletes a path from the store and throws
|
||||||
GCLimitReached if we've deleted enough garbage. */
|
GCLimitReached if we've deleted enough garbage. */
|
||||||
auto deleteFromStore = [&](std::string_view baseName)
|
auto deleteFromStore = [&](std::string const & storePathName, std::string const & baseName)
|
||||||
{
|
{
|
||||||
Path path = storeDir + "/" + std::string(baseName);
|
Path path = storeDir + "/" + std::string(baseName);
|
||||||
Path realPath = realStoreDir + "/" + std::string(baseName);
|
Path realPath = realStoreDir + "/" + std::string(baseName);
|
||||||
|
@ -625,15 +835,11 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
}
|
}
|
||||||
|
|
||||||
printInfo("deleting '%1%'", path);
|
printInfo("deleting '%1%'", path);
|
||||||
|
bool reachedLimit = deleter.submitPathForDeletion(storePathName, baseName);
|
||||||
|
|
||||||
results.paths.insert(path);
|
results.paths.insert(path);
|
||||||
|
|
||||||
uint64_t bytesFreed;
|
if (reachedLimit) {
|
||||||
deletePath(realPath, bytesFreed);
|
|
||||||
results.bytesFreed += bytesFreed;
|
|
||||||
|
|
||||||
if (results.bytesFreed > options.maxFreed) {
|
|
||||||
printInfo("deleted more than %d bytes; stopping", options.maxFreed);
|
|
||||||
throw GCLimitReached();
|
throw GCLimitReached();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -739,7 +945,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
if (!dead.insert(path).second) continue;
|
if (!dead.insert(path).second) continue;
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
invalidatePathChecked(path);
|
invalidatePathChecked(path);
|
||||||
deleteFromStore(path.to_string());
|
deleteFromStore(std::string(path.name()), path.asStringRef());
|
||||||
referrersCache.erase(path);
|
referrersCache.erase(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -761,10 +967,17 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
|
|
||||||
} else if (options.maxFreed > 0) {
|
} else if (options.maxFreed > 0) {
|
||||||
|
|
||||||
if (shouldDelete)
|
if (shouldDelete) {
|
||||||
printInfo("deleting garbage...");
|
printInfo("deleting garbage...");
|
||||||
else
|
// Check if we can satisfy the deletion request by deleting garbage
|
||||||
|
// we already have marked but which didn't get deleted due to a
|
||||||
|
// previously interrupted process.
|
||||||
|
if (deleter.deleteExistingGarbage()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
printInfo("determining live/dead paths...");
|
printInfo("determining live/dead paths...");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AutoCloseDir dir(opendir(realStoreDir.get().c_str()));
|
AutoCloseDir dir(opendir(realStoreDir.get().c_str()));
|
||||||
|
@ -779,12 +992,12 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
while (errno = 0, dirent = readdir(dir.get())) {
|
while (errno = 0, dirent = readdir(dir.get())) {
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
std::string name = dirent->d_name;
|
std::string name = dirent->d_name;
|
||||||
if (name == "." || name == ".." || name == linksName) continue;
|
if (name == "." || name == ".." || name == linksName || name == gcGarbageBinName) continue;
|
||||||
|
|
||||||
if (auto storePath = maybeParseStorePath(storeDir + "/" + name))
|
if (auto storePath = maybeParseStorePath(storeDir + "/" + name))
|
||||||
deleteReferrersClosure(*storePath);
|
deleteReferrersClosure(*storePath);
|
||||||
else
|
else
|
||||||
deleteFromStore(name);
|
deleteFromStore(name, name);
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (GCLimitReached & e) {
|
} catch (GCLimitReached & e) {
|
||||||
|
|
|
@ -40,6 +40,11 @@ public:
|
||||||
return baseName;
|
return baseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string const & asStringRef() const
|
||||||
|
{
|
||||||
|
return baseName;
|
||||||
|
}
|
||||||
|
|
||||||
bool operator < (const StorePath & other) const
|
bool operator < (const StorePath & other) const
|
||||||
{
|
{
|
||||||
return baseName < other.baseName;
|
return baseName < other.baseName;
|
||||||
|
|
|
@ -282,8 +282,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
|
||||||
|
|
||||||
DirEntries readDirectory(const Path & path)
|
DirEntries readDirectory(const Path & path)
|
||||||
{
|
{
|
||||||
AutoCloseDir dir(opendir(path.c_str()));
|
AutoCloseDir dir(path);
|
||||||
if (!dir) throw SysError("opening directory '%1%'", path);
|
|
||||||
|
|
||||||
return readDirectory(dir.get(), path);
|
return readDirectory(dir.get(), path);
|
||||||
}
|
}
|
||||||
|
@ -380,6 +379,7 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
|
||||||
|
|
||||||
std::string name(baseNameOf(path));
|
std::string name(baseNameOf(path));
|
||||||
|
|
||||||
|
// FIXME: this needs to be completely redone to open the damn thing first so it is not TOCTOUable
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
|
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
|
||||||
if (errno == ENOENT) return;
|
if (errno == ENOENT) return;
|
||||||
|
@ -418,14 +418,14 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
|
||||||
throw SysError("chmod '%1%'", path);
|
throw SysError("chmod '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
int fd = openat(parentfd, path.c_str(), O_RDONLY);
|
int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY);
|
||||||
if (fd == -1)
|
if (fd == -1)
|
||||||
throw SysError("opening directory '%1%'", path);
|
throw SysError("opening directory '%1%'", path);
|
||||||
AutoCloseDir dir(fdopendir(fd));
|
AutoCloseDir dir(fdopendir(fd));
|
||||||
if (!dir)
|
if (!dir)
|
||||||
throw SysError("opening directory '%1%'", path);
|
throw SysError("opening directory '%1%'", path);
|
||||||
for (auto & i : readDirectory(dir.get(), path))
|
for (auto & i : readDirectory(dir.get(), path))
|
||||||
_deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed);
|
_deletePath(dir.dirfd(), path + "/" + i.name, bytesFreed);
|
||||||
}
|
}
|
||||||
|
|
||||||
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
|
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
|
||||||
|
@ -465,6 +465,12 @@ void deletePath(const Path & path, uint64_t & bytesFreed)
|
||||||
_deletePath(path, bytesFreed);
|
_deletePath(path, bytesFreed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void deletePath(int dirfd, const std::string & name, uint64_t & bytesFreed)
|
||||||
|
{
|
||||||
|
assert(name.find("/") == std::string::npos);
|
||||||
|
_deletePath(dirfd, name, bytesFreed);
|
||||||
|
}
|
||||||
|
|
||||||
Paths createDirs(const Path & path)
|
Paths createDirs(const Path & path)
|
||||||
{
|
{
|
||||||
Paths created;
|
Paths created;
|
||||||
|
|
|
@ -149,8 +149,16 @@ struct DirEntry
|
||||||
: name(std::move(name)), ino(ino), type(type) { }
|
: name(std::move(name)), ino(ino), type(type) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<DirEntry> DirEntries;
|
using DirEntries = std::vector<DirEntry>;
|
||||||
|
|
||||||
|
/** Reads the directory entries of the provided directory as a vector
|
||||||
|
* \param dir Directory fd to read the entries of.
|
||||||
|
* \param path Human readable path name of the directory for use in error messages.
|
||||||
|
*/
|
||||||
|
DirEntries readDirectory(DIR *dir, const Path & path);
|
||||||
|
/** Reads the directory entries of the provided directory as a vector
|
||||||
|
* \param path Path to the directory.
|
||||||
|
*/
|
||||||
DirEntries readDirectory(const Path & path);
|
DirEntries readDirectory(const Path & path);
|
||||||
|
|
||||||
unsigned char getFileType(const Path & path);
|
unsigned char getFileType(const Path & path);
|
||||||
|
@ -182,6 +190,14 @@ void deletePath(const Path & path);
|
||||||
|
|
||||||
void deletePath(const Path & path, uint64_t & bytesFreed);
|
void deletePath(const Path & path, uint64_t & bytesFreed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively deletes a path by name from a dirfd.
|
||||||
|
* \param dirfd dirfd of the parent directory
|
||||||
|
* \param name Name of the file, must not contain any slashes
|
||||||
|
* \param bytesFreed Out parameter for the bytes freed by this operation
|
||||||
|
*/
|
||||||
|
void deletePath(int dirfd, const std::string & name, uint64_t & bytesFreed);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a directory and all its parents, if necessary. Returns the
|
* Create a directory and all its parents, if necessary. Returns the
|
||||||
* list of created directories, in order of creation.
|
* list of created directories, in order of creation.
|
||||||
|
@ -261,7 +277,93 @@ struct DIRDeleter
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::unique_ptr<DIR, DIRDeleter> AutoCloseDir;
|
/** A smart pointer for a `DIR *` object */
|
||||||
|
class AutoCloseDir
|
||||||
|
{
|
||||||
|
using InnerT = std::unique_ptr<DIR, DIRDeleter>;
|
||||||
|
InnerT inner;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AutoCloseDir(AutoCloseDir const & other) = delete;
|
||||||
|
AutoCloseDir(AutoCloseDir && other)
|
||||||
|
{
|
||||||
|
auto tmp = std::move(other.inner);
|
||||||
|
other.inner = nullptr;
|
||||||
|
this->inner = std::move(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool()
|
||||||
|
{
|
||||||
|
return bool(this->inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoCloseDir & operator=(AutoCloseDir && other)
|
||||||
|
{
|
||||||
|
inner = std::move(other.inner);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR const * operator->() const
|
||||||
|
{
|
||||||
|
return inner.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR * operator->()
|
||||||
|
{
|
||||||
|
return inner.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR const * get() const
|
||||||
|
{
|
||||||
|
return inner.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR * get()
|
||||||
|
{
|
||||||
|
return inner.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Releases the inner pointer of the AutoCloseDir, giving an owning pointer. */
|
||||||
|
[[nodiscard("leaking an AutoCloseDir")]]
|
||||||
|
DIR * release()
|
||||||
|
{
|
||||||
|
return inner.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(InnerT::pointer newValue = nullptr)
|
||||||
|
{
|
||||||
|
inner.reset(std::move(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
int dirfd() const
|
||||||
|
{
|
||||||
|
assert(inner);
|
||||||
|
return ::dirfd(inner.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Make an AutoCloseDir that points to nothing. */
|
||||||
|
AutoCloseDir() {}
|
||||||
|
/** Make an AutoCloseDir of an existing DIR *.
|
||||||
|
* Sets the provided pointer to nullptr as it takes ownership.
|
||||||
|
*/
|
||||||
|
AutoCloseDir(DIR * & dir) : inner(dir)
|
||||||
|
{
|
||||||
|
dir = nullptr;
|
||||||
|
}
|
||||||
|
/** Makes an AutoCloseDir of a DIR * temporary. */
|
||||||
|
AutoCloseDir(DIR * && dir) : inner(dir) {}
|
||||||
|
|
||||||
|
/** Open the given directory by path.
|
||||||
|
* \throws SysError if opening failed
|
||||||
|
*/
|
||||||
|
AutoCloseDir(std::string const & path)
|
||||||
|
{
|
||||||
|
AutoCloseDir dir(opendir(path.c_str()));
|
||||||
|
if (!dir.inner) throw SysError("opening directory '%1%'", path);
|
||||||
|
|
||||||
|
new (this) AutoCloseDir(std::move(dir));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a temporary directory.
|
* Create a temporary directory.
|
||||||
|
|
|
@ -33,7 +33,7 @@ private:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Sync() { }
|
Sync() : data{} { }
|
||||||
Sync(const T & data) : data(data) { }
|
Sync(const T & data) : data(data) { }
|
||||||
Sync(T && data) noexcept : data(std::move(data)) { }
|
Sync(T && data) noexcept : data(std::move(data)) { }
|
||||||
|
|
||||||
|
|
|
@ -209,7 +209,7 @@ git -C $flake3Dir commit -m 'Add lockfile'
|
||||||
# Test whether registry caching works.
|
# Test whether registry caching works.
|
||||||
nix registry list --flake-registry file://$registry | grepQuiet flake3
|
nix registry list --flake-registry file://$registry | grepQuiet flake3
|
||||||
mv $registry $registry.tmp
|
mv $registry $registry.tmp
|
||||||
nix store gc
|
_RR_TRACE_DIR=/home/jade/.local/share/rr rr record -- nix store gc
|
||||||
nix registry list --flake-registry file://$registry --refresh | grepQuiet flake3
|
nix registry list --flake-registry file://$registry --refresh | grepQuiet flake3
|
||||||
mv $registry.tmp $registry
|
mv $registry.tmp $registry
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue