From 4ca01065c3df106eb9610c425b2c604ba96db365 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 7 Dec 2006 20:47:30 +0000 Subject: * Rename all those main.cc files. --- src/nix-env/Makefile.am | 2 +- src/nix-env/main.cc | 1210 ---------------------------- src/nix-env/nix-env.cc | 1210 ++++++++++++++++++++++++++++ src/nix-instantiate/Makefile.am | 2 +- src/nix-instantiate/main.cc | 159 ---- src/nix-instantiate/nix-instantiate.cc | 159 ++++ src/nix-setuid-helper/Makefile.am | 2 +- src/nix-setuid-helper/main.cc | 256 ------ src/nix-setuid-helper/nix-setuid-helper.cc | 256 ++++++ src/nix-store/Makefile.am | 2 +- src/nix-store/main.cc | 731 ----------------- src/nix-store/nix-store.cc | 731 +++++++++++++++++ src/nix-worker/Makefile.am | 2 +- src/nix-worker/main.cc | 569 ------------- src/nix-worker/nix-worker.cc | 569 +++++++++++++ 15 files changed, 2930 insertions(+), 2930 deletions(-) delete mode 100644 src/nix-env/main.cc create mode 100644 src/nix-env/nix-env.cc delete mode 100644 src/nix-instantiate/main.cc create mode 100644 src/nix-instantiate/nix-instantiate.cc delete mode 100644 src/nix-setuid-helper/main.cc create mode 100644 src/nix-setuid-helper/nix-setuid-helper.cc delete mode 100644 src/nix-store/main.cc create mode 100644 src/nix-store/nix-store.cc delete mode 100644 src/nix-worker/main.cc create mode 100644 src/nix-worker/nix-worker.cc diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am index 5511b2c8edd1..013d7ff7723c 100644 --- a/src/nix-env/Makefile.am +++ b/src/nix-env/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix-env -nix_env_SOURCES = main.cc names.cc names.hh \ +nix_env_SOURCES = nix-env.cc names.cc names.hh \ profiles.cc profiles.hh help.txt nix_env_LDADD = ../libmain/libmain.la ../libexpr/libexpr.la \ ../libstore/libstore.la ../libutil/libutil.la \ diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc deleted file mode 100644 index 90d799224efd..000000000000 --- a/src/nix-env/main.cc +++ /dev/null @@ -1,1210 +0,0 @@ -#include "profiles.hh" -#include "names.hh" -#include "globals.hh" -#include "misc.hh" -#include "shared.hh" -#include "parser.hh" -#include "eval.hh" -#include "help.txt.hh" -#include "nixexpr-ast.hh" -#include "get-drvs.hh" -#include "attr-path.hh" -#include "pathlocks.hh" -#include "xml-writer.hh" -#include "store-api.hh" -#include "db.hh" -#include "util.hh" - -#include -#include -#include -#include -#include - -#include - - -using namespace nix; -using std::cout; - - -typedef enum { - srcNixExprDrvs, - srcNixExprs, - srcStorePaths, - srcProfile, - srcAttrPath, - srcUnknown -} InstallSourceType; - - -struct InstallSourceInfo -{ - InstallSourceType type; - Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */ - Path profile; /* for srcProfile */ - string systemFilter; /* for srcNixExprDrvs */ - ATermMap autoArgs; - InstallSourceInfo() : autoArgs(128) { }; -}; - - -struct Globals -{ - InstallSourceInfo instSource; - Path profile; - EvalState state; - bool dryRun; - bool preserveInstalled; - bool keepDerivations; - string forceName; -}; - - -typedef void (* Operation) (Globals & globals, - Strings opFlags, Strings opArgs); - - -void printHelp() -{ - cout << string((char *) helpText, sizeof helpText); -} - - -static void loadDerivations(EvalState & state, Path nixExprPath, - string systemFilter, const ATermMap & autoArgs, DrvInfos & elems) -{ - getDerivations(state, - parseExprFromFile(state, absPath(nixExprPath)), "", autoArgs, elems); - - /* Filter out all derivations not applicable to the current - system. */ - for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) { - j = i; j++; - if (systemFilter != "*" && i->system != systemFilter) - elems.erase(i); - } -} - - -static Path getHomeDir() -{ - Path homeDir(getEnv("HOME", "")); - if (homeDir == "") throw Error("HOME environment variable not set"); - return homeDir; -} - - -static Path getDefNixExprPath() -{ - return getHomeDir() + "/.nix-defexpr"; -} - - -struct AddPos : TermFun -{ - ATerm operator () (ATerm e) - { - ATerm x, y; - if (matchObsoleteBind(e, x, y)) - return makeBind(x, y, makeNoPos()); - if (matchObsoleteStr(e, x)) - return makeStr(x, ATempty); - return e; - } -}; - - -static DrvInfos queryInstalled(EvalState & state, const Path & userEnv) -{ - Path path = userEnv + "/manifest"; - - if (!pathExists(path)) - return DrvInfos(); /* not an error, assume nothing installed */ - - Expr e = ATreadFromNamedFile(path.c_str()); - if (!e) throw Error(format("cannot read Nix expression from `%1%'") % path); - - /* Compatibility: Bind(x, y) -> Bind(x, y, NoPos). */ - AddPos addPos; - e = bottomupRewrite(addPos, e); - - DrvInfos elems; - getDerivations(state, e, "", ATermMap(1), elems); - return elems; -} - - -static void createUserEnv(EvalState & state, const DrvInfos & elems, - const Path & profile, bool keepDerivations) -{ - /* Build the components in the user environment, if they don't - exist already. */ - PathSet drvsToBuild; - for (DrvInfos::const_iterator i = elems.begin(); - i != elems.end(); ++i) - /* Call to `isDerivation' is for compatibility with Nix <= 0.7 - user environments. */ - if (i->queryDrvPath(state) != "" && - isDerivation(i->queryDrvPath(state))) - drvsToBuild.insert(i->queryDrvPath(state)); - - debug(format("building user environment dependencies")); - store->buildDerivations(drvsToBuild); - - /* Get the environment builder expression. */ - Expr envBuilder = parseExprFromFile(state, - nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */ - - /* Construct the whole top level derivation. */ - PathSet references; - ATermList manifest = ATempty; - ATermList inputs = ATempty; - for (DrvInfos::const_iterator i = elems.begin(); - i != elems.end(); ++i) - { - Path drvPath = keepDerivations ? i->queryDrvPath(state) : ""; - ATermList as = ATmakeList4( - makeBind(toATerm("type"), - makeStr("derivation"), makeNoPos()), - makeBind(toATerm("name"), - makeStr(i->name), makeNoPos()), - makeBind(toATerm("system"), - makeStr(i->system), makeNoPos()), - makeBind(toATerm("outPath"), - makeStr(i->queryOutPath(state)), makeNoPos())); - if (drvPath != "") as = ATinsert(as, - makeBind(toATerm("drvPath"), - makeStr(drvPath), makeNoPos())); - manifest = ATinsert(manifest, makeAttrs(as)); - inputs = ATinsert(inputs, makeStr(i->queryOutPath(state))); - - /* This is only necessary when installing store paths, e.g., - `nix-env -i /nix/store/abcd...-foo'. */ - store->addTempRoot(i->queryOutPath(state)); - store->ensurePath(i->queryOutPath(state)); - - references.insert(i->queryOutPath(state)); - if (drvPath != "") references.insert(drvPath); - } - - /* Also write a copy of the list of inputs to the store; we need - it for future modifications of the environment. */ - Path manifestFile = store->addTextToStore("env-manifest", - atPrint(makeList(ATreverse(manifest))), references); - - Expr topLevel = makeCall(envBuilder, makeAttrs(ATmakeList3( - makeBind(toATerm("system"), - makeStr(thisSystem), makeNoPos()), - makeBind(toATerm("derivations"), - makeList(ATreverse(inputs)), makeNoPos()), - makeBind(toATerm("manifest"), - makeStr(manifestFile, singleton(manifestFile)), makeNoPos()) - ))); - - /* Instantiate it. */ - debug(format("evaluating builder expression `%1%'") % topLevel); - DrvInfo topLevelDrv; - if (!getDerivation(state, topLevel, topLevelDrv)) - abort(); - - /* Realise the resulting store expression. */ - debug(format("building user environment")); - store->buildDerivations(singleton(topLevelDrv.queryDrvPath(state))); - - /* Switch the current user environment to the output path. */ - debug(format("switching to new user environment")); - Path generation = createGeneration(profile, topLevelDrv.queryOutPath(state)); - switchLink(profile, generation); -} - - -static DrvInfos filterBySelector(EvalState & state, - const DrvInfos & allElems, - const Strings & args, bool newestOnly) -{ - DrvNames selectors = drvNamesFromArgs(args); - - DrvInfos elems; - set done; - - for (DrvNames::iterator i = selectors.begin(); - i != selectors.end(); ++i) - { - typedef list > Matches; - Matches matches; - unsigned int n = 0; - for (DrvInfos::const_iterator j = allElems.begin(); - j != allElems.end(); ++j, ++n) - { - DrvName drvName(j->name); - if (i->matches(drvName)) { - i->hits++; - matches.push_back(std::pair(*j, n)); - } - } - - /* If `newestOnly', if a selector matches multiple derivations - with the same name, pick the one with the highest version. - If there are multiple derivations with the same name *and* - version, then pick the first one. */ - if (newestOnly) { - - /* Map from package names to derivations. */ - typedef map > Newest; - Newest newest; - StringSet multiple; - - for (Matches::iterator j = matches.begin(); j != matches.end(); ++j) { - DrvName drvName(j->first.name); - Newest::iterator k = newest.find(drvName.name); - if (k != newest.end()) { - int d = compareVersions(drvName.version, DrvName(k->second.first.name).version); - if (d > 0) newest[drvName.name] = *j; - else if (d == 0) multiple.insert(j->first.name); - } else - newest[drvName.name] = *j; - } - - matches.clear(); - for (Newest::iterator j = newest.begin(); j != newest.end(); ++j) { - if (multiple.find(j->second.first.name) != multiple.end()) - printMsg(lvlInfo, - format("warning: there are multiple derivations named `%1%'; using the first one") - % j->second.first.name); - matches.push_back(j->second); - } - } - - /* Insert only those elements in the final list that we - haven't inserted before. */ - for (Matches::iterator j = matches.begin(); j != matches.end(); ++j) - if (done.find(j->second) == done.end()) { - done.insert(j->second); - elems.push_back(j->first); - } - } - - /* Check that all selectors have been used. */ - for (DrvNames::iterator i = selectors.begin(); - i != selectors.end(); ++i) - if (i->hits == 0) - throw Error(format("selector `%1%' matches no derivations") - % i->fullName); - - return elems; -} - - -static void queryInstSources(EvalState & state, - const InstallSourceInfo & instSource, const Strings & args, - DrvInfos & elems, bool newestOnly) -{ - InstallSourceType type = instSource.type; - if (type == srcUnknown && args.size() > 0 && args.front()[0] == '/') - type = srcStorePaths; - - switch (type) { - - /* Get the available user environment elements from the - derivations specified in a Nix expression, including only - those with names matching any of the names in `args'. */ - case srcUnknown: - case srcNixExprDrvs: { - - /* Load the derivations from the (default or specified) - Nix expression. */ - DrvInfos allElems; - loadDerivations(state, instSource.nixExprPath, - instSource.systemFilter, instSource.autoArgs, allElems); - - elems = filterBySelector(state, allElems, args, newestOnly); - - break; - } - - /* Get the available user environment elements from the Nix - expressions specified on the command line; these should be - functions that take the default Nix expression file as - argument, e.g., if the file is `./foo.nix', then the - argument `x: x.bar' is equivalent to `(x: x.bar) - (import ./foo.nix)' = `(import ./foo.nix).bar'. */ - case srcNixExprs: { - - - Expr e1 = parseExprFromFile(state, - absPath(instSource.nixExprPath)); - - for (Strings::const_iterator i = args.begin(); - i != args.end(); ++i) - { - Expr e2 = parseExprFromString(state, *i, absPath(".")); - Expr call = makeCall(e2, e1); - getDerivations(state, call, "", instSource.autoArgs, elems); - } - - break; - } - - /* The available user environment elements are specified as a - list of store paths (which may or may not be - derivations). */ - case srcStorePaths: { - - for (Strings::const_iterator i = args.begin(); - i != args.end(); ++i) - { - assertStorePath(*i); - - DrvInfo elem; - elem.attrs = boost::shared_ptr(new ATermMap(0)); /* ugh... */ - string name = baseNameOf(*i); - string::size_type dash = name.find('-'); - if (dash != string::npos) - name = string(name, dash + 1); - - if (isDerivation(*i)) { - elem.setDrvPath(*i); - elem.setOutPath(findOutput(derivationFromPath(*i), "out")); - if (name.size() >= drvExtension.size() && - string(name, name.size() - drvExtension.size()) == drvExtension) - name = string(name, 0, name.size() - drvExtension.size()); - } - else elem.setOutPath(*i); - - elem.name = name; - - elems.push_back(elem); - } - - break; - } - - /* Get the available user environment elements from another - user environment. These are then filtered as in the - `srcNixExprDrvs' case. */ - case srcProfile: { - elems = filterBySelector(state, - queryInstalled(state, instSource.profile), - args, newestOnly); - break; - } - - case srcAttrPath: { - for (Strings::const_iterator i = args.begin(); - i != args.end(); ++i) - getDerivations(state, - findAlongAttrPath(state, *i, instSource.autoArgs, - parseExprFromFile(state, instSource.nixExprPath)), - "", instSource.autoArgs, elems); - break; - } - } -} - - -static void printMissing(EvalState & state, const DrvInfos & elems) -{ - PathSet targets, willBuild, willSubstitute; - for (DrvInfos::const_iterator i = elems.begin(); i != elems.end(); ++i) { - Path drvPath = i->queryDrvPath(state); - if (drvPath != "") - targets.insert(drvPath); - else - targets.insert(i->queryOutPath(state)); - } - - queryMissing(targets, willBuild, willSubstitute); - - if (!willBuild.empty()) { - printMsg(lvlInfo, format("the following derivations will be built:")); - for (PathSet::iterator i = willBuild.begin(); i != willBuild.end(); ++i) - printMsg(lvlInfo, format(" %1%") % *i); - } - - if (!willSubstitute.empty()) { - printMsg(lvlInfo, format("the following paths will be substituted:")); - for (PathSet::iterator i = willSubstitute.begin(); i != willSubstitute.end(); ++i) - printMsg(lvlInfo, format(" %1%") % *i); - } -} - - -static void lockProfile(PathLocks & lock, const Path & profile) -{ - lock.lockPaths(singleton(profile), - (format("waiting for lock on profile `%1%'") % profile).str()); - lock.setDeletion(true); -} - - -static void installDerivations(Globals & globals, - const Strings & args, const Path & profile) -{ - debug(format("installing derivations")); - - /* Get the set of user environment elements to be installed. */ - DrvInfos newElems; - queryInstSources(globals.state, globals.instSource, args, newElems, true); - - StringSet newNames; - for (DrvInfos::iterator i = newElems.begin(); i != newElems.end(); ++i) { - /* `forceName' is a hack to get package names right in some - one-click installs, namely those where the name used in the - path is not the one we want (e.g., `java-front' versus - `java-front-0.9pre15899'). */ - if (globals.forceName != "") - i->name = globals.forceName; - newNames.insert(DrvName(i->name).name); - } - - /* Add in the already installed derivations, unless they have the - same name as a to-be-installed element. */ - PathLocks lock; - lockProfile(lock, profile); - DrvInfos installedElems = queryInstalled(globals.state, profile); - - DrvInfos allElems(newElems); - for (DrvInfos::iterator i = installedElems.begin(); - i != installedElems.end(); ++i) - { - DrvName drvName(i->name); - if (!globals.preserveInstalled && - newNames.find(drvName.name) != newNames.end()) - printMsg(lvlInfo, - format("replacing old `%1%'") % i->name); - else - allElems.push_back(*i); - } - - for (DrvInfos::iterator i = newElems.begin(); i != newElems.end(); ++i) - printMsg(lvlInfo, - format("installing `%1%'") % i->name); - - if (globals.dryRun) { - printMissing(globals.state, newElems); - return; - } - - createUserEnv(globals.state, allElems, - profile, globals.keepDerivations); -} - - -static void opInstall(Globals & globals, - Strings opFlags, Strings opArgs) -{ - if (opFlags.size() > 0) - throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - - installDerivations(globals, opArgs, globals.profile); -} - - -typedef enum { utLt, utLeq, utEq, utAlways } UpgradeType; - - -static void upgradeDerivations(Globals & globals, - const Strings & args, const Path & profile, - UpgradeType upgradeType) -{ - debug(format("upgrading derivations")); - - /* Upgrade works as follows: we take all currently installed - derivations, and for any derivation matching any selector, look - for a derivation in the input Nix expression that has the same - name and a higher version number. */ - - /* Load the currently installed derivations. */ - PathLocks lock; - lockProfile(lock, profile); - DrvInfos installedElems = queryInstalled(globals.state, profile); - - /* Fetch all derivations from the input file. */ - DrvInfos availElems; - queryInstSources(globals.state, globals.instSource, args, availElems, false); - - /* Go through all installed derivations. */ - DrvInfos newElems; - for (DrvInfos::iterator i = installedElems.begin(); - i != installedElems.end(); ++i) - { - DrvName drvName(i->name); - - /* Find the derivation in the input Nix expression with the - same name and satisfying the version constraints specified - by upgradeType. If there are multiple matches, take the - one with highest version. */ - DrvInfos::iterator bestElem = availElems.end(); - DrvName bestName; - for (DrvInfos::iterator j = availElems.begin(); - j != availElems.end(); ++j) - { - DrvName newName(j->name); - if (newName.name == drvName.name) { - int d = compareVersions(drvName.version, newName.version); - if (upgradeType == utLt && d < 0 || - upgradeType == utLeq && d <= 0 || - upgradeType == utEq && d == 0 || - upgradeType == utAlways) - { - if ((bestElem == availElems.end() || - compareVersions( - bestName.version, newName.version) < 0)) - { - bestElem = j; - bestName = newName; - } - } - } - } - - if (bestElem != availElems.end() && - i->queryOutPath(globals.state) != - bestElem->queryOutPath(globals.state)) - { - printMsg(lvlInfo, - format("upgrading `%1%' to `%2%'") - % i->name % bestElem->name); - newElems.push_back(*bestElem); - } else newElems.push_back(*i); - } - - if (globals.dryRun) { - printMissing(globals.state, newElems); - return; - } - - createUserEnv(globals.state, newElems, - profile, globals.keepDerivations); -} - - -static void opUpgrade(Globals & globals, - Strings opFlags, Strings opArgs) -{ - UpgradeType upgradeType = utLt; - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--lt") upgradeType = utLt; - else if (*i == "--leq") upgradeType = utLeq; - else if (*i == "--eq") upgradeType = utEq; - else if (*i == "--always") upgradeType = utAlways; - else throw UsageError(format("unknown flag `%1%'") % *i); - - upgradeDerivations(globals, opArgs, globals.profile, upgradeType); -} - - -static void uninstallDerivations(Globals & globals, DrvNames & selectors, - Path & profile) -{ - PathLocks lock; - lockProfile(lock, profile); - DrvInfos installedElems = queryInstalled(globals.state, profile); - DrvInfos newElems; - - for (DrvInfos::iterator i = installedElems.begin(); - i != installedElems.end(); ++i) - { - DrvName drvName(i->name); - bool found = false; - for (DrvNames::iterator j = selectors.begin(); - j != selectors.end(); ++j) - if (j->matches(drvName)) { - printMsg(lvlInfo, - format("uninstalling `%1%'") % i->name); - found = true; - break; - } - if (!found) newElems.push_back(*i); - } - - if (globals.dryRun) return; - - createUserEnv(globals.state, newElems, - profile, globals.keepDerivations); -} - - -static void opUninstall(Globals & globals, - Strings opFlags, Strings opArgs) -{ - if (opFlags.size() > 0) - throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - - DrvNames drvNames = drvNamesFromArgs(opArgs); - - uninstallDerivations(globals, drvNames, - globals.profile); -} - - -static bool cmpChars(char a, char b) -{ - return toupper(a) < toupper(b); -} - - -static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b) -{ - return lexicographical_compare( - a.name.begin(), a.name.end(), - b.name.begin(), b.name.end(), cmpChars); -} - - -typedef list Table; - - -void printTable(Table & table) -{ - unsigned int nrColumns = table.size() > 0 ? table.front().size() : 0; - - vector widths; - widths.resize(nrColumns); - - for (Table::iterator i = table.begin(); i != table.end(); ++i) { - assert(i->size() == nrColumns); - Strings::iterator j; - unsigned int column; - for (j = i->begin(), column = 0; j != i->end(); ++j, ++column) - if (j->size() > widths[column]) widths[column] = j->size(); - } - - for (Table::iterator i = table.begin(); i != table.end(); ++i) { - Strings::iterator j; - unsigned int column; - for (j = i->begin(), column = 0; j != i->end(); ++j, ++column) - { - cout << *j; - if (column < nrColumns - 1) - cout << string(widths[column] - j->size() + 2, ' '); - } - cout << std::endl; - } -} - - -/* This function compares the version of a element against the - versions in the given set of elements. `cvLess' means that only - lower versions are in the set, `cvEqual' means that at most an - equal version is in the set, and `cvGreater' means that there is at - least one element with a higher version in the set. `cvUnavail' - means that there are no elements with the same name in the set. */ - -typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff; - -static VersionDiff compareVersionAgainstSet( - const DrvInfo & elem, const DrvInfos & elems, string & version) -{ - DrvName name(elem.name); - - VersionDiff diff = cvUnavail; - version = "?"; - - for (DrvInfos::const_iterator i = elems.begin(); i != elems.end(); ++i) { - DrvName name2(i->name); - if (name.name == name2.name) { - int d = compareVersions(name.version, name2.version); - if (d < 0) { - diff = cvGreater; - version = name2.version; - } - else if (diff != cvGreater && d == 0) { - diff = cvEqual; - version = name2.version; - } - else if (diff != cvGreater && diff != cvEqual && d > 0) { - diff = cvLess; - if (version == "" || compareVersions(version, name2.version) < 0) - version = name2.version; - } - } - } - - return diff; -} - - -static string colorString(const string & s) -{ - if (!isatty(STDOUT_FILENO)) return s; - return "\e[1;31m" + s + "\e[0m"; -} - - -static void opQuery(Globals & globals, - Strings opFlags, Strings opArgs) -{ - typedef vector< map > ResultSet; - - bool printStatus = false; - bool printName = true; - bool printAttrPath = false; - bool printSystem = false; - bool printDrvPath = false; - bool printOutPath = false; - bool printDescription = false; - bool compareVersions = false; - bool xmlOutput = false; - - enum { sInstalled, sAvailable } source = sInstalled; - - readOnlyMode = true; /* makes evaluation a bit faster */ - - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--status" || *i == "-s") printStatus = true; - else if (*i == "--no-name") printName = false; - else if (*i == "--system") printSystem = true; - else if (*i == "--description") printDescription = true; - else if (*i == "--compare-versions" || *i == "-c") compareVersions = true; - else if (*i == "--drv-path") printDrvPath = true; - else if (*i == "--out-path") printOutPath = true; - else if (*i == "--installed") source = sInstalled; - else if (*i == "--available" || *i == "-a") source = sAvailable; - else if (*i == "--xml") xmlOutput = true; - else throw UsageError(format("unknown flag `%1%'") % *i); - - if (globals.instSource.type == srcAttrPath) printAttrPath = true; /* hack */ - - if (opArgs.size() == 0) { - printMsg(lvlInfo, "warning: you probably meant to specify the argument '*' to show all packages"); - } - - - /* Obtain derivation information from the specified source. */ - DrvInfos availElems, installedElems; - - if (source == sInstalled || compareVersions || printStatus) { - installedElems = queryInstalled(globals.state, globals.profile); - } - - if (source == sAvailable || compareVersions) { - loadDerivations(globals.state, globals.instSource.nixExprPath, - globals.instSource.systemFilter, globals.instSource.autoArgs, - availElems); - } - - DrvInfos elems = filterBySelector(globals.state, - source == sInstalled ? installedElems : availElems, - opArgs, false); - - DrvInfos & otherElems(source == sInstalled ? availElems : installedElems); - - - /* Sort them by name. */ - /* !!! */ - vector elems2; - for (DrvInfos::iterator i = elems.begin(); i != elems.end(); ++i) - elems2.push_back(*i); - sort(elems2.begin(), elems2.end(), cmpElemByName); - - - /* We only need to know the installed paths when we are querying - the status of the derivation. */ - PathSet installed; /* installed paths */ - - if (printStatus) { - for (DrvInfos::iterator i = installedElems.begin(); - i != installedElems.end(); ++i) - installed.insert(i->queryOutPath(globals.state)); - } - - - /* Print the desired columns, or XML output. */ - Table table; - std::ostringstream dummy; - XMLWriter xml(true, *(xmlOutput ? &cout : &dummy)); - XMLOpenElement xmlRoot(xml, "items"); - - for (vector::iterator i = elems2.begin(); - i != elems2.end(); ++i) - { - try { - - /* For table output. */ - Strings columns; - - /* For XML output. */ - XMLAttrs attrs; - - if (printStatus) { - bool hasSubs = store->hasSubstitutes(i->queryOutPath(globals.state)); - bool isInstalled = installed.find(i->queryOutPath(globals.state)) != installed.end(); - bool isValid = store->isValidPath(i->queryOutPath(globals.state)); - if (xmlOutput) { - attrs["installed"] = isInstalled ? "1" : "0"; - attrs["valid"] = isValid ? "1" : "0"; - attrs["substitutable"] = hasSubs ? "1" : "0"; - } else - columns.push_back( - (string) (isInstalled ? "I" : "-") - + (isValid ? "P" : "-") - + (hasSubs ? "S" : "-")); - } - - if (xmlOutput) - attrs["attrPath"] = i->attrPath; - else if (printAttrPath) - columns.push_back(i->attrPath); - - if (xmlOutput) - attrs["name"] = i->name; - else if (printName) - columns.push_back(i->name); - - if (compareVersions) { - /* Compare this element against the versions of the - same named packages in either the set of available - elements, or the set of installed elements. !!! - This is O(N * M), should be O(N * lg M). */ - string version; - VersionDiff diff = compareVersionAgainstSet(*i, otherElems, version); - - char ch; - switch (diff) { - case cvLess: ch = '>'; break; - case cvEqual: ch = '='; break; - case cvGreater: ch = '<'; break; - case cvUnavail: ch = '-'; break; - default: abort(); - } - - if (xmlOutput) { - if (diff != cvUnavail) { - attrs["versionDiff"] = ch; - attrs["maxComparedVersion"] = version; - } - } else { - string column = (string) "" + ch + " " + version; - if (diff == cvGreater) column = colorString(column); - columns.push_back(column); - } - } - - if (xmlOutput) { - if (i->system != "") attrs["system"] = i->system; - } - else if (printSystem) - columns.push_back(i->system); - - if (printDrvPath) { - string drvPath = i->queryDrvPath(globals.state); - if (xmlOutput) { - if (drvPath != "") attrs["drvPath"] = drvPath; - } else - columns.push_back(drvPath == "" ? "-" : drvPath); - } - - if (printOutPath) { - string outPath = i->queryOutPath(globals.state); - if (xmlOutput) { - if (outPath != "") attrs["outPath"] = outPath; - } else - columns.push_back(outPath); - } - - if (printDescription) { - MetaInfo meta = i->queryMetaInfo(globals.state); - string descr = meta["description"]; - if (xmlOutput) { - if (descr != "") attrs["description"] = descr; - } else - columns.push_back(descr); - } - - if (xmlOutput) - xml.writeEmptyElement("item", attrs); - else - table.push_back(columns); - - cout.flush(); - - } catch (AssertionError & e) { - /* !!! hm, maybe we should give some sort of warning here? */ - } - } - - if (!xmlOutput) printTable(table); -} - - -static void opSwitchProfile(Globals & globals, - Strings opFlags, Strings opArgs) -{ - if (opFlags.size() > 0) - throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - if (opArgs.size() != 1) - throw UsageError(format("exactly one argument expected")); - - Path profile = absPath(opArgs.front()); - Path profileLink = getHomeDir() + "/.nix-profile"; - - switchLink(profileLink, profile); -} - - -static const int prevGen = -2; - - -static void switchGeneration(Globals & globals, int dstGen) -{ - PathLocks lock; - lockProfile(lock, globals.profile); - - int curGen; - Generations gens = findGenerations(globals.profile, curGen); - - Generation dst; - for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) - if ((dstGen == prevGen && i->number < curGen) || - (dstGen >= 0 && i->number == dstGen)) - dst = *i; - - if (!dst) - if (dstGen == prevGen) - throw Error(format("no generation older than the current (%1%) exists") - % curGen); - else - throw Error(format("generation %1% does not exist") % dstGen); - - printMsg(lvlInfo, format("switching from generation %1% to %2%") - % curGen % dst.number); - - if (globals.dryRun) return; - - switchLink(globals.profile, dst.path); -} - - -static void opSwitchGeneration(Globals & globals, - Strings opFlags, Strings opArgs) -{ - if (opFlags.size() > 0) - throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - if (opArgs.size() != 1) - throw UsageError(format("exactly one argument expected")); - - int dstGen; - if (!string2Int(opArgs.front(), dstGen)) - throw UsageError(format("expected a generation number")); - - switchGeneration(globals, dstGen); -} - - -static void opRollback(Globals & globals, - Strings opFlags, Strings opArgs) -{ - if (opFlags.size() > 0) - throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - if (opArgs.size() != 0) - throw UsageError(format("no arguments expected")); - - switchGeneration(globals, prevGen); -} - - -static void opListGenerations(Globals & globals, - Strings opFlags, Strings opArgs) -{ - if (opFlags.size() > 0) - throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - if (opArgs.size() != 0) - throw UsageError(format("no arguments expected")); - - PathLocks lock; - lockProfile(lock, globals.profile); - - int curGen; - Generations gens = findGenerations(globals.profile, curGen); - - for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) { - tm t; - if (!localtime_r(&i->creationTime, &t)) throw Error("cannot convert time"); - cout << format("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||\n") - % i->number - % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday - % t.tm_hour % t.tm_min % t.tm_sec - % (i->number == curGen ? "(current)" : ""); - } -} - - -static void deleteGeneration2(const Path & profile, unsigned int gen) -{ - printMsg(lvlInfo, format("removing generation %1%") % gen); - deleteGeneration(profile, gen); -} - - -static void opDeleteGenerations(Globals & globals, - Strings opFlags, Strings opArgs) -{ - if (opFlags.size() > 0) - throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - - PathLocks lock; - lockProfile(lock, globals.profile); - - int curGen; - Generations gens = findGenerations(globals.profile, curGen); - - for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { - - if (*i == "old") { - for (Generations::iterator j = gens.begin(); j != gens.end(); ++j) - if (j->number != curGen) - deleteGeneration2(globals.profile, j->number); - } - - else { - int n; - if (!string2Int(*i, n) || n < 0) - throw UsageError(format("invalid generation specifier `%1%'") % *i); - bool found = false; - for (Generations::iterator j = gens.begin(); j != gens.end(); ++j) { - if (j->number == n) { - deleteGeneration2(globals.profile, j->number); - found = true; - break; - } - } - if (!found) - printMsg(lvlError, format("generation %1% does not exist") % n); - } - } -} - - -static void opDefaultExpr(Globals & globals, - Strings opFlags, Strings opArgs) -{ - if (opFlags.size() > 0) - throw UsageError(format("unknown flag `%1%'") % opFlags.front()); - if (opArgs.size() != 1) - throw UsageError(format("exactly one argument expected")); - - Path defNixExpr = absPath(opArgs.front()); - Path defNixExprLink = getDefNixExprPath(); - - switchLink(defNixExprLink, defNixExpr); -} - - -static string needArg(Strings::iterator & i, - Strings & args, const string & arg) -{ - ++i; - if (i == args.end()) throw UsageError( - format("`%1%' requires an argument") % arg); - return *i; -} - - -void run(Strings args) -{ - Strings opFlags, opArgs; - Operation op = 0; - - Globals globals; - - globals.instSource.type = srcUnknown; - globals.instSource.nixExprPath = getDefNixExprPath(); - globals.instSource.systemFilter = thisSystem; - - globals.dryRun = false; - globals.preserveInstalled = false; - - globals.keepDerivations = - queryBoolSetting("env-keep-derivations", false); - - for (Strings::iterator i = args.begin(); i != args.end(); ++i) { - string arg = *i; - - Operation oldOp = op; - - if (arg == "--install" || arg == "-i") - op = opInstall; - else if (arg == "--from-expression" || arg == "-E") - globals.instSource.type = srcNixExprs; - else if (arg == "--from-profile") { - globals.instSource.type = srcProfile; - globals.instSource.profile = needArg(i, args, arg); - } - else if (arg == "--attr" || arg == "-A") - globals.instSource.type = srcAttrPath; - else if (arg == "--arg") { /* !!! code duplication from nix-instantiate */ - i++; - if (i == args.end()) - throw UsageError("`--arg' requires two arguments"); - string name = *i++; - if (i == args.end()) - throw UsageError("`--arg' requires two arguments"); - Expr value = parseExprFromString(globals.state, *i, absPath(".")); - globals.instSource.autoArgs.set(toATerm(name), value); - } - else if (arg == "--force-name") // undocumented flag for nix-install-package - globals.forceName = needArg(i, args, arg); - else if (arg == "--uninstall" || arg == "-e") - op = opUninstall; - else if (arg == "--upgrade" || arg == "-u") - op = opUpgrade; - else if (arg == "--query" || arg == "-q") - op = opQuery; - else if (arg == "--import" || arg == "-I") /* !!! bad name */ - op = opDefaultExpr; - else if (arg == "--profile" || arg == "-p") { - globals.profile = absPath(needArg(i, args, arg)); - } - else if (arg == "--file" || arg == "-f") { - globals.instSource.nixExprPath = absPath(needArg(i, args, arg)); - } - else if (arg == "--switch-profile" || arg == "-S") - op = opSwitchProfile; - else if (arg == "--switch-generation" || arg == "-G") - op = opSwitchGeneration; - else if (arg == "--rollback") - op = opRollback; - else if (arg == "--list-generations") - op = opListGenerations; - else if (arg == "--delete-generations") - op = opDeleteGenerations; - else if (arg == "--dry-run") { - printMsg(lvlInfo, "(dry run; not doing anything)"); - globals.dryRun = true; - } - else if (arg == "--preserve-installed" || arg == "-P") - globals.preserveInstalled = true; - else if (arg == "--system-filter") { - globals.instSource.systemFilter = needArg(i, args, arg); - } - else if (arg[0] == '-') - opFlags.push_back(arg); - else - opArgs.push_back(arg); - - if (oldOp && oldOp != op) - throw UsageError("only one operation may be specified"); - } - - if (!op) throw UsageError("no operation specified"); - - if (globals.profile == "") { - Path profileLink = getHomeDir() + "/.nix-profile"; - globals.profile = pathExists(profileLink) - ? absPath(readLink(profileLink), dirOf(profileLink)) - : canonPath(nixStateDir + "/profiles/default"); - } - - store = openStore(); - - op(globals, opFlags, opArgs); - - printEvalStats(globals.state); -} - - -string programId = "nix-env"; diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc new file mode 100644 index 000000000000..90d799224efd --- /dev/null +++ b/src/nix-env/nix-env.cc @@ -0,0 +1,1210 @@ +#include "profiles.hh" +#include "names.hh" +#include "globals.hh" +#include "misc.hh" +#include "shared.hh" +#include "parser.hh" +#include "eval.hh" +#include "help.txt.hh" +#include "nixexpr-ast.hh" +#include "get-drvs.hh" +#include "attr-path.hh" +#include "pathlocks.hh" +#include "xml-writer.hh" +#include "store-api.hh" +#include "db.hh" +#include "util.hh" + +#include +#include +#include +#include +#include + +#include + + +using namespace nix; +using std::cout; + + +typedef enum { + srcNixExprDrvs, + srcNixExprs, + srcStorePaths, + srcProfile, + srcAttrPath, + srcUnknown +} InstallSourceType; + + +struct InstallSourceInfo +{ + InstallSourceType type; + Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */ + Path profile; /* for srcProfile */ + string systemFilter; /* for srcNixExprDrvs */ + ATermMap autoArgs; + InstallSourceInfo() : autoArgs(128) { }; +}; + + +struct Globals +{ + InstallSourceInfo instSource; + Path profile; + EvalState state; + bool dryRun; + bool preserveInstalled; + bool keepDerivations; + string forceName; +}; + + +typedef void (* Operation) (Globals & globals, + Strings opFlags, Strings opArgs); + + +void printHelp() +{ + cout << string((char *) helpText, sizeof helpText); +} + + +static void loadDerivations(EvalState & state, Path nixExprPath, + string systemFilter, const ATermMap & autoArgs, DrvInfos & elems) +{ + getDerivations(state, + parseExprFromFile(state, absPath(nixExprPath)), "", autoArgs, elems); + + /* Filter out all derivations not applicable to the current + system. */ + for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) { + j = i; j++; + if (systemFilter != "*" && i->system != systemFilter) + elems.erase(i); + } +} + + +static Path getHomeDir() +{ + Path homeDir(getEnv("HOME", "")); + if (homeDir == "") throw Error("HOME environment variable not set"); + return homeDir; +} + + +static Path getDefNixExprPath() +{ + return getHomeDir() + "/.nix-defexpr"; +} + + +struct AddPos : TermFun +{ + ATerm operator () (ATerm e) + { + ATerm x, y; + if (matchObsoleteBind(e, x, y)) + return makeBind(x, y, makeNoPos()); + if (matchObsoleteStr(e, x)) + return makeStr(x, ATempty); + return e; + } +}; + + +static DrvInfos queryInstalled(EvalState & state, const Path & userEnv) +{ + Path path = userEnv + "/manifest"; + + if (!pathExists(path)) + return DrvInfos(); /* not an error, assume nothing installed */ + + Expr e = ATreadFromNamedFile(path.c_str()); + if (!e) throw Error(format("cannot read Nix expression from `%1%'") % path); + + /* Compatibility: Bind(x, y) -> Bind(x, y, NoPos). */ + AddPos addPos; + e = bottomupRewrite(addPos, e); + + DrvInfos elems; + getDerivations(state, e, "", ATermMap(1), elems); + return elems; +} + + +static void createUserEnv(EvalState & state, const DrvInfos & elems, + const Path & profile, bool keepDerivations) +{ + /* Build the components in the user environment, if they don't + exist already. */ + PathSet drvsToBuild; + for (DrvInfos::const_iterator i = elems.begin(); + i != elems.end(); ++i) + /* Call to `isDerivation' is for compatibility with Nix <= 0.7 + user environments. */ + if (i->queryDrvPath(state) != "" && + isDerivation(i->queryDrvPath(state))) + drvsToBuild.insert(i->queryDrvPath(state)); + + debug(format("building user environment dependencies")); + store->buildDerivations(drvsToBuild); + + /* Get the environment builder expression. */ + Expr envBuilder = parseExprFromFile(state, + nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */ + + /* Construct the whole top level derivation. */ + PathSet references; + ATermList manifest = ATempty; + ATermList inputs = ATempty; + for (DrvInfos::const_iterator i = elems.begin(); + i != elems.end(); ++i) + { + Path drvPath = keepDerivations ? i->queryDrvPath(state) : ""; + ATermList as = ATmakeList4( + makeBind(toATerm("type"), + makeStr("derivation"), makeNoPos()), + makeBind(toATerm("name"), + makeStr(i->name), makeNoPos()), + makeBind(toATerm("system"), + makeStr(i->system), makeNoPos()), + makeBind(toATerm("outPath"), + makeStr(i->queryOutPath(state)), makeNoPos())); + if (drvPath != "") as = ATinsert(as, + makeBind(toATerm("drvPath"), + makeStr(drvPath), makeNoPos())); + manifest = ATinsert(manifest, makeAttrs(as)); + inputs = ATinsert(inputs, makeStr(i->queryOutPath(state))); + + /* This is only necessary when installing store paths, e.g., + `nix-env -i /nix/store/abcd...-foo'. */ + store->addTempRoot(i->queryOutPath(state)); + store->ensurePath(i->queryOutPath(state)); + + references.insert(i->queryOutPath(state)); + if (drvPath != "") references.insert(drvPath); + } + + /* Also write a copy of the list of inputs to the store; we need + it for future modifications of the environment. */ + Path manifestFile = store->addTextToStore("env-manifest", + atPrint(makeList(ATreverse(manifest))), references); + + Expr topLevel = makeCall(envBuilder, makeAttrs(ATmakeList3( + makeBind(toATerm("system"), + makeStr(thisSystem), makeNoPos()), + makeBind(toATerm("derivations"), + makeList(ATreverse(inputs)), makeNoPos()), + makeBind(toATerm("manifest"), + makeStr(manifestFile, singleton(manifestFile)), makeNoPos()) + ))); + + /* Instantiate it. */ + debug(format("evaluating builder expression `%1%'") % topLevel); + DrvInfo topLevelDrv; + if (!getDerivation(state, topLevel, topLevelDrv)) + abort(); + + /* Realise the resulting store expression. */ + debug(format("building user environment")); + store->buildDerivations(singleton(topLevelDrv.queryDrvPath(state))); + + /* Switch the current user environment to the output path. */ + debug(format("switching to new user environment")); + Path generation = createGeneration(profile, topLevelDrv.queryOutPath(state)); + switchLink(profile, generation); +} + + +static DrvInfos filterBySelector(EvalState & state, + const DrvInfos & allElems, + const Strings & args, bool newestOnly) +{ + DrvNames selectors = drvNamesFromArgs(args); + + DrvInfos elems; + set done; + + for (DrvNames::iterator i = selectors.begin(); + i != selectors.end(); ++i) + { + typedef list > Matches; + Matches matches; + unsigned int n = 0; + for (DrvInfos::const_iterator j = allElems.begin(); + j != allElems.end(); ++j, ++n) + { + DrvName drvName(j->name); + if (i->matches(drvName)) { + i->hits++; + matches.push_back(std::pair(*j, n)); + } + } + + /* If `newestOnly', if a selector matches multiple derivations + with the same name, pick the one with the highest version. + If there are multiple derivations with the same name *and* + version, then pick the first one. */ + if (newestOnly) { + + /* Map from package names to derivations. */ + typedef map > Newest; + Newest newest; + StringSet multiple; + + for (Matches::iterator j = matches.begin(); j != matches.end(); ++j) { + DrvName drvName(j->first.name); + Newest::iterator k = newest.find(drvName.name); + if (k != newest.end()) { + int d = compareVersions(drvName.version, DrvName(k->second.first.name).version); + if (d > 0) newest[drvName.name] = *j; + else if (d == 0) multiple.insert(j->first.name); + } else + newest[drvName.name] = *j; + } + + matches.clear(); + for (Newest::iterator j = newest.begin(); j != newest.end(); ++j) { + if (multiple.find(j->second.first.name) != multiple.end()) + printMsg(lvlInfo, + format("warning: there are multiple derivations named `%1%'; using the first one") + % j->second.first.name); + matches.push_back(j->second); + } + } + + /* Insert only those elements in the final list that we + haven't inserted before. */ + for (Matches::iterator j = matches.begin(); j != matches.end(); ++j) + if (done.find(j->second) == done.end()) { + done.insert(j->second); + elems.push_back(j->first); + } + } + + /* Check that all selectors have been used. */ + for (DrvNames::iterator i = selectors.begin(); + i != selectors.end(); ++i) + if (i->hits == 0) + throw Error(format("selector `%1%' matches no derivations") + % i->fullName); + + return elems; +} + + +static void queryInstSources(EvalState & state, + const InstallSourceInfo & instSource, const Strings & args, + DrvInfos & elems, bool newestOnly) +{ + InstallSourceType type = instSource.type; + if (type == srcUnknown && args.size() > 0 && args.front()[0] == '/') + type = srcStorePaths; + + switch (type) { + + /* Get the available user environment elements from the + derivations specified in a Nix expression, including only + those with names matching any of the names in `args'. */ + case srcUnknown: + case srcNixExprDrvs: { + + /* Load the derivations from the (default or specified) + Nix expression. */ + DrvInfos allElems; + loadDerivations(state, instSource.nixExprPath, + instSource.systemFilter, instSource.autoArgs, allElems); + + elems = filterBySelector(state, allElems, args, newestOnly); + + break; + } + + /* Get the available user environment elements from the Nix + expressions specified on the command line; these should be + functions that take the default Nix expression file as + argument, e.g., if the file is `./foo.nix', then the + argument `x: x.bar' is equivalent to `(x: x.bar) + (import ./foo.nix)' = `(import ./foo.nix).bar'. */ + case srcNixExprs: { + + + Expr e1 = parseExprFromFile(state, + absPath(instSource.nixExprPath)); + + for (Strings::const_iterator i = args.begin(); + i != args.end(); ++i) + { + Expr e2 = parseExprFromString(state, *i, absPath(".")); + Expr call = makeCall(e2, e1); + getDerivations(state, call, "", instSource.autoArgs, elems); + } + + break; + } + + /* The available user environment elements are specified as a + list of store paths (which may or may not be + derivations). */ + case srcStorePaths: { + + for (Strings::const_iterator i = args.begin(); + i != args.end(); ++i) + { + assertStorePath(*i); + + DrvInfo elem; + elem.attrs = boost::shared_ptr(new ATermMap(0)); /* ugh... */ + string name = baseNameOf(*i); + string::size_type dash = name.find('-'); + if (dash != string::npos) + name = string(name, dash + 1); + + if (isDerivation(*i)) { + elem.setDrvPath(*i); + elem.setOutPath(findOutput(derivationFromPath(*i), "out")); + if (name.size() >= drvExtension.size() && + string(name, name.size() - drvExtension.size()) == drvExtension) + name = string(name, 0, name.size() - drvExtension.size()); + } + else elem.setOutPath(*i); + + elem.name = name; + + elems.push_back(elem); + } + + break; + } + + /* Get the available user environment elements from another + user environment. These are then filtered as in the + `srcNixExprDrvs' case. */ + case srcProfile: { + elems = filterBySelector(state, + queryInstalled(state, instSource.profile), + args, newestOnly); + break; + } + + case srcAttrPath: { + for (Strings::const_iterator i = args.begin(); + i != args.end(); ++i) + getDerivations(state, + findAlongAttrPath(state, *i, instSource.autoArgs, + parseExprFromFile(state, instSource.nixExprPath)), + "", instSource.autoArgs, elems); + break; + } + } +} + + +static void printMissing(EvalState & state, const DrvInfos & elems) +{ + PathSet targets, willBuild, willSubstitute; + for (DrvInfos::const_iterator i = elems.begin(); i != elems.end(); ++i) { + Path drvPath = i->queryDrvPath(state); + if (drvPath != "") + targets.insert(drvPath); + else + targets.insert(i->queryOutPath(state)); + } + + queryMissing(targets, willBuild, willSubstitute); + + if (!willBuild.empty()) { + printMsg(lvlInfo, format("the following derivations will be built:")); + for (PathSet::iterator i = willBuild.begin(); i != willBuild.end(); ++i) + printMsg(lvlInfo, format(" %1%") % *i); + } + + if (!willSubstitute.empty()) { + printMsg(lvlInfo, format("the following paths will be substituted:")); + for (PathSet::iterator i = willSubstitute.begin(); i != willSubstitute.end(); ++i) + printMsg(lvlInfo, format(" %1%") % *i); + } +} + + +static void lockProfile(PathLocks & lock, const Path & profile) +{ + lock.lockPaths(singleton(profile), + (format("waiting for lock on profile `%1%'") % profile).str()); + lock.setDeletion(true); +} + + +static void installDerivations(Globals & globals, + const Strings & args, const Path & profile) +{ + debug(format("installing derivations")); + + /* Get the set of user environment elements to be installed. */ + DrvInfos newElems; + queryInstSources(globals.state, globals.instSource, args, newElems, true); + + StringSet newNames; + for (DrvInfos::iterator i = newElems.begin(); i != newElems.end(); ++i) { + /* `forceName' is a hack to get package names right in some + one-click installs, namely those where the name used in the + path is not the one we want (e.g., `java-front' versus + `java-front-0.9pre15899'). */ + if (globals.forceName != "") + i->name = globals.forceName; + newNames.insert(DrvName(i->name).name); + } + + /* Add in the already installed derivations, unless they have the + same name as a to-be-installed element. */ + PathLocks lock; + lockProfile(lock, profile); + DrvInfos installedElems = queryInstalled(globals.state, profile); + + DrvInfos allElems(newElems); + for (DrvInfos::iterator i = installedElems.begin(); + i != installedElems.end(); ++i) + { + DrvName drvName(i->name); + if (!globals.preserveInstalled && + newNames.find(drvName.name) != newNames.end()) + printMsg(lvlInfo, + format("replacing old `%1%'") % i->name); + else + allElems.push_back(*i); + } + + for (DrvInfos::iterator i = newElems.begin(); i != newElems.end(); ++i) + printMsg(lvlInfo, + format("installing `%1%'") % i->name); + + if (globals.dryRun) { + printMissing(globals.state, newElems); + return; + } + + createUserEnv(globals.state, allElems, + profile, globals.keepDerivations); +} + + +static void opInstall(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + + installDerivations(globals, opArgs, globals.profile); +} + + +typedef enum { utLt, utLeq, utEq, utAlways } UpgradeType; + + +static void upgradeDerivations(Globals & globals, + const Strings & args, const Path & profile, + UpgradeType upgradeType) +{ + debug(format("upgrading derivations")); + + /* Upgrade works as follows: we take all currently installed + derivations, and for any derivation matching any selector, look + for a derivation in the input Nix expression that has the same + name and a higher version number. */ + + /* Load the currently installed derivations. */ + PathLocks lock; + lockProfile(lock, profile); + DrvInfos installedElems = queryInstalled(globals.state, profile); + + /* Fetch all derivations from the input file. */ + DrvInfos availElems; + queryInstSources(globals.state, globals.instSource, args, availElems, false); + + /* Go through all installed derivations. */ + DrvInfos newElems; + for (DrvInfos::iterator i = installedElems.begin(); + i != installedElems.end(); ++i) + { + DrvName drvName(i->name); + + /* Find the derivation in the input Nix expression with the + same name and satisfying the version constraints specified + by upgradeType. If there are multiple matches, take the + one with highest version. */ + DrvInfos::iterator bestElem = availElems.end(); + DrvName bestName; + for (DrvInfos::iterator j = availElems.begin(); + j != availElems.end(); ++j) + { + DrvName newName(j->name); + if (newName.name == drvName.name) { + int d = compareVersions(drvName.version, newName.version); + if (upgradeType == utLt && d < 0 || + upgradeType == utLeq && d <= 0 || + upgradeType == utEq && d == 0 || + upgradeType == utAlways) + { + if ((bestElem == availElems.end() || + compareVersions( + bestName.version, newName.version) < 0)) + { + bestElem = j; + bestName = newName; + } + } + } + } + + if (bestElem != availElems.end() && + i->queryOutPath(globals.state) != + bestElem->queryOutPath(globals.state)) + { + printMsg(lvlInfo, + format("upgrading `%1%' to `%2%'") + % i->name % bestElem->name); + newElems.push_back(*bestElem); + } else newElems.push_back(*i); + } + + if (globals.dryRun) { + printMissing(globals.state, newElems); + return; + } + + createUserEnv(globals.state, newElems, + profile, globals.keepDerivations); +} + + +static void opUpgrade(Globals & globals, + Strings opFlags, Strings opArgs) +{ + UpgradeType upgradeType = utLt; + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--lt") upgradeType = utLt; + else if (*i == "--leq") upgradeType = utLeq; + else if (*i == "--eq") upgradeType = utEq; + else if (*i == "--always") upgradeType = utAlways; + else throw UsageError(format("unknown flag `%1%'") % *i); + + upgradeDerivations(globals, opArgs, globals.profile, upgradeType); +} + + +static void uninstallDerivations(Globals & globals, DrvNames & selectors, + Path & profile) +{ + PathLocks lock; + lockProfile(lock, profile); + DrvInfos installedElems = queryInstalled(globals.state, profile); + DrvInfos newElems; + + for (DrvInfos::iterator i = installedElems.begin(); + i != installedElems.end(); ++i) + { + DrvName drvName(i->name); + bool found = false; + for (DrvNames::iterator j = selectors.begin(); + j != selectors.end(); ++j) + if (j->matches(drvName)) { + printMsg(lvlInfo, + format("uninstalling `%1%'") % i->name); + found = true; + break; + } + if (!found) newElems.push_back(*i); + } + + if (globals.dryRun) return; + + createUserEnv(globals.state, newElems, + profile, globals.keepDerivations); +} + + +static void opUninstall(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + + DrvNames drvNames = drvNamesFromArgs(opArgs); + + uninstallDerivations(globals, drvNames, + globals.profile); +} + + +static bool cmpChars(char a, char b) +{ + return toupper(a) < toupper(b); +} + + +static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b) +{ + return lexicographical_compare( + a.name.begin(), a.name.end(), + b.name.begin(), b.name.end(), cmpChars); +} + + +typedef list Table; + + +void printTable(Table & table) +{ + unsigned int nrColumns = table.size() > 0 ? table.front().size() : 0; + + vector widths; + widths.resize(nrColumns); + + for (Table::iterator i = table.begin(); i != table.end(); ++i) { + assert(i->size() == nrColumns); + Strings::iterator j; + unsigned int column; + for (j = i->begin(), column = 0; j != i->end(); ++j, ++column) + if (j->size() > widths[column]) widths[column] = j->size(); + } + + for (Table::iterator i = table.begin(); i != table.end(); ++i) { + Strings::iterator j; + unsigned int column; + for (j = i->begin(), column = 0; j != i->end(); ++j, ++column) + { + cout << *j; + if (column < nrColumns - 1) + cout << string(widths[column] - j->size() + 2, ' '); + } + cout << std::endl; + } +} + + +/* This function compares the version of a element against the + versions in the given set of elements. `cvLess' means that only + lower versions are in the set, `cvEqual' means that at most an + equal version is in the set, and `cvGreater' means that there is at + least one element with a higher version in the set. `cvUnavail' + means that there are no elements with the same name in the set. */ + +typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff; + +static VersionDiff compareVersionAgainstSet( + const DrvInfo & elem, const DrvInfos & elems, string & version) +{ + DrvName name(elem.name); + + VersionDiff diff = cvUnavail; + version = "?"; + + for (DrvInfos::const_iterator i = elems.begin(); i != elems.end(); ++i) { + DrvName name2(i->name); + if (name.name == name2.name) { + int d = compareVersions(name.version, name2.version); + if (d < 0) { + diff = cvGreater; + version = name2.version; + } + else if (diff != cvGreater && d == 0) { + diff = cvEqual; + version = name2.version; + } + else if (diff != cvGreater && diff != cvEqual && d > 0) { + diff = cvLess; + if (version == "" || compareVersions(version, name2.version) < 0) + version = name2.version; + } + } + } + + return diff; +} + + +static string colorString(const string & s) +{ + if (!isatty(STDOUT_FILENO)) return s; + return "\e[1;31m" + s + "\e[0m"; +} + + +static void opQuery(Globals & globals, + Strings opFlags, Strings opArgs) +{ + typedef vector< map > ResultSet; + + bool printStatus = false; + bool printName = true; + bool printAttrPath = false; + bool printSystem = false; + bool printDrvPath = false; + bool printOutPath = false; + bool printDescription = false; + bool compareVersions = false; + bool xmlOutput = false; + + enum { sInstalled, sAvailable } source = sInstalled; + + readOnlyMode = true; /* makes evaluation a bit faster */ + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--status" || *i == "-s") printStatus = true; + else if (*i == "--no-name") printName = false; + else if (*i == "--system") printSystem = true; + else if (*i == "--description") printDescription = true; + else if (*i == "--compare-versions" || *i == "-c") compareVersions = true; + else if (*i == "--drv-path") printDrvPath = true; + else if (*i == "--out-path") printOutPath = true; + else if (*i == "--installed") source = sInstalled; + else if (*i == "--available" || *i == "-a") source = sAvailable; + else if (*i == "--xml") xmlOutput = true; + else throw UsageError(format("unknown flag `%1%'") % *i); + + if (globals.instSource.type == srcAttrPath) printAttrPath = true; /* hack */ + + if (opArgs.size() == 0) { + printMsg(lvlInfo, "warning: you probably meant to specify the argument '*' to show all packages"); + } + + + /* Obtain derivation information from the specified source. */ + DrvInfos availElems, installedElems; + + if (source == sInstalled || compareVersions || printStatus) { + installedElems = queryInstalled(globals.state, globals.profile); + } + + if (source == sAvailable || compareVersions) { + loadDerivations(globals.state, globals.instSource.nixExprPath, + globals.instSource.systemFilter, globals.instSource.autoArgs, + availElems); + } + + DrvInfos elems = filterBySelector(globals.state, + source == sInstalled ? installedElems : availElems, + opArgs, false); + + DrvInfos & otherElems(source == sInstalled ? availElems : installedElems); + + + /* Sort them by name. */ + /* !!! */ + vector elems2; + for (DrvInfos::iterator i = elems.begin(); i != elems.end(); ++i) + elems2.push_back(*i); + sort(elems2.begin(), elems2.end(), cmpElemByName); + + + /* We only need to know the installed paths when we are querying + the status of the derivation. */ + PathSet installed; /* installed paths */ + + if (printStatus) { + for (DrvInfos::iterator i = installedElems.begin(); + i != installedElems.end(); ++i) + installed.insert(i->queryOutPath(globals.state)); + } + + + /* Print the desired columns, or XML output. */ + Table table; + std::ostringstream dummy; + XMLWriter xml(true, *(xmlOutput ? &cout : &dummy)); + XMLOpenElement xmlRoot(xml, "items"); + + for (vector::iterator i = elems2.begin(); + i != elems2.end(); ++i) + { + try { + + /* For table output. */ + Strings columns; + + /* For XML output. */ + XMLAttrs attrs; + + if (printStatus) { + bool hasSubs = store->hasSubstitutes(i->queryOutPath(globals.state)); + bool isInstalled = installed.find(i->queryOutPath(globals.state)) != installed.end(); + bool isValid = store->isValidPath(i->queryOutPath(globals.state)); + if (xmlOutput) { + attrs["installed"] = isInstalled ? "1" : "0"; + attrs["valid"] = isValid ? "1" : "0"; + attrs["substitutable"] = hasSubs ? "1" : "0"; + } else + columns.push_back( + (string) (isInstalled ? "I" : "-") + + (isValid ? "P" : "-") + + (hasSubs ? "S" : "-")); + } + + if (xmlOutput) + attrs["attrPath"] = i->attrPath; + else if (printAttrPath) + columns.push_back(i->attrPath); + + if (xmlOutput) + attrs["name"] = i->name; + else if (printName) + columns.push_back(i->name); + + if (compareVersions) { + /* Compare this element against the versions of the + same named packages in either the set of available + elements, or the set of installed elements. !!! + This is O(N * M), should be O(N * lg M). */ + string version; + VersionDiff diff = compareVersionAgainstSet(*i, otherElems, version); + + char ch; + switch (diff) { + case cvLess: ch = '>'; break; + case cvEqual: ch = '='; break; + case cvGreater: ch = '<'; break; + case cvUnavail: ch = '-'; break; + default: abort(); + } + + if (xmlOutput) { + if (diff != cvUnavail) { + attrs["versionDiff"] = ch; + attrs["maxComparedVersion"] = version; + } + } else { + string column = (string) "" + ch + " " + version; + if (diff == cvGreater) column = colorString(column); + columns.push_back(column); + } + } + + if (xmlOutput) { + if (i->system != "") attrs["system"] = i->system; + } + else if (printSystem) + columns.push_back(i->system); + + if (printDrvPath) { + string drvPath = i->queryDrvPath(globals.state); + if (xmlOutput) { + if (drvPath != "") attrs["drvPath"] = drvPath; + } else + columns.push_back(drvPath == "" ? "-" : drvPath); + } + + if (printOutPath) { + string outPath = i->queryOutPath(globals.state); + if (xmlOutput) { + if (outPath != "") attrs["outPath"] = outPath; + } else + columns.push_back(outPath); + } + + if (printDescription) { + MetaInfo meta = i->queryMetaInfo(globals.state); + string descr = meta["description"]; + if (xmlOutput) { + if (descr != "") attrs["description"] = descr; + } else + columns.push_back(descr); + } + + if (xmlOutput) + xml.writeEmptyElement("item", attrs); + else + table.push_back(columns); + + cout.flush(); + + } catch (AssertionError & e) { + /* !!! hm, maybe we should give some sort of warning here? */ + } + } + + if (!xmlOutput) printTable(table); +} + + +static void opSwitchProfile(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + if (opArgs.size() != 1) + throw UsageError(format("exactly one argument expected")); + + Path profile = absPath(opArgs.front()); + Path profileLink = getHomeDir() + "/.nix-profile"; + + switchLink(profileLink, profile); +} + + +static const int prevGen = -2; + + +static void switchGeneration(Globals & globals, int dstGen) +{ + PathLocks lock; + lockProfile(lock, globals.profile); + + int curGen; + Generations gens = findGenerations(globals.profile, curGen); + + Generation dst; + for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) + if ((dstGen == prevGen && i->number < curGen) || + (dstGen >= 0 && i->number == dstGen)) + dst = *i; + + if (!dst) + if (dstGen == prevGen) + throw Error(format("no generation older than the current (%1%) exists") + % curGen); + else + throw Error(format("generation %1% does not exist") % dstGen); + + printMsg(lvlInfo, format("switching from generation %1% to %2%") + % curGen % dst.number); + + if (globals.dryRun) return; + + switchLink(globals.profile, dst.path); +} + + +static void opSwitchGeneration(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + if (opArgs.size() != 1) + throw UsageError(format("exactly one argument expected")); + + int dstGen; + if (!string2Int(opArgs.front(), dstGen)) + throw UsageError(format("expected a generation number")); + + switchGeneration(globals, dstGen); +} + + +static void opRollback(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + if (opArgs.size() != 0) + throw UsageError(format("no arguments expected")); + + switchGeneration(globals, prevGen); +} + + +static void opListGenerations(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + if (opArgs.size() != 0) + throw UsageError(format("no arguments expected")); + + PathLocks lock; + lockProfile(lock, globals.profile); + + int curGen; + Generations gens = findGenerations(globals.profile, curGen); + + for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) { + tm t; + if (!localtime_r(&i->creationTime, &t)) throw Error("cannot convert time"); + cout << format("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||\n") + % i->number + % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday + % t.tm_hour % t.tm_min % t.tm_sec + % (i->number == curGen ? "(current)" : ""); + } +} + + +static void deleteGeneration2(const Path & profile, unsigned int gen) +{ + printMsg(lvlInfo, format("removing generation %1%") % gen); + deleteGeneration(profile, gen); +} + + +static void opDeleteGenerations(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + + PathLocks lock; + lockProfile(lock, globals.profile); + + int curGen; + Generations gens = findGenerations(globals.profile, curGen); + + for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) { + + if (*i == "old") { + for (Generations::iterator j = gens.begin(); j != gens.end(); ++j) + if (j->number != curGen) + deleteGeneration2(globals.profile, j->number); + } + + else { + int n; + if (!string2Int(*i, n) || n < 0) + throw UsageError(format("invalid generation specifier `%1%'") % *i); + bool found = false; + for (Generations::iterator j = gens.begin(); j != gens.end(); ++j) { + if (j->number == n) { + deleteGeneration2(globals.profile, j->number); + found = true; + break; + } + } + if (!found) + printMsg(lvlError, format("generation %1% does not exist") % n); + } + } +} + + +static void opDefaultExpr(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + if (opArgs.size() != 1) + throw UsageError(format("exactly one argument expected")); + + Path defNixExpr = absPath(opArgs.front()); + Path defNixExprLink = getDefNixExprPath(); + + switchLink(defNixExprLink, defNixExpr); +} + + +static string needArg(Strings::iterator & i, + Strings & args, const string & arg) +{ + ++i; + if (i == args.end()) throw UsageError( + format("`%1%' requires an argument") % arg); + return *i; +} + + +void run(Strings args) +{ + Strings opFlags, opArgs; + Operation op = 0; + + Globals globals; + + globals.instSource.type = srcUnknown; + globals.instSource.nixExprPath = getDefNixExprPath(); + globals.instSource.systemFilter = thisSystem; + + globals.dryRun = false; + globals.preserveInstalled = false; + + globals.keepDerivations = + queryBoolSetting("env-keep-derivations", false); + + for (Strings::iterator i = args.begin(); i != args.end(); ++i) { + string arg = *i; + + Operation oldOp = op; + + if (arg == "--install" || arg == "-i") + op = opInstall; + else if (arg == "--from-expression" || arg == "-E") + globals.instSource.type = srcNixExprs; + else if (arg == "--from-profile") { + globals.instSource.type = srcProfile; + globals.instSource.profile = needArg(i, args, arg); + } + else if (arg == "--attr" || arg == "-A") + globals.instSource.type = srcAttrPath; + else if (arg == "--arg") { /* !!! code duplication from nix-instantiate */ + i++; + if (i == args.end()) + throw UsageError("`--arg' requires two arguments"); + string name = *i++; + if (i == args.end()) + throw UsageError("`--arg' requires two arguments"); + Expr value = parseExprFromString(globals.state, *i, absPath(".")); + globals.instSource.autoArgs.set(toATerm(name), value); + } + else if (arg == "--force-name") // undocumented flag for nix-install-package + globals.forceName = needArg(i, args, arg); + else if (arg == "--uninstall" || arg == "-e") + op = opUninstall; + else if (arg == "--upgrade" || arg == "-u") + op = opUpgrade; + else if (arg == "--query" || arg == "-q") + op = opQuery; + else if (arg == "--import" || arg == "-I") /* !!! bad name */ + op = opDefaultExpr; + else if (arg == "--profile" || arg == "-p") { + globals.profile = absPath(needArg(i, args, arg)); + } + else if (arg == "--file" || arg == "-f") { + globals.instSource.nixExprPath = absPath(needArg(i, args, arg)); + } + else if (arg == "--switch-profile" || arg == "-S") + op = opSwitchProfile; + else if (arg == "--switch-generation" || arg == "-G") + op = opSwitchGeneration; + else if (arg == "--rollback") + op = opRollback; + else if (arg == "--list-generations") + op = opListGenerations; + else if (arg == "--delete-generations") + op = opDeleteGenerations; + else if (arg == "--dry-run") { + printMsg(lvlInfo, "(dry run; not doing anything)"); + globals.dryRun = true; + } + else if (arg == "--preserve-installed" || arg == "-P") + globals.preserveInstalled = true; + else if (arg == "--system-filter") { + globals.instSource.systemFilter = needArg(i, args, arg); + } + else if (arg[0] == '-') + opFlags.push_back(arg); + else + opArgs.push_back(arg); + + if (oldOp && oldOp != op) + throw UsageError("only one operation may be specified"); + } + + if (!op) throw UsageError("no operation specified"); + + if (globals.profile == "") { + Path profileLink = getHomeDir() + "/.nix-profile"; + globals.profile = pathExists(profileLink) + ? absPath(readLink(profileLink), dirOf(profileLink)) + : canonPath(nixStateDir + "/profiles/default"); + } + + store = openStore(); + + op(globals, opFlags, opArgs); + + printEvalStats(globals.state); +} + + +string programId = "nix-env"; diff --git a/src/nix-instantiate/Makefile.am b/src/nix-instantiate/Makefile.am index d1a87986f53d..d4f8b4ccaeff 100644 --- a/src/nix-instantiate/Makefile.am +++ b/src/nix-instantiate/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix-instantiate -nix_instantiate_SOURCES = main.cc help.txt +nix_instantiate_SOURCES = nix-instantiate.cc help.txt nix_instantiate_LDADD = ../libmain/libmain.la ../libexpr/libexpr.la \ ../libstore/libstore.la ../libutil/libutil.la \ ../boost/format/libformat.la ${bdb_lib} ${aterm_lib} diff --git a/src/nix-instantiate/main.cc b/src/nix-instantiate/main.cc deleted file mode 100644 index ffd8a2e71f40..000000000000 --- a/src/nix-instantiate/main.cc +++ /dev/null @@ -1,159 +0,0 @@ -#include -#include - -#include "globals.hh" -#include "shared.hh" -#include "eval.hh" -#include "parser.hh" -#include "get-drvs.hh" -#include "attr-path.hh" -#include "expr-to-xml.hh" -#include "util.hh" -#include "store-api.hh" -#include "help.txt.hh" - - -using namespace nix; - - -void printHelp() -{ - std::cout << string((char *) helpText, sizeof helpText); -} - - -static Expr parseStdin(EvalState & state) -{ - startNest(nest, lvlTalkative, format("parsing standard input")); - string s, s2; - while (getline(std::cin, s2)) s += s2 + "\n"; - return parseExprFromString(state, s, absPath(".")); -} - - -static Path gcRoot; -static int rootNr = 0; -static bool indirectRoot = false; - - -static void printResult(EvalState & state, Expr e, - bool evalOnly, bool xmlOutput, const ATermMap & autoArgs) -{ - PathSet context; - - if (evalOnly) - if (xmlOutput) - printTermAsXML(e, std::cout, context); - else - std::cout << format("%1%\n") % e; - - else { - DrvInfos drvs; - getDerivations(state, e, "", autoArgs, drvs); - for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) { - Path drvPath = i->queryDrvPath(state); - if (gcRoot == "") - printGCWarning(); - else - drvPath = addPermRoot(drvPath, - makeRootName(gcRoot, rootNr), - indirectRoot); - std::cout << format("%1%\n") % drvPath; - } - } -} - - -Expr doEval(EvalState & state, string attrPath, bool parseOnly, bool strict, - const ATermMap & autoArgs, Expr e) -{ - e = findAlongAttrPath(state, attrPath, autoArgs, e); - if (!parseOnly) - if (strict) - e = strictEvalExpr(state, e); - else - e = evalExpr(state, e); - return e; -} - - -void run(Strings args) -{ - EvalState state; - Strings files; - bool readStdin = false; - bool evalOnly = false; - bool parseOnly = false; - bool xmlOutput = false; - bool strict = false; - string attrPath; - ATermMap autoArgs(128); - - for (Strings::iterator i = args.begin(); - i != args.end(); ) - { - string arg = *i++; - - if (arg == "-") - readStdin = true; - else if (arg == "--eval-only") { - readOnlyMode = true; - evalOnly = true; - } - else if (arg == "--parse-only") { - readOnlyMode = true; - parseOnly = evalOnly = true; - } - else if (arg == "--attr" || arg == "-A") { - if (i == args.end()) - throw UsageError("`--attr' requires an argument"); - attrPath = *i++; - } - else if (arg == "--arg") { - if (i == args.end()) - throw UsageError("`--arg' requires two arguments"); - string name = *i++; - if (i == args.end()) - throw UsageError("`--arg' requires two arguments"); - Expr value = parseExprFromString(state, *i++, absPath(".")); - autoArgs.set(toATerm(name), value); - } - else if (arg == "--add-root") { - if (i == args.end()) - throw UsageError("`--add-root' requires an argument"); - gcRoot = absPath(*i++); - } - else if (arg == "--indirect") - indirectRoot = true; - else if (arg == "--xml") - xmlOutput = true; - else if (arg == "--strict") - strict = true; - else if (arg[0] == '-') - throw UsageError(format("unknown flag `%1%'") % arg); - else - files.push_back(arg); - } - - store = openStore(); - - if (readStdin) { - Expr e = parseStdin(state); - e = doEval(state, attrPath, parseOnly, strict, autoArgs, e); - printResult(state, e, evalOnly, xmlOutput, autoArgs); - } - - for (Strings::iterator i = files.begin(); - i != files.end(); i++) - { - Path path = absPath(*i); - Expr e = parseExprFromFile(state, path); - e = doEval(state, attrPath, parseOnly, strict, autoArgs, e); - printResult(state, e, evalOnly, xmlOutput, autoArgs); - } - - printEvalStats(state); -} - - -string programId = "nix-instantiate"; diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc new file mode 100644 index 000000000000..ffd8a2e71f40 --- /dev/null +++ b/src/nix-instantiate/nix-instantiate.cc @@ -0,0 +1,159 @@ +#include +#include + +#include "globals.hh" +#include "shared.hh" +#include "eval.hh" +#include "parser.hh" +#include "get-drvs.hh" +#include "attr-path.hh" +#include "expr-to-xml.hh" +#include "util.hh" +#include "store-api.hh" +#include "help.txt.hh" + + +using namespace nix; + + +void printHelp() +{ + std::cout << string((char *) helpText, sizeof helpText); +} + + +static Expr parseStdin(EvalState & state) +{ + startNest(nest, lvlTalkative, format("parsing standard input")); + string s, s2; + while (getline(std::cin, s2)) s += s2 + "\n"; + return parseExprFromString(state, s, absPath(".")); +} + + +static Path gcRoot; +static int rootNr = 0; +static bool indirectRoot = false; + + +static void printResult(EvalState & state, Expr e, + bool evalOnly, bool xmlOutput, const ATermMap & autoArgs) +{ + PathSet context; + + if (evalOnly) + if (xmlOutput) + printTermAsXML(e, std::cout, context); + else + std::cout << format("%1%\n") % e; + + else { + DrvInfos drvs; + getDerivations(state, e, "", autoArgs, drvs); + for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) { + Path drvPath = i->queryDrvPath(state); + if (gcRoot == "") + printGCWarning(); + else + drvPath = addPermRoot(drvPath, + makeRootName(gcRoot, rootNr), + indirectRoot); + std::cout << format("%1%\n") % drvPath; + } + } +} + + +Expr doEval(EvalState & state, string attrPath, bool parseOnly, bool strict, + const ATermMap & autoArgs, Expr e) +{ + e = findAlongAttrPath(state, attrPath, autoArgs, e); + if (!parseOnly) + if (strict) + e = strictEvalExpr(state, e); + else + e = evalExpr(state, e); + return e; +} + + +void run(Strings args) +{ + EvalState state; + Strings files; + bool readStdin = false; + bool evalOnly = false; + bool parseOnly = false; + bool xmlOutput = false; + bool strict = false; + string attrPath; + ATermMap autoArgs(128); + + for (Strings::iterator i = args.begin(); + i != args.end(); ) + { + string arg = *i++; + + if (arg == "-") + readStdin = true; + else if (arg == "--eval-only") { + readOnlyMode = true; + evalOnly = true; + } + else if (arg == "--parse-only") { + readOnlyMode = true; + parseOnly = evalOnly = true; + } + else if (arg == "--attr" || arg == "-A") { + if (i == args.end()) + throw UsageError("`--attr' requires an argument"); + attrPath = *i++; + } + else if (arg == "--arg") { + if (i == args.end()) + throw UsageError("`--arg' requires two arguments"); + string name = *i++; + if (i == args.end()) + throw UsageError("`--arg' requires two arguments"); + Expr value = parseExprFromString(state, *i++, absPath(".")); + autoArgs.set(toATerm(name), value); + } + else if (arg == "--add-root") { + if (i == args.end()) + throw UsageError("`--add-root' requires an argument"); + gcRoot = absPath(*i++); + } + else if (arg == "--indirect") + indirectRoot = true; + else if (arg == "--xml") + xmlOutput = true; + else if (arg == "--strict") + strict = true; + else if (arg[0] == '-') + throw UsageError(format("unknown flag `%1%'") % arg); + else + files.push_back(arg); + } + + store = openStore(); + + if (readStdin) { + Expr e = parseStdin(state); + e = doEval(state, attrPath, parseOnly, strict, autoArgs, e); + printResult(state, e, evalOnly, xmlOutput, autoArgs); + } + + for (Strings::iterator i = files.begin(); + i != files.end(); i++) + { + Path path = absPath(*i); + Expr e = parseExprFromFile(state, path); + e = doEval(state, attrPath, parseOnly, strict, autoArgs, e); + printResult(state, e, evalOnly, xmlOutput, autoArgs); + } + + printEvalStats(state); +} + + +string programId = "nix-instantiate"; diff --git a/src/nix-setuid-helper/Makefile.am b/src/nix-setuid-helper/Makefile.am index afff2bde7c13..a0fbdf39d61b 100644 --- a/src/nix-setuid-helper/Makefile.am +++ b/src/nix-setuid-helper/Makefile.am @@ -1,6 +1,6 @@ libexec_PROGRAMS = nix-setuid-helper -nix_setuid_helper_SOURCES = main.cc +nix_setuid_helper_SOURCES = nix-setuid-helper.cc nix_setuid_helper_LDADD = ../libutil/libutil.la \ ../boost/format/libformat.la ${aterm_lib} diff --git a/src/nix-setuid-helper/main.cc b/src/nix-setuid-helper/main.cc deleted file mode 100644 index 168cff40a3f6..000000000000 --- a/src/nix-setuid-helper/main.cc +++ /dev/null @@ -1,256 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include "config.h" -#include "util.hh" - -using namespace nix; - - -extern char * * environ; - - -/* Recursively change the ownership of `path' to user `uidTo' and - group `gidTo'. `path' must currently be owned by user `uidFrom', - or, if `uidFrom' is -1, by group `gidFrom'. */ -static void secureChown(uid_t uidFrom, gid_t gidFrom, - uid_t uidTo, gid_t gidTo, const Path & path) -{ - format error = format("cannot change ownership of `%1%'") % path; - - struct stat st; - if (lstat(path.c_str(), &st) == -1) - /* Important: don't give any detailed error messages here. - Otherwise, the Nix account can discover information about - the existence of paths that it doesn't normally have access - to. */ - throw Error(error); - - if (uidFrom != -1) { - assert(uidFrom != 0); - if (st.st_uid != uidFrom) - throw Error(error); - } else { - assert(gidFrom != 0); - if (st.st_gid != gidFrom) - throw Error(error); - } - - assert(uidTo != 0 && gidTo != 0); - -#if HAVE_LCHOWN - if (lchown(path.c_str(), uidTo, gidTo) == -1) - throw Error(error); -#else - if (!S_ISLNK(st.st_mode) && - chown(path.c_str(), uidTo, gidTo) == -1) - throw Error(error); -#endif - - if (S_ISDIR(st.st_mode)) { - Strings names = readDirectory(path); - for (Strings::iterator i = names.begin(); i != names.end(); ++i) - /* !!! recursion; check stack depth */ - secureChown(uidFrom, gidFrom, uidTo, gidTo, path + "/" + *i); - } -} - - -static uid_t nameToUid(const string & userName) -{ - struct passwd * pw = getpwnam(userName.c_str()); - if (!pw) - throw Error(format("user `%1%' does not exist") % userName); - return pw->pw_uid; -} - - -static void checkIfBuildUser(const StringSet & buildUsers, - const string & userName) -{ - if (buildUsers.find(userName) == buildUsers.end()) - throw Error(format("user `%1%' is not a member of the build users group") - % userName); -} - - -/* Run `program' under user account `targetUser'. `targetUser' should - be a member of `buildUsersGroup'. The ownership of the current - directory is changed from the Nix user (uidNix) to the target - user. */ -static void runBuilder(uid_t uidNix, gid_t gidBuildUsers, - const StringSet & buildUsers, const string & targetUser, - string program, int argc, char * * argv, char * * env) -{ - uid_t uidTargetUser = nameToUid(targetUser); - - /* Sanity check. */ - if (uidTargetUser == 0) - throw Error("won't setuid to root"); - - /* Verify that the target user is a member of the build users - group. */ - checkIfBuildUser(buildUsers, targetUser); - - /* Chown the current directory, *if* it is owned by the Nix - account. The idea is that the current directory is the - temporary build directory in /tmp or somewhere else, and we - don't want to create that directory here. */ - secureChown(uidNix, -1, uidTargetUser, gidBuildUsers, "."); - - /* Set the real, effective and saved gid. Must be done before - setuid(), otherwise it won't set the real and saved gids. */ - if (setgroups(0, 0) == -1) - throw SysError("cannot clear the set of supplementary groups"); - - if (setgid(gidBuildUsers) == -1 || - getgid() != gidBuildUsers || - getegid() != gidBuildUsers) - throw SysError("setgid failed"); - - /* Set the real, effective and saved uid. */ - if (setuid(uidTargetUser) == -1 || - getuid() != uidTargetUser || - geteuid() != uidTargetUser) - throw SysError("setuid failed"); - - /* Execute the program. */ - std::vector args; - for (int i = 0; i < argc; ++i) - args.push_back(argv[i]); - args.push_back(0); - - if (execve(program.c_str(), (char * *) &args[0], env) == -1) - throw SysError(format("cannot execute `%1%'") % program); -} - - -void killBuildUser(gid_t gidBuildUsers, - const StringSet & buildUsers, const string & userName) -{ - uid_t uid = nameToUid(userName); - - /* Verify that the user whose processes we are to kill is a member - of the build users group. */ - checkIfBuildUser(buildUsers, userName); - - assert(uid != 0); - - killUser(uid); -} - - -#ifndef NIX_SETUID_CONFIG_FILE -#define NIX_SETUID_CONFIG_FILE "/etc/nix-setuid.conf" -#endif - - -static void run(int argc, char * * argv) -{ - char * * oldEnviron = environ; - - setuidCleanup(); - - if (geteuid() != 0) - throw Error("nix-setuid-wrapper must be setuid root"); - - - /* Read the configuration file. It should consist of two words: - - - - The first is the privileged account under which the main Nix - processes run (i.e., the supposed caller). It should match our - real uid. The second is the Unix group to which the Nix - builders belong (and nothing else!). */ - string configFile = NIX_SETUID_CONFIG_FILE; - AutoCloseFD fdConfig = open(configFile.c_str(), O_RDONLY); - if (fdConfig == -1) - throw SysError(format("opening `%1%'") % configFile); - - /* Config file should be owned by root. */ - struct stat st; - if (fstat(fdConfig, &st) == -1) throw SysError("statting file"); - if (st.st_uid != 0) - throw Error(format("`%1%' not owned by root") % configFile); - if (st.st_mode & (S_IWGRP | S_IWOTH)) - throw Error(format("`%1%' should not be group or world-writable") % configFile); - - Strings tokens = tokenizeString(readFile(fdConfig)); - - fdConfig.close(); - - if (tokens.size() != 2) - throw Error(format("parse error in `%1%'") % configFile); - - Strings::iterator i = tokens.begin(); - string nixUser = *i++; - string buildUsersGroup = *i++; - - - /* Check that the caller (real uid) is the one allowed to call - this program. */ - uid_t uidNix = nameToUid(nixUser); - if (uidNix != getuid()) - throw Error("you are not allowed to call this program, go away"); - - - /* Get the gid and members of buildUsersGroup. */ - struct group * gr = getgrnam(buildUsersGroup.c_str()); - if (!gr) - throw Error(format("group `%1%' does not exist") % buildUsersGroup); - gid_t gidBuildUsers = gr->gr_gid; - - StringSet buildUsers; - for (char * * p = gr->gr_mem; *p; ++p) - buildUsers.insert(*p); - - - /* Perform the desired command. */ - if (argc < 2) - throw Error("invalid arguments"); - - string command(argv[1]); - - if (command == "run-builder") { - /* Syntax: nix-setuid-helper run-builder - */ - if (argc < 4) throw Error("missing user name / program name"); - runBuilder(uidNix, gidBuildUsers, buildUsers, - argv[2], argv[3], argc - 4, argv + 4, oldEnviron); - } - - else if (command == "get-ownership") { - /* Syntax: nix-setuid-helper get-ownership */ - if (argc != 3) throw Error("missing path"); - secureChown(-1, gidBuildUsers, uidNix, gidBuildUsers, argv[2]); - } - - else if (command == "kill") { - /* Syntax: nix-setuid-helper kill */ - if (argc != 3) throw Error("missing user name"); - killBuildUser(gidBuildUsers, buildUsers, argv[2]); - } - - else throw Error ("invalid command"); -} - - -int main(int argc, char * * argv) -{ - try { - run(argc, argv); - } catch (Error & e) { - std::cerr << e.msg() << std::endl; - return 1; - } -} diff --git a/src/nix-setuid-helper/nix-setuid-helper.cc b/src/nix-setuid-helper/nix-setuid-helper.cc new file mode 100644 index 000000000000..168cff40a3f6 --- /dev/null +++ b/src/nix-setuid-helper/nix-setuid-helper.cc @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "config.h" +#include "util.hh" + +using namespace nix; + + +extern char * * environ; + + +/* Recursively change the ownership of `path' to user `uidTo' and + group `gidTo'. `path' must currently be owned by user `uidFrom', + or, if `uidFrom' is -1, by group `gidFrom'. */ +static void secureChown(uid_t uidFrom, gid_t gidFrom, + uid_t uidTo, gid_t gidTo, const Path & path) +{ + format error = format("cannot change ownership of `%1%'") % path; + + struct stat st; + if (lstat(path.c_str(), &st) == -1) + /* Important: don't give any detailed error messages here. + Otherwise, the Nix account can discover information about + the existence of paths that it doesn't normally have access + to. */ + throw Error(error); + + if (uidFrom != -1) { + assert(uidFrom != 0); + if (st.st_uid != uidFrom) + throw Error(error); + } else { + assert(gidFrom != 0); + if (st.st_gid != gidFrom) + throw Error(error); + } + + assert(uidTo != 0 && gidTo != 0); + +#if HAVE_LCHOWN + if (lchown(path.c_str(), uidTo, gidTo) == -1) + throw Error(error); +#else + if (!S_ISLNK(st.st_mode) && + chown(path.c_str(), uidTo, gidTo) == -1) + throw Error(error); +#endif + + if (S_ISDIR(st.st_mode)) { + Strings names = readDirectory(path); + for (Strings::iterator i = names.begin(); i != names.end(); ++i) + /* !!! recursion; check stack depth */ + secureChown(uidFrom, gidFrom, uidTo, gidTo, path + "/" + *i); + } +} + + +static uid_t nameToUid(const string & userName) +{ + struct passwd * pw = getpwnam(userName.c_str()); + if (!pw) + throw Error(format("user `%1%' does not exist") % userName); + return pw->pw_uid; +} + + +static void checkIfBuildUser(const StringSet & buildUsers, + const string & userName) +{ + if (buildUsers.find(userName) == buildUsers.end()) + throw Error(format("user `%1%' is not a member of the build users group") + % userName); +} + + +/* Run `program' under user account `targetUser'. `targetUser' should + be a member of `buildUsersGroup'. The ownership of the current + directory is changed from the Nix user (uidNix) to the target + user. */ +static void runBuilder(uid_t uidNix, gid_t gidBuildUsers, + const StringSet & buildUsers, const string & targetUser, + string program, int argc, char * * argv, char * * env) +{ + uid_t uidTargetUser = nameToUid(targetUser); + + /* Sanity check. */ + if (uidTargetUser == 0) + throw Error("won't setuid to root"); + + /* Verify that the target user is a member of the build users + group. */ + checkIfBuildUser(buildUsers, targetUser); + + /* Chown the current directory, *if* it is owned by the Nix + account. The idea is that the current directory is the + temporary build directory in /tmp or somewhere else, and we + don't want to create that directory here. */ + secureChown(uidNix, -1, uidTargetUser, gidBuildUsers, "."); + + /* Set the real, effective and saved gid. Must be done before + setuid(), otherwise it won't set the real and saved gids. */ + if (setgroups(0, 0) == -1) + throw SysError("cannot clear the set of supplementary groups"); + + if (setgid(gidBuildUsers) == -1 || + getgid() != gidBuildUsers || + getegid() != gidBuildUsers) + throw SysError("setgid failed"); + + /* Set the real, effective and saved uid. */ + if (setuid(uidTargetUser) == -1 || + getuid() != uidTargetUser || + geteuid() != uidTargetUser) + throw SysError("setuid failed"); + + /* Execute the program. */ + std::vector args; + for (int i = 0; i < argc; ++i) + args.push_back(argv[i]); + args.push_back(0); + + if (execve(program.c_str(), (char * *) &args[0], env) == -1) + throw SysError(format("cannot execute `%1%'") % program); +} + + +void killBuildUser(gid_t gidBuildUsers, + const StringSet & buildUsers, const string & userName) +{ + uid_t uid = nameToUid(userName); + + /* Verify that the user whose processes we are to kill is a member + of the build users group. */ + checkIfBuildUser(buildUsers, userName); + + assert(uid != 0); + + killUser(uid); +} + + +#ifndef NIX_SETUID_CONFIG_FILE +#define NIX_SETUID_CONFIG_FILE "/etc/nix-setuid.conf" +#endif + + +static void run(int argc, char * * argv) +{ + char * * oldEnviron = environ; + + setuidCleanup(); + + if (geteuid() != 0) + throw Error("nix-setuid-wrapper must be setuid root"); + + + /* Read the configuration file. It should consist of two words: + + + + The first is the privileged account under which the main Nix + processes run (i.e., the supposed caller). It should match our + real uid. The second is the Unix group to which the Nix + builders belong (and nothing else!). */ + string configFile = NIX_SETUID_CONFIG_FILE; + AutoCloseFD fdConfig = open(configFile.c_str(), O_RDONLY); + if (fdConfig == -1) + throw SysError(format("opening `%1%'") % configFile); + + /* Config file should be owned by root. */ + struct stat st; + if (fstat(fdConfig, &st) == -1) throw SysError("statting file"); + if (st.st_uid != 0) + throw Error(format("`%1%' not owned by root") % configFile); + if (st.st_mode & (S_IWGRP | S_IWOTH)) + throw Error(format("`%1%' should not be group or world-writable") % configFile); + + Strings tokens = tokenizeString(readFile(fdConfig)); + + fdConfig.close(); + + if (tokens.size() != 2) + throw Error(format("parse error in `%1%'") % configFile); + + Strings::iterator i = tokens.begin(); + string nixUser = *i++; + string buildUsersGroup = *i++; + + + /* Check that the caller (real uid) is the one allowed to call + this program. */ + uid_t uidNix = nameToUid(nixUser); + if (uidNix != getuid()) + throw Error("you are not allowed to call this program, go away"); + + + /* Get the gid and members of buildUsersGroup. */ + struct group * gr = getgrnam(buildUsersGroup.c_str()); + if (!gr) + throw Error(format("group `%1%' does not exist") % buildUsersGroup); + gid_t gidBuildUsers = gr->gr_gid; + + StringSet buildUsers; + for (char * * p = gr->gr_mem; *p; ++p) + buildUsers.insert(*p); + + + /* Perform the desired command. */ + if (argc < 2) + throw Error("invalid arguments"); + + string command(argv[1]); + + if (command == "run-builder") { + /* Syntax: nix-setuid-helper run-builder + */ + if (argc < 4) throw Error("missing user name / program name"); + runBuilder(uidNix, gidBuildUsers, buildUsers, + argv[2], argv[3], argc - 4, argv + 4, oldEnviron); + } + + else if (command == "get-ownership") { + /* Syntax: nix-setuid-helper get-ownership */ + if (argc != 3) throw Error("missing path"); + secureChown(-1, gidBuildUsers, uidNix, gidBuildUsers, argv[2]); + } + + else if (command == "kill") { + /* Syntax: nix-setuid-helper kill */ + if (argc != 3) throw Error("missing user name"); + killBuildUser(gidBuildUsers, buildUsers, argv[2]); + } + + else throw Error ("invalid command"); +} + + +int main(int argc, char * * argv) +{ + try { + run(argc, argv); + } catch (Error & e) { + std::cerr << e.msg() << std::endl; + return 1; + } +} diff --git a/src/nix-store/Makefile.am b/src/nix-store/Makefile.am index 83fa24fca159..b96b8dc36293 100644 --- a/src/nix-store/Makefile.am +++ b/src/nix-store/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix-store -nix_store_SOURCES = main.cc dotgraph.cc dotgraph.hh help.txt +nix_store_SOURCES = nix-store.cc dotgraph.cc dotgraph.hh help.txt nix_store_LDADD = ../libmain/libmain.la ../libstore/libstore.la ../libutil/libutil.la \ ../boost/format/libformat.la ${bdb_lib} ${aterm_lib} diff --git a/src/nix-store/main.cc b/src/nix-store/main.cc deleted file mode 100644 index 701e8397427b..000000000000 --- a/src/nix-store/main.cc +++ /dev/null @@ -1,731 +0,0 @@ -#include -#include - -#include "globals.hh" -#include "misc.hh" -#include "archive.hh" -#include "shared.hh" -#include "dotgraph.hh" -#include "local-store.hh" -#include "db.hh" -#include "util.hh" -#include "help.txt.hh" - - -using namespace nix; -using std::cin; -using std::cout; - - -typedef void (* Operation) (Strings opFlags, Strings opArgs); - - -void printHelp() -{ - cout << string((char *) helpText, sizeof helpText); -} - - -static Path gcRoot; -static int rootNr = 0; -static bool indirectRoot = false; - - -static Path fixPath(Path path) -{ - path = absPath(path); - while (!isInStore(path)) { - if (!isLink(path)) break; - string target = readLink(path); - path = absPath(target, dirOf(path)); - } - return toStorePath(path); -} - - -static Path useDeriver(Path path) -{ - if (!isDerivation(path)) { - path = queryDeriver(noTxn, path); - if (path == "") - throw Error(format("deriver of path `%1%' is not known") % path); - } - return path; -} - - -/* Realisation the given path. For a derivation that means build it; - for other paths it means ensure their validity. */ -static Path realisePath(const Path & path) -{ - if (isDerivation(path)) { - PathSet paths; - paths.insert(path); - store->buildDerivations(paths); - Path outPath = findOutput(derivationFromPath(path), "out"); - - if (gcRoot == "") - printGCWarning(); - else - outPath = addPermRoot(outPath, - makeRootName(gcRoot, rootNr), - indirectRoot); - - return outPath; - } else { - store->ensurePath(path); - return path; - } -} - - -/* Realise the given paths. */ -static void opRealise(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - *i = fixPath(*i); - - if (opArgs.size() > 1) { - PathSet drvPaths; - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - if (isDerivation(*i)) - drvPaths.insert(*i); - store->buildDerivations(drvPaths); - } - - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - cout << format("%1%\n") % realisePath(*i); -} - - -/* Add files to the Nix store and print the resulting paths. */ -static void opAdd(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - - for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) - cout << format("%1%\n") % store->addToStore(*i); -} - - -/* Preload the output of a fixed-output derivation into the Nix - store. */ -static void opAddFixed(Strings opFlags, Strings opArgs) -{ - bool recursive = false; - - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--recursive") recursive = true; - else throw UsageError(format("unknown flag `%1%'") % *i); - - if (opArgs.empty()) - throw UsageError("first argument must be hash algorithm"); - - string hashAlgo = opArgs.front(); - opArgs.pop_front(); - - for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) - cout << format("%1%\n") % store->addToStore(*i, true, recursive, hashAlgo); -} - - -static Hash parseHash16or32(HashType ht, const string & s) -{ - return s.size() == Hash(ht).hashSize * 2 - ? parseHash(ht, s) - : parseHash32(ht, s); -} - - -/* Hack to support caching in `nix-prefetch-url'. */ -static void opPrintFixedPath(Strings opFlags, Strings opArgs) -{ - bool recursive = false; - - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--recursive") recursive = true; - else throw UsageError(format("unknown flag `%1%'") % *i); - - Strings::iterator i = opArgs.begin(); - string hashAlgo = *i++; - string hash = *i++; - string name = *i++; - - cout << format("%1%\n") % - makeFixedOutputPath(recursive, hashAlgo, - parseHash16or32(parseHashType(hashAlgo), hash), name); -} - - -/* Place in `paths' the set of paths that are required to `realise' - the given store path, i.e., all paths necessary for valid - deployment of the path. For a derivation, this is the union of - requisites of the inputs, plus the derivation; for other store - paths, it is the set of paths in the FS closure of the path. If - `includeOutputs' is true, include the requisites of the output - paths of derivations as well. - - Note that this function can be used to implement three different - deployment policies: - - - Source deployment (when called on a derivation). - - Binary deployment (when called on an output path). - - Source/binary deployment (when called on a derivation with - `includeOutputs' set to true). -*/ -static void storePathRequisites(const Path & storePath, - bool includeOutputs, PathSet & paths) -{ - computeFSClosure(storePath, paths); - - if (includeOutputs) { - for (PathSet::iterator i = paths.begin(); - i != paths.end(); ++i) - if (isDerivation(*i)) { - Derivation drv = derivationFromPath(*i); - for (DerivationOutputs::iterator j = drv.outputs.begin(); - j != drv.outputs.end(); ++j) - if (store->isValidPath(j->second.path)) - computeFSClosure(j->second.path, paths); - } - } -} - - -static Path maybeUseOutput(const Path & storePath, bool useOutput, bool forceRealise) -{ - if (forceRealise) realisePath(storePath); - if (useOutput && isDerivation(storePath)) { - Derivation drv = derivationFromPath(storePath); - return findOutput(drv, "out"); - } - else return storePath; -} - - -static void printPathSet(const PathSet & paths) -{ - for (PathSet::iterator i = paths.begin(); - i != paths.end(); ++i) - cout << format("%s\n") % *i; -} - - -/* Some code to print a tree representation of a derivation dependency - graph. Topological sorting is used to keep the tree relatively - flat. */ - -const string treeConn = "+---"; -const string treeLine = "| "; -const string treeNull = " "; - - -static void dfsVisit(const PathSet & paths, const Path & path, - PathSet & visited, Paths & sorted) -{ - if (visited.find(path) != visited.end()) return; - visited.insert(path); - - PathSet closure; - computeFSClosure(path, closure); - - for (PathSet::iterator i = closure.begin(); - i != closure.end(); ++i) - if (*i != path && paths.find(*i) != paths.end()) - dfsVisit(paths, *i, visited, sorted); - - sorted.push_front(path); -} - - -static Paths topoSort(const PathSet & paths) -{ - Paths sorted; - PathSet visited; - for (PathSet::const_iterator i = paths.begin(); i != paths.end(); ++i) - dfsVisit(paths, *i, visited, sorted); - return sorted; -} - - -static void printTree(const Path & path, - const string & firstPad, const string & tailPad, PathSet & done) -{ - if (done.find(path) != done.end()) { - cout << format("%1%%2% [...]\n") % firstPad % path; - return; - } - done.insert(path); - - cout << format("%1%%2%\n") % firstPad % path; - - PathSet references; - store->queryReferences(path, references); - -#if 0 - for (PathSet::iterator i = drv.inputSrcs.begin(); - i != drv.inputSrcs.end(); ++i) - cout << format("%1%%2%\n") % (tailPad + treeConn) % *i; -#endif - - /* Topologically sort under the relation A < B iff A \in - closure(B). That is, if derivation A is an (possibly indirect) - input of B, then A is printed first. This has the effect of - flattening the tree, preventing deeply nested structures. */ - Paths sorted = topoSort(references); - reverse(sorted.begin(), sorted.end()); - - for (Paths::iterator i = sorted.begin(); i != sorted.end(); ++i) { - Paths::iterator j = i; ++j; - printTree(*i, tailPad + treeConn, - j == sorted.end() ? tailPad + treeNull : tailPad + treeLine, - done); - } -} - - -/* Perform various sorts of queries. */ -static void opQuery(Strings opFlags, Strings opArgs) -{ - enum { qOutputs, qRequisites, qReferences, qReferrers - , qReferrersClosure, qDeriver, qBinding, qHash - , qTree, qGraph } query = qOutputs; - bool useOutput = false; - bool includeOutputs = false; - bool forceRealise = false; - string bindingName; - - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--outputs") query = qOutputs; - else if (*i == "--requisites" || *i == "-R") query = qRequisites; - else if (*i == "--references") query = qReferences; - else if (*i == "--referrers" || *i == "--referers") query = qReferrers; - else if (*i == "--referrers-closure" || *i == "--referers-closure") query = qReferrersClosure; - else if (*i == "--deriver" || *i == "-d") query = qDeriver; - else if (*i == "--binding" || *i == "-b") { - if (opArgs.size() == 0) - throw UsageError("expected binding name"); - bindingName = opArgs.front(); - opArgs.pop_front(); - query = qBinding; - } - else if (*i == "--hash") query = qHash; - else if (*i == "--tree") query = qTree; - else if (*i == "--graph") query = qGraph; - else if (*i == "--use-output" || *i == "-u") useOutput = true; - else if (*i == "--force-realise" || *i == "-f") forceRealise = true; - else if (*i == "--include-outputs") includeOutputs = true; - else throw UsageError(format("unknown flag `%1%'") % *i); - - switch (query) { - - case qOutputs: { - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - { - *i = fixPath(*i); - if (forceRealise) realisePath(*i); - Derivation drv = derivationFromPath(*i); - cout << format("%1%\n") % findOutput(drv, "out"); - } - break; - } - - case qRequisites: - case qReferences: - case qReferrers: - case qReferrersClosure: { - PathSet paths; - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - { - Path path = maybeUseOutput(fixPath(*i), useOutput, forceRealise); - if (query == qRequisites) - storePathRequisites(path, includeOutputs, paths); - else if (query == qReferences) store->queryReferences(path, paths); - else if (query == qReferrers) store->queryReferrers(path, paths); - else if (query == qReferrersClosure) computeFSClosure(path, paths, true); - } - printPathSet(paths); - break; - } - - case qDeriver: - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - { - Path deriver = queryDeriver(noTxn, fixPath(*i)); - cout << format("%1%\n") % - (deriver == "" ? "unknown-deriver" : deriver); - } - break; - - case qBinding: - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - { - Path path = useDeriver(fixPath(*i)); - Derivation drv = derivationFromPath(path); - StringPairs::iterator j = drv.env.find(bindingName); - if (j == drv.env.end()) - throw Error(format("derivation `%1%' has no environment binding named `%2%'") - % path % bindingName); - cout << format("%1%\n") % j->second; - } - break; - - case qHash: - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - { - Path path = maybeUseOutput(fixPath(*i), useOutput, forceRealise); - Hash hash = store->queryPathHash(path); - assert(hash.type == htSHA256); - cout << format("sha256:%1%\n") % printHash32(hash); - } - break; - - case qTree: { - PathSet done; - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - printTree(fixPath(*i), "", "", done); - break; - } - - case qGraph: { - PathSet roots; - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - roots.insert(maybeUseOutput(fixPath(*i), useOutput, forceRealise)); - printDotGraph(roots); - break; - } - - default: - abort(); - } -} - - -static void opReadLog(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - { - Path path = useDeriver(fixPath(*i)); - - Path logPath = (format("%1%/%2%/%3%") % - nixLogDir % drvsLogDir % baseNameOf(path)).str(); - - if (!pathExists(logPath)) - throw Error(format("build log of derivation `%1%' is not available") % path); - - /* !!! Make this run in O(1) memory. */ - string log = readFile(logPath); - writeFull(STDOUT_FILENO, (const unsigned char *) log.c_str(), log.size()); - } -} - - -static void opRegisterSubstitutes(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - if (!opArgs.empty()) throw UsageError("no arguments expected"); - - Transaction txn; - createStoreTransaction(txn); - - while (1) { - Path srcPath; - Substitute sub; - PathSet references; - getline(cin, srcPath); - if (cin.eof()) break; - getline(cin, sub.deriver); - getline(cin, sub.program); - string s; int n; - getline(cin, s); - if (!string2Int(s, n)) throw Error("number expected"); - while (n--) { - getline(cin, s); - sub.args.push_back(s); - } - getline(cin, s); - if (!string2Int(s, n)) throw Error("number expected"); - while (n--) { - getline(cin, s); - references.insert(s); - } - if (!cin || cin.eof()) throw Error("missing input"); - registerSubstitute(txn, srcPath, sub); - setReferences(txn, srcPath, references); - } - - txn.commit(); -} - - -static void opClearSubstitutes(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - if (!opArgs.empty()) - throw UsageError("no arguments expected"); - - clearSubstitutes(); -} - - -static void opRegisterValidity(Strings opFlags, Strings opArgs) -{ - bool reregister = false; // !!! maybe this should be the default - - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--reregister") reregister = true; - else throw UsageError(format("unknown flag `%1%'") % *i); - - if (!opArgs.empty()) throw UsageError("no arguments expected"); - - ValidPathInfos infos; - - while (1) { - ValidPathInfo info; - getline(cin, info.path); - if (cin.eof()) break; - getline(cin, info.deriver); - string s; int n; - getline(cin, s); - if (!string2Int(s, n)) throw Error("number expected"); - while (n--) { - getline(cin, s); - info.references.insert(s); - } - if (!cin || cin.eof()) throw Error("missing input"); - if (!store->isValidPath(info.path) || reregister) { - /* !!! races */ - canonicalisePathMetaData(info.path); - info.hash = hashPath(htSHA256, info.path); - infos.push_back(info); - } - } - - Transaction txn; - createStoreTransaction(txn); - registerValidPaths(txn, infos); - txn.commit(); -} - - -static void opCheckValidity(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - if (!store->isValidPath(*i)) - throw Error(format("path `%1%' is not valid") % *i); -} - - -struct PrintFreed -{ - bool show, dryRun; - unsigned long long bytesFreed; - PrintFreed(bool show, bool dryRun) - : show(show), dryRun(dryRun), bytesFreed(0) { } - ~PrintFreed() - { - if (show) - cout << format( - (dryRun - ? "%d bytes would be freed (%.2f MiB)\n" - : "%d bytes freed (%.2f MiB)\n")) - % bytesFreed % (bytesFreed / (1024.0 * 1024.0)); - } -}; - - -static void opGC(Strings opFlags, Strings opArgs) -{ - GCAction action = gcDeleteDead; - - /* Do what? */ - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--print-roots") action = gcReturnRoots; - else if (*i == "--print-live") action = gcReturnLive; - else if (*i == "--print-dead") action = gcReturnDead; - else if (*i == "--delete") action = gcDeleteDead; - else throw UsageError(format("bad sub-operation `%1%' in GC") % *i); - - PathSet result; - PrintFreed freed(action == gcDeleteDead || action == gcReturnDead, - action == gcReturnDead); - store->collectGarbage(action, PathSet(), false, result, freed.bytesFreed); - - if (action != gcDeleteDead) { - for (PathSet::iterator i = result.begin(); i != result.end(); ++i) - cout << *i << std::endl; - } -} - - -/* Remove paths from the Nix store if possible (i.e., if they do not - have any remaining referrers and are not reachable from any GC - roots). */ -static void opDelete(Strings opFlags, Strings opArgs) -{ - bool ignoreLiveness = false; - - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--ignore-liveness") ignoreLiveness = true; - else throw UsageError(format("unknown flag `%1%'") % *i); - - PathSet pathsToDelete; - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - pathsToDelete.insert(fixPath(*i)); - - PathSet dummy; - PrintFreed freed(true, false); - store->collectGarbage(gcDeleteSpecific, pathsToDelete, ignoreLiveness, - dummy, freed.bytesFreed); -} - - -/* Dump a path as a Nix archive. The archive is written to standard - output. */ -static void opDump(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - if (opArgs.size() != 1) throw UsageError("only one argument allowed"); - - FdSink sink(STDOUT_FILENO); - string path = *opArgs.begin(); - dumpPath(path, sink); -} - - -/* Restore a value from a Nix archive. The archive is read from - standard input. */ -static void opRestore(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - if (opArgs.size() != 1) throw UsageError("only one argument allowed"); - - FdSource source(STDIN_FILENO); - restorePath(*opArgs.begin(), source); -} - - -/* Initialise the Nix databases. */ -static void opInit(Strings opFlags, Strings opArgs) -{ - if (!opFlags.empty()) throw UsageError("unknown flag"); - if (!opArgs.empty()) - throw UsageError("no arguments expected"); - /* Doesn't do anything right now; database tables are initialised - automatically. */ -} - - -/* Verify the consistency of the Nix environment. */ -static void opVerify(Strings opFlags, Strings opArgs) -{ - if (!opArgs.empty()) - throw UsageError("no arguments expected"); - - bool checkContents = false; - - for (Strings::iterator i = opFlags.begin(); - i != opFlags.end(); ++i) - if (*i == "--check-contents") checkContents = true; - else throw UsageError(format("unknown flag `%1%'") % *i); - - verifyStore(checkContents); -} - - -/* Scan the arguments; find the operation, set global flags, put all - other flags in a list, and put all other arguments in another - list. */ -void run(Strings args) -{ - Strings opFlags, opArgs; - Operation op = 0; - - for (Strings::iterator i = args.begin(); i != args.end(); ) { - string arg = *i++; - - Operation oldOp = op; - - if (arg == "--realise" || arg == "-r") - op = opRealise; - else if (arg == "--add" || arg == "-A") - op = opAdd; - else if (arg == "--add-fixed") - op = opAddFixed; - else if (arg == "--print-fixed-path") - op = opPrintFixedPath; - else if (arg == "--delete") - op = opDelete; - else if (arg == "--query" || arg == "-q") - op = opQuery; - else if (arg == "--read-log" || arg == "-l") - op = opReadLog; - else if (arg == "--register-substitutes") - op = opRegisterSubstitutes; - else if (arg == "--clear-substitutes") - op = opClearSubstitutes; - else if (arg == "--register-validity") - op = opRegisterValidity; - else if (arg == "--check-validity") - op = opCheckValidity; - else if (arg == "--gc") - op = opGC; - else if (arg == "--dump") - op = opDump; - else if (arg == "--restore") - op = opRestore; - else if (arg == "--init") - op = opInit; - else if (arg == "--verify") - op = opVerify; - else if (arg == "--add-root") { - if (i == args.end()) - throw UsageError("`--add-root requires an argument"); - gcRoot = absPath(*i++); - } - else if (arg == "--indirect") - indirectRoot = true; - else if (arg[0] == '-') - opFlags.push_back(arg); - else - opArgs.push_back(arg); - - if (oldOp && oldOp != op) - throw UsageError("only one operation may be specified"); - } - - if (!op) throw UsageError("no operation specified"); - - if (op != opDump && op != opRestore) /* !!! hack */ - store = openStore(op != opGC); - - op(opFlags, opArgs); -} - - -string programId = "nix-store"; diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc new file mode 100644 index 000000000000..701e8397427b --- /dev/null +++ b/src/nix-store/nix-store.cc @@ -0,0 +1,731 @@ +#include +#include + +#include "globals.hh" +#include "misc.hh" +#include "archive.hh" +#include "shared.hh" +#include "dotgraph.hh" +#include "local-store.hh" +#include "db.hh" +#include "util.hh" +#include "help.txt.hh" + + +using namespace nix; +using std::cin; +using std::cout; + + +typedef void (* Operation) (Strings opFlags, Strings opArgs); + + +void printHelp() +{ + cout << string((char *) helpText, sizeof helpText); +} + + +static Path gcRoot; +static int rootNr = 0; +static bool indirectRoot = false; + + +static Path fixPath(Path path) +{ + path = absPath(path); + while (!isInStore(path)) { + if (!isLink(path)) break; + string target = readLink(path); + path = absPath(target, dirOf(path)); + } + return toStorePath(path); +} + + +static Path useDeriver(Path path) +{ + if (!isDerivation(path)) { + path = queryDeriver(noTxn, path); + if (path == "") + throw Error(format("deriver of path `%1%' is not known") % path); + } + return path; +} + + +/* Realisation the given path. For a derivation that means build it; + for other paths it means ensure their validity. */ +static Path realisePath(const Path & path) +{ + if (isDerivation(path)) { + PathSet paths; + paths.insert(path); + store->buildDerivations(paths); + Path outPath = findOutput(derivationFromPath(path), "out"); + + if (gcRoot == "") + printGCWarning(); + else + outPath = addPermRoot(outPath, + makeRootName(gcRoot, rootNr), + indirectRoot); + + return outPath; + } else { + store->ensurePath(path); + return path; + } +} + + +/* Realise the given paths. */ +static void opRealise(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + *i = fixPath(*i); + + if (opArgs.size() > 1) { + PathSet drvPaths; + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + if (isDerivation(*i)) + drvPaths.insert(*i); + store->buildDerivations(drvPaths); + } + + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + cout << format("%1%\n") % realisePath(*i); +} + + +/* Add files to the Nix store and print the resulting paths. */ +static void opAdd(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) + cout << format("%1%\n") % store->addToStore(*i); +} + + +/* Preload the output of a fixed-output derivation into the Nix + store. */ +static void opAddFixed(Strings opFlags, Strings opArgs) +{ + bool recursive = false; + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--recursive") recursive = true; + else throw UsageError(format("unknown flag `%1%'") % *i); + + if (opArgs.empty()) + throw UsageError("first argument must be hash algorithm"); + + string hashAlgo = opArgs.front(); + opArgs.pop_front(); + + for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) + cout << format("%1%\n") % store->addToStore(*i, true, recursive, hashAlgo); +} + + +static Hash parseHash16or32(HashType ht, const string & s) +{ + return s.size() == Hash(ht).hashSize * 2 + ? parseHash(ht, s) + : parseHash32(ht, s); +} + + +/* Hack to support caching in `nix-prefetch-url'. */ +static void opPrintFixedPath(Strings opFlags, Strings opArgs) +{ + bool recursive = false; + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--recursive") recursive = true; + else throw UsageError(format("unknown flag `%1%'") % *i); + + Strings::iterator i = opArgs.begin(); + string hashAlgo = *i++; + string hash = *i++; + string name = *i++; + + cout << format("%1%\n") % + makeFixedOutputPath(recursive, hashAlgo, + parseHash16or32(parseHashType(hashAlgo), hash), name); +} + + +/* Place in `paths' the set of paths that are required to `realise' + the given store path, i.e., all paths necessary for valid + deployment of the path. For a derivation, this is the union of + requisites of the inputs, plus the derivation; for other store + paths, it is the set of paths in the FS closure of the path. If + `includeOutputs' is true, include the requisites of the output + paths of derivations as well. + + Note that this function can be used to implement three different + deployment policies: + + - Source deployment (when called on a derivation). + - Binary deployment (when called on an output path). + - Source/binary deployment (when called on a derivation with + `includeOutputs' set to true). +*/ +static void storePathRequisites(const Path & storePath, + bool includeOutputs, PathSet & paths) +{ + computeFSClosure(storePath, paths); + + if (includeOutputs) { + for (PathSet::iterator i = paths.begin(); + i != paths.end(); ++i) + if (isDerivation(*i)) { + Derivation drv = derivationFromPath(*i); + for (DerivationOutputs::iterator j = drv.outputs.begin(); + j != drv.outputs.end(); ++j) + if (store->isValidPath(j->second.path)) + computeFSClosure(j->second.path, paths); + } + } +} + + +static Path maybeUseOutput(const Path & storePath, bool useOutput, bool forceRealise) +{ + if (forceRealise) realisePath(storePath); + if (useOutput && isDerivation(storePath)) { + Derivation drv = derivationFromPath(storePath); + return findOutput(drv, "out"); + } + else return storePath; +} + + +static void printPathSet(const PathSet & paths) +{ + for (PathSet::iterator i = paths.begin(); + i != paths.end(); ++i) + cout << format("%s\n") % *i; +} + + +/* Some code to print a tree representation of a derivation dependency + graph. Topological sorting is used to keep the tree relatively + flat. */ + +const string treeConn = "+---"; +const string treeLine = "| "; +const string treeNull = " "; + + +static void dfsVisit(const PathSet & paths, const Path & path, + PathSet & visited, Paths & sorted) +{ + if (visited.find(path) != visited.end()) return; + visited.insert(path); + + PathSet closure; + computeFSClosure(path, closure); + + for (PathSet::iterator i = closure.begin(); + i != closure.end(); ++i) + if (*i != path && paths.find(*i) != paths.end()) + dfsVisit(paths, *i, visited, sorted); + + sorted.push_front(path); +} + + +static Paths topoSort(const PathSet & paths) +{ + Paths sorted; + PathSet visited; + for (PathSet::const_iterator i = paths.begin(); i != paths.end(); ++i) + dfsVisit(paths, *i, visited, sorted); + return sorted; +} + + +static void printTree(const Path & path, + const string & firstPad, const string & tailPad, PathSet & done) +{ + if (done.find(path) != done.end()) { + cout << format("%1%%2% [...]\n") % firstPad % path; + return; + } + done.insert(path); + + cout << format("%1%%2%\n") % firstPad % path; + + PathSet references; + store->queryReferences(path, references); + +#if 0 + for (PathSet::iterator i = drv.inputSrcs.begin(); + i != drv.inputSrcs.end(); ++i) + cout << format("%1%%2%\n") % (tailPad + treeConn) % *i; +#endif + + /* Topologically sort under the relation A < B iff A \in + closure(B). That is, if derivation A is an (possibly indirect) + input of B, then A is printed first. This has the effect of + flattening the tree, preventing deeply nested structures. */ + Paths sorted = topoSort(references); + reverse(sorted.begin(), sorted.end()); + + for (Paths::iterator i = sorted.begin(); i != sorted.end(); ++i) { + Paths::iterator j = i; ++j; + printTree(*i, tailPad + treeConn, + j == sorted.end() ? tailPad + treeNull : tailPad + treeLine, + done); + } +} + + +/* Perform various sorts of queries. */ +static void opQuery(Strings opFlags, Strings opArgs) +{ + enum { qOutputs, qRequisites, qReferences, qReferrers + , qReferrersClosure, qDeriver, qBinding, qHash + , qTree, qGraph } query = qOutputs; + bool useOutput = false; + bool includeOutputs = false; + bool forceRealise = false; + string bindingName; + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--outputs") query = qOutputs; + else if (*i == "--requisites" || *i == "-R") query = qRequisites; + else if (*i == "--references") query = qReferences; + else if (*i == "--referrers" || *i == "--referers") query = qReferrers; + else if (*i == "--referrers-closure" || *i == "--referers-closure") query = qReferrersClosure; + else if (*i == "--deriver" || *i == "-d") query = qDeriver; + else if (*i == "--binding" || *i == "-b") { + if (opArgs.size() == 0) + throw UsageError("expected binding name"); + bindingName = opArgs.front(); + opArgs.pop_front(); + query = qBinding; + } + else if (*i == "--hash") query = qHash; + else if (*i == "--tree") query = qTree; + else if (*i == "--graph") query = qGraph; + else if (*i == "--use-output" || *i == "-u") useOutput = true; + else if (*i == "--force-realise" || *i == "-f") forceRealise = true; + else if (*i == "--include-outputs") includeOutputs = true; + else throw UsageError(format("unknown flag `%1%'") % *i); + + switch (query) { + + case qOutputs: { + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + { + *i = fixPath(*i); + if (forceRealise) realisePath(*i); + Derivation drv = derivationFromPath(*i); + cout << format("%1%\n") % findOutput(drv, "out"); + } + break; + } + + case qRequisites: + case qReferences: + case qReferrers: + case qReferrersClosure: { + PathSet paths; + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + { + Path path = maybeUseOutput(fixPath(*i), useOutput, forceRealise); + if (query == qRequisites) + storePathRequisites(path, includeOutputs, paths); + else if (query == qReferences) store->queryReferences(path, paths); + else if (query == qReferrers) store->queryReferrers(path, paths); + else if (query == qReferrersClosure) computeFSClosure(path, paths, true); + } + printPathSet(paths); + break; + } + + case qDeriver: + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + { + Path deriver = queryDeriver(noTxn, fixPath(*i)); + cout << format("%1%\n") % + (deriver == "" ? "unknown-deriver" : deriver); + } + break; + + case qBinding: + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + { + Path path = useDeriver(fixPath(*i)); + Derivation drv = derivationFromPath(path); + StringPairs::iterator j = drv.env.find(bindingName); + if (j == drv.env.end()) + throw Error(format("derivation `%1%' has no environment binding named `%2%'") + % path % bindingName); + cout << format("%1%\n") % j->second; + } + break; + + case qHash: + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + { + Path path = maybeUseOutput(fixPath(*i), useOutput, forceRealise); + Hash hash = store->queryPathHash(path); + assert(hash.type == htSHA256); + cout << format("sha256:%1%\n") % printHash32(hash); + } + break; + + case qTree: { + PathSet done; + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + printTree(fixPath(*i), "", "", done); + break; + } + + case qGraph: { + PathSet roots; + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + roots.insert(maybeUseOutput(fixPath(*i), useOutput, forceRealise)); + printDotGraph(roots); + break; + } + + default: + abort(); + } +} + + +static void opReadLog(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + { + Path path = useDeriver(fixPath(*i)); + + Path logPath = (format("%1%/%2%/%3%") % + nixLogDir % drvsLogDir % baseNameOf(path)).str(); + + if (!pathExists(logPath)) + throw Error(format("build log of derivation `%1%' is not available") % path); + + /* !!! Make this run in O(1) memory. */ + string log = readFile(logPath); + writeFull(STDOUT_FILENO, (const unsigned char *) log.c_str(), log.size()); + } +} + + +static void opRegisterSubstitutes(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (!opArgs.empty()) throw UsageError("no arguments expected"); + + Transaction txn; + createStoreTransaction(txn); + + while (1) { + Path srcPath; + Substitute sub; + PathSet references; + getline(cin, srcPath); + if (cin.eof()) break; + getline(cin, sub.deriver); + getline(cin, sub.program); + string s; int n; + getline(cin, s); + if (!string2Int(s, n)) throw Error("number expected"); + while (n--) { + getline(cin, s); + sub.args.push_back(s); + } + getline(cin, s); + if (!string2Int(s, n)) throw Error("number expected"); + while (n--) { + getline(cin, s); + references.insert(s); + } + if (!cin || cin.eof()) throw Error("missing input"); + registerSubstitute(txn, srcPath, sub); + setReferences(txn, srcPath, references); + } + + txn.commit(); +} + + +static void opClearSubstitutes(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (!opArgs.empty()) + throw UsageError("no arguments expected"); + + clearSubstitutes(); +} + + +static void opRegisterValidity(Strings opFlags, Strings opArgs) +{ + bool reregister = false; // !!! maybe this should be the default + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--reregister") reregister = true; + else throw UsageError(format("unknown flag `%1%'") % *i); + + if (!opArgs.empty()) throw UsageError("no arguments expected"); + + ValidPathInfos infos; + + while (1) { + ValidPathInfo info; + getline(cin, info.path); + if (cin.eof()) break; + getline(cin, info.deriver); + string s; int n; + getline(cin, s); + if (!string2Int(s, n)) throw Error("number expected"); + while (n--) { + getline(cin, s); + info.references.insert(s); + } + if (!cin || cin.eof()) throw Error("missing input"); + if (!store->isValidPath(info.path) || reregister) { + /* !!! races */ + canonicalisePathMetaData(info.path); + info.hash = hashPath(htSHA256, info.path); + infos.push_back(info); + } + } + + Transaction txn; + createStoreTransaction(txn); + registerValidPaths(txn, infos); + txn.commit(); +} + + +static void opCheckValidity(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + if (!store->isValidPath(*i)) + throw Error(format("path `%1%' is not valid") % *i); +} + + +struct PrintFreed +{ + bool show, dryRun; + unsigned long long bytesFreed; + PrintFreed(bool show, bool dryRun) + : show(show), dryRun(dryRun), bytesFreed(0) { } + ~PrintFreed() + { + if (show) + cout << format( + (dryRun + ? "%d bytes would be freed (%.2f MiB)\n" + : "%d bytes freed (%.2f MiB)\n")) + % bytesFreed % (bytesFreed / (1024.0 * 1024.0)); + } +}; + + +static void opGC(Strings opFlags, Strings opArgs) +{ + GCAction action = gcDeleteDead; + + /* Do what? */ + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--print-roots") action = gcReturnRoots; + else if (*i == "--print-live") action = gcReturnLive; + else if (*i == "--print-dead") action = gcReturnDead; + else if (*i == "--delete") action = gcDeleteDead; + else throw UsageError(format("bad sub-operation `%1%' in GC") % *i); + + PathSet result; + PrintFreed freed(action == gcDeleteDead || action == gcReturnDead, + action == gcReturnDead); + store->collectGarbage(action, PathSet(), false, result, freed.bytesFreed); + + if (action != gcDeleteDead) { + for (PathSet::iterator i = result.begin(); i != result.end(); ++i) + cout << *i << std::endl; + } +} + + +/* Remove paths from the Nix store if possible (i.e., if they do not + have any remaining referrers and are not reachable from any GC + roots). */ +static void opDelete(Strings opFlags, Strings opArgs) +{ + bool ignoreLiveness = false; + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--ignore-liveness") ignoreLiveness = true; + else throw UsageError(format("unknown flag `%1%'") % *i); + + PathSet pathsToDelete; + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + pathsToDelete.insert(fixPath(*i)); + + PathSet dummy; + PrintFreed freed(true, false); + store->collectGarbage(gcDeleteSpecific, pathsToDelete, ignoreLiveness, + dummy, freed.bytesFreed); +} + + +/* Dump a path as a Nix archive. The archive is written to standard + output. */ +static void opDump(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() != 1) throw UsageError("only one argument allowed"); + + FdSink sink(STDOUT_FILENO); + string path = *opArgs.begin(); + dumpPath(path, sink); +} + + +/* Restore a value from a Nix archive. The archive is read from + standard input. */ +static void opRestore(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() != 1) throw UsageError("only one argument allowed"); + + FdSource source(STDIN_FILENO); + restorePath(*opArgs.begin(), source); +} + + +/* Initialise the Nix databases. */ +static void opInit(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (!opArgs.empty()) + throw UsageError("no arguments expected"); + /* Doesn't do anything right now; database tables are initialised + automatically. */ +} + + +/* Verify the consistency of the Nix environment. */ +static void opVerify(Strings opFlags, Strings opArgs) +{ + if (!opArgs.empty()) + throw UsageError("no arguments expected"); + + bool checkContents = false; + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--check-contents") checkContents = true; + else throw UsageError(format("unknown flag `%1%'") % *i); + + verifyStore(checkContents); +} + + +/* Scan the arguments; find the operation, set global flags, put all + other flags in a list, and put all other arguments in another + list. */ +void run(Strings args) +{ + Strings opFlags, opArgs; + Operation op = 0; + + for (Strings::iterator i = args.begin(); i != args.end(); ) { + string arg = *i++; + + Operation oldOp = op; + + if (arg == "--realise" || arg == "-r") + op = opRealise; + else if (arg == "--add" || arg == "-A") + op = opAdd; + else if (arg == "--add-fixed") + op = opAddFixed; + else if (arg == "--print-fixed-path") + op = opPrintFixedPath; + else if (arg == "--delete") + op = opDelete; + else if (arg == "--query" || arg == "-q") + op = opQuery; + else if (arg == "--read-log" || arg == "-l") + op = opReadLog; + else if (arg == "--register-substitutes") + op = opRegisterSubstitutes; + else if (arg == "--clear-substitutes") + op = opClearSubstitutes; + else if (arg == "--register-validity") + op = opRegisterValidity; + else if (arg == "--check-validity") + op = opCheckValidity; + else if (arg == "--gc") + op = opGC; + else if (arg == "--dump") + op = opDump; + else if (arg == "--restore") + op = opRestore; + else if (arg == "--init") + op = opInit; + else if (arg == "--verify") + op = opVerify; + else if (arg == "--add-root") { + if (i == args.end()) + throw UsageError("`--add-root requires an argument"); + gcRoot = absPath(*i++); + } + else if (arg == "--indirect") + indirectRoot = true; + else if (arg[0] == '-') + opFlags.push_back(arg); + else + opArgs.push_back(arg); + + if (oldOp && oldOp != op) + throw UsageError("only one operation may be specified"); + } + + if (!op) throw UsageError("no operation specified"); + + if (op != opDump && op != opRestore) /* !!! hack */ + store = openStore(op != opGC); + + op(opFlags, opArgs); +} + + +string programId = "nix-store"; diff --git a/src/nix-worker/Makefile.am b/src/nix-worker/Makefile.am index 1be7e3d54449..6c537a3f9312 100644 --- a/src/nix-worker/Makefile.am +++ b/src/nix-worker/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix-worker -nix_worker_SOURCES = main.cc help.txt +nix_worker_SOURCES = nix-worker.cc help.txt nix_worker_LDADD = ../libmain/libmain.la ../libstore/libstore.la ../libutil/libutil.la \ ../boost/format/libformat.la ${bdb_lib} ${aterm_lib} diff --git a/src/nix-worker/main.cc b/src/nix-worker/main.cc deleted file mode 100644 index 0be4b8e64cc4..000000000000 --- a/src/nix-worker/main.cc +++ /dev/null @@ -1,569 +0,0 @@ -#include "shared.hh" -#include "local-store.hh" -#include "util.hh" -#include "serialise.hh" -#include "worker-protocol.hh" -#include "archive.hh" -#include "globals.hh" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace nix; - - -#ifndef SIGPOLL -#define SIGPOLL SIGIO -#endif - - -static FdSource from(STDIN_FILENO); -static FdSink to(STDOUT_FILENO); - -bool canSendStderr; -pid_t myPid; - - - -/* This function is called anytime we want to write something to - stderr. If we're in a state where the protocol allows it (i.e., - when canSendStderr), send the message to the client over the - socket. */ -static void tunnelStderr(const unsigned char * buf, size_t count) -{ - /* Don't send the message to the client if we're a child of the - process handling the connection. Otherwise we could screw up - the protocol. It's up to the parent to redirect stderr and - send it to the client somehow (e.g., as in build.cc). */ - if (canSendStderr && myPid == getpid()) { - try { - writeInt(STDERR_NEXT, to); - writeString(string((char *) buf, count), to); - } catch (...) { - /* Write failed; that means that the other side is - gone. */ - canSendStderr = false; - throw; - } - } else - writeFull(STDERR_FILENO, buf, count); -} - - -/* Return true if the remote side has closed its end of the - connection, false otherwise. Should not be called on any socket on - which we expect input! */ -static bool isFarSideClosed(int socket) -{ - struct timeval timeout; - timeout.tv_sec = timeout.tv_usec = 0; - - fd_set fds; - FD_ZERO(&fds); - FD_SET(socket, &fds); - - if (select(socket + 1, &fds, 0, 0, &timeout) == -1) - throw SysError("select()"); - - if (!FD_ISSET(socket, &fds)) return false; - - /* Destructive read to determine whether the select() marked the - socket as readable because there is actual input or because - we've reached EOF (i.e., a read of size 0 is available). */ - char c; - if (read(socket, &c, 1) != 0) - throw Error("EOF expected (protocol error?)"); - - return true; -} - - -/* A SIGPOLL signal is received when data is available on the client - communication scoket, or when the client has closed its side of the - socket. This handler is enabled at precisely those moments in the - protocol when we're doing work and the client is supposed to be - quiet. Thus, if we get a SIGPOLL signal, it means that the client - has quit. So we should quit as well. - - Too bad most operating systems don't support the POLL_HUP value for - si_code in siginfo_t. That would make most of the SIGPOLL - complexity unnecessary, i.e., we could just enable SIGPOLL all the - time and wouldn't have to worry about races. */ -static void sigPollHandler(int sigNo) -{ - try { - /* Check that the far side actually closed. We're still - getting spurious signals every once in a while. I.e., - there is no input available, but we get a signal with - POLL_IN set. Maybe it's delayed or something. */ - if (isFarSideClosed(from.fd)) { - if (!blockInt) { - _isInterrupted = 1; - blockInt = 1; - canSendStderr = false; - write(STDERR_FILENO, "SIGPOLL\n", 8); - } - } else { - string s = "spurious SIGPOLL\n"; - write(STDERR_FILENO, s.c_str(), s.size()); - } - } - catch (Error & e) { - /* Shouldn't happen. */ - write(STDERR_FILENO, e.msg().c_str(), e.msg().size()); - abort(); - } -} - - -static void setSigPollAction(bool enable) -{ - struct sigaction act, oact; - act.sa_handler = enable ? sigPollHandler : SIG_IGN; - sigfillset(&act.sa_mask); - act.sa_flags = 0; - if (sigaction(SIGPOLL, &act, &oact)) - throw SysError("setting handler for SIGPOLL"); -} - - -/* startWork() means that we're starting an operation for which we - want to send out stderr to the client. */ -static void startWork() -{ - canSendStderr = true; - - /* Handle client death asynchronously. */ - setSigPollAction(true); - - /* Of course, there is a race condition here: the socket could - have closed between when we last read from / wrote to it, and - between the time we set the handler for SIGPOLL. In that case - we won't get the signal. So do a non-blocking select() to find - out if any input is available on the socket. If there is, it - has to be the 0-byte read that indicates that the socket has - closed. */ - if (isFarSideClosed(from.fd)) { - _isInterrupted = 1; - checkInterrupt(); - } -} - - -/* stopWork() means that we're done; stop sending stderr to the - client. */ -static void stopWork(bool success = true, const string & msg = "") -{ - /* Stop handling async client death; we're going to a state where - we're either sending or receiving from the client, so we'll be - notified of client death anyway. */ - setSigPollAction(false); - - canSendStderr = false; - - if (success) - writeInt(STDERR_LAST, to); - else { - writeInt(STDERR_ERROR, to); - writeString(msg, to); - } -} - - -static void performOp(Source & from, Sink & to, unsigned int op) -{ - switch (op) { - -#if 0 - case wopQuit: { - /* Close the database. */ - store.reset((StoreAPI *) 0); - writeInt(1, to); - break; - } -#endif - - case wopIsValidPath: { - Path path = readStorePath(from); - startWork(); - bool result = store->isValidPath(path); - stopWork(); - writeInt(result, to); - break; - } - - case wopHasSubstitutes: { - Path path = readStorePath(from); - startWork(); - bool result = store->hasSubstitutes(path); - stopWork(); - writeInt(result, to); - break; - } - - case wopQueryPathHash: { - Path path = readStorePath(from); - startWork(); - Hash hash = store->queryPathHash(path); - stopWork(); - writeString(printHash(hash), to); - break; - } - - case wopQueryReferences: - case wopQueryReferrers: { - Path path = readStorePath(from); - startWork(); - PathSet paths; - if (op == wopQueryReferences) - store->queryReferences(path, paths); - else - store->queryReferrers(path, paths); - stopWork(); - writeStringSet(paths, to); - break; - } - - case wopAddToStore: { - /* !!! uberquick hack */ - string baseName = readString(from); - bool fixed = readInt(from) == 1; - bool recursive = readInt(from) == 1; - string hashAlgo = readString(from); - - Path tmp = createTempDir(); - Path tmp2 = tmp + "/" + baseName; - restorePath(tmp2, from); - - startWork(); - Path path = store->addToStore(tmp2, fixed, recursive, hashAlgo); - stopWork(); - - writeString(path, to); - - deletePath(tmp); - break; - } - - case wopAddTextToStore: { - string suffix = readString(from); - string s = readString(from); - PathSet refs = readStorePaths(from); - startWork(); - Path path = store->addTextToStore(suffix, s, refs); - stopWork(); - writeString(path, to); - break; - } - - case wopBuildDerivations: { - PathSet drvs = readStorePaths(from); - startWork(); - store->buildDerivations(drvs); - stopWork(); - writeInt(1, to); - break; - } - - case wopEnsurePath: { - Path path = readStorePath(from); - startWork(); - store->ensurePath(path); - stopWork(); - writeInt(1, to); - break; - } - - case wopAddTempRoot: { - Path path = readStorePath(from); - startWork(); - store->addTempRoot(path); - stopWork(); - writeInt(1, to); - break; - } - - case wopAddIndirectRoot: { - Path path = absPath(readString(from)); - startWork(); - store->addIndirectRoot(path); - stopWork(); - writeInt(1, to); - break; - } - - case wopSyncWithGC: { - startWork(); - store->syncWithGC(); - stopWork(); - writeInt(1, to); - break; - } - - case wopFindRoots: { - startWork(); - Roots roots = store->findRoots(); - stopWork(); - writeInt(roots.size(), to); - for (Roots::iterator i = roots.begin(); i != roots.end(); ++i) { - writeString(i->first, to); - writeString(i->second, to); - } - break; - } - - case wopCollectGarbage: { - GCAction action = (GCAction) readInt(from); - PathSet pathsToDelete = readStorePaths(from); - bool ignoreLiveness = readInt(from); - - PathSet result; - unsigned long long bytesFreed; - - startWork(); - if (ignoreLiveness) - throw Error("you are not allowed to ignore liveness"); - store->collectGarbage(action, pathsToDelete, ignoreLiveness, - result, bytesFreed); - stopWork(); - - writeStringSet(result, to); - writeInt(bytesFreed & 0xffffffff, to); - writeInt(bytesFreed >> 32, to); - - break; - } - - default: - throw Error(format("invalid operation %1%") % op); - } -} - - -static void processConnection() -{ - canSendStderr = false; - myPid = getpid(); - writeToStderr = tunnelStderr; - - /* Allow us to receive SIGPOLL for events on the client socket. */ - setSigPollAction(false); - if (fcntl(from.fd, F_SETOWN, getpid()) == -1) - throw SysError("F_SETOWN"); - if (fcntl(from.fd, F_SETFL, fcntl(from.fd, F_GETFL, 0) | FASYNC) == -1) - throw SysError("F_SETFL"); - - /* Exchange the greeting. */ - unsigned int magic = readInt(from); - if (magic != WORKER_MAGIC_1) throw Error("protocol mismatch"); - verbosity = (Verbosity) readInt(from); - writeInt(WORKER_MAGIC_2, to); - - /* Send startup error messages to the client. */ - startWork(); - - try { - - /* Prevent users from doing something very dangerous. */ - if (geteuid() == 0 && - querySetting("build-users-group", "") == "") - throw Error("if you run `nix-worker' as root, then you MUST set `build-users-group'!"); - - /* Open the store. */ - store = boost::shared_ptr(new LocalStore(true)); - - stopWork(); - - } catch (Error & e) { - stopWork(false, e.msg()); - return; - } - - /* Process client requests. */ - unsigned int opCount = 0; - - while (true) { - WorkerOp op; - try { - op = (WorkerOp) readInt(from); - } catch (EndOfFile & e) { - break; - } - - opCount++; - - try { - performOp(from, to, op); - } catch (Error & e) { - stopWork(false, e.msg()); - } - - assert(!canSendStderr); - }; - - printMsg(lvlError, format("%1% worker operations") % opCount); -} - - -static void sigChldHandler(int sigNo) -{ - /* Reap all dead children. */ - while (waitpid(-1, 0, WNOHANG) == 0) ; -} - - -static void setSigChldAction(bool autoReap) -{ - struct sigaction act, oact; - act.sa_handler = autoReap ? sigChldHandler : SIG_DFL; - sigfillset(&act.sa_mask); - act.sa_flags = 0; - if (sigaction(SIGCHLD, &act, &oact)) - throw SysError("setting SIGCHLD handler"); -} - - -static void daemonLoop() -{ - /* Get rid of children automatically; don't let them become - zombies. */ - setSigChldAction(true); - - /* Create and bind to a Unix domain socket. */ - AutoCloseFD fdSocket = socket(PF_UNIX, SOCK_STREAM, 0); - if (fdSocket == -1) - throw SysError("cannot create Unix domain socket"); - - string socketPath = nixStateDir + DEFAULT_SOCKET_PATH; - - struct sockaddr_un addr; - addr.sun_family = AF_UNIX; - if (socketPath.size() >= sizeof(addr.sun_path)) - throw Error(format("socket path `%1%' is too long") % socketPath); - strcpy(addr.sun_path, socketPath.c_str()); - - unlink(socketPath.c_str()); - - /* Make sure that the socket is created with 0666 permission - (everybody can connect). */ - mode_t oldMode = umask(0111); - int res = bind(fdSocket, (struct sockaddr *) &addr, sizeof(addr)); - umask(oldMode); - if (res == -1) - throw SysError(format("cannot bind to socket `%1%'") % socketPath); - - if (listen(fdSocket, 5) == -1) - throw SysError(format("cannot listen on socket `%1%'") % socketPath); - - /* Loop accepting connections. */ - while (1) { - - try { - /* Important: the server process *cannot* open the - Berkeley DB environment, because it doesn't like forks - very much. */ - assert(!store); - - /* Accept a connection. */ - struct sockaddr_un remoteAddr; - socklen_t remoteAddrLen = sizeof(remoteAddr); - - AutoCloseFD remote = accept(fdSocket, - (struct sockaddr *) &remoteAddr, &remoteAddrLen); - checkInterrupt(); - if (remote == -1) - if (errno == EINTR) - continue; - else - throw SysError("accepting connection"); - - printMsg(lvlInfo, format("accepted connection %1%") % remote); - - /* Fork a child to handle the connection. */ - pid_t child; - child = fork(); - - switch (child) { - - case -1: - throw SysError("unable to fork"); - - case 0: - try { /* child */ - - /* Background the worker. */ - if (setsid() == -1) - throw SysError(format("creating a new session")); - - /* Restore normal handling of SIGCHLD. */ - setSigChldAction(false); - - /* Handle the connection. */ - from.fd = remote; - to.fd = remote; - processConnection(); - - } catch (std::exception & e) { - std::cerr << format("child error: %1%\n") % e.what(); - } - exit(0); - } - - } catch (Interrupted & e) { - throw; - } catch (Error & e) { - printMsg(lvlError, format("error processing connection: %1%") % e.msg()); - } - } -} - - -void run(Strings args) -{ - bool slave = false; - bool daemon = false; - - for (Strings::iterator i = args.begin(); i != args.end(); ) { - string arg = *i++; - if (arg == "--slave") slave = true; - if (arg == "--daemon") daemon = true; - } - - if (slave) { - /* This prevents us from receiving signals from the terminal - when we're running in setuid mode. */ - if (setsid() == -1) - throw SysError(format("creating a new session")); - - processConnection(); - } - - else if (daemon) { - if (setuidMode) - throw Error("daemon cannot be started in setuid mode"); - chdir("/"); - daemonLoop(); - } - - else - throw Error("must be run in either --slave or --daemon mode"); -} - - -#include "help.txt.hh" - -void printHelp() -{ - std::cout << string((char *) helpText, sizeof helpText); -} - - -string programId = "nix-worker"; diff --git a/src/nix-worker/nix-worker.cc b/src/nix-worker/nix-worker.cc new file mode 100644 index 000000000000..0be4b8e64cc4 --- /dev/null +++ b/src/nix-worker/nix-worker.cc @@ -0,0 +1,569 @@ +#include "shared.hh" +#include "local-store.hh" +#include "util.hh" +#include "serialise.hh" +#include "worker-protocol.hh" +#include "archive.hh" +#include "globals.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace nix; + + +#ifndef SIGPOLL +#define SIGPOLL SIGIO +#endif + + +static FdSource from(STDIN_FILENO); +static FdSink to(STDOUT_FILENO); + +bool canSendStderr; +pid_t myPid; + + + +/* This function is called anytime we want to write something to + stderr. If we're in a state where the protocol allows it (i.e., + when canSendStderr), send the message to the client over the + socket. */ +static void tunnelStderr(const unsigned char * buf, size_t count) +{ + /* Don't send the message to the client if we're a child of the + process handling the connection. Otherwise we could screw up + the protocol. It's up to the parent to redirect stderr and + send it to the client somehow (e.g., as in build.cc). */ + if (canSendStderr && myPid == getpid()) { + try { + writeInt(STDERR_NEXT, to); + writeString(string((char *) buf, count), to); + } catch (...) { + /* Write failed; that means that the other side is + gone. */ + canSendStderr = false; + throw; + } + } else + writeFull(STDERR_FILENO, buf, count); +} + + +/* Return true if the remote side has closed its end of the + connection, false otherwise. Should not be called on any socket on + which we expect input! */ +static bool isFarSideClosed(int socket) +{ + struct timeval timeout; + timeout.tv_sec = timeout.tv_usec = 0; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(socket, &fds); + + if (select(socket + 1, &fds, 0, 0, &timeout) == -1) + throw SysError("select()"); + + if (!FD_ISSET(socket, &fds)) return false; + + /* Destructive read to determine whether the select() marked the + socket as readable because there is actual input or because + we've reached EOF (i.e., a read of size 0 is available). */ + char c; + if (read(socket, &c, 1) != 0) + throw Error("EOF expected (protocol error?)"); + + return true; +} + + +/* A SIGPOLL signal is received when data is available on the client + communication scoket, or when the client has closed its side of the + socket. This handler is enabled at precisely those moments in the + protocol when we're doing work and the client is supposed to be + quiet. Thus, if we get a SIGPOLL signal, it means that the client + has quit. So we should quit as well. + + Too bad most operating systems don't support the POLL_HUP value for + si_code in siginfo_t. That would make most of the SIGPOLL + complexity unnecessary, i.e., we could just enable SIGPOLL all the + time and wouldn't have to worry about races. */ +static void sigPollHandler(int sigNo) +{ + try { + /* Check that the far side actually closed. We're still + getting spurious signals every once in a while. I.e., + there is no input available, but we get a signal with + POLL_IN set. Maybe it's delayed or something. */ + if (isFarSideClosed(from.fd)) { + if (!blockInt) { + _isInterrupted = 1; + blockInt = 1; + canSendStderr = false; + write(STDERR_FILENO, "SIGPOLL\n", 8); + } + } else { + string s = "spurious SIGPOLL\n"; + write(STDERR_FILENO, s.c_str(), s.size()); + } + } + catch (Error & e) { + /* Shouldn't happen. */ + write(STDERR_FILENO, e.msg().c_str(), e.msg().size()); + abort(); + } +} + + +static void setSigPollAction(bool enable) +{ + struct sigaction act, oact; + act.sa_handler = enable ? sigPollHandler : SIG_IGN; + sigfillset(&act.sa_mask); + act.sa_flags = 0; + if (sigaction(SIGPOLL, &act, &oact)) + throw SysError("setting handler for SIGPOLL"); +} + + +/* startWork() means that we're starting an operation for which we + want to send out stderr to the client. */ +static void startWork() +{ + canSendStderr = true; + + /* Handle client death asynchronously. */ + setSigPollAction(true); + + /* Of course, there is a race condition here: the socket could + have closed between when we last read from / wrote to it, and + between the time we set the handler for SIGPOLL. In that case + we won't get the signal. So do a non-blocking select() to find + out if any input is available on the socket. If there is, it + has to be the 0-byte read that indicates that the socket has + closed. */ + if (isFarSideClosed(from.fd)) { + _isInterrupted = 1; + checkInterrupt(); + } +} + + +/* stopWork() means that we're done; stop sending stderr to the + client. */ +static void stopWork(bool success = true, const string & msg = "") +{ + /* Stop handling async client death; we're going to a state where + we're either sending or receiving from the client, so we'll be + notified of client death anyway. */ + setSigPollAction(false); + + canSendStderr = false; + + if (success) + writeInt(STDERR_LAST, to); + else { + writeInt(STDERR_ERROR, to); + writeString(msg, to); + } +} + + +static void performOp(Source & from, Sink & to, unsigned int op) +{ + switch (op) { + +#if 0 + case wopQuit: { + /* Close the database. */ + store.reset((StoreAPI *) 0); + writeInt(1, to); + break; + } +#endif + + case wopIsValidPath: { + Path path = readStorePath(from); + startWork(); + bool result = store->isValidPath(path); + stopWork(); + writeInt(result, to); + break; + } + + case wopHasSubstitutes: { + Path path = readStorePath(from); + startWork(); + bool result = store->hasSubstitutes(path); + stopWork(); + writeInt(result, to); + break; + } + + case wopQueryPathHash: { + Path path = readStorePath(from); + startWork(); + Hash hash = store->queryPathHash(path); + stopWork(); + writeString(printHash(hash), to); + break; + } + + case wopQueryReferences: + case wopQueryReferrers: { + Path path = readStorePath(from); + startWork(); + PathSet paths; + if (op == wopQueryReferences) + store->queryReferences(path, paths); + else + store->queryReferrers(path, paths); + stopWork(); + writeStringSet(paths, to); + break; + } + + case wopAddToStore: { + /* !!! uberquick hack */ + string baseName = readString(from); + bool fixed = readInt(from) == 1; + bool recursive = readInt(from) == 1; + string hashAlgo = readString(from); + + Path tmp = createTempDir(); + Path tmp2 = tmp + "/" + baseName; + restorePath(tmp2, from); + + startWork(); + Path path = store->addToStore(tmp2, fixed, recursive, hashAlgo); + stopWork(); + + writeString(path, to); + + deletePath(tmp); + break; + } + + case wopAddTextToStore: { + string suffix = readString(from); + string s = readString(from); + PathSet refs = readStorePaths(from); + startWork(); + Path path = store->addTextToStore(suffix, s, refs); + stopWork(); + writeString(path, to); + break; + } + + case wopBuildDerivations: { + PathSet drvs = readStorePaths(from); + startWork(); + store->buildDerivations(drvs); + stopWork(); + writeInt(1, to); + break; + } + + case wopEnsurePath: { + Path path = readStorePath(from); + startWork(); + store->ensurePath(path); + stopWork(); + writeInt(1, to); + break; + } + + case wopAddTempRoot: { + Path path = readStorePath(from); + startWork(); + store->addTempRoot(path); + stopWork(); + writeInt(1, to); + break; + } + + case wopAddIndirectRoot: { + Path path = absPath(readString(from)); + startWork(); + store->addIndirectRoot(path); + stopWork(); + writeInt(1, to); + break; + } + + case wopSyncWithGC: { + startWork(); + store->syncWithGC(); + stopWork(); + writeInt(1, to); + break; + } + + case wopFindRoots: { + startWork(); + Roots roots = store->findRoots(); + stopWork(); + writeInt(roots.size(), to); + for (Roots::iterator i = roots.begin(); i != roots.end(); ++i) { + writeString(i->first, to); + writeString(i->second, to); + } + break; + } + + case wopCollectGarbage: { + GCAction action = (GCAction) readInt(from); + PathSet pathsToDelete = readStorePaths(from); + bool ignoreLiveness = readInt(from); + + PathSet result; + unsigned long long bytesFreed; + + startWork(); + if (ignoreLiveness) + throw Error("you are not allowed to ignore liveness"); + store->collectGarbage(action, pathsToDelete, ignoreLiveness, + result, bytesFreed); + stopWork(); + + writeStringSet(result, to); + writeInt(bytesFreed & 0xffffffff, to); + writeInt(bytesFreed >> 32, to); + + break; + } + + default: + throw Error(format("invalid operation %1%") % op); + } +} + + +static void processConnection() +{ + canSendStderr = false; + myPid = getpid(); + writeToStderr = tunnelStderr; + + /* Allow us to receive SIGPOLL for events on the client socket. */ + setSigPollAction(false); + if (fcntl(from.fd, F_SETOWN, getpid()) == -1) + throw SysError("F_SETOWN"); + if (fcntl(from.fd, F_SETFL, fcntl(from.fd, F_GETFL, 0) | FASYNC) == -1) + throw SysError("F_SETFL"); + + /* Exchange the greeting. */ + unsigned int magic = readInt(from); + if (magic != WORKER_MAGIC_1) throw Error("protocol mismatch"); + verbosity = (Verbosity) readInt(from); + writeInt(WORKER_MAGIC_2, to); + + /* Send startup error messages to the client. */ + startWork(); + + try { + + /* Prevent users from doing something very dangerous. */ + if (geteuid() == 0 && + querySetting("build-users-group", "") == "") + throw Error("if you run `nix-worker' as root, then you MUST set `build-users-group'!"); + + /* Open the store. */ + store = boost::shared_ptr(new LocalStore(true)); + + stopWork(); + + } catch (Error & e) { + stopWork(false, e.msg()); + return; + } + + /* Process client requests. */ + unsigned int opCount = 0; + + while (true) { + WorkerOp op; + try { + op = (WorkerOp) readInt(from); + } catch (EndOfFile & e) { + break; + } + + opCount++; + + try { + performOp(from, to, op); + } catch (Error & e) { + stopWork(false, e.msg()); + } + + assert(!canSendStderr); + }; + + printMsg(lvlError, format("%1% worker operations") % opCount); +} + + +static void sigChldHandler(int sigNo) +{ + /* Reap all dead children. */ + while (waitpid(-1, 0, WNOHANG) == 0) ; +} + + +static void setSigChldAction(bool autoReap) +{ + struct sigaction act, oact; + act.sa_handler = autoReap ? sigChldHandler : SIG_DFL; + sigfillset(&act.sa_mask); + act.sa_flags = 0; + if (sigaction(SIGCHLD, &act, &oact)) + throw SysError("setting SIGCHLD handler"); +} + + +static void daemonLoop() +{ + /* Get rid of children automatically; don't let them become + zombies. */ + setSigChldAction(true); + + /* Create and bind to a Unix domain socket. */ + AutoCloseFD fdSocket = socket(PF_UNIX, SOCK_STREAM, 0); + if (fdSocket == -1) + throw SysError("cannot create Unix domain socket"); + + string socketPath = nixStateDir + DEFAULT_SOCKET_PATH; + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + if (socketPath.size() >= sizeof(addr.sun_path)) + throw Error(format("socket path `%1%' is too long") % socketPath); + strcpy(addr.sun_path, socketPath.c_str()); + + unlink(socketPath.c_str()); + + /* Make sure that the socket is created with 0666 permission + (everybody can connect). */ + mode_t oldMode = umask(0111); + int res = bind(fdSocket, (struct sockaddr *) &addr, sizeof(addr)); + umask(oldMode); + if (res == -1) + throw SysError(format("cannot bind to socket `%1%'") % socketPath); + + if (listen(fdSocket, 5) == -1) + throw SysError(format("cannot listen on socket `%1%'") % socketPath); + + /* Loop accepting connections. */ + while (1) { + + try { + /* Important: the server process *cannot* open the + Berkeley DB environment, because it doesn't like forks + very much. */ + assert(!store); + + /* Accept a connection. */ + struct sockaddr_un remoteAddr; + socklen_t remoteAddrLen = sizeof(remoteAddr); + + AutoCloseFD remote = accept(fdSocket, + (struct sockaddr *) &remoteAddr, &remoteAddrLen); + checkInterrupt(); + if (remote == -1) + if (errno == EINTR) + continue; + else + throw SysError("accepting connection"); + + printMsg(lvlInfo, format("accepted connection %1%") % remote); + + /* Fork a child to handle the connection. */ + pid_t child; + child = fork(); + + switch (child) { + + case -1: + throw SysError("unable to fork"); + + case 0: + try { /* child */ + + /* Background the worker. */ + if (setsid() == -1) + throw SysError(format("creating a new session")); + + /* Restore normal handling of SIGCHLD. */ + setSigChldAction(false); + + /* Handle the connection. */ + from.fd = remote; + to.fd = remote; + processConnection(); + + } catch (std::exception & e) { + std::cerr << format("child error: %1%\n") % e.what(); + } + exit(0); + } + + } catch (Interrupted & e) { + throw; + } catch (Error & e) { + printMsg(lvlError, format("error processing connection: %1%") % e.msg()); + } + } +} + + +void run(Strings args) +{ + bool slave = false; + bool daemon = false; + + for (Strings::iterator i = args.begin(); i != args.end(); ) { + string arg = *i++; + if (arg == "--slave") slave = true; + if (arg == "--daemon") daemon = true; + } + + if (slave) { + /* This prevents us from receiving signals from the terminal + when we're running in setuid mode. */ + if (setsid() == -1) + throw SysError(format("creating a new session")); + + processConnection(); + } + + else if (daemon) { + if (setuidMode) + throw Error("daemon cannot be started in setuid mode"); + chdir("/"); + daemonLoop(); + } + + else + throw Error("must be run in either --slave or --daemon mode"); +} + + +#include "help.txt.hh" + +void printHelp() +{ + std::cout << string((char *) helpText, sizeof helpText); +} + + +string programId = "nix-worker"; -- cgit 1.4.1