diff options
Diffstat (limited to 'src/nix/run.cc')
-rw-r--r-- | src/nix/run.cc | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/src/nix/run.cc b/src/nix/run.cc new file mode 100644 index 000000000000..bcfa74eb5f5f --- /dev/null +++ b/src/nix/run.cc @@ -0,0 +1,104 @@ +#include "command.hh" +#include "common-args.hh" +#include "shared.hh" +#include "store-api.hh" +#include "derivations.hh" +#include "local-store.hh" +#include "finally.hh" + +#if __linux__ +#include <sys/mount.h> +#endif + +using namespace nix; + +struct CmdRun : InstallablesCommand +{ + CmdRun() + { + } + + std::string name() override + { + return "run"; + } + + std::string description() override + { + return "run a shell in which the specified packages are available"; + } + + void run(ref<Store> store) override + { + auto outPaths = buildInstallables(store, false); + + auto store2 = store.dynamic_pointer_cast<LocalStore>(); + + if (store2 && store->storeDir != store2->realStoreDir) { +#if __linux__ + uid_t uid = getuid(); + uid_t gid = getgid(); + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == -1) + throw SysError("setting up a private mount namespace"); + + /* Bind-mount realStoreDir on /nix/store. If the latter + mount point doesn't already exists, we have to create a + chroot environment containing the mount point and bind + mounts for the children of /. Would be nice if we could + use overlayfs here, but that doesn't work in a user + namespace yet (Ubuntu has a patch for this: + https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1478578). */ + if (!pathExists(store->storeDir)) { + // FIXME: Use overlayfs? + + Path tmpDir = createTempDir(); + + createDirs(tmpDir + store->storeDir); + + if (mount(store2->realStoreDir.c_str(), (tmpDir + store->storeDir).c_str(), "", MS_BIND, 0) == -1) + throw SysError(format("mounting ‘%s’ on ‘%s’") % store2->realStoreDir % store->storeDir); + + for (auto entry : readDirectory("/")) { + Path dst = tmpDir + "/" + entry.name; + if (pathExists(dst)) continue; + if (mkdir(dst.c_str(), 0700) == -1) + throw SysError(format("creating directory ‘%s’") % dst); + if (mount(("/" + entry.name).c_str(), dst.c_str(), "", MS_BIND | MS_REC, 0) == -1) + throw SysError(format("mounting ‘%s’ on ‘%s’") % ("/" + entry.name) % dst); + } + + char * cwd = getcwd(0, 0); + if (!cwd) throw SysError("getting current directory"); + Finally freeCwd([&]() { free(cwd); }); + + if (chroot(tmpDir.c_str()) == -1) + throw SysError(format("chrooting into ‘%s’") % tmpDir); + + if (chdir(cwd) == -1) + throw SysError(format("chdir to ‘%s’ in chroot") % cwd); + } else + if (mount(store2->realStoreDir.c_str(), store->storeDir.c_str(), "", MS_BIND, 0) == -1) + throw SysError(format("mounting ‘%s’ on ‘%s’") % store2->realStoreDir % store->storeDir); + + writeFile("/proc/self/setgroups", "deny"); + writeFile("/proc/self/uid_map", (format("%d %d %d") % uid % uid % 1).str()); + writeFile("/proc/self/gid_map", (format("%d %d %d") % gid % gid % 1).str()); +#else + throw Error(format("mounting the Nix store on ‘%s’ is not supported on this platform") % store->storeDir); +#endif + } + + + auto unixPath = tokenizeString<Strings>(getEnv("PATH"), ":"); + for (auto & path : outPaths) + if (pathExists(path + "/bin")) + unixPath.push_front(path + "/bin"); + setenv("PATH", concatStringsSep(":", unixPath).c_str(), 1); + + if (execlp("bash", "bash", nullptr) == -1) + throw SysError("unable to exec ‘bash’"); + } +}; + +static RegisterCommand r1(make_ref<CmdRun>()); |