Skip to content

Commit

Permalink
Add importNative primop
Browse files Browse the repository at this point in the history
This can be used to import a dynamic shared object and return an
arbitrary value, including new primops. This can be used both to test
new primops without having to recompile nix every time, and to build
specialized primops that probably don't belong upstream (e.g. a function
that calls out to gpg to decrypt a nixops secret as-needed).

The imported function should initialize the Value & as needed. A single
import can define multiple values by creating an attrset or list, of
course.

An example initialization function might look like:

extern "C" void initialize(nix::EvalState & state, nix::Value & v)
{
    v.type = nix::tPrimOp;
    v.primOp = NEW nix::PrimOp(myFun, 1, state.symbols.create("myFun"));
}

Then `builtins.importNative ./example.so "initialize"` will evaluate to
the primop defined in the myFun function.
  • Loading branch information
shlevy committed Jun 17, 2014
1 parent 9d0709e commit 5cd022d
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/libexpr/local.mk
Expand Up @@ -8,6 +8,8 @@ libexpr_SOURCES := $(wildcard $(d)/*.cc) $(d)/lexer-tab.cc $(d)/parser-tab.cc

libexpr_LIBS = libutil libstore libformat

libexpr_LDFLAGS = -ldl

# The dependency on libgc must be propagated (i.e. meaning that
# programs/libraries that use libexpr must explicitly pass -lgc),
# because inline functions in libexpr's header files call libgc.
Expand Down
42 changes: 42 additions & 0 deletions src/libexpr/primops.cc
Expand Up @@ -15,6 +15,7 @@

#include <algorithm>
#include <cstring>
#include <dlfcn.h>


namespace nix {
Expand Down Expand Up @@ -129,6 +130,46 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
}


/* Want reasonable symbol names, so extern C */
/* !!! Should we pass the Pos or the file name too? */
extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);

/* Load a ValueInitializer from a dso and return whatever it initializes */
static void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[0], context);

try {
realiseContext(context);
} catch (InvalidPathError & e) {
throw EvalError(format("cannot import `%1%', since path `%2%' is not valid, at %3%")
% path % e.path % pos);
}

string sym = state.forceStringNoCtx(*args[1], pos);

void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle)
throw EvalError(format("could not open `%1%': %2%") % path % dlerror());

dlerror();
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
if(!func) {
char *message = dlerror();
if (message)
throw EvalError(format("could not load symbol `%1%' from `%2%': %3%") % sym % path % message);
else
throw EvalError(format("symbol `%1%' from `%2%' resolved to NULL when a function pointer was expected")
% sym % path);
}

(func)(state, v);

/* We don't dlclose because v may be a primop referencing a function in the shared object file */
}


/* Return a string representing the type of the expression. */
static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
Expand Down Expand Up @@ -1327,6 +1368,7 @@ void EvalState::createBaseEnv()
mkApp(v, *baseEnv.values[baseEnvDispl - 1], *v2);
forceValue(v);
addConstant("import", v);
addPrimOp("__importNative", 2, prim_importNative);
addPrimOp("__typeOf", 1, prim_typeOf);
addPrimOp("isNull", 1, prim_isNull);
addPrimOp("__isFunction", 1, prim_isFunction);
Expand Down

0 comments on commit 5cd022d

Please sign in to comment.