diff --git a/local.mk b/local.mk index 0a2254237..64c26ea1c 100644 --- a/local.mk +++ b/local.mk @@ -7,7 +7,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 +GLOBAL_CXXFLAGS += -I . -I src -I src/libutil -I src/libstore -I src/libmain -I src/libexpr -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.nix b/release.nix index c5c2170f7..590d768fb 100644 --- a/release.nix +++ b/release.nix @@ -79,7 +79,7 @@ let git mercurial ] - ++ lib.optional stdenv.isLinux libseccomp + ++ lib.optionals stdenv.isLinux [ libseccomp fuse ] ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) (aws-sdk-cpp.override { diff --git a/shell.nix b/shell.nix index d6ae5a552..bf43f67c5 100644 --- a/shell.nix +++ b/shell.nix @@ -27,7 +27,7 @@ with import ./release-common.nix { inherit pkgs; }; git mercurial ] - ++ lib.optional stdenv.isLinux libseccomp; + ++ lib.optionals stdenv.isLinux [ libseccomp fuse ]; inherit configureFlags; diff --git a/src/nix/local.mk b/src/nix/local.mk index bddd53b16..371bf618e 100644 --- a/src/nix/local.mk +++ b/src/nix/local.mk @@ -6,4 +6,6 @@ nix_SOURCES := $(wildcard $(d)/*.cc) $(wildcard src/linenoise/*.cpp) nix_LIBS = libexpr libmain libstore libutil libformat +nix_LDFLAGS = -lfuse + $(eval $(call install-symlink, nix, $(bindir)/nix-hash)) diff --git a/src/nix/mount.cc b/src/nix/mount.cc new file mode 100644 index 000000000..befb1bb58 --- /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());