diff options
Diffstat (limited to 'src/nix.cc')
-rw-r--r-- | src/nix.cc | 341 |
1 files changed, 267 insertions, 74 deletions
diff --git a/src/nix.cc b/src/nix.cc index 0c786527082c..8108c2fca9fc 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,6 +1,10 @@ #include <iostream> +#include <fstream> #include <memory> #include <string> +#include <sstream> +#include <list> +#include <cstdio> #include <unistd.h> #include <sys/stat.h> @@ -15,10 +19,16 @@ using namespace std; #define PKGINFO_PATH "/pkg/sys/var/pkginfo" +static string dbRefs = "refs"; +static string dbInstPkgs = "pkginst"; + + static string prog; static string dbfile = PKGINFO_PATH; +/* Wrapper class that ensures that the database is closed upon + object destruction. */ class Db2 : public Db { public: @@ -81,85 +91,250 @@ void delDB(const string & dbname, const string & key) } -void getPkg(int argc, char * * argv) +/* Verify that a reference is valid (that is, is a MD5 hash code). */ +void checkRef(const string & s) { - string pkg; - string src; - string inst; - string cmd; - int res; + string err = "invalid reference: " + s; + if (s.length() != 32) + throw err; + for (int i = 0; i < 32; i++) { + char c = s[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f'))) + throw err; + } +} - if (argc != 1) - throw string("arguments missing in get-pkg"); - pkg = argv[0]; +/* Compute the MD5 hash of a file. */ +string makeRef(string filename) +{ + char hash[33]; + + FILE * pipe = popen(("md5sum " + filename).c_str(), "r"); + if (!pipe) throw string("cannot execute md5sum"); + + if (fread(hash, 32, 1, pipe) != 1) + throw string("cannot read hash from md5sum"); + hash[32] = 0; + + pclose(pipe); + + checkRef(hash); + return hash; +} - if (queryDB("pkginst", pkg, inst)) { - cout << inst << endl; - return; + +struct Dep +{ + string name; + string ref; + Dep(string _name, string _ref) + { + name = _name; + ref = _ref; } +}; + +typedef list<Dep> DepList; + + +void readPkgDescr(const string & pkgfile, + DepList & pkgImports, DepList & fileImports) +{ + ifstream file; + file.exceptions(ios::badbit); + file.open(pkgfile.c_str()); - cerr << "package " << pkg << " is not yet installed\n"; - - if (!queryDB("pkgsrc", pkg, src)) - throw string("source of package " + string(pkg) + " is not known"); + while (!file.eof()) { + string line; + getline(file, line); - inst = "/pkg/" + pkg; + int n = line.find('#'); + if (n >= 0) line = line.erase(n); - cmd = "rsync -a \"" + src + "\"/ \"" + inst + "\""; + if ((int) line.find_first_not_of(" ") < 0) continue; - res = system(cmd.c_str()); - if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) - throw string("unable to copy sources"); + istringstream str(line); + + string name, op, ref; + str >> name >> op >> ref; + + checkRef(ref); + + if (op == "<-") + pkgImports.push_back(Dep(name, ref)); + else if (op == "=") + fileImports.push_back(Dep(name, ref)); + else throw string("invalid operator " + op); + } +} + + +string getPkg(string pkgref); + + +typedef pair<string, string> EnvPair; +typedef list<EnvPair> Environment; + + +void installPkg(string pkgref) +{ + string pkgfile; + string src; + string path; + string cmd; + string builder; + + if (!queryDB("refs", pkgref, pkgfile)) + throw string("unknown package " + pkgref); + + cerr << "installing package " + pkgref + " from " + pkgfile + "\n"; + + /* Verify that the file hasn't changed. !!! race */ + if (makeRef(pkgfile) != pkgref) + throw string("file " + pkgfile + " is stale"); + + /* Read the package description file. */ + DepList pkgImports, fileImports; + readPkgDescr(pkgfile, pkgImports, fileImports); + + /* Recursively fetch all the dependencies, filling in the + environment as we go along. */ + Environment env; + + for (DepList::iterator it = pkgImports.begin(); + it != pkgImports.end(); it++) + { + cerr << "fetching package dependency " + << it->name << " <- " << it->ref + << endl; + env.push_back(EnvPair(it->name, getPkg(it->ref))); + } + + for (DepList::iterator it = fileImports.begin(); + it != fileImports.end(); it++) + { + cerr << "fetching file dependency " + << it->name << " = " << it->ref + << endl; - if (chdir(inst.c_str())) - throw string("unable to chdir to package directory"); + string file; - /* Prepare for building. Clean the environment so that the - build process does not inherit things it shouldn't. */ - setenv("PATH", "/pkg/sys/bin", 1); + if (!queryDB("refs", it->ref, file)) + throw string("unknown file " + it->ref); - res = system("./buildme"); - if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) + if (makeRef(file) != it->ref) + throw string("file " + file + " is stale"); + + if (it->name == "build") + builder = file; + else + env.push_back(EnvPair(it->name, file)); + } + + if (builder == "") + throw string("no builder specified"); + + /* Construct a path for the installed package. */ + path = "/pkg/" + pkgref; + + /* Create the path. */ + if (mkdir(path.c_str(), 0777)) + throw string("unable to create directory " + path); + + /* Fork a child to build the package. */ + pid_t pid; + switch (pid = fork()) { + + case -1: + throw string("unable to fork"); + + case 0: /* child */ + + /* Go to the build directory. */ + if (chdir(path.c_str())) { + cout << "unable to chdir to package directory\n"; + _exit(1); + } + + /* Fill in the environment. We don't bother freeing the + strings, since we'll exec or die soon anyway. */ + const char * env2[env.size() + 1]; + int i = 0; + for (Environment::iterator it = env.begin(); + it != env.end(); it++, i++) + env2[i] = (new string(it->first + "=" + it->second))->c_str(); + env2[i] = 0; + + /* Execute the builder. This should not return. */ + execle(builder.c_str(), builder.c_str(), 0, env2); + + cout << strerror(errno) << endl; + + cout << "unable to execute builder\n"; + _exit(1); + } + + /* parent */ + + /* Wait for the child to finish. */ + int status; + if (waitpid(pid, &status, 0) != pid) + throw string("unable to wait for child"); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) throw string("unable to build package"); - setDB("pkginst", pkg, inst); + setDB(dbInstPkgs, pkgref, path); +} + - cout << inst << endl; +string getPkg(string pkgref) +{ + string path; + checkRef(pkgref); + while (!queryDB(dbInstPkgs, pkgref, path)) + installPkg(pkgref); + return path; } -void registerPkg(int argc, char * * argv) +string absPath(string filename) { - char * pkg; - char * src; - - if (argc != 2) - throw string("arguments missing in register-pkg"); + if (filename[0] != '/') { + char buf[PATH_MAX]; + if (!getcwd(buf, sizeof(buf))) + throw string("cannot get cwd"); + filename = string(buf) + "/" + filename; + /* !!! canonicalise */ + } + return filename; +} - pkg = argv[0]; - src = argv[1]; - setDB("pkgsrc", pkg, src); +void registerFile(string filename) +{ + filename = absPath(filename); + setDB(dbRefs, makeRef(filename), filename); } /* This is primarily used for bootstrapping. */ -void registerInstalledPkg(int argc, char * * argv) +void registerInstalledPkg(string pkgref, string path) { - string pkg; - string inst; - - if (argc != 2) - throw string("arguments missing in register-installed-pkg"); - - pkg = argv[0]; - inst = argv[1]; - - if (inst == "") - delDB("pkginst", pkg); + checkRef(pkgref); + if (path == "") + delDB(dbInstPkgs, pkgref); else - setDB("pkginst", pkg, inst); + setDB(dbInstPkgs, pkgref, path); +} + + +void initDB() +{ + openDB(dbRefs, false); + openDB(dbInstPkgs, false); } @@ -168,18 +343,29 @@ void run(int argc, char * * argv) string cmd; if (argc < 1) - throw string("command not specified\n"); + throw string("command not specified"); cmd = argv[0]; argc--, argv++; - if (cmd == "get-pkg") - getPkg(argc, argv); - else if (cmd == "register-pkg") - registerPkg(argc, argv); - else if (cmd == "register-installed-pkg") - registerInstalledPkg(argc, argv); - else + if (cmd == "init") { + if (argc != 0) + throw string("init doesn't have arguments"); + initDB(); + } else if (cmd == "getpkg") { + if (argc != 1) + throw string("arguments missing in getpkg"); + string path = getPkg(argv[0]); + cout << path << endl; + } else if (cmd == "reg") { + if (argc != 1) + throw string("arguments missing in reg"); + registerFile(argv[0]); + } else if (cmd == "regpkg") { + if (argc != 2) + throw string("arguments missing in regpkg"); + registerInstalledPkg(argv[0], argv[1]); + } else throw string("unknown command: " + string(cmd)); } @@ -190,31 +376,38 @@ int main(int argc, char * * argv) prog = argv[0]; - while ((c = getopt(argc, argv, "d:")) != EOF) { - - switch (c) { + umask(0022); - case 'd': - dbfile = optarg; - break; + try { - default: - cerr << "unknown option\n"; - break; - } + while ((c = getopt(argc, argv, "d:")) != EOF) { + + switch (c) { - } + case 'd': + dbfile = optarg; + break; - argc -= optind, argv += optind; + default: + throw string("unknown option"); + break; - try { + } + } + + argc -= optind, argv += optind; run(argc, argv); - return 0; + } catch (DbException e) { cerr << "db exception: " << e.what() << endl; return 1; + } catch (exception e) { + cerr << e.what() << endl; + return 1; } catch (string s) { cerr << s << endl; return 1; } + + return 0; } |