about summary refs log blame commit diff
path: root/src/nix.cc
blob: 8108c2fca9fc4d2e94529d063d1da55264b76a2a (plain) (tree)
1
2
3
4
5
6
7
                   
                  

                 


                  













                                           



                                     



                                    

                                                              





























































                                                                          

                                                                     
 









                                           
 
 
















                                                             
 








                                  
     










                                                
    


                            
 

                                         
 
                                                            
        






























































                                                                       
 
                    
 

                                                    
 



























































                                                                          

                                                


                                    
 






                                              


 
                               
 








                                                
 
 



                                               



                                               
                                                     
 


                                  
        







                                        







                                 
                                              



                   

















                                                        









                                                        
                
 
         
 


                                                       
 


                                
 


                                               
 



                                       
                        
 


                                                     


                                 



                          

             
 
#include <iostream>
#include <fstream>
#include <memory>
#include <string>
#include <sstream>
#include <list>
#include <cstdio>

#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <db4/db_cxx.h>

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<Db2> openDB(const string & dbname, bool readonly)
{
    auto_ptr<Db2> db;

    db = auto_ptr<Db2>(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<Db2> 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<Db2> 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<Db2> 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<Dep> 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<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;

        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;
}