From 5219b5bd8d07527edae97e6b0fa2f838769d7a64 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 22 Dec 2017 00:12:19 +0100 Subject: [PATCH] Add "nix mount-store" command This mounts an arbitrary Nix store on the specified mount point. Typical usage: $ /nix/store/d0am5d8gwh2kfdcgyxh4y684mb5b2v54-blender-2.79/bin/blender --version bash: /nix/store/d0am5d8gwh2kfdcgyxh4y684mb5b2v54-blender-2.79/bin/blender: No such file or directory $ nix mount-store /tmp/mp --store https://cache.nixos.org?local-nar-cache=/tmp/nars $ unshare -m -r $ mount -o bind /tmp/mp /nix/store $ /nix/store/d0am5d8gwh2kfdcgyxh4y684mb5b2v54-blender-2.79/bin/blender --version [after a lot of downloading...] Blender 2.79 (sub 0) One application is to replace the current remote store file access in hydra-server implemented via "nix {cat,ls}-store", which doesn't work all that well (e.g. it doesn't resolve symlinks properly). Another application would be on-demand fetching of build inputs on Hydra build slaves (to speed up builds that don't access their entire closure). However, that will require a lot more machinery. --- local.mk | 2 +- release-common.nix | 2 +- src/nix/local.mk | 2 +- src/nix/mount.cc | 207 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 src/nix/mount.cc diff --git a/local.mk b/local.mk index 4b380176f2e..e130c62605c 100644 --- a/local.mk +++ b/local.mk @@ -6,7 +6,7 @@ dist-files += configure config.h.in nix.spec perl/configure clean-files += Makefile.config -GLOBAL_CXXFLAGS += -I . -I src -I src/libutil -I src/libstore -I src/libmain -I src/libexpr -I src/nix +GLOBAL_CXXFLAGS += -I . -I src -I src/libutil -I src/libstore -I src/libmain -I src/libexpr -I src/nix -D_FILE_OFFSET_BITS=64 $(foreach i, config.h $(call rwildcard, src/lib*, *.hh), \ $(eval $(call install-file-in, $(i), $(includedir)/nix, 0644))) diff --git a/release-common.nix b/release-common.nix index 4c556598526..59ed80dd54f 100644 --- a/release-common.nix +++ b/release-common.nix @@ -57,7 +57,7 @@ rec { git mercurial ] - ++ lib.optionals stdenv.isLinux [libseccomp utillinuxMinimal] + ++ lib.optionals stdenv.isLinux [ libseccomp utillinuxMinimal fuse ] ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) ((aws-sdk-cpp.override { diff --git a/src/nix/local.mk b/src/nix/local.mk index ca4604d566c..f2b0c1fa302 100644 --- a/src/nix/local.mk +++ b/src/nix/local.mk @@ -17,7 +17,7 @@ nix_SOURCES := \ nix_LIBS = libexpr libmain libstore libutil -nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) +nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) -lfuse $(foreach name, \ nix-build nix-channel nix-collect-garbage nix-copy-closure nix-daemon nix-env nix-hash nix-instantiate nix-prefetch-url nix-shell nix-store, \ diff --git a/src/nix/mount.cc b/src/nix/mount.cc new file mode 100644 index 00000000000..befb1bb58fb --- /dev/null +++ b/src/nix/mount.cc @@ -0,0 +1,207 @@ +#include "command.hh" +#include "store-api.hh" +#include "fs-accessor.hh" +#include "nar-accessor.hh" + +#define FUSE_USE_VERSION 30 +#include + +#include + +using namespace nix; + +std::shared_ptr store; +std::shared_ptr accessor; + +static int op_getattr(const char * path_, struct stat * stbuf) +{ + try { + + Path path(path_); + + memset(stbuf, 0, sizeof(struct stat)); + stbuf->st_uid = 0; + stbuf->st_gid = 0; + stbuf->st_nlink = 1; + + if (path == "/") { + stbuf->st_mode = S_IFDIR | 0111; + } else { + auto st = accessor->stat(store->storeDir + path); + + switch (st.type) { + case FSAccessor::tRegular: + stbuf->st_mode = S_IFREG | (st.isExecutable ? 0555 : 0444); + stbuf->st_size = st.fileSize; + break; + case FSAccessor::tSymlink: + stbuf->st_mode = S_IFLNK | 0777; + break; + case FSAccessor::tDirectory: + stbuf->st_mode = S_IFDIR | 0555; + break; + default: + return -ENOENT; + } + } + + return 0; + + } catch (...) { + ignoreException(); + return -EIO; + } +} + +static int op_readdir(const char * path_, void * buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info * fi) +{ + try { + + Path path(path_); + + if (path == "/") + /* FIXME: could use queryAllValidPaths(), but it will be + superslow for binary caches, and won't include name + parts. */ + return 0; + + auto st = accessor->stat(store->storeDir + path); + if (st.type == FSAccessor::tMissing) return -ENOENT; + if (st.type != FSAccessor::tDirectory) return -ENOTDIR; + + for (auto & entry : accessor->readDirectory(store->storeDir + path)) + filler(buf, entry.c_str(), nullptr, 0); + + return 0; + + } catch (...) { + ignoreException(); + return -EIO; + } +} + +static int op_open(const char * path_, struct fuse_file_info * fi) +{ + try { + + Path path(path_); + + auto st = accessor->stat(store->storeDir + path); + if (st.type == FSAccessor::tMissing) return -ENOENT; + if (st.type == FSAccessor::tDirectory) return -EISDIR; + if (st.type != FSAccessor::tRegular) return -EINVAL; + + return 0; + + } catch (...) { + ignoreException(); + return -EIO; + } +} + +static int op_read(const char * path_, char * buf, size_t size, off_t offset, + struct fuse_file_info * fi) +{ + try { + + Path path(path_); + + // FIXME: absolutely need to cache this and/or provide random + // access. + + auto s = accessor->readFile(store->storeDir + path); + + if (offset >= (off_t) s.size()) return 0; + + if (offset + size > s.size()) + size = s.size() - offset; + + memcpy(buf, s.data() + offset, size); + + return size; + + } catch (...) { + ignoreException(); + return -EIO; + } +} + +static int op_readlink(const char * path_, char * buf, size_t size) +{ + try { + + Path path(path_); + + auto st = accessor->stat(store->storeDir + path); + if (st.type == FSAccessor::tMissing) return -ENOENT; + if (st.type != FSAccessor::tSymlink) return -EINVAL; + + auto s = accessor->readLink(store->storeDir + path); + + if (s.size() >= size) return ENAMETOOLONG; // FIXME + + strncpy(buf, s.c_str(), size); + + return 0; + + } catch (...) { + ignoreException(); + return -EIO; + } +} + +struct CmdMountStore : StoreCommand +{ + Path mountPoint; + + CmdMountStore() + { + expectArg("mount-point", &mountPoint); + } + + std::string name() override + { + return "mount-store"; + } + + std::string description() override + { + return "mount a Nix store as a FUSE file system"; + } + + void run(ref store) override + { + ::store = store; + accessor = store->getFSAccessor(); + + Strings fuseArgs = { "nix", mountPoint, "-o", "debug" }; + auto fuseArgs2 = stringsToCharPtrs(fuseArgs); + + struct fuse * fuse; + char * mountpoint; + int multithreaded; + + fuse_operations oper; + memset(&oper, 0, sizeof(oper)); + oper.getattr = op_getattr; + oper.readdir = op_readdir; + oper.open = op_open; + oper.read = op_read; + oper.readlink = op_readlink; + + fuse = fuse_setup(fuseArgs2.size() - 1, fuseArgs2.data(), + &oper, sizeof(oper), + &mountpoint, &multithreaded, nullptr); + if (!fuse) throw Error("FUSE setup failed"); + + if (multithreaded) + fuse_loop_mt(fuse); + else + fuse_loop(fuse); + + fuse_teardown(fuse, mountpoint); + } +}; + +static RegisterCommand r(make_ref());