* 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:
Eelco Dolstra 2005-02-01 12:36:25 +00:00
parent a6b65fd5e1
commit dcc37c236c
11 changed files with 165 additions and 17 deletions

View file

@ -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

View file

@ -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);
} }

View file

@ -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;

View file

@ -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);

View file

@ -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 */

View file

@ -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

View file

@ -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.,

View file

@ -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

View file

@ -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);
} }

View file

@ -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

View file

@ -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