* nix-store, nix-instantiate: added an option `--add-root' to
immediately add the result as a permanent GC root. This is the only way to prevent a race with the garbage collector. For instance, the old style ln -s $(nix-store -r $(nix-instantiate foo.nix)) \ /nix/var/nix/gcroots/result has two time windows in which the garbage collector can interfere (by GC'ing the derivation and the output, respectively). On the other hand, nix-store --add-root /nix/var/nix/gcroots/result -r \ $(nix-instantiate --add-root /nix/var/nix/gcroots/drv \ foo.nix) is safe. * nix-build: use `--add-root' to prevent GC races.
This commit is contained in:
parent
a6b65fd5e1
commit
dcc37c236c
11 changed files with 165 additions and 17 deletions
|
@ -10,6 +10,9 @@ fi
|
||||||
extraArgs=
|
extraArgs=
|
||||||
noLink=
|
noLink=
|
||||||
|
|
||||||
|
userName=$USER
|
||||||
|
if test -z "$username"; then userName="unknown"; fi
|
||||||
|
|
||||||
for i in "$@"; do
|
for i in "$@"; do
|
||||||
case "$i" in
|
case "$i" in
|
||||||
--no-link)
|
--no-link)
|
||||||
|
@ -19,11 +22,15 @@ for i in "$@"; do
|
||||||
extraArgs="$extraArgs $i"
|
extraArgs="$extraArgs $i"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
storeExprs=$(@bindir@/nix-instantiate "$i")
|
storeExprs=$(@bindir@/nix-instantiate \
|
||||||
|
--add-root "@localstatedir@/nix/gcroots/nix-build/$userName-drv" \
|
||||||
|
"$i")
|
||||||
for j in $storeExprs; do
|
for j in $storeExprs; do
|
||||||
echo "store expression is $j" >&2
|
echo "store expression is $j" >&2
|
||||||
done
|
done
|
||||||
outPaths=$(@bindir@/nix-store -rv $extraArgs $storeExprs)
|
outPaths=$(@bindir@/nix-store \
|
||||||
|
--add-root "@localstatedir@/nix/gcroots/nix-build/$userName-out" \
|
||||||
|
-rv $extraArgs $storeExprs)
|
||||||
for j in $outPaths; do
|
for j in $outPaths; do
|
||||||
echo "$j"
|
echo "$j"
|
||||||
if test -z "$noLink"; then
|
if test -z "$noLink"; then
|
||||||
|
|
|
@ -31,6 +31,28 @@ void sigintHandler(int signo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Path makeRootName(const Path & gcRoot, int & counter)
|
||||||
|
{
|
||||||
|
counter++;
|
||||||
|
if (counter == 1)
|
||||||
|
return gcRoot;
|
||||||
|
else
|
||||||
|
return (format("%1%-%2%") % gcRoot % counter).str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void printGCWarning()
|
||||||
|
{
|
||||||
|
static bool warned = false;
|
||||||
|
if (!warned) {
|
||||||
|
printMsg(lvlInfo,
|
||||||
|
"warning: you did not specify `--add-root'; "
|
||||||
|
"the result might be removed by the garbage collector");
|
||||||
|
warned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void setLogType(string lt)
|
void setLogType(string lt)
|
||||||
{
|
{
|
||||||
if (lt == "pretty") logType = ltPretty;
|
if (lt == "pretty") logType = ltPretty;
|
||||||
|
@ -183,7 +205,7 @@ static void initAndRun(int argc, char * * argv)
|
||||||
|
|
||||||
/* Automatically clean up the temporary roots file when we
|
/* Automatically clean up the temporary roots file when we
|
||||||
exit. */
|
exit. */
|
||||||
RemoveTempRoots removeTempRoots;
|
RemoveTempRoots removeTempRoots; /* unused variable - don't remove */
|
||||||
|
|
||||||
run(remaining);
|
run(remaining);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@ void run(Strings args);
|
||||||
/* Should print a help message to stdout and return. */
|
/* Should print a help message to stdout and return. */
|
||||||
void printHelp();
|
void printHelp();
|
||||||
|
|
||||||
|
/* Ugh. No better place to put this. */
|
||||||
|
Path makeRootName(const Path & gcRoot, int & counter);
|
||||||
|
void printGCWarning();
|
||||||
|
|
||||||
|
|
||||||
extern string programId;
|
extern string programId;
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,48 @@ static int openGCLock(LockType lockType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void createDirs(const Path & path)
|
||||||
|
{
|
||||||
|
if (path == "") return;
|
||||||
|
createDirs(dirOf(path));
|
||||||
|
if (!pathExists(path))
|
||||||
|
if (mkdir(path.c_str(), 0777) == -1)
|
||||||
|
throw SysError(format("creating directory `%1%'") % path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Path addPermRoot(const Path & _storePath, const Path & _gcRoot)
|
||||||
|
{
|
||||||
|
Path storePath(canonPath(_storePath));
|
||||||
|
Path gcRoot(canonPath(_gcRoot));
|
||||||
|
|
||||||
|
Path rootsDir = canonPath((format("%1%/%2%") % nixStateDir % "gcroots").str());
|
||||||
|
|
||||||
|
if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/")
|
||||||
|
throw Error(format(
|
||||||
|
"path `%1%' is not a valid garbage collector root; "
|
||||||
|
"it's not in the `%1%' directory")
|
||||||
|
% gcRoot % rootsDir);
|
||||||
|
|
||||||
|
/* Grab the global GC root. This prevents the set of permanent
|
||||||
|
roots from increasing while a GC is in progress. */
|
||||||
|
AutoCloseFD fdGCLock = openGCLock(ltRead);
|
||||||
|
|
||||||
|
/* Create directories up to `gcRoot'. */
|
||||||
|
createDirs(dirOf(gcRoot));
|
||||||
|
|
||||||
|
/* Remove the old symlink. */
|
||||||
|
unlink(gcRoot.c_str());
|
||||||
|
|
||||||
|
/* And create the new own. */
|
||||||
|
if (symlink(storePath.c_str(), gcRoot.c_str()) == -1)
|
||||||
|
throw SysError(format("symlinking `%1%' to `%2%'")
|
||||||
|
% gcRoot % storePath);
|
||||||
|
|
||||||
|
return gcRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static string tempRootsDir = "temproots";
|
static string tempRootsDir = "temproots";
|
||||||
|
|
||||||
/* The file to which we write our temporary roots. */
|
/* The file to which we write our temporary roots. */
|
||||||
|
@ -210,6 +252,9 @@ void collectGarbage(const PathSet & roots, GCAction action,
|
||||||
b) Processes from creating new temporary root files. */
|
b) Processes from creating new temporary root files. */
|
||||||
AutoCloseFD fdGCLock = openGCLock(ltWrite);
|
AutoCloseFD fdGCLock = openGCLock(ltWrite);
|
||||||
|
|
||||||
|
/* !!! Find the roots here, after we've grabbed the GC lock, since
|
||||||
|
the set of permanent roots cannot increase now. */
|
||||||
|
|
||||||
/* Determine the live paths which is just the closure of the
|
/* Determine the live paths which is just the closure of the
|
||||||
roots under the `references' relation. */
|
roots under the `references' relation. */
|
||||||
PathSet livePaths;
|
PathSet livePaths;
|
||||||
|
@ -264,6 +309,7 @@ void collectGarbage(const PathSet & roots, GCAction action,
|
||||||
will not work anymore because we get cycles. */
|
will not work anymore because we get cycles. */
|
||||||
storePaths = topoSort(storePaths2);
|
storePaths = topoSort(storePaths2);
|
||||||
|
|
||||||
|
/* Try to delete store paths in the topologically sorted order. */
|
||||||
for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i) {
|
for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i) {
|
||||||
|
|
||||||
debug(format("considering deletion of `%1%'") % *i);
|
debug(format("considering deletion of `%1%'") % *i);
|
||||||
|
|
|
@ -26,5 +26,8 @@ void addTempRoot(const Path & path);
|
||||||
as a (permanent) root. */
|
as a (permanent) root. */
|
||||||
void removeTempRoots();
|
void removeTempRoots();
|
||||||
|
|
||||||
|
/* Register a permanent GC root. */
|
||||||
|
Path addPermRoot(const Path & storePath, const Path & gcRoot);
|
||||||
|
|
||||||
|
|
||||||
#endif /* !__GC_H */
|
#endif /* !__GC_H */
|
||||||
|
|
|
@ -168,7 +168,7 @@ void copyPath(const Path & src, const Path & dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool isInStore(const Path & path)
|
bool isStorePath(const Path & path)
|
||||||
{
|
{
|
||||||
return path[0] == '/'
|
return path[0] == '/'
|
||||||
&& path.compare(0, nixStore.size(), nixStore) == 0
|
&& path.compare(0, nixStore.size(), nixStore) == 0
|
||||||
|
@ -180,7 +180,7 @@ static bool isInStore(const Path & path)
|
||||||
|
|
||||||
void assertStorePath(const Path & path)
|
void assertStorePath(const Path & path)
|
||||||
{
|
{
|
||||||
if (!isInStore(path))
|
if (!isStorePath(path))
|
||||||
throw Error(format("path `%1%' is not in the Nix store") % path);
|
throw Error(format("path `%1%' is not in the Nix store") % path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,7 +579,7 @@ void verifyStore()
|
||||||
if (!pathExists(path)) {
|
if (!pathExists(path)) {
|
||||||
printMsg(lvlError, format("path `%1%' disappeared") % path);
|
printMsg(lvlError, format("path `%1%' disappeared") % path);
|
||||||
invalidatePath(path, txn);
|
invalidatePath(path, txn);
|
||||||
} else if (!isInStore(path)) {
|
} else if (!isStorePath(path)) {
|
||||||
printMsg(lvlError, format("path `%1%' is not in the Nix store") % path);
|
printMsg(lvlError, format("path `%1%' is not in the Nix store") % path);
|
||||||
invalidatePath(path, txn);
|
invalidatePath(path, txn);
|
||||||
} else
|
} else
|
||||||
|
|
|
@ -62,6 +62,8 @@ void registerValidPath(const Transaction & txn,
|
||||||
/* Throw an exception if `path' is not directly in the Nix store. */
|
/* Throw an exception if `path' is not directly in the Nix store. */
|
||||||
void assertStorePath(const Path & path);
|
void assertStorePath(const Path & path);
|
||||||
|
|
||||||
|
bool isStorePath(const Path & path);
|
||||||
|
|
||||||
/* "Fix", or canonicalise, the meta-data of the files in a store path
|
/* "Fix", or canonicalise, the meta-data of the files in a store path
|
||||||
after it has been built. In particular:
|
after it has been built. In particular:
|
||||||
- the last modification date on each file is set to 0 (i.e.,
|
- the last modification date on each file is set to 0 (i.e.,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
nix-instantiate [OPTIONS...] [FILES...]
|
nix-instantiate [OPTIONS...] [FILES...]
|
||||||
|
|
||||||
`nix-instantiate' turns Nix expressions into store expressions.
|
`nix-instantiate' turns Nix expressions into store derivations.
|
||||||
|
|
||||||
The argument `-' may be specified to read a Nix expression from
|
The argument `-' may be specified to read a Nix expression from
|
||||||
standard input.
|
standard input.
|
||||||
|
@ -14,3 +14,5 @@ Options:
|
||||||
|
|
||||||
--eval-only: evaluate and print resulting term; do not instantiate
|
--eval-only: evaluate and print resulting term; do not instantiate
|
||||||
--parse-only: parse and print abstract syntax tree
|
--parse-only: parse and print abstract syntax tree
|
||||||
|
|
||||||
|
--add-root: add garbage collector roots for the result
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "build.hh"
|
#include "build.hh"
|
||||||
|
#include "gc.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
#include "parser.hh"
|
#include "parser.hh"
|
||||||
|
@ -26,6 +27,14 @@ static Expr evalStdin(EvalState & state, bool parseOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Path gcRoot;
|
||||||
|
static int rootNr = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/* Print out the paths of the resulting derivation(s). If the user
|
||||||
|
specified the `--add-root' flag, we register the derivation as a
|
||||||
|
garbage collection root and print out the path of the GC root
|
||||||
|
symlink instead. */
|
||||||
static void printDrvPaths(EvalState & state, Expr e)
|
static void printDrvPaths(EvalState & state, Expr e)
|
||||||
{
|
{
|
||||||
ATermList es;
|
ATermList es;
|
||||||
|
@ -37,7 +46,13 @@ static void printDrvPaths(EvalState & state, Expr e)
|
||||||
if (a && evalString(state, a) == "derivation") {
|
if (a && evalString(state, a) == "derivation") {
|
||||||
a = queryAttr(e, "drvPath");
|
a = queryAttr(e, "drvPath");
|
||||||
if (a) {
|
if (a) {
|
||||||
cout << format("%1%\n") % evalPath(state, a);
|
Path drvPath = evalPath(state, a);
|
||||||
|
if (gcRoot == "")
|
||||||
|
printGCWarning();
|
||||||
|
else
|
||||||
|
drvPath = addPermRoot(drvPath,
|
||||||
|
makeRootName(gcRoot, rootNr));
|
||||||
|
cout << format("%1%\n") % drvPath;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw Error("bad derivation");
|
throw Error("bad derivation");
|
||||||
|
@ -77,10 +92,10 @@ void run(Strings args)
|
||||||
bool evalOnly = false;
|
bool evalOnly = false;
|
||||||
bool parseOnly = false;
|
bool parseOnly = false;
|
||||||
|
|
||||||
for (Strings::iterator it = args.begin();
|
for (Strings::iterator i = args.begin();
|
||||||
it != args.end(); )
|
i != args.end(); )
|
||||||
{
|
{
|
||||||
string arg = *it++;
|
string arg = *i++;
|
||||||
|
|
||||||
if (arg == "-")
|
if (arg == "-")
|
||||||
readStdin = true;
|
readStdin = true;
|
||||||
|
@ -92,6 +107,11 @@ void run(Strings args)
|
||||||
readOnlyMode = true;
|
readOnlyMode = true;
|
||||||
parseOnly = evalOnly = true;
|
parseOnly = evalOnly = true;
|
||||||
}
|
}
|
||||||
|
else if (arg == "--add-root") {
|
||||||
|
if (i == args.end())
|
||||||
|
throw UsageError("`--add-root requires an argument");
|
||||||
|
gcRoot = *i++;
|
||||||
|
}
|
||||||
else if (arg[0] == '-')
|
else if (arg[0] == '-')
|
||||||
throw UsageError(format("unknown flag `%1%`") % arg);
|
throw UsageError(format("unknown flag `%1%`") % arg);
|
||||||
else
|
else
|
||||||
|
@ -105,10 +125,10 @@ void run(Strings args)
|
||||||
printResult(state, e, evalOnly);
|
printResult(state, e, evalOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Strings::iterator it = files.begin();
|
for (Strings::iterator i = files.begin();
|
||||||
it != files.end(); it++)
|
i != files.end(); i++)
|
||||||
{
|
{
|
||||||
Expr e = evalFile(state, absPath(*it));
|
Expr e = evalFile(state, absPath(*i));
|
||||||
/* !!! parseOnly ignored */
|
/* !!! parseOnly ignored */
|
||||||
printResult(state, e, evalOnly);
|
printResult(state, e, evalOnly);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,3 +42,5 @@ Options:
|
||||||
|
|
||||||
--verbose / -v: verbose operation (may be repeated)
|
--verbose / -v: verbose operation (may be repeated)
|
||||||
--keep-failed / -K: keep temporary directories of failed builds
|
--keep-failed / -K: keep temporary directories of failed builds
|
||||||
|
|
||||||
|
--add-root: add garbage collector roots for the result
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "build.hh"
|
#include "build.hh"
|
||||||
#include "gc.hh"
|
#include "gc.hh"
|
||||||
|
@ -18,6 +22,10 @@ void printHelp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Path gcRoot;
|
||||||
|
static int rootNr = 0;
|
||||||
|
|
||||||
|
|
||||||
static Path findOutput(const Derivation & drv, string id)
|
static Path findOutput(const Derivation & drv, string id)
|
||||||
{
|
{
|
||||||
for (DerivationOutputs::const_iterator i = drv.outputs.begin();
|
for (DerivationOutputs::const_iterator i = drv.outputs.begin();
|
||||||
|
@ -27,6 +35,22 @@ static Path findOutput(const Derivation & drv, string id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Path followSymlinks(Path & path)
|
||||||
|
{
|
||||||
|
while (!isStorePath(path)) {
|
||||||
|
struct stat st;
|
||||||
|
if (lstat(path.c_str(), &st))
|
||||||
|
throw SysError(format("getting status of `%1%'") % path);
|
||||||
|
if (!S_ISLNK(st.st_mode)) return path;
|
||||||
|
string target = readLink(path);
|
||||||
|
path = canonPath(string(target, 0, 1) == "/"
|
||||||
|
? target
|
||||||
|
: path + "/" + target);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Realisation the given path. For a derivation that means build it;
|
/* Realisation the given path. For a derivation that means build it;
|
||||||
for other paths it means ensure their validity. */
|
for other paths it means ensure their validity. */
|
||||||
static Path realisePath(const Path & path)
|
static Path realisePath(const Path & path)
|
||||||
|
@ -35,7 +59,14 @@ static Path realisePath(const Path & path)
|
||||||
PathSet paths;
|
PathSet paths;
|
||||||
paths.insert(path);
|
paths.insert(path);
|
||||||
buildDerivations(paths);
|
buildDerivations(paths);
|
||||||
return findOutput(derivationFromPath(path), "out");
|
Path outPath = findOutput(derivationFromPath(path), "out");
|
||||||
|
|
||||||
|
if (gcRoot == "")
|
||||||
|
printGCWarning();
|
||||||
|
else
|
||||||
|
outPath = addPermRoot(outPath, makeRootName(gcRoot, rootNr));
|
||||||
|
|
||||||
|
return outPath;
|
||||||
} else {
|
} else {
|
||||||
ensurePath(path);
|
ensurePath(path);
|
||||||
return path;
|
return path;
|
||||||
|
@ -48,6 +79,10 @@ static void opRealise(Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||||
|
|
||||||
|
for (Strings::iterator i = opArgs.begin();
|
||||||
|
i != opArgs.end(); i++)
|
||||||
|
*i = followSymlinks(*i);
|
||||||
|
|
||||||
if (opArgs.size() > 1) {
|
if (opArgs.size() > 1) {
|
||||||
PathSet drvPaths;
|
PathSet drvPaths;
|
||||||
for (Strings::iterator i = opArgs.begin();
|
for (Strings::iterator i = opArgs.begin();
|
||||||
|
@ -374,8 +409,8 @@ void run(Strings args)
|
||||||
Strings opFlags, opArgs;
|
Strings opFlags, opArgs;
|
||||||
Operation op = 0;
|
Operation op = 0;
|
||||||
|
|
||||||
for (Strings::iterator i = args.begin(); i != args.end(); ++i) {
|
for (Strings::iterator i = args.begin(); i != args.end(); ) {
|
||||||
string arg = *i;
|
string arg = *i++;
|
||||||
|
|
||||||
Operation oldOp = op;
|
Operation oldOp = op;
|
||||||
|
|
||||||
|
@ -403,6 +438,11 @@ void run(Strings args)
|
||||||
op = opInit;
|
op = opInit;
|
||||||
else if (arg == "--verify")
|
else if (arg == "--verify")
|
||||||
op = opVerify;
|
op = opVerify;
|
||||||
|
else if (arg == "--add-root") {
|
||||||
|
if (i == args.end())
|
||||||
|
throw UsageError("`--add-root requires an argument");
|
||||||
|
gcRoot = *i++;
|
||||||
|
}
|
||||||
else if (arg[0] == '-')
|
else if (arg[0] == '-')
|
||||||
opFlags.push_back(arg);
|
opFlags.push_back(arg);
|
||||||
else
|
else
|
||||||
|
|
Loading…
Reference in a new issue