#include #include #include #include #include #include #include #include #include #include #include #include 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: Db2(DbEnv *env, u_int32_t flags) : Db(env, flags) { } ~Db2() { close(0); } }; auto_ptr openDB(const string & dbname, bool readonly) { auto_ptr db; db = auto_ptr(new Db2(0, 0)); db->open(dbfile.c_str(), dbname.c_str(), DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); return db; } bool queryDB(const string & dbname, const string & key, string & data) { int err; auto_ptr db = openDB(dbname, true); Dbt kt((void *) key.c_str(), key.length()); Dbt dt; err = db->get(0, &kt, &dt, 0); if (err) return false; data = string((char *) dt.get_data(), dt.get_size()); return true; } void setDB(const string & dbname, const string & key, const string & data) { auto_ptr db = openDB(dbname, false); Dbt kt((void *) key.c_str(), key.length()); Dbt dt((void *) data.c_str(), data.length()); db->put(0, &kt, &dt, 0); } void delDB(const string & dbname, const string & key) { auto_ptr db = openDB(dbname, false); Dbt kt((void *) key.c_str(), key.length()); db->del(0, &kt, 0); } /* Verify that a reference is valid (that is, is a MD5 hash code). */ void checkRef(const string & s) { 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; } } /* 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; } struct Dep { string name; string ref; Dep(string _name, string _ref) { name = _name; ref = _ref; } }; typedef list DepList; void readPkgDescr(const string & pkgfile, DepList & pkgImports, DepList & fileImports) { ifstream file; file.exceptions(ios::badbit); file.open(pkgfile.c_str()); while (!file.eof()) { string line; getline(file, line); int n = line.find('#'); if (n >= 0) line = line.erase(n); if ((int) line.find_first_not_of(" ") < 0) continue; 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 EnvPair; typedef list 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; string file; if (!queryDB("refs", it->ref, file)) throw string("unknown file " + it->ref); 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(dbInstPkgs, pkgref, path); } string getPkg(string pkgref) { string path; checkRef(pkgref); while (!queryDB(dbInstPkgs, pkgref, path)) installPkg(pkgref); return path; } string absPath(string filename) { if (filename[0] != '/') { char buf[PATH_MAX]; if (!getcwd(buf, sizeof(buf))) throw string("cannot get cwd"); filename = string(buf) + "/" + filename; /* !!! canonicalise */ } return filename; } void registerFile(string filename) { filename = absPath(filename); setDB(dbRefs, makeRef(filename), filename); } /* This is primarily used for bootstrapping. */ void registerInstalledPkg(string pkgref, string path) { checkRef(pkgref); if (path == "") delDB(dbInstPkgs, pkgref); else setDB(dbInstPkgs, pkgref, path); } void initDB() { openDB(dbRefs, false); openDB(dbInstPkgs, false); } void run(int argc, char * * argv) { string cmd; if (argc < 1) throw string("command not specified"); cmd = argv[0]; argc--, argv++; 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)); } int main(int argc, char * * argv) { int c; prog = argv[0]; umask(0022); try { while ((c = getopt(argc, argv, "d:")) != EOF) { switch (c) { case 'd': dbfile = optarg; break; default: throw string("unknown option"); break; } } argc -= optind, argv += optind; run(argc, argv); } 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; }