From 9898746ef3732979bf30e9048021b6232ddf15ac Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Nov 2003 17:27:16 +0000 Subject: [PATCH] * nix-env: a tool to manage user environments. * Replace all directory reading code by a generic readDirectory() function. --- configure.ac | 1 + corepkgs/buildenv/builder.pl | 72 +++++++++ corepkgs/buildenv/default.nix | 9 ++ src/Makefile.am | 2 +- src/libexpr/parser.cc | 2 + src/libmain/shared.cc | 1 + src/libstore/db.cc | 4 +- src/libstore/globals.cc | 1 + src/libstore/globals.hh | 3 + src/libstore/references.cc | 12 +- src/libutil/archive.cc | 21 +-- src/libutil/util.cc | 45 +++--- src/libutil/util.hh | 4 + src/nix-env/Makefile.am | 11 ++ src/nix-env/main.cc | 270 ++++++++++++++++++++++++++++++++++ src/nix-store/main.cc | 5 +- 16 files changed, 412 insertions(+), 51 deletions(-) create mode 100755 corepkgs/buildenv/builder.pl create mode 100644 corepkgs/buildenv/default.nix create mode 100644 src/nix-env/Makefile.am create mode 100644 src/nix-env/main.cc diff --git a/configure.ac b/configure.ac index 54a251b23..bc12b9a10 100644 --- a/configure.ac +++ b/configure.ac @@ -36,6 +36,7 @@ AC_CONFIG_FILES([Makefile src/nix-hash/Makefile src/libexpr/Makefile src/nix-instantiate/Makefile + src/nix-env/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile diff --git a/corepkgs/buildenv/builder.pl b/corepkgs/buildenv/builder.pl new file mode 100755 index 000000000..144ad3374 --- /dev/null +++ b/corepkgs/buildenv/builder.pl @@ -0,0 +1,72 @@ +#! /usr/bin/perl -w + +use strict; +use Cwd; + +my $selfdir = $ENV{"out"}; +mkdir "$selfdir", 0755 || die "error creating $selfdir"; + +# For each activated package, create symlinks. + +sub createLinks { + my $srcdir = shift; + my $dstdir = shift; + + my @srcfiles = glob("$srcdir/*"); + + foreach my $srcfile (@srcfiles) { + my $basename = $srcfile; + $basename =~ s/^.*\///g; # strip directory + my $dstfile = "$dstdir/$basename"; + if ($srcfile =~ /\/envpkgs$/) { + } elsif (-d $srcfile) { + # !!! hack for resolving name clashes + if (!-e $dstfile) { + mkdir $dstfile, 0755 || + die "error creating directory $dstfile"; + } + -d $dstfile or die "$dstfile is not a directory"; + createLinks($srcfile, $dstfile); + } elsif (-l $dstfile) { + my $target = readlink($dstfile); + die "collission between $srcfile and $target"; + } else { +# print "linking $dstfile to $srcfile\n"; + symlink($srcfile, $dstfile) || + die "error creating link $dstfile"; + } + } +} + +my %done; + +sub addPkg { + my $pkgdir = shift; + + return if (defined $done{$pkgdir}); + $done{$pkgdir} = 1; + +# print "merging $pkgdir\n"; + + createLinks("$pkgdir", "$selfdir"); + +# if (-f "$pkgdir/envpkgs") { +# my $envpkgs = `cat $pkgdir/envpkgs`; +# chomp $envpkgs; +# my @envpkgs = split / +/, $envpkgs; +# foreach my $envpkg (@envpkgs) { +# addPkg($envpkg); +# } +# } +} + +my @args = split ' ', $ENV{"derivations"}; + +while (scalar @args > 0) { + my $drvpath = shift @args; + print "adding $drvpath\n"; + addPkg($drvpath); +} + +symlink($ENV{"manifest"}, "$selfdir/manifest") or die "cannot create manifest"; + diff --git a/corepkgs/buildenv/default.nix b/corepkgs/buildenv/default.nix new file mode 100644 index 000000000..9bc704d8d --- /dev/null +++ b/corepkgs/buildenv/default.nix @@ -0,0 +1,9 @@ +{system, derivations, manifest}: + +derivation { + name = "user-environment"; + system = system; + builder = ./builder.pl; + derivations = derivations; + manifest = manifest; +} diff --git a/src/Makefile.am b/src/Makefile.am index fe8cbf1e3..0f1273426 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,2 @@ SUBDIRS = bin2c boost libutil libstore libmain nix-store nix-hash \ - libexpr nix-instantiate + libexpr nix-instantiate nix-env diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index 22d76c263..e3c863a55 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -68,6 +68,8 @@ struct Cleanup : TermFun Expr parseExprFromFile(Path path) { + assert(path[0] == '/'); + #if 0 /* Perhaps this is already an imploded parse tree? */ Expr e = ATreadFromNamedFile(path.c_str()); diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index b06f5eb8b..632794db3 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -19,6 +19,7 @@ static void initAndRun(int argc, char * * argv) nixStore = NIX_STORE_DIR; nixDataDir = NIX_DATA_DIR; nixLogDir = NIX_LOG_DIR; + nixStateDir = (string) NIX_STATE_DIR; nixDBPath = (string) NIX_STATE_DIR + "/db"; /* Put the arguments in a vector. */ diff --git a/src/libstore/db.cc b/src/libstore/db.cc index 63ec2724f..a97111a3a 100644 --- a/src/libstore/db.cc +++ b/src/libstore/db.cc @@ -166,7 +166,7 @@ void Database::open(const string & path) /* The following code provides automatic recovery of the database environment. Recovery is necessary when a process dies while it has the database open. To detect this, - processes atomically increment a counter when the open the + processes atomically increment a counter when they open the database, and decrement it when they close it. If we see that counter is > 0 but no processes are accessing the database---determined by attempting to obtain a write lock @@ -199,7 +199,7 @@ void Database::open(const string & path) other readers or writers. */ int n = getAccessorCount(fdAccessors); - setAccessorCount(fdAccessors, 1); + setAccessorCount(fdAccessors, 1); if (n != 0) { printMsg(lvlTalkative, diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index a292b49ae..e5d76ff48 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -3,6 +3,7 @@ string nixStore = "/UNINIT"; string nixDataDir = "/UNINIT"; string nixLogDir = "/UNINIT"; +string nixStateDir = "/UNINIT"; string nixDBPath = "/UNINIT"; bool keepFailed = false; diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 1b4d0bde3..3da294cc8 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -16,6 +16,9 @@ extern string nixDataDir; /* !!! fix */ /* nixLogDir is the directory where we log various operations. */ extern string nixLogDir; +/* nixStateDir is the directory where state is stored. */ +extern string nixStateDir; + /* nixDBPath is the path name of our Berkeley DB environment. */ extern string nixDBPath; diff --git a/src/libstore/references.cc b/src/libstore/references.cc index ab743f76d..2bea44131 100644 --- a/src/libstore/references.cc +++ b/src/libstore/references.cc @@ -36,14 +36,10 @@ void checkPath(const string & path, throw SysError(format("getting attributes of path `%1%'") % path); if (S_ISDIR(st.st_mode)) { - AutoCloseDir dir = opendir(path.c_str()); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - search(name, ids, seen); - checkPath(path + "/" + name, ids, seen); + Strings names = readDirectory(path); + for (Strings::iterator i = names.begin(); i != names.end(); i++) { + search(*i, ids, seen); + checkPath(path + "/" + *i, ids, seen); } } diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index ed57df4c9..f605e8b61 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -51,23 +51,12 @@ static void dump(const string & path, DumpSink & sink); static void dumpEntries(const Path & path, DumpSink & sink) { - AutoCloseDir dir = opendir(path.c_str()); - if (!dir) throw SysError("opening directory " + path); + Strings names = readDirectory(path); + vector names2(names.begin(), names.end()); + sort(names2.begin(), names2.end()); - vector names; - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - names.push_back(name); - } - if (errno) throw SysError("reading directory " + path); - - sort(names.begin(), names.end()); - - for (vector::iterator it = names.begin(); - it != names.end(); it++) + for (vector::iterator it = names2.begin(); + it != names2.end(); it++) { writeString("entry", sink); writeString("(", sink); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 299a37f6c..6a032a3f1 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -108,6 +108,25 @@ bool pathExists(const Path & path) } +Strings readDirectory(const Path & path) +{ + Strings names; + + AutoCloseDir dir = opendir(path.c_str()); + if (!dir) throw SysError(format("opening directory `%1%'") % path); + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { /* sic */ + string name = dirent->d_name; + if (name == "." || name == "..") continue; + names.push_back(name); + } + if (errno) throw SysError(format("reading directory `%1%'") % path); + + return names; +} + + void deletePath(const Path & path) { printMsg(lvlVomit, format("deleting path `%1%'") % path); @@ -117,18 +136,7 @@ void deletePath(const Path & path) throw SysError(format("getting attributes of path `%1%'") % path); if (S_ISDIR(st.st_mode)) { - Strings names; - - { - AutoCloseDir dir = opendir(path.c_str()); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - names.push_back(name); - } - } /* scoped to ensure that dir is closed at this point */ + Strings names = readDirectory(path); /* Make the directory writable. */ if (!(st.st_mode & S_IWUSR)) { @@ -136,7 +144,7 @@ void deletePath(const Path & path) throw SysError(format("making `%1%' writable")); } - for (Strings::iterator i = names.begin(); i != names.end(); i++) + for (Strings::iterator i = names.begin(); i != names.end(); ++i) deletePath(path + "/" + *i); } @@ -157,14 +165,9 @@ void makePathReadOnly(const Path & path) } if (S_ISDIR(st.st_mode)) { - AutoCloseDir dir = opendir(path.c_str()); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - makePathReadOnly(path + "/" + name); - } + Strings names = readDirectory(path); + for (Strings::iterator i = names.begin(); i != names.end(); ++i) + makePathReadOnly(path + "/" + *i); } } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index e6b600eff..d0e7b3ada 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -73,6 +73,10 @@ string baseNameOf(const Path & path); /* Return true iff the given path exists. */ bool pathExists(const Path & path); +/* Read the contents of a directory. The entries `.' and `..' are + removed. */ +Strings readDirectory(const Path & path); + /* Delete a path; i.e., in the case of a directory, it is deleted recursively. Don't use this at home, kids. */ void deletePath(const Path & path); diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am new file mode 100644 index 000000000..add54581b --- /dev/null +++ b/src/nix-env/Makefile.am @@ -0,0 +1,11 @@ +bin_PROGRAMS = nix-env + +nix_env_SOURCES = main.cc +nix_env_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ + ../libstore/libstore.a ../libutil/libutil.a \ + ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ + -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm + +AM_CXXFLAGS = \ + -I.. -I../../externals/inst/include -I../libutil -I../libstore \ + -I../libexpr -I../libmain diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc new file mode 100644 index 000000000..4296ce509 --- /dev/null +++ b/src/nix-env/main.cc @@ -0,0 +1,270 @@ +#include "globals.hh" +#include "normalise.hh" +#include "shared.hh" +#include "parser.hh" +#include "eval.hh" + + +typedef void (* Operation) (EvalState & state, + Strings opFlags, Strings opArgs); + + +struct DrvInfo +{ + string name; + Path drvPath; + Path outPath; +}; + +typedef map DrvInfos; + + +bool parseDerivation(EvalState & state, Expr e, DrvInfo & drv) +{ + ATMatcher m; + + e = evalExpr(state, e); + if (!(atMatch(m, e) >> "Attrs")) return false; + Expr a = queryAttr(e, "type"); + if (!a || evalString(state, a) != "derivation") return false; + + a = queryAttr(e, "name"); + if (!a) throw badTerm("derivation name missing", e); + drv.name = evalString(state, a); + + a = queryAttr(e, "drvPath"); + if (!a) throw badTerm("derivation path missing", e); + drv.drvPath = evalPath(state, a); + + a = queryAttr(e, "outPath"); + if (!a) throw badTerm("output path missing", e); + drv.outPath = evalPath(state, a); + + return true; +} + + +bool parseDerivations(EvalState & state, Expr e, DrvInfos & drvs) +{ + e = evalExpr(state, e); + + ATermMap drvMap; + queryAllAttrs(e, drvMap); + + for (ATermIterator i(drvMap.keys()); i; ++i) { + DrvInfo drv; + debug(format("evaluating attribute `%1%'") % *i); + if (parseDerivation(state, drvMap.get(*i), drv)) + drvs[drv.name] = drv; + } + + return true; +} + + +void loadDerivations(EvalState & state, Path nePath, DrvInfos & drvs) +{ + Expr e = parseExprFromFile(absPath(nePath)); + if (!parseDerivations(state, e, drvs)) + throw badTerm("expected set of derivations", e); +} + + +static Path getLinksDir() +{ + return canonPath(nixStateDir + "/links"); +} + + +Path createLink(Path outPath, Path drvPath) +{ + Path linksDir = getLinksDir(); + + unsigned int num = 0; + + Strings names = readDirectory(linksDir); + for (Strings::iterator i = names.begin(); i != names.end(); ++i) { + istringstream s(*i); + unsigned int n; + if (s >> n && s.eof() && n > num) num = n + 1; + } + + Path linkPath = (format("%1%/%2%") % linksDir % num).str(); + + if (symlink(outPath.c_str(), linkPath.c_str()) != 0) + throw SysError(format("creating symlink `%1%'") % linkPath); + + return linkPath; +} + + +void installDerivations(EvalState & state, + Path nePath, Strings drvNames) +{ + debug(format("installing derivations from `%1%'") % nePath); + + /* Fetch all derivations from the input file. */ + DrvInfos availDrvs; + loadDerivations(state, nePath, availDrvs); + + /* Filter out the ones we're not interested in. */ + DrvInfos selectedDrvs; + for (Strings::iterator i = drvNames.begin(); + i != drvNames.end(); ++i) + { + DrvInfos::iterator j = availDrvs.find(*i); + if (j == availDrvs.end()) + throw Error(format("unknown derivation `%1%'") % *i); + else + selectedDrvs[j->first] = j->second; + } + + /* Get the environment builder expression. */ + Expr envBuilder = parseExprFromFile("/home/eelco/nix/corepkgs/buildenv"); /* !!! */ + + /* Construct the whole top level derivation. */ + ATermList inputs = ATempty; + for (DrvInfos::iterator i = selectedDrvs.begin(); + i != selectedDrvs.end(); ++i) + { + ATerm t = ATmake( + "Attrs([" + "Bind(\"type\", Str(\"derivation\")), " + "Bind(\"name\", Str()), " + "Bind(\"drvPath\", Path()), " + "Bind(\"outPath\", Path())" + "])", + i->second.name.c_str(), + i->second.drvPath.c_str(), + i->second.outPath.c_str()); + inputs = ATinsert(inputs, t); + } + + ATerm inputs2 = ATmake("List()", ATreverse(inputs)); + + /* Also write a copy of the list of inputs to the store; we need + it for future modifications of the environment. */ + Path inputsFile = writeTerm(inputs2, "-env-inputs"); + + Expr topLevel = ATmake( + "Call(, Attrs([" + "Bind(\"system\", Str()), " + "Bind(\"derivations\", ), " // !!! redundant + "Bind(\"manifest\", Path())" + "]))", + envBuilder, thisSystem.c_str(), inputs2, inputsFile.c_str()); + + /* Instantiate it. */ + debug(format("evaluating builder expression `%1%'") % topLevel); + DrvInfo topLevelDrv; + if (!parseDerivation(state, topLevel, topLevelDrv)) + abort(); + + /* Realise the resulting store expression. */ + debug(format("realising user environment")); + Path nfPath = normaliseStoreExpr(topLevelDrv.drvPath); + realiseClosure(nfPath); + + /* Switch the current user environment to the output path. */ + debug(format("switching to new user environment")); + Path linkPath = createLink(topLevelDrv.outPath, topLevelDrv.drvPath); +// switchLink(current"), link); +} + + +static void opInstall(EvalState & state, + Strings opFlags, Strings opArgs) +{ + if (opArgs.size() < 1) throw UsageError("Nix expression expected"); + + Path nePath = opArgs.front(); + opArgs.pop_front(); + + installDerivations(state, nePath, + Strings(opArgs.begin(), opArgs.end())); +} + + +static void opQuery(EvalState & state, + Strings opFlags, Strings opArgs) +{ + enum { qName } query = qName; + enum { sInstalled, sAvailable } source = sInstalled; + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--name") query = qName; + else if (*i == "--installed") source = sInstalled; + else if (*i == "--available" || *i == "-f") source = sAvailable; + else throw UsageError(format("unknown flag `%1%'") % *i); + + /* Obtain derivation information from the specified source. */ + DrvInfos drvs; + + switch (source) { + + case sInstalled: + break; + + case sAvailable: { + Path nePath = opArgs.front(); + opArgs.pop_front(); + loadDerivations(state, nePath, drvs); + break; + } + + default: abort(); + } + + /* Perform the specified query on the derivations. */ + switch (query) { + + case qName: { + if (opArgs.size() != 0) throw UsageError("no arguments expected"); + for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) + cout << format("%1%\n") % i->second.name; + break; + } + + default: abort(); + } +} + + +void run(Strings args) +{ + EvalState state; + Strings opFlags, opArgs; + Operation op = 0; + + for (Strings::iterator i = args.begin(); i != args.end(); ++i) { + string arg = *i; + + Operation oldOp = op; + + if (arg == "--install" || arg == "-i") + op = opInstall; + if (arg == "--query" || arg == "-q") + op = opQuery; + else if (arg == "--verbose" || arg == "-v") + verbosity = (Verbosity) ((int) verbosity + 1); + 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"); + + openDB(); + + op(state, opFlags, opArgs); + + printEvalStats(state); +} + + +string programId = "nix-env"; diff --git a/src/nix-store/main.cc b/src/nix-store/main.cc index 0d87db9df..c73de5289 100644 --- a/src/nix-store/main.cc +++ b/src/nix-store/main.cc @@ -251,9 +251,8 @@ void run(Strings args) Strings opFlags, opArgs; Operation op = 0; - for (Strings::iterator it = args.begin(); it != args.end(); ) - { - string arg = *it++; + for (Strings::iterator i = args.begin(); i != args.end(); ++i) { + string arg = *i; Operation oldOp = op;