about summary refs log tree commit diff
path: root/src/nix.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix.cc')
-rw-r--r--src/nix.cc341
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;
 }