From 5cd022d6c099c583c0494bdacd06f4eb32661135 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sun, 1 Jun 2014 10:42:56 -0400 Subject: [PATCH 1/2] Add importNative primop 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. --- src/libexpr/local.mk | 2 ++ src/libexpr/primops.cc | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk index b3b4086916..75a0e185e3 100644 --- a/src/libexpr/local.mk +++ b/src/libexpr/local.mk @@ -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. diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 366911b54d..d6ac7c9578 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -15,6 +15,7 @@ #include #include +#include namespace nix { @@ -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) { @@ -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); From d62f46e500958bc97ae6837911e27c20a47cc181 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Tue, 24 Jun 2014 10:50:03 -0400 Subject: [PATCH 2/2] Only add the importNative primop if the allow-arbitrary-code-during-evaluation option is true (default false) --- src/libexpr/primops.cc | 3 ++- src/libstore/globals.cc | 2 ++ src/libstore/globals.hh | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index d6ac7c9578..ff82f36b52 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1368,7 +1368,8 @@ void EvalState::createBaseEnv() mkApp(v, *baseEnv.values[baseEnvDispl - 1], *v2); forceValue(v); addConstant("import", v); - addPrimOp("__importNative", 2, prim_importNative); + if (settings.enableImportNative) + addPrimOp("__importNative", 2, prim_importNative); addPrimOp("__typeOf", 1, prim_typeOf); addPrimOp("isNull", 1, prim_isNull); addPrimOp("__isFunction", 1, prim_isFunction); diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 180344e336..5d359e1281 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -61,6 +61,7 @@ Settings::Settings() envKeepDerivations = false; lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1"; showTrace = false; + enableImportNative = false; } @@ -148,6 +149,7 @@ void Settings::update() get(sshSubstituterHosts, "ssh-substituter-hosts"); get(useSshSubstituter, "use-ssh-substituter"); get(logServers, "log-servers"); + get(enableImportNative, "allow-arbitrary-code-during-evaluation"); string subs = getEnv("NIX_SUBSTITUTERS", "default"); if (subs == "default") { diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 65a6c388b8..8dd59a9c79 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -200,6 +200,9 @@ struct Settings { /* A list of URL prefixes that can return Nix build logs. */ Strings logServers; + /* Whether the importNative primop should be enabled */ + bool enableImportNative; + private: SettingsMap settings, overrides;