1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-26 04:00:59 +01:00

* Atomically update the info and referrer files. Lock the referrer

file when necessary.
This commit is contained in:
Eelco Dolstra 2008-03-08 17:49:55 +00:00
parent 0be3520d95
commit af7fd0b21f
3 changed files with 101 additions and 33 deletions

View file

@ -15,7 +15,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <utime.h> #include <utime.h>
#include <fcntl.h> // !!! remove #include <fcntl.h>
namespace nix { namespace nix {
@ -110,7 +110,7 @@ void canonicalisePathMetaData(const Path & path, bool recurse)
if (lstat(path.c_str(), &st)) if (lstat(path.c_str(), &st))
throw SysError(format("getting attributes of path `%1%'") % path); throw SysError(format("getting attributes of path `%1%'") % path);
/* Change ownership to the current uid. If its a symlink, use /* Change ownership to the current uid. If it's a symlink, use
lchown if available, otherwise don't bother. Wrong ownership lchown if available, otherwise don't bother. Wrong ownership
of a symlink doesn't matter, since the owning user can't change of a symlink doesn't matter, since the owning user can't change
the symlink and can't delete it because the directory is not the symlink and can't delete it because the directory is not
@ -176,46 +176,65 @@ void canonicalisePathMetaData(const Path & path)
} }
Path infoFileFor(const Path & path) static Path infoFileFor(const Path & path)
{ {
string baseName = baseNameOf(path); string baseName = baseNameOf(path);
return (format("%1%/info/%2%") % nixDBPath % baseName).str(); return (format("%1%/info/%2%") % nixDBPath % baseName).str();
} }
Path referrersFileFor(const Path & path) static Path referrersFileFor(const Path & path)
{ {
string baseName = baseNameOf(path); string baseName = baseNameOf(path);
return (format("%1%/referrer/%2%") % nixDBPath % baseName).str(); return (format("%1%/referrer/%2%") % nixDBPath % baseName).str();
} }
/* !!! move to util.cc */ static Path tmpFileForAtomicUpdate(const Path & path)
void appendFile(const Path & path, const string & s)
{ {
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666); return (format("%1%/.%2%.%3%") % dirOf(path) % getpid() % baseNameOf(path)).str();
if (fd == -1)
throw SysError(format("opening file `%1%'") % path);
writeFull(fd, (unsigned char *) s.c_str(), s.size());
} }
static void appendReferrer(const Path & from, const Path & to) static void appendReferrer(const Path & from, const Path & to)
{ {
Path referrersFile = referrersFileFor(from); Path referrersFile = referrersFileFor(from);
/* !!! locking */
appendFile(referrersFile, " " + to); PathLocks referrersLock(singleton<PathSet, Path>(referrersFile));
referrersLock.setDeletion(true);
AutoCloseFD fd = open(referrersFile.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);
if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
string s = " " + to;
writeFull(fd, (const unsigned char *) s.c_str(), s.size());
} }
/* Atomically update the referrers file. */ /* Atomically update the referrers file. */
static void rewriteReferrers(const Path & path, const PathSet & referrers) static void rewriteReferrers(const Path & path, const PathSet & referrers)
{ {
Path referrersFile = referrersFileFor(path);
PathLocks referrersLock(singleton<PathSet, Path>(referrersFile));
referrersLock.setDeletion(true);
Path tmpFile = tmpFileForAtomicUpdate(referrersFile);
AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
string s; string s;
for (PathSet::const_iterator i = referrers.begin(); i != referrers.end(); ++i) { for (PathSet::const_iterator i = referrers.begin(); i != referrers.end(); ++i) {
s += " "; s += *i; s += " "; s += *i;
} }
writeFile(referrersFileFor(path), s); /* !!! atomicity, locking */
writeFull(fd, (const unsigned char *) s.c_str(), s.size());
fd.close(); /* for Windows; can't rename open file */
if (rename(tmpFile.c_str(), referrersFile.c_str()) == -1)
throw SysError(format("cannot rename `%1%' to `%2%'") % tmpFile % referrersFile);
} }
@ -235,7 +254,12 @@ void LocalStore::registerValidPath(const Path & path,
void LocalStore::registerValidPath(const ValidPathInfo & info) void LocalStore::registerValidPath(const ValidPathInfo & info)
{ {
Path infoFile = infoFileFor(info.path); Path infoFile = infoFileFor(info.path);
if (pathExists(infoFile)) return;
ValidPathInfo oldInfo;
if (pathExists(infoFile)) oldInfo = queryPathInfo(info.path);
/* Note that it's possible for infoFile to already exist. !!!
check what happens in case of repeated registrations */
// !!! acquire PathLock on infoFile here? // !!! acquire PathLock on infoFile here?
@ -258,6 +282,7 @@ void LocalStore::registerValidPath(const ValidPathInfo & info)
have referrer mappings back to `path'. A " " is prefixed have referrer mappings back to `path'. A " " is prefixed
to separate it from the previous entry. It's not suffixed to separate it from the previous entry. It's not suffixed
to deal with interrupted partial writes to this file. */ to deal with interrupted partial writes to this file. */
if (oldInfo.references.find(*i) == oldInfo.references.end())
appendReferrer(*i, info.path); appendReferrer(*i, info.path);
} }
@ -266,10 +291,16 @@ void LocalStore::registerValidPath(const ValidPathInfo & info)
"References: %2%\n" "References: %2%\n"
"Deriver: %3%\n" "Deriver: %3%\n"
"Registered-At: %4%\n") "Registered-At: %4%\n")
% printHash(info.hash) % refs % info.deriver % time(0)).str(); % printHash(info.hash) % refs % info.deriver %
(oldInfo.registrationTime ? oldInfo.registrationTime : time(0))).str();
// !!! atomicity /* Atomically rewrite the info file. */
writeFile(infoFile, s); Path tmpFile = tmpFileForAtomicUpdate(infoFile);
writeFile(tmpFile, s);
if (rename(tmpFile.c_str(), infoFile.c_str()) == -1)
throw SysError(format("cannot rename `%1%' to `%2%'") % tmpFile % infoFile);
pathInfoCache[info.path] = info;
} }
@ -290,6 +321,7 @@ Hash parseHashField(const Path & path, const string & s)
ValidPathInfo LocalStore::queryPathInfo(const Path & path) ValidPathInfo LocalStore::queryPathInfo(const Path & path)
{ {
ValidPathInfo res; ValidPathInfo res;
res.path = path;
assertStorePath(path); assertStorePath(path);
@ -319,6 +351,10 @@ ValidPathInfo LocalStore::queryPathInfo(const Path & path)
res.deriver = value; res.deriver = value;
} else if (name == "Hash") { } else if (name == "Hash") {
res.hash = parseHashField(path, value); res.hash = parseHashField(path, value);
} else if (name == "Registered-At") {
int n = 0;
string2Int(value, n);
res.registrationTime = n;
} }
} }
@ -357,9 +393,19 @@ bool LocalStore::queryReferrersInternal(const Path & path, PathSet & referrers)
if (!isValidPath(path)) if (!isValidPath(path))
throw Error(format("path `%1%' is not valid") % path); throw Error(format("path `%1%' is not valid") % path);
Path p = referrersFileFor(path); /* No locking is necessary here: updates are only done by
if (!pathExists(p)) return true; appending or by atomically replacing the file. When appending,
Paths refs = tokenizeString(readFile(p), " "); there is a possibility that we see a partial entry, but it will
just be filtered out below (the partially written path will not
be valid, so it will be ignored). */
Path referrersFile = referrersFileFor(path);
if (!pathExists(referrersFile)) return true;
AutoCloseFD fd = open(referrersFile.c_str(), O_RDONLY);
if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
Paths refs = tokenizeString(readFile(fd), " ");
for (Paths::iterator i = refs.begin(); i != refs.end(); ++i) for (Paths::iterator i = refs.begin(); i != refs.end(); ++i)
/* Referrers can be invalid (see registerValidPath() for the /* Referrers can be invalid (see registerValidPath() for the
@ -455,22 +501,46 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
} }
#define foreach(it_type, it, collection) \
for (it_type it = collection.begin(); it != collection.end(); ++it)
/* Invalidate a path. The caller is responsible for checking that /* Invalidate a path. The caller is responsible for checking that
there are no referrers. */ there are no referrers. */
static void invalidatePath(const Path & path) void LocalStore::invalidatePath(const Path & path)
{ {
debug(format("invalidating path `%1%'") % path); debug(format("invalidating path `%1%'") % path);
ValidPathInfo info;
if (pathExists(infoFileFor(path))) {
info = queryPathInfo(path);
/* Remove the info file. */ /* Remove the info file. */
Path p = infoFileFor(path); Path p = infoFileFor(path);
if (unlink(p.c_str()) == -1) if (unlink(p.c_str()) == -1)
throw SysError(format("unlinking `%1%'") % p); throw SysError(format("unlinking `%1%'") % p);
}
/* Remove the referrers file for `path'. */
Path p = referrersFileFor(path);
if (pathExists(p) && unlink(p.c_str()) == -1)
throw SysError(format("unlinking `%1%'") % p);
/* Clear `path' from the info cache. */
pathInfoCache.erase(path);
/* Remove the corresponding referrer entries for each path /* Remove the corresponding referrer entries for each path
referenced by this one. This has to happen after removing the referenced by this one. This has to happen after removing the
info file to preserve the invariant (see info file to preserve the invariant (see
registerValidPath()). */ registerValidPath()). */
/* !!! */ foreach (PathSet::iterator, i, info.references) {
/* !!! O(n) */
if (*i == path) continue; /* self-reference */
PathSet referrers; queryReferrers(*i, referrers);
referrers.erase(path);
rewriteReferrers(*i, referrers);
}
} }
@ -743,10 +813,9 @@ Path LocalStore::importPath(bool requireSignature, Source & source)
} }
void LocalStore::deleteFromStore(const Path & _path, unsigned long long & bytesFreed) void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed)
{ {
bytesFreed = 0; bytesFreed = 0;
Path path(canonPath(_path));
assertStorePath(path); assertStorePath(path);
@ -766,9 +835,6 @@ void LocalStore::deleteFromStore(const Path & _path, unsigned long long & bytesF
void LocalStore::verifyStore(bool checkContents) void LocalStore::verifyStore(bool checkContents)
{ {
/* !!! acquire the GC lock or something? */
/* Check whether all valid paths actually exist. */ /* Check whether all valid paths actually exist. */
printMsg(lvlInfo, "checking path existence"); printMsg(lvlInfo, "checking path existence");
@ -833,8 +899,7 @@ void LocalStore::verifyStore(bool checkContents)
} }
} }
if (update) if (update) registerValidPath(info);
/* !!! */;
} }
referrersCache.clear(); referrersCache.clear();

View file

@ -124,6 +124,8 @@ private:
bool queryReferrersInternal(const Path & path, PathSet & referrers); bool queryReferrersInternal(const Path & path, PathSet & referrers);
void invalidatePath(const Path & path);
void upgradeStore12(); void upgradeStore12();
}; };

View file

@ -266,6 +266,7 @@ struct ValidPathInfo
Path deriver; Path deriver;
Hash hash; Hash hash;
PathSet references; PathSet references;
time_t registrationTime;
}; };
typedef list<ValidPathInfo> ValidPathInfos; typedef list<ValidPathInfo> ValidPathInfos;