├── .gitignore ├── LICENSE ├── Makefile.am ├── README.md ├── configure.ac ├── include └── nix-exec.h ├── nix └── unsafe-lib.nix.in ├── scripts └── fetchgit.sh.in └── src ├── fetchgit.cc ├── nix-exec-lib.cc ├── nix-exec.cc ├── nix-exec.hh └── reexec.cc /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | Makefile.in 3 | aclocal.m4 4 | autom4te.cache 5 | compile 6 | config.log 7 | config.status 8 | config.guess 9 | config.sub 10 | configure 11 | depcomp 12 | install-sh 13 | missing 14 | libtool 15 | ltmain.sh 16 | .deps 17 | .dirstamp 18 | .libs 19 | ar-lib 20 | *.o 21 | *.lo 22 | *.la 23 | 24 | nix/unsafe-lib.nix 25 | 26 | scripts/fetchgit.sh 27 | 28 | nix-exec 29 | 30 | nix-exec-*.tar.xz 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Shea Levy 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CXXFLAGS = $(NIX_CFLAGS) -std=c++11 -I$(srcdir)/include -I$(srcdir)/src \ 2 | -D NIXEXEC_DATA_DIR=\"$(datadir)\" -D NIXEXEC_PREFIX=\"$(prefix)\" \ 3 | -D NIXEXEC_PLUGIN_DIR=\"$(nixexecplugindir)\" -DSHREXT=\"$(SHREXT)\" \ 4 | -Wall -Wextra -O3 5 | 6 | pkglib_LTLIBRARIES = libnixexec.la 7 | 8 | libnixexec_la_SOURCES = src/nix-exec-lib.cc src/nix-exec.hh 9 | 10 | bin_PROGRAMS = nix-exec 11 | 12 | nix_exec_SOURCES = src/nix-exec.cc include/nix-exec.h src/nix-exec.hh 13 | nix_exec_LDADD = $(NIX_LIBS) libnixexec.la 14 | 15 | include_HEADERS = include/nix-exec.h 16 | 17 | nixlibdir = $(datadir)/nix 18 | nodist_nixlib_DATA = nix/unsafe-lib.nix 19 | 20 | nixexecplugindir = $(libdir)/nix-exec-plugins 21 | nixexecplugin_LTLIBRARIES = libfetchgit.la libreexec.la 22 | 23 | libfetchgit_la_SOURCES = src/fetchgit.cc 24 | libfetchgit_la_CXXFLAGS = $(AM_CXXFLAGS) -D NIXEXEC_LIBEXEC_DIR=\"$(libexecdir)\" 25 | 26 | libreexec_la_SOURCES = src/reexec.cc 27 | 28 | nodist_libexec_SCRIPTS = scripts/fetchgit.sh 29 | 30 | EXTRA_DIST=LICENSE README.md nix/unsafe-lib.nix.in scripts/fetchgit.sh.in 31 | 32 | SUFFIXES = .in 33 | 34 | .in: 35 | $(sed) \ 36 | -e 's,[@]pkglibdir[@],$(pkglibdir),g' \ 37 | -e 's,[@]SHREXT[@],'"$(SHREXT)"',g' \ 38 | $< >$@ 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nix-exec 2 | ========= 3 | 4 | `nix-exec` is a tool to run programs defined in nix expressions. It has 5 | two major goals: 6 | 7 | * Provide a common framework for defining libraries for tools that require 8 | complex interactions with `nix`, such as `hydra` and `nixops` (and arguably 9 | `nix-env` and `nixos-rebuild`), both to increase ease of development and to 10 | encourage reusability 11 | * Define a basic minimal bootstrapping environment for `nix`-using tools. For 12 | example, using `nixops` currently requires you to install `python` and 13 | several `python` libraries, and `hydra` requires a `perl` web framework. If 14 | both were rewritten to be `nix-exec` programs, a system containing only 15 | `nix-exec` and the top-level scripts for `hydra` and `nixops` could 16 | run either without any futher manual installation. 17 | 18 | `nix-exec` is designed to have a minimal interface to keep it usable in as 19 | wide of a context as possible. 20 | 21 | Invocation 22 | ----------- 23 | 24 | $ nix-exec SCRIPT [ARGS...] 25 | 26 | `nix-exec` is meant to be invoked on a `nix` script, with an optional set of 27 | arguments. Any arguments recognized by `nix` passed before the script name 28 | (such as `--verbose`) will be used to initialize `nix`. If the script name 29 | starts with a `-`, the `--` argument can be used to signify the end of 30 | arguments that should be possibly passed to `nix`. 31 | 32 | `nix-exec` is designed to be usable in a shebang. 33 | 34 | Expression entry point 35 | ----------------------- 36 | 37 | The top-level script should evaluate to a function of a single argument 38 | that returns a `nix-exec` IO value (see below). The argument will be an 39 | attribute set containing a list `args` of the arguments passed to the script 40 | (including the script name) and an attribute set `lib` containing the IO 41 | monad functions and `nix-exec`'s configuration settings. 42 | 43 | IO monad 44 | --------- 45 | 46 | `nix-exec` provides a [monad][1] 47 | for defining programs which it executes. The `lib` argument contains the monad functions: 48 | 49 | * `unit` (AKA `return`) :: a -> m a: Bring a value into the monad 50 | * `map` (AKA `fmap`) :: (a -> b) -> m a -> m b: Apply a function to a monadic 51 | value 52 | * `join` :: m m a -> m a: 'Flatten' a nested monadic value 53 | 54 | For Haskell programmers, note that this is the 55 | ['map and join'][2] 56 | definition of a monad, and that the familiar `>>=` can be defined in terms of 57 | `map` and `join`. 58 | 59 | dlopen 60 | ------- 61 | 62 | In addition, the `nix-exec` `lib` argument contains a `dlopen` function to 63 | allow native code to be executed when running the `IO` value. `dlopen` takes 64 | three arguments, `filename`, `symbol`, and `args`. 65 | 66 | When running a monadic value resulting from a call `dlopen`, `nix-exec` will 67 | dynamically load the file at `filename`, load a `nix::PrimOpFun` from the DSO 68 | at symbol `symbol`, and pass the values in the `args` list to the `PrimOpFun`. 69 | `PrimOpFun` is defined in ``. 70 | 71 | The `filename` argument can be the result of a derivation, in which case 72 | `nix-exec` will build the derivation before trying to dynamically load it. 73 | 74 | Note that the `PrimOpFun` must return a value that is properly forced, i.e. 75 | not a thunk or an un-called function application. 76 | 77 | Configuration settings 78 | ----------------------- 79 | 80 | The `configuration` attribute in the `nix-exec` `lib` argument is a set 81 | containing the following information about the compile-time configuration 82 | of `nix-exec`: 83 | 84 | * `prefix`: The installation prefix 85 | * `datadir`: The data directory 86 | * `version.major`: The major version number 87 | * `version.minor`: The minor version number 88 | * `version.patchlevel`: The version patchlevel. 89 | * `version.prelevel`: If present, the version pre-release level 90 | 91 | unsafe-perform-io 92 | ------------------ 93 | 94 | The `builtins` attribute in the `nix-exec` lib contains contains an 95 | `unsafe-perform-io` attribute that is a function that takes an IO value, runs 96 | it, and returns the produced value. It has largely similar pitfalls to Haskell's 97 | `unsafePerformIO` function. 98 | 99 | fetchgit 100 | --------- 101 | 102 | For bootstrapping purposes, the `builtins` attribute in the `nix-exec` lib 103 | contains a `fetchgit` attribute that is a function that takes a set with the 104 | following arguments: 105 | 106 | * `url`: The URL of the repository 107 | * `rev`: The desired revision 108 | * `fetchSubmodules`: Whether to fetch submodules (default `true`) 109 | * `cache-dir`: The directory to cache repos and archives in (default 110 | `$HOME/.cache/fetchgit`). 111 | 112 | When called, `fetchgit` returns an IO value that, when run, checks out 113 | the given revision of the given git repository into a directory and yields a 114 | `path` pointing to that directory. 115 | 116 | reexec 117 | ------- 118 | 119 | For bootstrapping purposes, the `builtins` attribute in the `nix-exec` lib 120 | contains a `reexec` attribute that is a function that takes a path to a 121 | `nix-exec` binary and returns an IO value that, when run, reexecutes itself with 122 | the passed in path if and only if the path is different than how `nix-exec` was 123 | originally executed, and yields null otherwise. This allows the use of a fixed 124 | version of `nix-exec` and its dependencies (especially `nix`), though of course 125 | the path to `nix-exec` itself must be evaluatable with the host version of 126 | `nix-exec`. 127 | 128 | Global symbols 129 | -------------- 130 | 131 | `nix-exec` defines a number of external variables in the C header 132 | `` to introspect the execution environment: 133 | 134 | * `nixexec_argc`: The number of arguments passed to `nix-exec` 135 | * `nixexec_argv`: A NULL-terminated list of arguments passed to `nix-exec` 136 | 137 | In addition, symbols defined in `libnixmain`, `libnixexpr`, and `libnixstore` 138 | are all available. 139 | 140 | unsafe-lib.nix 141 | ---------------- 142 | 143 | For cases where the expression author doesn't completely control the invocation 144 | of the evaluator (e.g. `nixops` has no way to specify that it should run 145 | `nix-exec`), `nix-exec` installs `unsafe-lib.nix` in `$(datadir)/nix`. Importing 146 | this file evaluates to the `lib` set passed to normal `nix-exec` programs. This 147 | uses `builtins.importNative` under the hood, so it requires the 148 | `allow-unsafe-native-code-during-evaluation` nix option to be set to true. 149 | 150 | Note that when using `unsafe-lib.nix`, `nixexec_argc` will be `0` and 151 | `nixexec_argv` will be `NULL` unless called within an actual `nix-exec` 152 | invocation. 153 | 154 | API stability 155 | -------------- 156 | 157 | The `nix::PrimOpFun` API is not necessarily stable from version to version of 158 | `nix`. As such, scripts should inspect `builtins.nixVersion` to ensure that 159 | loaded dynamic objects are compatible. 160 | 161 | Examples 162 | ------- 163 | 164 | This prints out the arguments passed to it, one per line: 165 | 166 | ```nix 167 | #!/usr/bin/env nix-exec 168 | { args, lib }: let 169 | pkgs = import {}; 170 | 171 | print-args-src = builtins.toFile "print-args.cc" '' 172 | #include 173 | #include 174 | #include 175 | 176 | extern "C" void print(nix::EvalState & state, const nix::Pos & pos, nix::Value ** args, nix::Value & v) { 177 | state.forceList(*args[0], pos); 178 | for (unsigned int index = 0; index < args[0]->list.length; ++index) { 179 | auto str = state.forceStringNoCtx(*args[0]->list.elems[index], pos); 180 | std::cout << str << std::endl; 181 | } 182 | v.type = nix::tNull; 183 | } 184 | ''; 185 | 186 | print-args-so = pkgs.runCommand "print-args.so" {} '' 187 | c++ -shared -fPIC -I${pkgs.nixUnstable}/include/nix -I${pkgs.boehmgc}/include -std=c++11 -O3 ${print-args-src} -o $out 188 | strip -S $out 189 | ''; 190 | 191 | printArgs = args: lib.dlopen print-args-so "print" [ args ]; 192 | in printArgs args 193 | ``` 194 | 195 | Sketch of what `nix-exec`-based `nixops` might look like: 196 | 197 | ```nix 198 | #!/usr/bin/env nix-exec 199 | { args, lib }: let 200 | nixops-src = lib.builtins.fetchgit { url = git://github.com/NixOS/nix-exec.git; rev = "v3.0.4"; }; 201 | 202 | nixops-import = lib.join (lib.map (src: import src lib) nixops-src); 203 | in lib.join (lib.map (nixops: let 204 | lib = nixops.lib.nix-exec; 205 | 206 | processed = nixops.process-args args; 207 | 208 | info = lib.bind processed (args: nixops.query-db args.uuid); 209 | 210 | drv = info: nixops.eval-network info.expr info.args info.nix-path; 211 | 212 | build = info: drv: lib.mapM (host: nixops.build-remote drv.${host} host) info.hosts; 213 | 214 | activate = info: drv: lib.mapM (host: nixops.activate drv.${host} host) info.hosts; 215 | in lib.bind info (info: lib.bind (drv info) (drv: lib.bind (build info drv) (results: 216 | if lib.all-success results then lib.bind (activate info drv) (results: if lib.all-success results then nixops.exit 0 else nixops.exit 1) else nixops.exit 1 217 | )))) nixops-import) 218 | ``` 219 | 220 | [1]: http://en.wikipedia.org/wiki/Monad_(functional_programming) 221 | [2]: http://en.wikipedia.org/wiki/Monad_(functional_programming)#fmap_and_join 222 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([nix-exec], m4_normalize(esyscmd(git describe --tags | cut -c 2-))) 2 | 3 | AM_INIT_AUTOMAKE([foreign -Wall subdir-objects dist-xz no-dist-gzip]) 4 | 5 | AM_PROG_AR 6 | 7 | LT_INIT([disable-static]) 8 | 9 | AC_CONFIG_SRCDIR([LICENSE]) 10 | 11 | AC_LANG([C++]) 12 | 13 | AC_PROG_CXX 14 | 15 | PKG_CHECK_MODULES([NIX], [nix-main nix-expr nix-store]) 16 | 17 | AC_SEARCH_LIBS([dlopen], [dl], [], AC_MSG_ERROR([unable to find the dlopen() function])) 18 | 19 | AC_PATH_PROG([git], git, git) 20 | AC_PATH_PROG([sed], sed, sed) 21 | AC_PATH_PROG([cut], cut, cut) 22 | AC_PATH_PROG([printf], printf, printf) 23 | AC_PATH_PROG([rmdir], rmdir, rmdir) 24 | AC_PATH_PROG([cp], cp, cp) 25 | AC_PATH_PROG([mktemp], mktemp, mktemp) 26 | AC_PATH_PROG([chmod], chmod, chmod) 27 | AC_PATH_PROG([tar], tar, tar) 28 | AC_PATH_PROG([sync], sync, sync) 29 | AC_PATH_PROG([mv], mv, mv) 30 | AC_PATH_PROG([basename], basename, basename) 31 | AC_PATH_PROG([dirname], dirname, dirname) 32 | AC_PATH_PROG([awk], awk, awk) 33 | AC_PATH_PROG([sh], sh, sh) 34 | AC_PATH_PROG([mkdir], mkdir, mkdir) 35 | 36 | AC_SUBST([SHREXT], ["$shrext_cmds"]) 37 | 38 | AC_CONFIG_FILES([Makefile scripts/fetchgit.sh]) 39 | 40 | AC_OUTPUT 41 | -------------------------------------------------------------------------------- /include/nix-exec.h: -------------------------------------------------------------------------------- 1 | extern int nixexec_argc; 2 | extern char ** nixexec_argv; 3 | -------------------------------------------------------------------------------- /nix/unsafe-lib.nix.in: -------------------------------------------------------------------------------- 1 | builtins.importNative @pkglibdir@/libnixexec@SHREXT@ "setup_lib" 2 | -------------------------------------------------------------------------------- /scripts/fetchgit.sh.in: -------------------------------------------------------------------------------- 1 | #!@sh@ -e 2 | 3 | do_submodules() { 4 | repo="$1" 5 | rev="$2" 6 | dir="$3" 7 | 8 | submodules=$( 9 | '@git@' config -zf "$dir"/.gitmodules --get-regexp '^submodule.*.path' | \ 10 | '@sed@' -e 's|^[^\x0]*\x0||g' -e 's|^submodule\.\(.*\)\.path$|\1|g' \ 11 | ) 12 | ( 13 | if [ -z "${IFS+x}" ]; then 14 | IFSUNSET=1 15 | else 16 | unset -v IFSUNSET 17 | fi 18 | OLDIFS="$IFS" 19 | IFS=" 20 | " 21 | for submodule in $submodules; do 22 | if [ -n "$IFSUNSET" ]; then 23 | unset -v IFS 24 | else 25 | IFS="$OLDIFS" 26 | fi 27 | path=$('@git@' config -f "$dir"/.gitmodules --get submodule."$submodule".path) 28 | subrev=$('@git@' --git-dir="$repo" ls-tree "$rev"^{tree} "$path" | '@cut@' -f 3 -d ' ' | '@cut@' -f 1) 29 | subrepo=$('@git@' config -f "$dir"/.gitmodules --get submodule."$submodule".url) 30 | submodules=$('@git@' config -f "$dir"/.gitmodules --get submodule."$submodule".fetchRecurseSubmodules || '@printf@' "true\n") 31 | subarchive=$("$0" "$cache" "$subrepo" "$subrev" "$submodules") 32 | '@rmdir@' "$dir"/"$path" 33 | '@cp@' -RPp "$subarchive" "$dir"/"$path" 34 | done 35 | ) 36 | } 37 | 38 | do_archive() { 39 | repo="$1" 40 | rev="$2" 41 | submodules="$3" 42 | archive="$4" 43 | 44 | dir=$('@mktemp@' -d "$archive".XXXXXX) 45 | '@chmod@' 0755 "$dir" 46 | '@git@' --git-dir="$repo" archive --format=tar "$rev" | 47 | '@tar@' -x -C "$dir" 48 | 49 | if [ "$submodules" = "true" -a -f "$dir"/.gitmodules ]; then 50 | do_submodules "$repo" "$rev" "$dir" 51 | fi 52 | 53 | '@sync@' 54 | if '@mv@' -T "$dir" "$archive"; then 55 | '@chmod@' a-w -R "$archive" 56 | else 57 | [ -d "$dir" ]; 58 | fi 59 | } 60 | 61 | cache="$1" 62 | url="$2" 63 | ish="$3" 64 | submodules="$4" 65 | 66 | base=$('@basename@' "$url") 67 | base=${base%.git} 68 | 69 | repo="$cache"/repos/"$base" 70 | '@mkdir@' -p $('@dirname@' "$repo") 71 | '@git@' --git-dir="$repo" init --bare &>/dev/null 72 | ty=$('@git@' --git-dir="$repo" cat-file -t "$ish" 2>/dev/null || '@printf@' "") 73 | if [ "$ty" != "commit" ]; then 74 | # Try fetching directly (maybe it's a tag?) 75 | '@git@' --git-dir="$repo" fetch "$url" "$ish" || true 76 | ty=$('@git@' --git-dir="$repo" cat-file -t "$ish" 2>/dev/null || '@printf@' "") 77 | if [ "$ty" != "commit" ]; then 78 | # OK, fetch everything 79 | '@git@' --git-dir="$repo" fetch "$url" '+refs/heads/*:refs/remotes/origin/*' --tags 80 | ty=$('@git@' --git-dir="$repo" cat-file -t "$ish") 81 | if [ "$ty" != "commit" ]; then 82 | '@printf@' "$rev is not a commit (it is a $ty)" >&2 83 | exit 1 84 | fi 85 | fi 86 | fi 87 | rev=$('@git@' --git-dir="$repo" rev-parse "$ish") 88 | 89 | archive="$cache"/archives/"$base"/"$submodules"/"$rev"/"$base" 90 | if [ ! -d "$archive" ]; then 91 | '@mkdir@' -p $('@dirname@' "$archive") 92 | do_archive "$repo" "$rev" "$submodules" "$archive" 93 | fi 94 | '@printf@' "$archive" 95 | -------------------------------------------------------------------------------- /src/fetchgit.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | extern "C" { 4 | #include 5 | #include 6 | #include 7 | #include 8 | } 9 | 10 | /* Work around nix's config.h */ 11 | #undef PACKAGE_NAME 12 | #undef PACKAGE_STRING 13 | #undef PACKAGE_TARNAME 14 | #undef PACKAGE_VERSION 15 | #include 16 | #include 17 | #include 18 | 19 | using boost::format; 20 | using nix::EvalError; 21 | using nix::SysError; 22 | using nix::Error; 23 | using nix::Value; 24 | using nix::Path; 25 | 26 | static Path get_default_cache_dir() { 27 | auto home = ::getenv("HOME"); 28 | if (!home) { 29 | errno = 0; 30 | auto pwd = getpwuid(getuid()); 31 | if (pwd) 32 | home = pwd->pw_dir; 33 | else if (errno) 34 | throw SysError("getting password file entry for current user"); 35 | } 36 | 37 | return home ? Path{home} + "/.cache/fetchgit" : "/var/lib/empty/.cache/fetchgit"; 38 | } 39 | 40 | extern "C" void fetchgit( nix::EvalState & state 41 | , const nix::Pos & pos 42 | , Value ** args 43 | , Value & v 44 | ) { 45 | static auto default_cache_dir = get_default_cache_dir(); 46 | 47 | auto cache_sym = state.symbols.create("cache-dir"); 48 | auto url_sym = state.symbols.create("url"); 49 | auto rev_sym = state.symbols.create("rev"); 50 | auto submodules_sym = state.symbols.create("fetchSubmodules"); 51 | 52 | state.forceAttrs(*args[0]); 53 | 54 | auto cache_iter = args[0]->attrs->find(cache_sym); 55 | auto context = nix::PathSet{}; 56 | auto cache_dir = cache_iter == args[0]->attrs->end() ? 57 | default_cache_dir : 58 | state.coerceToPath(*cache_iter->pos, *cache_iter->value, context); 59 | if (!context.empty()) 60 | throw EvalError(format( 61 | "the cache directory is not allowed to refer to a store path (such as `%1%'), at %2%" 62 | ) % *context.begin() % *cache_iter->pos); 63 | 64 | auto url_iter = args[0]->attrs->find(url_sym); 65 | if (url_iter == args[0]->attrs->end()) 66 | throw EvalError(format("required attribute `url' missing, at %1%") % pos); 67 | auto url = state.coerceToString(*url_iter->pos, *url_iter->value, context, false, false); 68 | if (!context.empty()) 69 | throw EvalError(format( 70 | "the url is not allowed to refer to a store path (such as `%1%'), at %2%" 71 | ) % *context.begin() % *url_iter->pos); 72 | 73 | auto rev_iter = args[0]->attrs->find(rev_sym); 74 | if (rev_iter == args[0]->attrs->end()) 75 | throw EvalError(format("required attribute `rev' missing, at %1%") % pos); 76 | auto rev = state.forceStringNoCtx(*rev_iter->value, *rev_iter->pos); 77 | 78 | auto submodules_iter = args[0]->attrs->find(submodules_sym); 79 | auto do_submodules = submodules_iter == args[0]->attrs->end() ? 80 | true : 81 | state.forceBool(*submodules_iter->value, *submodules_iter->pos); 82 | 83 | constexpr char fetchgit_path[] = NIXEXEC_LIBEXEC_DIR "/fetchgit.sh"; 84 | const char * const argv[] = { fetchgit_path 85 | , cache_dir.c_str() 86 | , url.c_str() 87 | , rev.c_str() 88 | , do_submodules ? "true" : "false" 89 | , nullptr 90 | }; 91 | auto pipe = nix::Pipe(); 92 | pipe.create(); 93 | 94 | auto child = fork(); 95 | switch (child) { 96 | case -1: 97 | throw SysError("forking to run fetchgit"); 98 | case 0: 99 | pipe.readSide = -1; 100 | if (dup2(pipe.writeSide.get(), STDOUT_FILENO) == -1) 101 | err(214, "duping pipe to stdout"); 102 | /* const-correct, execv doesn't modify it c just has dumb casting rules */ 103 | execv(fetchgit_path, const_cast(argv)); 104 | err(212, "executing %s", fetchgit_path); 105 | } 106 | pipe.writeSide = -1; 107 | auto path = nix::drainFD(pipe.readSide.get()); 108 | 109 | int status; 110 | errno = 0; 111 | while (waitpid(child, &status, 0) == -1 && errno == EINTR); 112 | if (errno && errno != EINTR) 113 | throw SysError("waiting for fetchgit"); 114 | if (WIFEXITED(status)) { 115 | auto code = WEXITSTATUS(status); 116 | if (code) 117 | throw Error(format("fetchgit exited with non-zero exit code %1%") % code); 118 | } else if (WIFSIGNALED(status)) 119 | throw Error(format("fetchgit killed by signal %1%") % strsignal(WTERMSIG(status))); 120 | else 121 | throw Error("fetchgit died in unknown manner"); 122 | 123 | nix::mkPath(v, path.c_str()); 124 | } 125 | -------------------------------------------------------------------------------- /src/nix-exec-lib.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | extern "C" { 5 | #include 6 | } 7 | 8 | /* Work around nix's config.h */ 9 | #undef PACKAGE_NAME 10 | #undef PACKAGE_STRING 11 | #undef PACKAGE_TARNAME 12 | #undef PACKAGE_VERSION 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #if HAVE_BOEHMGC 19 | #include 20 | #define NEW new (UseGC) 21 | #else 22 | #define NEW new 23 | #endif 24 | 25 | #include "nix-exec.hh" 26 | 27 | int nixexec_argc; 28 | char ** nixexec_argv; 29 | 30 | using nix::Value; 31 | using nix::Pos; 32 | using nix::EvalState; 33 | using boost::format; 34 | using std::string; 35 | 36 | /* In order to achieve a tail-recursive implementation of run_io, we pass a 37 | * stack of nix function values around internally. The implementation below is 38 | * the moral equivalent of something like: 39 | * 40 | * call x [] = x 41 | * call x (Func f : fs) = call (f x) fs 42 | * call x (Run : fs) = run x fs 43 | * 44 | * run (Unit a) fs = call a fs 45 | * run (Map f ma) fs = run ma (Func f : fs) 46 | * run (Join mma) fs = run mma (Run : fs) 47 | * run (Dlopen path sym args) fs = run (Unit (runNativeCode path sym args)) fs 48 | */ 49 | 50 | struct fn_stack_elem { 51 | Value * fun; 52 | const Pos & pos; 53 | fn_stack_elem(Value * fun, const Pos & pos) : fun(fun), pos(pos) {}; 54 | }; 55 | 56 | typedef std::stack fn_stack; 57 | 58 | class io_value : public nix::ExternalValueBase { 59 | string showType() const override { 60 | return "a nix-exec IO value"; 61 | }; 62 | 63 | string typeOf() const override { 64 | return "nix-exec-io"; 65 | }; 66 | 67 | public: 68 | virtual void run(EvalState & state, fn_stack & fns, Value & v) = 0; 69 | }; 70 | 71 | static io_value & force_io_value( EvalState & state 72 | , Value & v 73 | , const Pos & pos 74 | ) { 75 | state.forceValue(v); 76 | io_value * val; 77 | auto is_io = v.type == nix::tExternal 78 | && (val = dynamic_cast(v.external)); 79 | if (!is_io) 80 | nix::throwTypeError("value is %1% while a nix-exec IO value was expected, at %2%", v, pos); 81 | return *val; 82 | } 83 | 84 | static void apply_fns( EvalState & state 85 | , fn_stack & fns 86 | , Value * arg 87 | , Value & v 88 | ) { 89 | assert(!fns.empty()); 90 | while (true) { 91 | auto elem = fns.top(); 92 | fns.pop(); 93 | if (elem.fun) { 94 | if (fns.empty()) 95 | return state.callFunction(*elem.fun, *arg, v, elem.pos); 96 | auto & res = *state.allocValue(); 97 | state.callFunction(*elem.fun, *arg, res, elem.pos); 98 | arg = &res; 99 | } else { 100 | return force_io_value(state, *arg, elem.pos).run(state, fns, v); 101 | } 102 | } 103 | } 104 | 105 | class unit_value : public io_value { 106 | Value & a; 107 | 108 | std::ostream & print(std::ostream & str) const override { 109 | return str << "nix-exec-lib.unit (" << a << ")"; 110 | }; 111 | 112 | void run(EvalState & state, fn_stack & fns, Value & v) override { 113 | if (fns.empty()) { 114 | state.forceValue(a); 115 | v = a; 116 | } else { 117 | return apply_fns(state, fns, &a, v); 118 | } 119 | }; 120 | 121 | size_t valueSize(std::set & seen) const override { 122 | size_t res = sizeof *this; 123 | if (seen.find(&a) == seen.end()) { 124 | seen.insert(&a); 125 | /* Overestimates, since valueSize doesn't take the seen set. Oh well */ 126 | res += nix::valueSize(a); 127 | } 128 | return res; 129 | }; 130 | 131 | /* Should we have operator==? There's no general way to write it such that 132 | * (unit x) == (map id x), so for now no 133 | */ 134 | 135 | public: 136 | unit_value(Value & a) : a(a) {}; 137 | }; 138 | 139 | class map_value : public io_value { 140 | Value & f; 141 | const Pos & pos; 142 | Value & ma_val; 143 | 144 | std::ostream & print(std::ostream & str) const override { 145 | return str << "nix-exec-lib.map (" << f << ") (" << ma_val <<")"; 146 | }; 147 | 148 | void run(EvalState & state, fn_stack & fns, Value & v) override { 149 | state.forceFunction(f, pos); 150 | fns.emplace(&f, pos); 151 | auto & ma = force_io_value(state, ma_val, pos); 152 | if (nix::settings.showTrace) { 153 | try { 154 | return ma.run(state, fns, v); 155 | } catch (nix::Error & e) { 156 | if (f.type == nix::tLambda) { 157 | e.addPrefix( format("while mapping %1% over %2%, at %3%:\n") 158 | % f.lambda.fun->showNamePos() 159 | % ma 160 | % pos 161 | ); 162 | } else { 163 | auto op = &f; 164 | while (op->type == nix::tPrimOpApp) 165 | op = op->primOpApp.left; 166 | e.addPrefix( format("while mapping primop %1% over %2%, at %3%:\n") 167 | % op->primOp->name 168 | % ma 169 | % pos 170 | ); 171 | } 172 | throw; 173 | } 174 | } else 175 | return ma.run(state, fns, v); 176 | }; 177 | 178 | size_t valueSize(std::set & seen) const override { 179 | size_t res = sizeof *this; 180 | if (seen.find(&f) == seen.end()) { 181 | seen.insert(&f); 182 | res += nix::valueSize(f); 183 | } 184 | if (seen.find(&pos) == seen.end()) { 185 | seen.insert(&pos); 186 | res += sizeof pos; 187 | } 188 | if (seen.find(&ma_val) == seen.end()) { 189 | seen.insert(&ma_val); 190 | res += nix::valueSize(ma_val); 191 | } 192 | return res; 193 | }; 194 | 195 | public: 196 | map_value(Value & f, Value & ma_val, const Pos & pos) : 197 | f(f), pos(pos), ma_val(ma_val) {}; 198 | }; 199 | 200 | class join_value : public io_value { 201 | const Pos & pos; 202 | Value & mma_val; 203 | 204 | std::ostream & print(std::ostream & str) const override { 205 | return str << "nix-exec-lib.join (" << mma_val << ")"; 206 | }; 207 | 208 | void run(EvalState & state, fn_stack & fns, Value & v) override { 209 | auto & mma = force_io_value(state, mma_val, pos); 210 | fns.emplace(nullptr, pos); 211 | if (nix::settings.showTrace) { 212 | try { 213 | mma.run(state, fns, v); 214 | } catch (nix::Error & e) { 215 | e.addPrefix( format("while joining %1%, at %2%:\n") 216 | % mma 217 | % pos 218 | ); 219 | throw; 220 | } 221 | } else 222 | return mma.run(state, fns, v); 223 | }; 224 | 225 | size_t valueSize(std::set & seen) const override { 226 | size_t res = sizeof *this; 227 | if (seen.find(&mma_val) == seen.end()) { 228 | seen.insert(&mma_val); 229 | res += nix::valueSize(mma_val); 230 | } 231 | if (seen.find(&pos) == seen.end()) { 232 | seen.insert(&pos); 233 | res += sizeof pos; 234 | } 235 | return res; 236 | }; 237 | 238 | public: 239 | join_value(Value & mma_val, const Pos & pos) : pos(pos), mma_val(mma_val) {}; 240 | }; 241 | 242 | class dlopen_value : public io_value { 243 | Value & filename_val; 244 | Value & symbol_val; 245 | Value & args; 246 | const Pos & pos; 247 | 248 | std::ostream & print(std::ostream & str) const override { 249 | return str << "nix-exec-lib.dlopen (" << filename_val << ") (" << symbol_val 250 | << ") (" << args << ")"; 251 | }; 252 | 253 | void run(EvalState & state, fn_stack & fns, Value & v) override { 254 | auto & arg = *state.allocValue(); 255 | { 256 | auto ctx = nix::PathSet{}; 257 | auto filename = state.coerceToString( pos 258 | , filename_val 259 | , ctx 260 | , false 261 | , false 262 | ); 263 | try { 264 | state.realiseContext(ctx); 265 | } catch (nix::InvalidPathError & e) { 266 | throw nix::EvalError(format("cannot dlopen `%1%', since path `%2%' is not valid, at %3%") 267 | % filename % e.path % pos); 268 | } 269 | 270 | auto handle = ::dlopen(filename.c_str(), RTLD_LAZY | RTLD_LOCAL); 271 | if (!handle) 272 | throw nix::EvalError(format("could not open `%1%': %2%") % filename % ::dlerror()); 273 | 274 | auto symbol = state.forceStringNoCtx(symbol_val, pos); 275 | ::dlerror(); 276 | nix::PrimOpFun fn = (nix::PrimOpFun) ::dlsym(handle, symbol.c_str()); 277 | auto err = ::dlerror(); 278 | if (err) 279 | throw nix::EvalError(format("could not load symbol `%1%' from `%2%': %3%") % symbol % filename % err); 280 | 281 | state.forceList(args, pos); 282 | fn(state, pos, args.listElems(), arg); 283 | } 284 | if (fns.empty()) { 285 | v = arg; 286 | } else { 287 | return apply_fns(state, fns, &arg, v); 288 | } 289 | }; 290 | 291 | size_t valueSize(std::set & seen) const override { 292 | auto res = sizeof *this; 293 | if (seen.find(&filename_val) == seen.end()) { 294 | seen.insert(&filename_val); 295 | res += nix::valueSize(filename_val); 296 | } 297 | if (seen.find(&symbol_val) == seen.end()) { 298 | seen.insert(&symbol_val); 299 | res += nix::valueSize(symbol_val); 300 | } 301 | if (seen.find(&args) == seen.end()) { 302 | seen.insert(&args); 303 | res += nix::valueSize(args); 304 | } 305 | if (seen.find(&pos) == seen.end()) { 306 | seen.insert(&pos); 307 | res += sizeof pos; 308 | } 309 | return res; 310 | }; 311 | 312 | public: 313 | dlopen_value( Value & filename_val 314 | , Value & symbol_val 315 | , Value & args 316 | , const Pos & pos 317 | ) : 318 | filename_val(filename_val), symbol_val(symbol_val), args(args), pos(pos) {}; 319 | }; 320 | 321 | void run_io(EvalState & state, Value & arg, const Pos & pos, Value & v) { 322 | fn_stack fns; 323 | return force_io_value(state, arg, pos).run(state, fns, v); 324 | } 325 | 326 | static void unit(EvalState & state, const Pos & pos, Value ** args, Value & v) { 327 | v.type = nix::tExternal; 328 | v.external = NEW unit_value(*args[0]); 329 | } 330 | 331 | static void join(EvalState & state, const Pos & pos, Value ** args, Value & v) { 332 | v.type = nix::tExternal; 333 | v.external = NEW join_value(*args[0], pos); 334 | } 335 | 336 | static void map(EvalState & state, const Pos & pos, Value ** args, Value & v) { 337 | v.type = nix::tExternal; 338 | v.external = NEW map_value(*args[0], *args[1], pos); 339 | } 340 | 341 | static void prim_dlopen( EvalState & state 342 | , const Pos & pos 343 | , Value ** args 344 | , Value & v 345 | ) { 346 | v.type = nix::tExternal; 347 | v.external = NEW dlopen_value(*args[0], *args[1], *args[2], pos); 348 | } 349 | 350 | struct exploded_version { 351 | nix::NixInt major; 352 | nix::NixInt minor; 353 | nix::NixInt patch; 354 | size_t pre_off; 355 | }; 356 | 357 | enum class acc_tag { major, minor, patch }; 358 | 359 | static constexpr nix::NixInt char_to_digit(char c) { 360 | return c == '0' ? 361 | 0 : 362 | (c == '1' ? 363 | 1 : 364 | (c == '2' ? 365 | 2 : 366 | (c == '3' ? 367 | 3 : 368 | (c == '4' ? 369 | 4 : 370 | (c == '5' ? 371 | 5 : 372 | (c == '6' ? 373 | 6 : 374 | (c == '7' ? 375 | 7 : 376 | (c == '8' ? 377 | 8 : 378 | (c == '9' ? 379 | 9 : 380 | throw std::domain_error("invalid char in version string")))))))))); 381 | } 382 | 383 | template static constexpr 384 | exploded_version explode_version_impl( const char(&str)[N] 385 | , size_t off 386 | , exploded_version acc 387 | , acc_tag tag 388 | ) { 389 | /* God I want c++14 constexpr... */ 390 | return off < (N - 1) ? 391 | (tag == acc_tag::major ? 392 | (str[off] == '.' ? 393 | explode_version_impl( str 394 | , off + 1 395 | , exploded_version{acc.major, 0, -1, 0} 396 | , acc_tag::minor 397 | ) : 398 | explode_version_impl( str 399 | , off + 1 400 | , exploded_version{ 10 * acc.major + char_to_digit( 401 | str[off] 402 | ) 403 | , -1 404 | , -1 405 | , 0 406 | } 407 | , tag 408 | ) 409 | ) : 410 | (tag == acc_tag::minor ? 411 | (str[off] == '.' ? 412 | explode_version_impl( str 413 | , off + 1 414 | , exploded_version{acc.major, acc.minor, 0, 0} 415 | , acc_tag::patch 416 | ) : 417 | explode_version_impl( str 418 | , off + 1 419 | , exploded_version{ acc.major 420 | , 10 * acc.minor + char_to_digit( 421 | str[off] 422 | ) 423 | , -1 424 | , 0 425 | } 426 | , tag 427 | ) 428 | ) : 429 | (tag == acc_tag::patch ? 430 | (str[off] == '-' ? 431 | exploded_version{ acc.major 432 | , acc.minor 433 | , acc.patch 434 | , off 435 | } : 436 | (off + 2 == N ? 437 | exploded_version{ acc.major 438 | , acc.minor 439 | , 10 * acc.patch + char_to_digit(str[off]) 440 | , 0 441 | } : 442 | explode_version_impl( str 443 | , off + 1 444 | , exploded_version{ acc.major 445 | , acc.minor 446 | , 10 * acc.patch + char_to_digit( 447 | str[off] 448 | ) 449 | , 0 450 | } 451 | , tag 452 | ) 453 | )) : 454 | throw std::domain_error("internal error") 455 | ))) : 456 | throw std::out_of_range("not enough dots in version"); 457 | } 458 | 459 | template 460 | static constexpr exploded_version explode_version(const char(&str)[N]) { 461 | return explode_version_impl( str 462 | , 0 463 | , exploded_version{0, -1, -1, 0} 464 | , acc_tag::major 465 | ); 466 | } 467 | 468 | static void setup_version(EvalState & state, Value & v) { 469 | state.mkAttrs(v, 4); 470 | 471 | constexpr auto version = explode_version(VERSION); 472 | static_assert( version.major > 0 473 | && version.minor != -1 474 | && version.patch != -1 475 | , "invalid exploded version" 476 | ); 477 | 478 | state.mkAttrs(v, version.pre_off == 0 ? 3 : 4); 479 | 480 | auto & major = *state.allocAttr(v, state.symbols.create("major")); 481 | mkInt(major, version.major); 482 | 483 | auto & minor = *state.allocAttr(v, state.symbols.create("minor")); 484 | mkInt(minor, version.minor); 485 | 486 | auto & patch = *state.allocAttr(v, state.symbols.create("patchlevel")); 487 | mkInt(patch, version.patch); 488 | 489 | if (version.pre_off != 0) { 490 | auto & pre = *state.allocAttr(v, state.symbols.create("prelevel")); 491 | mkStringNoCopy(pre, VERSION + version.pre_off + 1); 492 | } 493 | 494 | v.attrs->sort(); 495 | } 496 | 497 | static void setup_config(EvalState & state, Value & v) { 498 | state.mkAttrs(v, 3); 499 | 500 | auto & prefix = *state.allocAttr(v, state.symbols.create("prefix")); 501 | nix::mkPathNoCopy(prefix, NIXEXEC_PREFIX); 502 | 503 | auto & datadir = *state.allocAttr(v, state.symbols.create("datadir")); 504 | nix::mkPathNoCopy(datadir, NIXEXEC_DATA_DIR); 505 | 506 | auto & version = *state.allocAttr(v, state.symbols.create("version")); 507 | setup_version(state, version); 508 | 509 | v.attrs->sort(); 510 | } 511 | 512 | static void unsafe( EvalState & state 513 | , const Pos & pos 514 | , Value ** args 515 | , Value & v 516 | ) { 517 | run_io(state, *args[0], pos, v); 518 | } 519 | 520 | static void setup_builtins(EvalState & state, Value & dlopen_prim, Value & v) { 521 | state.mkAttrs(v, 3); 522 | 523 | auto unsafe_sym = state.symbols.create("unsafe-perform-io"); 524 | auto & unsafe_perform_io = *state.allocAttr(v, unsafe_sym); 525 | unsafe_perform_io.type = nix::tPrimOp; 526 | unsafe_perform_io.primOp = NEW nix::PrimOp(unsafe, 1, unsafe_sym); 527 | 528 | auto fetchgit_expr = state.parseExprFromString( "dlopen: spec: dlopen \"" 529 | NIXEXEC_PLUGIN_DIR 530 | "/libfetchgit" 531 | SHREXT 532 | "\" \"fetchgit\" [ spec ]" 533 | , __FILE__ 534 | ); 535 | auto & fetchgit_fun = *state.allocValue(); 536 | state.eval(fetchgit_expr, fetchgit_fun); 537 | auto & fetchgit = *state.allocAttr(v, state.symbols.create("fetchgit")); 538 | state.callFunction(fetchgit_fun, dlopen_prim, fetchgit, Pos{}); 539 | 540 | auto reexec_expr = state.parseExprFromString( "dlopen: path: dlopen \"" 541 | NIXEXEC_PLUGIN_DIR 542 | "/libreexec" 543 | SHREXT 544 | "\" \"reexec\" [ path ]" 545 | , __FILE__ 546 | ); 547 | auto & reexec_fun = *state.allocValue(); 548 | state.eval(reexec_expr, reexec_fun); 549 | auto & reexec = *state.allocAttr(v, state.symbols.create("reexec")); 550 | state.callFunction(reexec_fun, dlopen_prim, reexec, Pos{}); 551 | 552 | v.attrs->sort(); 553 | } 554 | 555 | extern "C" void setup_lib(EvalState & state, Value & v) { 556 | state.mkAttrs(v, 6); 557 | 558 | auto unit_sym = state.symbols.create("unit"); 559 | auto & unit_prim = *state.allocAttr(v, unit_sym); 560 | unit_prim.type = nix::tPrimOp; 561 | unit_prim.primOp = NEW nix::PrimOp(unit, 1, unit_sym); 562 | 563 | auto join_sym = state.symbols.create("join"); 564 | auto & join_prim = *state.allocAttr(v, join_sym); 565 | join_prim.type = nix::tPrimOp; 566 | join_prim.primOp = NEW nix::PrimOp(join, 1, join_sym); 567 | 568 | auto map_sym = state.symbols.create("map"); 569 | auto & map_prim = *state.allocAttr(v, map_sym); 570 | map_prim.type = nix::tPrimOp; 571 | map_prim.primOp = NEW nix::PrimOp(map, 2, map_sym); 572 | 573 | auto dlopen_sym = state.symbols.create("dlopen"); 574 | auto & dlopen_prim = *state.allocAttr(v, dlopen_sym); 575 | dlopen_prim.type = nix::tPrimOp; 576 | dlopen_prim.primOp = NEW nix::PrimOp(prim_dlopen, 3, dlopen_sym); 577 | 578 | auto & config = *state.allocAttr(v, state.symbols.create("configuration")); 579 | setup_config(state, config); 580 | 581 | auto & builtins = *state.allocAttr(v, state.symbols.create("builtins")); 582 | setup_builtins(state, dlopen_prim, builtins); 583 | 584 | v.attrs->sort(); 585 | } 586 | -------------------------------------------------------------------------------- /src/nix-exec.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* Work around nix's config.h */ 4 | #undef PACKAGE_NAME 5 | #undef PACKAGE_STRING 6 | #undef PACKAGE_TARNAME 7 | #undef PACKAGE_VERSION 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "nix-exec.hh" 14 | 15 | static void setup_args(nix::EvalState & state, nix::Value & args, nix::Strings::difference_type arg_count) { 16 | state.mkList(args, arg_count); 17 | auto elem = args.listElems(); 18 | auto argv = nixexec_argv + (nixexec_argc - arg_count); 19 | do { 20 | *elem = state.allocValue(); 21 | mkStringNoCopy(**elem, *argv); 22 | ++elem; 23 | ++argv; 24 | } while (--arg_count); 25 | } 26 | 27 | static void run() { 28 | nix::initNix(); 29 | nix::initGC(); 30 | 31 | auto search_path = nix::Strings{}; 32 | auto arg_count = nix::Strings::difference_type{0}; 33 | 34 | nix::parseCmdLine(nixexec_argc, nixexec_argv, 35 | [&] (nix::Strings::iterator & arg, const nix::Strings::iterator & end) { 36 | if (*arg == "--help" || *arg == "-h") { 37 | std::cerr << "Usage: " << nixexec_argv[0] << " FILE ARGS..." << std::endl; 38 | throw nix::Exit(); 39 | } else if (*arg == "--version") { 40 | std::cout << nixexec_argv[0] << " " VERSION " (Nix " << nix::nixVersion << ")" << std::endl; 41 | throw nix::Exit(); 42 | } else if (nix::parseSearchPathArg(arg, end, search_path)) { 43 | return true; 44 | } 45 | 46 | if (*arg == "--" && ++arg == end) 47 | --arg; 48 | 49 | arg_count = 1; 50 | while (++arg != end) 51 | arg_count++; 52 | 53 | --arg; 54 | 55 | return true; 56 | }); 57 | 58 | if (arg_count == 0) 59 | throw nix::UsageError("No file given"); 60 | 61 | auto store = nix::openStore(); 62 | auto state = nix::EvalState{search_path, store}; 63 | 64 | auto expr_path = nixexec_argv[nixexec_argc - arg_count]; 65 | 66 | auto expr = state.parseExprFromFile(nix::lookupFileArg(state, expr_path)); 67 | 68 | auto & fn = *state.allocValue(); 69 | 70 | state.eval(expr, fn); 71 | 72 | auto top_pos = nix::Pos{state.symbols.create(expr_path), 1, 1}; 73 | 74 | state.forceFunction(fn, top_pos); 75 | 76 | auto & fn_args = *state.allocValue(); 77 | 78 | state.mkAttrs(fn_args, 2); 79 | 80 | auto & args = *state.allocAttr(fn_args, state.symbols.create("args")); 81 | setup_args(state, args, arg_count); 82 | 83 | auto & lib = *state.allocAttr(fn_args, state.symbols.create("lib")); 84 | setup_lib(state, lib); 85 | 86 | fn_args.attrs->sort(); 87 | 88 | auto & result = *state.allocValue(); 89 | state.callFunction(fn, fn_args, result, top_pos); 90 | 91 | auto & fn_pos = fn.type == nix::tLambda 92 | ? fn.lambda.fun->pos 93 | : top_pos; 94 | nix::Value v; 95 | run_io(state, result, fn_pos, v); 96 | } 97 | 98 | int main(int argc, char ** argv) { 99 | nixexec_argc = argc; 100 | nixexec_argv = argv; 101 | return nix::handleExceptions(argv[0], run); 102 | } 103 | -------------------------------------------------------------------------------- /src/nix-exec.hh: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #include "nix-exec.h" 3 | } 4 | 5 | namespace nix { 6 | class EvalState; 7 | struct Value; 8 | struct Pos; 9 | } 10 | 11 | void run_io( nix::EvalState & state 12 | , nix::Value & arg 13 | , const nix::Pos & pos 14 | , nix::Value & v 15 | ); 16 | 17 | extern "C" void setup_lib(nix::EvalState & state, nix::Value & v); 18 | -------------------------------------------------------------------------------- /src/reexec.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | extern "C" { 4 | #include 5 | } 6 | 7 | /* Work around nix's config.h */ 8 | #undef PACKAGE_NAME 9 | #undef PACKAGE_STRING 10 | #undef PACKAGE_TARNAME 11 | #undef PACKAGE_VERSION 12 | #include 13 | 14 | #include 15 | 16 | using boost::format; 17 | using nix::Value; 18 | 19 | extern "C" void reexec( nix::EvalState & state 20 | , const nix::Pos & pos 21 | , Value ** args 22 | , Value & v 23 | ) { 24 | if (nixexec_argv == NULL) { 25 | throw nix::Error("cannot reexec within unsafe-perform-io"); 26 | } 27 | auto ctx = nix::PathSet{}; 28 | auto filename = state.coerceToString( pos 29 | , *args[0] 30 | , ctx 31 | , false 32 | , false 33 | ); 34 | if (filename == nixexec_argv[0]) { 35 | v.type = nix::tNull; 36 | } else { 37 | try { 38 | state.realiseContext(ctx); 39 | } catch (nix::InvalidPathError & e) { 40 | throw nix::EvalError(format("cannot exec `%1%', since path `%2%' is not valid, at %3%") 41 | % filename % e.path % pos); 42 | } 43 | /* const_cast legal because execvp respects constness */ 44 | auto old_argv0 = nixexec_argv[0]; 45 | nixexec_argv[0] = const_cast(filename.c_str()); 46 | execvp(nixexec_argv[0], nixexec_argv); 47 | nixexec_argv[0] = old_argv0; 48 | throw nix::SysError(format("executing `%1%'") % filename); 49 | } 50 | } 51 | --------------------------------------------------------------------------------