diff options
80 files changed, 3702 insertions, 2248 deletions
diff --git a/Jamroot.jam b/Jamroot.jam index 0edd310..eb3aceb 100644 --- a/Jamroot.jam +++ b/Jamroot.jam @@ -1,42 +1,68 @@ import os ; import slice ; +import testing ; -using gcc : : [ os.environ CXX ] ; +using pkg-config ; +import pkg-config ; variant coverage : debug ; -project - : requirements +project netfs : requirements <define>ICE_CPP11_MAPPING - <define>_FILE_OFFSET_BITS=64 - <cxxflags>"-std=c++17 -fvisibility=hidden -fvisibility-inlines-hidden" - <linkflags>"-Wl,-z,defs,--warn-once,--gc-sections" - <variant>release:<cxxflags>"-flto=3" - <variant>release:<linkflags>"-flto=3" - <variant>debug:<cxxflags>"-W -Wall -Werror -Wextra" - <variant>coverage:<cxxflags>"--coverage" - <variant>coverage:<linkflags>"--coverage" + <cxxstd>23 + <visibility>hidden + <linkflags>"-Wl,-z,defs,--warn-once,--gc-sections" + <variant>release:<lto>on + <variant>debug:<warnings>extra + <variant>debug:<warnings-as-errors>on + <variant>debug:<cflags>-Wnon-virtual-dtor + <variant>debug:<cflags>-Wold-style-cast + <variant>debug:<cflags>-Wcast-align + <variant>debug:<cflags>-Wunused + <variant>debug:<cflags>-Woverloaded-virtual + <toolset>gcc,<variant>debug:<cflags>-Wpedantic + <variant>debug:<cflags>-Wconversion + <variant>debug:<cflags>-Wsign-conversion + <variant>debug:<cflags>-Wnull-dereference + <variant>debug:<cflags>-Wdouble-promotion + <variant>debug:<cflags>-Wformat=2 + <variant>debug:<cflags>-Wshadow + <toolset>gcc,<variant>debug:<cflags>-Wduplicated-cond + <toolset>gcc,<variant>debug:<cflags>-Wduplicated-branches + <toolset>gcc,<variant>debug:<cflags>-Wlogical-op + <toolset>gcc,<variant>debug:<cflags>-Wuseless-cast + <variant>coverage:<coverage>on + <toolset>tidy:<enable>all + <toolset>tidy:<define>ICE_IGNORE_VERSION + <toolset>tidy:<checkxx>boost-* + <toolset>tidy:<checkxx>bugprone-* + <toolset>tidy:<xcheckxx>bugprone-easily-swappable-parameters + <toolset>tidy:<checkxx>clang-* + <toolset>tidy:<checkxx>misc-* + <toolset>tidy:<checkxx>modernize-* + <toolset>tidy:<xcheckxx>modernize-use-trailing-return-type + <toolset>tidy:<xcheckxx>misc-non-private-member-variables-in-classes + <toolset>tidy:<checkxx>hicpp-* + <toolset>tidy:<xcheckxx>hicpp-vararg + <toolset>tidy:<xcheckxx>hicpp-signed-bitwise + <toolset>tidy:<xcheckxx>hicpp-no-array-decay + <toolset>tidy:<xcheckxx>hicpp-named-parameter + <toolset>tidy:<checkxx>performance-* + <toolset>tidy:<exclude>netfs/daemon/bin/tidy/debug/checker-none/cxxstd-20-iso/daemonConfig.h + <toolset>tidy:<exclude>netfs/fuse/bin/tidy/debug/checker-none/cxxstd-20-iso/fuseConfig.h + <toolset>tidy:<exclude>netfs/fuse/bin/tidy/debug/checker-none/cxxstd-20-iso/fuseMappers.h + <toolset>tidy:<exclude>netfs/ice/bin/directory.h + <toolset>tidy:<exclude>netfs/ice/bin/exceptions.h + <toolset>tidy:<exclude>netfs/ice/bin/file.h + <toolset>tidy:<exclude>netfs/ice/bin/service.h + <toolset>tidy:<exclude>netfs/ice/bin/types.h + <toolset>tidy:<exclude>netfs/ice/bin/volume.h + <toolset>tidy:<librarydef>std + <toolset>tidy:<librarydef>boost ; build-project netfs ; -# Some useful aliases - -lib glibmm-2.4 ; -lib gobject-2.0 ; -lib glib-2.0 ; -lib sigc-2.0 ; - -alias glibmm : : : : - <include>/usr/include/glibmm-2.4 - <include>/usr/lib/glibmm-2.4/include - <include>/usr/include/glib-2.0 - <include>/usr/lib/glib-2.0/include - <include>/usr/include/sigc++-2.0 - <include>/usr/lib/sigc++-2.0/include - <library>glibmm-2.4 - <library>gobject-2.0 - <library>glib-2.0 - <library>sigc-2.0 - ; +pkg-config.import glibmm : : <name>glibmm-2.68 ; +pkg-config.import fuse : : <name>fuse3 ; diff --git a/netfs/daemon/Jamfile.jam b/netfs/daemon/Jamfile.jam index 4809935..447d2e8 100644 --- a/netfs/daemon/Jamfile.jam +++ b/netfs/daemon/Jamfile.jam @@ -1,24 +1,38 @@ import package ; +obj daemonConfig : daemonConfig.ice : + <toolset>tidy:<checker>none + <library>../ice//netfs-api + <implicit-dependency>../ice//netfs-api +; lib netfsd-configuration : + daemonConfig daemonConfig.ice daemonConfigImpl.cpp : <library>../ice//netfs-api <implicit-dependency>../ice//netfs-api + <implicit-dependency>daemonConfig <library>..//Ice <library>..//stdc++fs <library>..//slicer <library>..//adhocutil - <slicer>yes + <slicer>pure : : <include>. <library>..//Ice <library>..//slicer ; +obj modeCheck : modeCheck.cpp + : + <implicit-dependency>../ice//netfs-api + <use>../ice//netfs-api + <use>../lib//netfs-common + ; lib netfsd++11 : - [ glob *.cpp : daemonConfigImpl.cpp ] + [ glob *.cpp : modeCheck.cpp daemonConfigImpl.cpp ] + modeCheck : <implicit-dependency>../ice//netfs-api <implicit-dependency>netfsd-configuration diff --git a/netfs/daemon/daemon.cpp b/netfs/daemon/daemon.cpp index 27dfbb9..831c409 100644 --- a/netfs/daemon/daemon.cpp +++ b/netfs/daemon/daemon.cpp @@ -1,15 +1,11 @@ -#include <Ice/Ice.h> #include "daemon.h" #include "daemonService.h" #include "daemonVolume.h" -#include <slicer/slicer.h> #include "modeCheck.h" -#include <sys/stat.h> +#include <Ice/Ice.h> #include <safeMapFind.h> - -NetFSDaemon::NetFSDaemon() -{ -} +#include <slicer/slicer.h> +#include <sys/stat.h> NetFSDaemon::~NetFSDaemon() { @@ -24,8 +20,11 @@ NetFSDaemon::hostname() auto props = ic->getProperties(); auto hostNameOverride = props->getProperty("NetFSD.HostNameOverride"); if (hostNameOverride.empty()) { - char buf[128]; - gethostname(buf, sizeof(buf)); + std::string buf(128, 0); + if (gethostname(buf.data(), buf.size())) { + throw NetFS::Daemon::HostNotConfigured(strerror(errno)); + } + buf.resize(strlen(buf.data())); return buf; } return hostNameOverride; @@ -33,9 +32,9 @@ NetFSDaemon::hostname() // name = NetFSDaemonAdapter void -NetFSDaemon::start(const std::string & name, const Ice::CommunicatorPtr & ic, const Ice::StringSeq&) +NetFSDaemon::start(const std::string & name, const Ice::CommunicatorPtr & c, const Ice::StringSeq &) { - this->ic = ic; + ic = c; Ice::PropertiesPtr props = ic->getProperties(); LoadConfiguration(props->getProperty("NetFSD.ConfigPath")); @@ -67,11 +66,11 @@ NetFSDaemon::stop() } extern "C" { - DLL_PUBLIC - IceBox::Service * - createNetFSDaemon(Ice::CommunicatorPtr) - { - return new NetFSDaemon(); - } +DLL_PUBLIC +IceBox::Service * +// NOLINTNEXTLINE(performance-unnecessary-value-param) +createNetFSDaemon(Ice::CommunicatorPtr) +{ + return new NetFSDaemon(); +} } - diff --git a/netfs/daemon/daemon.h b/netfs/daemon/daemon.h index 57e2023..353c89b 100644 --- a/netfs/daemon/daemon.h +++ b/netfs/daemon/daemon.h @@ -1,32 +1,30 @@ -#ifndef DAEMON_H -#define DAEMON_H +#pragma once #include <Ice/Ice.h> #include <IceBox/IceBox.h> +#include <c++11Helpers.h> #include <daemonConfig.h> #include <filesystem> #include <visibility.h> class DLL_PUBLIC NetFSDaemon : public IceBox::Service { - public: - NetFSDaemon(); - virtual ~NetFSDaemon(); +public: + NetFSDaemon() = default; + SPECIAL_MEMBERS_MOVE_RO(NetFSDaemon); + ~NetFSDaemon() override; - virtual void start(const std::string&, const Ice::CommunicatorPtr&, const Ice::StringSeq&) override; - virtual void stop() override; + void start(const std::string &, const Ice::CommunicatorPtr &, const Ice::StringSeq &) override; + void stop() override; - protected: - virtual NetFS::Daemon::ConfigurationPtr ReadConfiguration(const std::filesystem::path & path) const; +protected: + [[nodiscard]] virtual NetFS::Daemon::ConfigurationPtr ReadConfiguration(const std::filesystem::path & path) const; - private: - void LoadConfiguration(const std::filesystem::path & path); +private: + void LoadConfiguration(const std::filesystem::path & path); - Ice::CommunicatorPtr ic; - Ice::ObjectAdapterPtr adapter; - NetFS::Daemon::RuntimeConfigurationPtr dc; + Ice::CommunicatorPtr ic; + Ice::ObjectAdapterPtr adapter; + NetFS::Daemon::RuntimeConfigurationPtr dc; - std::string hostname(); + std::string hostname(); }; - -#endif - diff --git a/netfs/daemon/daemonConfigImpl.cpp b/netfs/daemon/daemonConfigImpl.cpp index 48b1d0e..e45cf97 100644 --- a/netfs/daemon/daemonConfigImpl.cpp +++ b/netfs/daemon/daemonConfigImpl.cpp @@ -1,5 +1,5 @@ -#include <daemonConfig.h> #include <compileTimeFormatter.h> +#include <daemonConfig.h> AdHocFormatter(HostNotConfiguredMsg, "This host [%?] is not defined in the configuration"); void @@ -7,4 +7,3 @@ NetFS::Daemon::HostNotConfigured::ice_print(std::ostream & s) const { HostNotConfiguredMsg::write(s, hostName); } - diff --git a/netfs/daemon/daemonDirectory.cpp b/netfs/daemon/daemonDirectory.cpp index b03bd1f..5412bbe 100644 --- a/netfs/daemon/daemonDirectory.cpp +++ b/netfs/daemon/daemonDirectory.cpp @@ -1,21 +1,13 @@ +#include "daemonDirectory.h" #include <Ice/ObjectAdapter.h> +#include <cerrno> #include <dirent.h> -#include <errno.h> +#include <fcntl.h> #include <map> #include <sys/stat.h> #include <sys/types.h> -#include <fcntl.h> -#include "daemonDirectory.h" -DirectoryServer::DirectoryServer(DIR * d, EntryTypeConverter & t) : - EntryTypeConverter(t), - od(d) -{ -} - -DirectoryServer::~DirectoryServer() -{ -} +DirectoryServer::DirectoryServer(DIR * d, EntryTypeConverter & t) : EntryTypeConverter(t), od(d) { } void DirectoryServer::close(const Ice::Current & ice) @@ -30,34 +22,29 @@ DirectoryServer::close(const Ice::Current & ice) } NetFS::NameList -DirectoryServer::readdir(const Ice::Current&) +DirectoryServer::readdir(const Ice::Current &) { errno = 0; NetFS::NameList list; while (dirent * d = ::readdir(od)) { - if (errno) { - // LCOV_EXCL_START - throw NetFS::SystemError(errno); - // LCOV_EXCL_STOP - } list.emplace_back(d->d_name); } + if (errno) { + // LCOV_EXCL_START + throw NetFS::SystemError(errno); + // LCOV_EXCL_STOP + } return list; } NetFS::DirectoryContents -DirectoryServer::listdir(const Ice::Current&) +DirectoryServer::listdir(const Ice::Current &) { errno = 0; NetFS::DirectoryContents list; int fd = dirfd(od); while (dirent * d = ::readdir(od)) { - if (errno) { - // LCOV_EXCL_START - throw NetFS::SystemError(errno); - // LCOV_EXCL_STOP - } - struct stat s; + struct stat s { }; if (::fstatat(fd, d->d_name, &s, AT_SYMLINK_NOFOLLOW) != 0) { // LCOV_EXCL_START throw NetFS::SystemError(errno); @@ -65,6 +52,10 @@ DirectoryServer::listdir(const Ice::Current&) } list.emplace(d->d_name, convert(s)); } + if (errno) { + // LCOV_EXCL_START + throw NetFS::SystemError(errno); + // LCOV_EXCL_STOP + } return list; } - diff --git a/netfs/daemon/daemonDirectory.h b/netfs/daemon/daemonDirectory.h index 5e549fe..91a0bc2 100644 --- a/netfs/daemon/daemonDirectory.h +++ b/netfs/daemon/daemonDirectory.h @@ -1,24 +1,17 @@ -#ifndef DAEMONDIRECTORY_H -#define DAEMONDIRECTORY_H +#pragma once #include <directory.h> #include <dirent.h> #include <typeConverter.h> -class DirectoryServer : public NetFS::DirectoryV2, EntryTypeConverter { - public: - DirectoryServer(DIR * od, EntryTypeConverter &); - virtual ~DirectoryServer(); +class DirectoryServer : public NetFS::Directory, NetFS::EntryTypeConverter { +public: + DirectoryServer(DIR * od, EntryTypeConverter &); - virtual void close(const Ice::Current&) override; - virtual NetFS::NameList readdir(const Ice::Current&) override; - virtual NetFS::DirectoryContents listdir(const Ice::Current&) override; + void close(const Ice::Current &) override; + NetFS::NameList readdir(const Ice::Current &) override; + NetFS::DirectoryContents listdir(const Ice::Current &) override; - private: - DIR * od; +private: + DIR * od; }; - -#endif - - - diff --git a/netfs/daemon/daemonFile.cpp b/netfs/daemon/daemonFile.cpp index 0df3c2a..cf34b7d 100644 --- a/netfs/daemon/daemonFile.cpp +++ b/netfs/daemon/daemonFile.cpp @@ -1,26 +1,19 @@ +#include "daemonFile.h" #include <Ice/ObjectAdapter.h> -#include <errno.h> -#include <map> +#include <cerrno> +#include <entCache.h> #include <fcntl.h> -#include <typeConverter.h> +#include <map> +#include <numeric.h> #include <sys/stat.h> -#include "daemonFile.h" -#include <entCache.h> +#include <typeConverter.h> +#include <unistd.h> -FileServer::FileServer(int f, EntryTypeConverter & t) : - EntryTypeConverter(t), - fd(f) -{ -} - -FileServer::~FileServer() -{ -} +FileServer::FileServer(int f, EntryTypeConverter & t) : EntryTypeConverter(t), fd(f) { } void -FileServer::ftruncate(const NetFS::ReqEnv re, Ice::Long size, const Ice::Current&) +FileServer::ftruncate(Ice::Long size, const Ice::Current &) { - (void)re; errno = 0; if (::ftruncate(fd, size) != 0) { throw NetFS::SystemError(errno); @@ -28,10 +21,9 @@ FileServer::ftruncate(const NetFS::ReqEnv re, Ice::Long size, const Ice::Current } NetFS::Attr -FileServer::fgetattr(const NetFS::ReqEnv re, const Ice::Current &) +FileServer::fgetattr(const Ice::Current &) { - (void)re; - struct stat s; + struct stat s { }; if (::fstat(fd, &s) != 0) { throw NetFS::SystemError(errno); } @@ -49,27 +41,48 @@ FileServer::close(const Ice::Current & ice) } NetFS::Buffer -FileServer::read(Ice::Long offset, Ice::Long size, const Ice::Current&) +FileServer::read(Ice::Long offset, Ice::Long size, const Ice::Current &) { NetFS::Buffer buf; - buf.resize(size); - errno = 0; - int r = pread(fd, &buf[0], size, offset); - if (r == -1) { - throw NetFS::SystemError(errno); - } - else if (r != size) { - buf.resize(r); + if (size) { + buf.resize(safe {size}); + errno = 0; + auto r = pread(fd, buf.data(), safe {size}, offset); + if (r == -1) { + throw NetFS::SystemError(errno); + } + else if (std::cmp_not_equal(r, size)) { + buf.resize(safe {r}); + } } return buf; } void -FileServer::write(Ice::Long offset, Ice::Long size, const NetFS::Buffer data, const Ice::Current&) +FileServer::write(Ice::Long offset, Ice::Long size, const ::std::pair<const ::Ice::Byte *, const ::Ice::Byte *> data, + const Ice::Current &) { errno = 0; - if (pwrite(fd, &data.front(), size, offset) != size) { + if (pwrite(fd, data.first, safe {size}, offset) != size) { throw NetFS::SystemError(errno); } } +Ice::Long +FileServer::copyrange(NetFS::FilePrxPtr to, Ice::Long offsrc, Ice::Long offdst, Ice::Long size, Ice::Int flags, + const Ice::Current & ice) +{ + if (auto obj = ice.adapter->findByProxy(to); auto file = std::dynamic_pointer_cast<FileServer>(obj)) { + errno = 0; + off_t src = offsrc, dst = offdst; + if (auto rtn = copy_file_range(fd, &src, file->fd, &dst, safe {size}, safe {flags}); rtn != -1) { + return rtn; + } + throw NetFS::SystemError(errno); + } + else { + const auto bytes = read(offsrc, size, ice); + to->write(offdst, size, {bytes.data(), bytes.data() + size}); + return size; + } +} diff --git a/netfs/daemon/daemonFile.h b/netfs/daemon/daemonFile.h index 9a01abf..623de26 100644 --- a/netfs/daemon/daemonFile.h +++ b/netfs/daemon/daemonFile.h @@ -1,25 +1,21 @@ -#ifndef DAEMONFILE_H -#define DAEMONFILE_H +#pragma once #include <file.h> #include <typeConverter.h> -class FileServer : public NetFS::File, EntryTypeConverter { - public: - FileServer(int fd, EntryTypeConverter &); - virtual ~FileServer(); +class FileServer : public NetFS::File, NetFS::EntryTypeConverter { +public: + FileServer(int fd, EntryTypeConverter &); - virtual void close(const Ice::Current&) override; - virtual void ftruncate(const NetFS::ReqEnv, Ice::Long size, const Ice::Current&) override; - virtual NetFS::Attr fgetattr(const NetFS::ReqEnv, const Ice::Current&) override; + void close(const Ice::Current &) override; + void ftruncate(Ice::Long size, const Ice::Current &) override; + NetFS::Attr fgetattr(const Ice::Current &) override; - virtual NetFS::Buffer read(Ice::Long offset, Ice::Long size, const Ice::Current&) override; - virtual void write(Ice::Long offset, Ice::Long size, const NetFS::Buffer data, const Ice::Current&) override; + NetFS::Buffer read(Ice::Long offset, Ice::Long size, const Ice::Current &) override; + void write(Ice::Long offset, Ice::Long size, const ::std::pair<const ::Ice::Byte *, const ::Ice::Byte *> data, + const Ice::Current &) override; + Ice::Long copyrange(NetFS::FilePrxPtr, Ice::Long, Ice::Long, Ice::Long, Ice::Int, const Ice::Current &) override; - private: - const int fd; +private: + const int fd; }; - -#endif - - diff --git a/netfs/daemon/daemonService.cpp b/netfs/daemon/daemonService.cpp index a516cb2..93d48a8 100644 --- a/netfs/daemon/daemonService.cpp +++ b/netfs/daemon/daemonService.cpp @@ -1,21 +1,30 @@ -#include "daemon.h" #include "daemonService.h" +#include "daemon.h" #include "daemonVolume.h" +#include <entCache.h> #include <safeMapFind.h> ServiceServer::ServiceServer(NetFS::Daemon::ConfigurationPtr c) : - config(c) + userLookup(std::make_shared<UserEntCache>()), groupLookup(std::make_shared<GroupEntCache>(userLookup)), + config(std::move(c)) { } NetFS::VolumePrxPtr +// cppcheck-suppress passedByValue; ServiceServer::connect(const std::string share, const std::string authtoken, const Ice::Current & ice) { - auto ex = AdHoc::safeMapLookup<NetFS::ExportNotFound>(config->Exports, share); + const auto & ex = AdHoc::safeMapLookup<NetFS::ExportNotFound>(config->Exports, share); if (!ex->AuthToken.empty() && ex->AuthToken != authtoken) { throw NetFS::AuthError(); } return Ice::uncheckedCast<NetFS::VolumePrx>(ice.adapter->addFacetWithUUID( - std::make_shared<VolumeServer>(ex->RootPath, userLookup, groupLookup), "v01")); + std::make_shared<VolumeServer>(ex->RootPath, userLookup, groupLookup), "v01")); } +NetFS::SettingsPtr +ServiceServer::getSettings(const Ice::Current & ice) +{ + const auto props = ice.adapter->getCommunicator()->getProperties(); + return std::make_shared<NetFS::Settings>(props->getPropertyAsIntWithDefault("Ice.MessageSizeMax", 1024)); +} diff --git a/netfs/daemon/daemonService.h b/netfs/daemon/daemonService.h index d1c77b3..e796dc5 100644 --- a/netfs/daemon/daemonService.h +++ b/netfs/daemon/daemonService.h @@ -1,23 +1,21 @@ -#ifndef DAEMONSERVICE_H -#define DAEMONSERVICE_H +#pragma once -#include <service.h> -#include <entCache.h> #include <daemonConfig.h> -#include <entCache.h> +#include <entries.h> +#include <entryResolver.h> +#include <service.h> class ServiceServer : public NetFS::Service { - public: - ServiceServer(NetFS::Daemon::ConfigurationPtr c); +public: + explicit ServiceServer(NetFS::Daemon::ConfigurationPtr c); - virtual NetFS::VolumePrxPtr connect(const std::string share, const std::string auth, const Ice::Current&) override; + // cppcheck-suppress passedByValue; + NetFS::VolumePrxPtr connect(const std::string share, const std::string auth, const Ice::Current &) override; + NetFS::SettingsPtr getSettings(const Ice::Current &) override; - private: - EntCache<User> userLookup; - EntCache<Group> groupLookup; +private: + EntryResolverPtr<User> userLookup; + EntryResolverPtr<Group> groupLookup; - NetFS::Daemon::ConfigurationPtr config; + NetFS::Daemon::ConfigurationPtr config; }; - -#endif - diff --git a/netfs/daemon/daemonVolume.cpp b/netfs/daemon/daemonVolume.cpp index 2a6cc09..ab2f0fe 100644 --- a/netfs/daemon/daemonVolume.cpp +++ b/netfs/daemon/daemonVolume.cpp @@ -1,29 +1,26 @@ -#include <Ice/ObjectAdapter.h> -#include <errno.h> -#include <map> -#include <unistd.h> -#include <sys/stat.h> -#include <limits.h> -#include <fcntl.h> #include "daemonVolume.h" -#include "daemonFile.h" +#include "daemon.h" #include "daemonDirectory.h" +#include "daemonFile.h" #include "modeCheck.h" +#include <Ice/ObjectAdapter.h> #include <boost/algorithm/string/predicate.hpp> +#include <cerrno> +#include <climits> +#include <defaultMapper.h> #include <entCache.h> -#include "daemon.h" +#include <fcntl.h> +#include <map> +#include <numeric.h> +#include <sys/stat.h> +#include <unistd.h> extern std::map<Ice::Int, int> files; -VolumeServer::VolumeServer(const std::filesystem::path & r, const EntCache<User> & u, const EntCache<Group> & g) : +VolumeServer::VolumeServer( + const std::filesystem::path & r, const EntryResolverPtr<User> & u, const EntryResolverPtr<Group> & g) : root(std::filesystem::canonical(r)), - userLookup(u), - groupLookup(g), - converter(u, g) -{ -} - -VolumeServer::~VolumeServer() + userLookup(*u), groupLookup(*g), converter(std::make_shared<NetFS::Mapping::DefaultMapper>(u, g)) { } @@ -34,11 +31,12 @@ VolumeServer::disconnect(const Ice::Current & ice) } Ice::Int -VolumeServer::access(const NetFS::ReqEnv re, const std::string path, Ice::Int mode, const Ice::Current &) +// cppcheck-suppress passedByValue; +VolumeServer::access(const NetFS::ReqEnv re, std::string path, Ice::Int mode, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); - struct stat s; - std::filesystem::path p(resolvePath(path)); + struct stat s { }; + std::filesystem::path p(resolvePath(std::move(path))); if (::stat(p.c_str(), &s) != 0) { return errno; } @@ -60,11 +58,12 @@ VolumeServer::access(const NetFS::ReqEnv re, const std::string path, Ice::Int mo } NetFS::Attr -VolumeServer::getattr(const NetFS::ReqEnv re, const std::string path, const Ice::Current &) +// cppcheck-suppress passedByValue; +VolumeServer::getattr(const NetFS::ReqEnv re, std::string path, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); - struct stat s; - std::filesystem::path p(resolvePath(path)); + struct stat s { }; + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertReadParent(p); if (::lstat(p.c_str(), &s) != 0) { throw NetFS::SystemError(errno); @@ -73,23 +72,24 @@ VolumeServer::getattr(const NetFS::ReqEnv re, const std::string path, const Ice: } void -VolumeServer::mknod(const NetFS::ReqEnv re, const std::string path, Ice::Int mode, Ice::Int dev, const Ice::Current&) +// cppcheck-suppress passedByValue; +VolumeServer::mknod(const NetFS::ReqEnv re, std::string path, Ice::Int mode, Ice::Int dev, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWriteParent(p); - if (::mknod(p.c_str(), mode, dev) != 0) { + if (::mknod(p.c_str(), safe {mode}, safe {dev}) != 0) { throw NetFS::SystemError(errno); } } void -VolumeServer::symlink(const NetFS::ReqEnv re, const std::string path1, const std::string path2, const Ice::Current &) +VolumeServer::symlink(const NetFS::ReqEnv re, const std::string path1, std::string path2, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path2)); + std::filesystem::path p(resolvePath(std::move(path2))); mc.AssertWriteParent(p); if (::symlink(path1.c_str(), p.c_str()) != 0) { throw NetFS::SystemError(errno); @@ -101,12 +101,13 @@ VolumeServer::symlink(const NetFS::ReqEnv re, const std::string path1, const std } void -VolumeServer::link(const NetFS::ReqEnv re, const std::string path1, const std::string path2, const Ice::Current &) +// cppcheck-suppress passedByValue; +VolumeServer::link(const NetFS::ReqEnv re, std::string path1, std::string path2, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p1(resolvePath(path1)); - std::filesystem::path p2(resolvePath(path2)); + std::filesystem::path p1(resolvePath(std::move(path1))); + std::filesystem::path p2(resolvePath(std::move(path2))); if (::link(p1.c_str(), p2.c_str()) != 0) { throw NetFS::SystemError(errno); } @@ -117,12 +118,16 @@ VolumeServer::link(const NetFS::ReqEnv re, const std::string path1, const std::s } void -VolumeServer::rename(const NetFS::ReqEnv re, const std::string from, const std::string to, const Ice::Current &) +VolumeServer::rename(const NetFS::ReqEnv re, std::string from, std::string to, const Ice::optional<Ice::Int> flags, + const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path f(resolvePath(from)); - std::filesystem::path t(resolvePath(to)); + std::filesystem::path t(resolvePath(std::move(to))); + if (flags && *flags == RENAME_NOREPLACE && ::access(t.c_str(), F_OK) == 0) { + throw NetFS::SystemError(EEXIST); + } + std::filesystem::path f(resolvePath(std::move(from))); mc.AssertWriteParent(f); mc.AssertWriteParent(t); if (::rename(f.c_str(), t.c_str()) != 0) { @@ -131,69 +136,71 @@ VolumeServer::rename(const NetFS::ReqEnv re, const std::string from, const std:: } std::string -VolumeServer::readlink(const NetFS::ReqEnv re, const std::string path, const Ice::Current &) +// cppcheck-suppress passedByValue; +VolumeServer::readlink(const NetFS::ReqEnv re, std::string path, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - char buf[PATH_MAX]; - std::filesystem::path p(resolvePath(path)); + std::string buf(PATH_MAX, 0); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertRead(p); - ssize_t rc = ::readlink(p.c_str(), buf, PATH_MAX); - if (rc == -1) { + auto rc = ::readlink(p.c_str(), buf.data(), PATH_MAX); + if (rc < 0) { throw NetFS::SystemError(errno); } - return std::string(buf, rc); + buf.resize(safe {rc}); + return buf; } void -VolumeServer::chmod(const NetFS::ReqEnv re, const std::string path, Ice::Int mode, const Ice::Current &) +// cppcheck-suppress passedByValue; +VolumeServer::chmod(const NetFS::ReqEnv re, std::string path, Ice::Int mode, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWritePerms(p); - if (::chmod(p.c_str(), mode) != 0) { + if (::chmod(p.c_str(), safe {mode}) != 0) { throw NetFS::SystemError(errno); } } void -VolumeServer::chown(const NetFS::ReqEnv re, const std::string path, Ice::Int uid, Ice::Int gid, const Ice::Current &) +// cppcheck-suppress passedByValue; +VolumeServer::chown(const NetFS::ReqEnv re, std::string path, Ice::Int uid, Ice::Int gid, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWrite(p); - if (::lchown(p.c_str(), uid, gid) != 0) { + if (::lchown(p.c_str(), safe {uid}, safe {gid}) != 0) { throw NetFS::SystemError(errno); } } void -VolumeServer::utimens(const NetFS::ReqEnv re, const std::string path, - Ice::Long s0, Ice::Long ns0, Ice::Long s1, Ice::Long ns1, const Ice::Current&) +// cppcheck-suppress passedByValue; +VolumeServer::utimens(const NetFS::ReqEnv re, std::string path, Ice::Long s0, Ice::Long ns0, Ice::Long s1, + Ice::Long ns1, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - struct timespec times[2]; - times[0].tv_sec = s0; - times[0].tv_nsec = ns0; - times[1].tv_sec = s1; - times[1].tv_nsec = ns1; - std::filesystem::path p(resolvePath(path)); + std::array<struct timespec, 2> times {{{s0, ns0}, {s1, ns1}}}; + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWrite(p); - if (::utimensat(0, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { + if (::utimensat(0, p.c_str(), times.data(), AT_SYMLINK_NOFOLLOW) != 0) { throw NetFS::SystemError(errno); } } NetFS::VFS -VolumeServer::statfs(const NetFS::ReqEnv re, const std::string path, const Ice::Current&) +// cppcheck-suppress passedByValue; +VolumeServer::statfs(const NetFS::ReqEnv re, std::string path, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - struct statvfs s; - std::filesystem::path p(resolvePath(path)); + struct statvfs s { }; + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertRead(p); if (::statvfs(p.c_str(), &s) != 0) { throw NetFS::SystemError(errno); @@ -202,11 +209,12 @@ VolumeServer::statfs(const NetFS::ReqEnv re, const std::string path, const Ice:: } void -VolumeServer::truncate(const NetFS::ReqEnv re, const std::string path, Ice::Long size, const Ice::Current&) +// cppcheck-suppress passedByValue; +VolumeServer::truncate(const NetFS::ReqEnv re, std::string path, Ice::Long size, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWrite(p); if (::truncate(p.c_str(), size) != 0) { throw NetFS::SystemError(errno); @@ -214,11 +222,12 @@ VolumeServer::truncate(const NetFS::ReqEnv re, const std::string path, Ice::Long } void -VolumeServer::unlink(const NetFS::ReqEnv re, const std::string path, const Ice::Current&) +// cppcheck-suppress passedByValue; +VolumeServer::unlink(const NetFS::ReqEnv re, std::string path, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWriteParent(p); if (::unlink(p.c_str()) != 0) { throw NetFS::SystemError(errno); @@ -226,11 +235,12 @@ VolumeServer::unlink(const NetFS::ReqEnv re, const std::string path, const Ice:: } NetFS::FilePrxPtr -VolumeServer::open(const NetFS::ReqEnv re, const std::string path, Ice::Int flags, const Ice::Current & ice) +// cppcheck-suppress passedByValue; +VolumeServer::open(const NetFS::ReqEnv re, std::string path, Ice::Int flags, const Ice::Current & ice) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertRead(p); if (flags & O_CREAT) { throw NetFS::SystemError(EINVAL); @@ -239,16 +249,17 @@ VolumeServer::open(const NetFS::ReqEnv re, const std::string path, Ice::Int flag if (fd == -1) { throw NetFS::SystemError(errno); } - return Ice::uncheckedCast<NetFS::FilePrx>(ice.adapter->addFacetWithUUID( - std::make_shared<FileServer>(fd, converter), "v01")); + return Ice::uncheckedCast<NetFS::FilePrx>( + ice.adapter->addFacetWithUUID(std::make_shared<FileServer>(fd, converter), "v01")); } NetFS::FilePrxPtr -VolumeServer::create(const NetFS::ReqEnv re, const std::string path, Ice::Int flags, Ice::Int mode, const Ice::Current & ice) +// cppcheck-suppress passedByValue; +VolumeServer::create(const NetFS::ReqEnv re, std::string path, Ice::Int flags, Ice::Int mode, const Ice::Current & ice) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWriteParent(p); int fd = ::open(p.c_str(), O_CREAT | O_EXCL | flags, mode); if (fd == -1) { @@ -259,33 +270,35 @@ VolumeServer::create(const NetFS::ReqEnv re, const std::string path, Ice::Int fl ::unlink(p.c_str()); throw NetFS::SystemError(errno); } - return Ice::uncheckedCast<NetFS::FilePrx>(ice.adapter->addFacetWithUUID( - std::make_shared<FileServer>(fd, converter), "v01")); + return Ice::uncheckedCast<NetFS::FilePrx>( + ice.adapter->addFacetWithUUID(std::make_shared<FileServer>(fd, converter), "v01")); } NetFS::DirectoryPrxPtr -VolumeServer::opendir(const NetFS::ReqEnv re, const std::string path, const Ice::Current & ice) +// cppcheck-suppress passedByValue; +VolumeServer::opendir(const NetFS::ReqEnv re, std::string path, const Ice::Current & ice) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertRead(p); DIR * od = ::opendir(p.c_str()); if (!od) { throw NetFS::SystemError(errno); } - return Ice::uncheckedCast<NetFS::DirectoryPrx>(ice.adapter->addFacetWithUUID( - std::make_shared<DirectoryServer>(od, converter), "v02")); + return Ice::uncheckedCast<NetFS::DirectoryPrx>( + ice.adapter->addWithUUID(std::make_shared<DirectoryServer>(od, converter))); } void -VolumeServer::mkdir(const NetFS::ReqEnv re, const std::string path, Ice::Int mode, const Ice::Current&) +// cppcheck-suppress passedByValue; +VolumeServer::mkdir(const NetFS::ReqEnv re, std::string path, Ice::Int mode, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWriteParent(p); - if (::mkdir(p.c_str(), mode) != 0) { + if (::mkdir(p.c_str(), safe {mode}) != 0) { throw NetFS::SystemError(errno); } if (::chown(p.c_str(), mc.myu, mc.myg) != 0) { @@ -295,11 +308,12 @@ VolumeServer::mkdir(const NetFS::ReqEnv re, const std::string path, Ice::Int mod } void -VolumeServer::rmdir(const NetFS::ReqEnv re, const std::string path, const Ice::Current&) +// cppcheck-suppress passedByValue; +VolumeServer::rmdir(const NetFS::ReqEnv re, std::string path, const Ice::Current &) { ModeCheck mc(re, root, userLookup, groupLookup); errno = 0; - std::filesystem::path p(resolvePath(path)); + std::filesystem::path p(resolvePath(std::move(path))); mc.AssertWriteParent(p); if (::rmdir(p.c_str()) != 0) { throw NetFS::SystemError(errno); @@ -307,10 +321,10 @@ VolumeServer::rmdir(const NetFS::ReqEnv re, const std::string path, const Ice::C } std::filesystem::path -normalizedAppend(std::filesystem::path out, const std::filesystem::path & in) +normalizedAppend(std::filesystem::path out, const std::filesystem::path && in) { unsigned int depth = 0; - for(auto e : in) { + for (const auto & e : in) { if (e.empty() || e == "." || e == "/") { continue; } @@ -330,8 +344,7 @@ normalizedAppend(std::filesystem::path out, const std::filesystem::path & in) } std::filesystem::path -VolumeServer::resolvePath(const std::string & path) const +VolumeServer::resolvePath(std::string && path) const { - return normalizedAppend(root, path); + return normalizedAppend(root, std::move(path)); } - diff --git a/netfs/daemon/daemonVolume.h b/netfs/daemon/daemonVolume.h index 1549f6d..c7d6c56 100644 --- a/netfs/daemon/daemonVolume.h +++ b/netfs/daemon/daemonVolume.h @@ -1,54 +1,55 @@ -#ifndef DAEMONVOLUME_H -#define DAEMONVOLUME_H +#pragma once -#include <volume.h> -#include <shared_mutex> +#include <entries.h> +#include <entryResolver.h> #include <filesystem> -#include <entCache.h> +#include <shared_mutex> #include <typeConverter.h> +#include <volume.h> class VolumeServer : public NetFS::Volume { - public: - VolumeServer(const std::filesystem::path & root, const EntCache<User> &, const EntCache<Group> &); - virtual ~VolumeServer(); +public: + VolumeServer(const std::filesystem::path & root, const EntryResolverPtr<User> &, const EntryResolverPtr<Group> &); - virtual NetFS::DirectoryPrxPtr opendir(const NetFS::ReqEnv, const std::string path, const Ice::Current&) override; + NetFS::DirectoryPrxPtr opendir(const NetFS::ReqEnv, std::string path, const Ice::Current &) override; - virtual void mkdir(const NetFS::ReqEnv, const std::string path, Ice::Int id, const Ice::Current&) override; - virtual void rmdir(const NetFS::ReqEnv, const std::string path, const Ice::Current&) override; + void mkdir(const NetFS::ReqEnv, std::string path, Ice::Int id, const Ice::Current &) override; + void rmdir(const NetFS::ReqEnv, std::string path, const Ice::Current &) override; - virtual void truncate(const NetFS::ReqEnv, const std::string path, Ice::Long size, const Ice::Current&) override; + void truncate(const NetFS::ReqEnv, std::string path, Ice::Long size, const Ice::Current &) override; - virtual void unlink(const NetFS::ReqEnv, const std::string path, const Ice::Current&) override; + void unlink(const NetFS::ReqEnv, std::string path, const Ice::Current &) override; - virtual NetFS::FilePrxPtr open(const NetFS::ReqEnv, const std::string path, Ice::Int flags, const Ice::Current&) override; - virtual NetFS::FilePrxPtr create(const NetFS::ReqEnv, const std::string path, Ice::Int flags, Ice::Int mode, const Ice::Current&) override; + NetFS::FilePrxPtr open(const NetFS::ReqEnv, std::string path, Ice::Int flags, const Ice::Current &) override; + NetFS::FilePrxPtr create( + const NetFS::ReqEnv, std::string path, Ice::Int flags, Ice::Int mode, const Ice::Current &) override; - virtual NetFS::VFS statfs(const NetFS::ReqEnv, const std::string path, const Ice::Current&) override; + NetFS::VFS statfs(const NetFS::ReqEnv, std::string path, const Ice::Current &) override; - virtual Ice::Int access(const NetFS::ReqEnv, const std::string path, Ice::Int mode, const Ice::Current&) override; - virtual NetFS::Attr getattr(const NetFS::ReqEnv, const std::string path, const Ice::Current&) override; - virtual void mknod(const NetFS::ReqEnv, const std::string path, Ice::Int mode, Ice::Int dev, const Ice::Current&) override; - virtual void symlink(const NetFS::ReqEnv, const std::string path1, const std::string path2, const Ice::Current&) override; - virtual void link(const NetFS::ReqEnv, const std::string path1, const std::string path2, const Ice::Current&) override; - virtual void rename(const NetFS::ReqEnv, const std::string path1, const std::string path2, const Ice::Current&) override; - virtual std::string readlink(const NetFS::ReqEnv, const std::string path, const Ice::Current&) override; - virtual void chmod(const NetFS::ReqEnv, const std::string path, Ice::Int mode, const Ice::Current&) override; - virtual void chown(const NetFS::ReqEnv, const std::string path, Ice::Int uid, Ice::Int gid, const Ice::Current&) override; - virtual void utimens(const NetFS::ReqEnv, const std::string path, Ice::Long, Ice::Long, Ice::Long, Ice::Long, const Ice::Current&) override; + Ice::Int access(const NetFS::ReqEnv, std::string path, Ice::Int mode, const Ice::Current &) override; + NetFS::Attr getattr(const NetFS::ReqEnv, std::string path, const Ice::Current &) override; + void mknod(const NetFS::ReqEnv, std::string path, Ice::Int mode, Ice::Int dev, const Ice::Current &) override; + // cppcheck-suppress passedByValue; + void symlink(const NetFS::ReqEnv, const std::string path1, const std::string path2, const Ice::Current &) override; + void link(const NetFS::ReqEnv, std::string path1, std::string path2, const Ice::Current &) override; + // cppcheck-suppress passedByValue; + void rename(const NetFS::ReqEnv, std::string path1, const std::string path2, const Ice::optional<Ice::Int>, + const Ice::Current &) override; + std::string readlink(const NetFS::ReqEnv, std::string path, const Ice::Current &) override; + void chmod(const NetFS::ReqEnv, std::string path, Ice::Int mode, const Ice::Current &) override; + void chown(const NetFS::ReqEnv, std::string path, Ice::Int uid, Ice::Int gid, const Ice::Current &) override; + void utimens(const NetFS::ReqEnv, std::string path, Ice::Long, Ice::Long, Ice::Long, Ice::Long, + const Ice::Current &) override; - virtual void disconnect(const Ice::Current&) override; + void disconnect(const Ice::Current &) override; - protected: - std::filesystem::path resolvePath(const std::string & path) const; +protected: + inline std::filesystem::path resolvePath(std::string && path) const; - private: - const std::filesystem::path root; +private: + const std::filesystem::path root; - const EntCache<User> & userLookup; - const EntCache<Group> & groupLookup; - EntryTypeConverter converter; + EntryResolver<User> & userLookup; + EntryResolver<Group> & groupLookup; + NetFS::EntryTypeConverter converter; }; - -#endif - diff --git a/netfs/daemon/modeCheck.cpp b/netfs/daemon/modeCheck.cpp index 91bbe20..a9096fd 100644 --- a/netfs/daemon/modeCheck.cpp +++ b/netfs/daemon/modeCheck.cpp @@ -1,13 +1,20 @@ #include "modeCheck.h" -#include <entCache.h> #include <exceptions.h> -ModeCheck::ModeCheck(const NetFS::ReqEnv & re, const std::filesystem::path & r, const EntCache<User> & u, const EntCache<Group> & g) : - myu(u.getEntry(re.user)->id), - myg(g.getEntry(re.grp)->id), - root(r), - userLookup(u), - groupLookup(g) +template<typename Ex, typename T, typename... ExP> +T && +assert_truthy(T && v, ExP &&... p) +{ + if (!v) { + throw Ex {std::forward<ExP>(p)...}; + } + return std::forward<T>(v); +} + +ModeCheck::ModeCheck(const NetFS::ReqEnv & re, const std::filesystem::path & r, const EntryResolver<User> & u, + const EntryResolver<Group> & g) : + myu(assert_truthy<NetFS::SystemError>(u.getEntry(re.user), EPERM)->id), + myg(assert_truthy<NetFS::SystemError>(g.getEntry(re.grp), EPERM)->id), root(r), userLookup(u), groupLookup(g) { } @@ -20,6 +27,7 @@ ModeCheck::AssertReadParent(const std::filesystem::path & p) const } void +// NOLINTNEXTLINE(misc-no-recursion) ModeCheck::AssertRead(const std::filesystem::path & p) const { if (p != root) { @@ -67,7 +75,7 @@ ModeCheck::AssertWritePerms(const std::filesystem::path & p) const struct stat ModeCheck::lstat(const std::filesystem::path & p) { - struct stat s; + struct stat s { }; if (::lstat(p.c_str(), &s) != 0) { throw NetFS::SystemError(errno); } @@ -77,30 +85,59 @@ ModeCheck::lstat(const std::filesystem::path & p) bool ModeCheck::ReadableBy(const struct stat & s, uid_t u, gid_t g) const { - if (u == 0) return true; - if (s.st_mode & S_IROTH) return true; - if (s.st_mode & S_IRUSR && s.st_uid == u) return true; - if (s.st_mode & S_IRGRP && (s.st_gid == g || userLookup.getEntry(u)->group == s.st_gid || groupLookup.getEntry(s.st_gid)->hasMember(u))) return true; + if (u == 0) { + return true; + } + if (s.st_mode & S_IROTH) { + return true; + } + if (s.st_mode & S_IRUSR && s.st_uid == u) { + return true; + } + if (s.st_mode & S_IRGRP + && (s.st_gid == g || userLookup.getEntry(u)->group == s.st_gid + || groupLookup.getEntry(s.st_gid)->hasMember(u))) { + return true; + } return false; } bool ModeCheck::WritableBy(const struct stat & s, uid_t u, gid_t g) const { - if (u == 0) return true; - if (s.st_mode & S_IWOTH) return true; - if (s.st_mode & S_IWUSR && s.st_uid == u) return true; - if (s.st_mode & S_IWGRP && (s.st_gid == g || userLookup.getEntry(u)->group == s.st_gid || groupLookup.getEntry(s.st_gid)->hasMember(u))) return true; + if (u == 0) { + return true; + } + if (s.st_mode & S_IWOTH) { + return true; + } + if (s.st_mode & S_IWUSR && s.st_uid == u) { + return true; + } + if (s.st_mode & S_IWGRP + && (s.st_gid == g || userLookup.getEntry(u)->group == s.st_gid + || groupLookup.getEntry(s.st_gid)->hasMember(u))) { + return true; + } return false; } bool ModeCheck::ExecutableBy(const struct stat & s, uid_t u, gid_t g) const { - if (u == 0 && (s.st_mode & (S_IXOTH | S_IXGRP | S_IXUSR))) return true; - if (s.st_mode & S_IXOTH) return true; - if (s.st_mode & S_IXUSR && s.st_uid == u) return true; - if (s.st_mode & S_IXGRP && (s.st_gid == g || userLookup.getEntry(u)->group == s.st_gid || groupLookup.getEntry(s.st_gid)->hasMember(u))) return true; + if (u == 0 && (s.st_mode & (S_IXOTH | S_IXGRP | S_IXUSR))) { + return true; + } + if (s.st_mode & S_IXOTH) { + return true; + } + if (s.st_mode & S_IXUSR && s.st_uid == u) { + return true; + } + if (s.st_mode & S_IXGRP + && (s.st_gid == g || userLookup.getEntry(u)->group == s.st_gid + || groupLookup.getEntry(s.st_gid)->hasMember(u))) { + return true; + } return false; } - diff --git a/netfs/daemon/modeCheck.h b/netfs/daemon/modeCheck.h index 78c1708..4a57317 100644 --- a/netfs/daemon/modeCheck.h +++ b/netfs/daemon/modeCheck.h @@ -1,35 +1,33 @@ -#ifndef NETFS_DAEMON_IOHELPERS -#define NETFS_DAEMON_IOHELPERS +#pragma once +#include <entries.h> +#include <entryResolver.h> +#include <filesystem> #include <sys/stat.h> #include <types.h> -#include <filesystem> -#include <entCache.h> class ModeCheck { - public: - ModeCheck(const NetFS::ReqEnv & re, const std::filesystem::path &, const EntCache<User> &, const EntCache<Group> &); +public: + ModeCheck(const NetFS::ReqEnv & re, const std::filesystem::path &, const EntryResolver<User> &, + const EntryResolver<Group> &); - void AssertReadParent(const std::filesystem::path &) const; - void AssertRead(const std::filesystem::path &) const; - void AssertWriteParent(const std::filesystem::path &) const; - void AssertWrite(const std::filesystem::path &) const; - void AssertWritePerms(const std::filesystem::path &) const; + void AssertReadParent(const std::filesystem::path &) const; + void AssertRead(const std::filesystem::path &) const; + void AssertWriteParent(const std::filesystem::path &) const; + void AssertWrite(const std::filesystem::path &) const; + void AssertWritePerms(const std::filesystem::path &) const; - const uid_t myu; - const gid_t myg; - const std::filesystem::path & root; + const uid_t myu; + const gid_t myg; + const std::filesystem::path & root; - bool ReadableBy(const struct stat &, uid_t u, gid_t g) const; - bool WritableBy(const struct stat &, uid_t u, gid_t g) const; - bool ExecutableBy(const struct stat &, uid_t u, gid_t g) const; + [[nodiscard]] bool ReadableBy(const struct stat &, uid_t u, gid_t g) const; + [[nodiscard]] bool WritableBy(const struct stat &, uid_t u, gid_t g) const; + [[nodiscard]] bool ExecutableBy(const struct stat &, uid_t u, gid_t g) const; - private: - static struct stat lstat(const std::filesystem::path &); +private: + static struct stat lstat(const std::filesystem::path &); - const EntCache<User> & userLookup; - const EntCache<Group> & groupLookup; + const EntryResolver<User> & userLookup; + const EntryResolver<Group> & groupLookup; }; - -#endif - diff --git a/netfs/fuse/Jamfile.jam b/netfs/fuse/Jamfile.jam index 6972e3f..4d000be 100644 --- a/netfs/fuse/Jamfile.jam +++ b/netfs/fuse/Jamfile.jam @@ -1,27 +1,48 @@ import package ; -lib fuse : : <name>fuse ; lib Glacier2 : : <name>Glacier2++11 ; +constant FUSE_VER : 35 ; + +rule iceobj ( name : ice ) { + obj $(name) : $(ice) : + <toolset>tidy:<checker>none + <library>../ice//netfs-api + <implicit-dependency>../ice//netfs-api + <library>..//slicer + <include>. + ; +} +iceobj fuseConfig : fuseConfig.ice ; +iceobj fuseMappers : fuseMappers.ice ; lib netfs-client-configuration : + fuseConfig + fuseMappers fuseConfig.ice + fuseMappers.ice fuseConfigImpl.cpp + fuseMappersImpl.cpp : - <slicer>yes + <slicer>pure <library>../ice//netfs-api + <library>../lib//netfs-common <implicit-dependency>../ice//netfs-api + <implicit-dependency>fuseConfig <library>..//Ice <library>..//slicer <library>..//adhocutil + <include>. : : <library>..//Ice <library>..//slicer + <include>. ; lib netfs-client : netfs-client-configuration [ glob *.cpp : fuseConfigImpl.cpp netfs.cpp ] : + <define>FUSE_USE_VERSION=$(FUSE_VER) <implicit-dependency>../ice//netfs-api <library>netfs-client-configuration <implicit-dependency>netfs-client-configuration @@ -33,7 +54,9 @@ lib netfs-client : <library>..//slicer <library>..//adhocutil <library>..//slicer-xml + <use>../..//fuse : : + <define>FUSE_USE_VERSION=$(FUSE_VER) <include>. <library>../ice//netfs-api <library>Glacier2 @@ -45,7 +68,7 @@ lib netfs-client : exe netfs : netfs.cpp : <library>netfs-client - <library>fuse + <library>../..//fuse <library>..//adhocutil ; diff --git a/netfs/fuse/fuseApp.cpp b/netfs/fuse/fuseApp.cpp index 79fa0dd..c09a7e7 100644 --- a/netfs/fuse/fuseApp.cpp +++ b/netfs/fuse/fuseApp.cpp @@ -1,280 +1,320 @@ -#include <Glacier2/Router.h> -#include <string.h> #include "fuseApp.h" -#include "lockHelpers.h" #include "cache.impl.h" +#include "fuseDirs.h" // IWYU pragma: keep - OpenDir definition +#include "fuseFiles.h" // IWYU pragma: keep - OpenFile definition +#include "fuseMappersImpl.h" +#include "lockHelpers.h" +#include <Glacier2/Router.h> +#include <boost/lexical_cast.hpp> +#include <compileTimeFormatter.h> +#include <cstring> +#include <defaultMapper.h> #include <entCache.h> +#include <numeric.h> +#include <safeMapFind.h> #include <slicer/slicer.h> #include <uriParse.h> -#include <safeMapFind.h> -#include <compileTimeFormatter.h> namespace AdHoc { - template class Cache<struct stat, std::string>; - template class CallCacheable<struct stat, std::string>; + template class Cache<struct stat, std::size_t>; + template class CallCacheable<struct stat, std::size_t>; } -NetFS::FuseApp::FuseApp(const Ice::StringSeq & a) : - iceArgs(a), - sessionOpened(false), - openHandleId(0), - converter(userLookup, groupLookup) -{ -} +namespace NetFS { + constexpr static unsigned short DEFAULT_PORT = 4000; + constexpr static unsigned short DEFAULT_MAX_MESSAGE_SIZE = 1024; + constexpr static unsigned short KILO_MULTIPLIER = 1024; + + FuseApp::FuseApp(Ice::StringSeq args) : iceArgs(std::move(args)) { } -NetFS::FuseApp::~FuseApp() -{ - for (const OpenDirs::value_type & of : openDirs) { - // LCOV_EXCL_START - try { - of.second->remote->close(); + FuseApp::~FuseApp() + { + for (const OpenDirs::value_type & openDir : openDirs) { + // LCOV_EXCL_START + try { + openDir.second->remote->close(); + } + catch (...) { // NOLINT(bugprone-empty-catch) - Can't do anything useful here + } + // LCOV_EXCL_STOP } - catch (...) { - // Can't do anything useful here + for (const OpenFiles::value_type & openFile : openFiles) { + // LCOV_EXCL_START + try { + openFile.second->remote->close(); + } + catch (...) { // NOLINT(bugprone-empty-catch) - Can't do anything useful here + } + // LCOV_EXCL_STOP } - // LCOV_EXCL_STOP - } - for (const OpenFiles::value_type & of : openFiles) { - // LCOV_EXCL_START - try { - of.second->remote->close(); + if (volume) { + try { + volume->disconnect(); + } + catch (...) { // NOLINT(bugprone-empty-catch) - Can't do anything useful here + } } - catch (...) { - // Can't do anything useful here + if (session) { + try { + session->destroy(); + } + catch (...) { // NOLINT(bugprone-empty-catch) - Can't do anything useful here + } } - // LCOV_EXCL_STOP - } - if (volume) { - try { - volume->disconnect(); - } - catch (...) { - // Can't do anything useful here + if (ic) { + ic->destroy(); } } - if (session) { - try { - session->destroy(); + + void * + FuseApp::init(struct fuse_conn_info *, struct fuse_config *) + { + BOOST_ASSERT(!ic); + ic = Ice::initialize(iceArgs); + BOOST_ASSERT(ic); + if (!iceArgs.empty()) { + const auto & arg = iceArgs.front(); + if (arg.find("://") != std::string::npos) { + fcr = configureFromUri(arg); + } + else if (auto colon = arg.find(':'); colon != std::string::npos) { + fcr = configureFromFile(arg.substr(0, colon), arg.substr(colon + 1)); + } } - catch (...) { - // Can't do anything useful here + if (!fcr->Mapper) { + fcr->Mapper = std::make_shared<Mapping::DefaultMapper>(); } + BOOST_ASSERT(fcr); + converter.mapper = fcr->Mapper; + clientSettings.MessageSizeMax + = ic->getProperties()->getPropertyAsIntWithDefault("Ice.MessageSizeMax", DEFAULT_MAX_MESSAGE_SIZE); + return this; } - if (ic) { - ic->destroy(); - } -} - -NetFS::Client::ConfigurationPtr -NetFS::FuseApp::ReadConfiguration(const std::filesystem::path & path) const -{ - auto s = Slicer::FileDeserializerFactory::createNew(path.extension().string(), path); - return Slicer::DeserializeAnyWith<NetFS::Client::ConfigurationPtr>(s); -} -void * -NetFS::FuseApp::init(struct fuse_conn_info *) -{ - ic = Ice::initialize(iceArgs); - fcr = configurator(); - return NULL; -} + Client::ResourcePtr + FuseApp::configureFromFile(const std::filesystem::path & path, const std::string & resourceName) + { + auto deserialiser = Slicer::FileDeserializerFactory::createNew(path.extension().string(), path); + auto configuration = Slicer::DeserializeAnyWith<NetFS::Client::ConfigurationPtr>(deserialiser); + return AdHoc::safeMapLookup<Client::ResourceNotFound>(configuration->Resources, resourceName); + } -NetFS::Client::ResourcePtr -NetFS::FuseApp::configureFromFile(const std::string & configPath, const std::string & resourceName) const -{ - return AdHoc::safeMapLookup<Client::ResourceNotFound>(ReadConfiguration(configPath)->Resources, resourceName); -} + AdHocFormatter(IceEndpointFmt, "%? -h %? -p %?"); -AdHocFormatter(IceEndpointFmt, "%? -h %? -p %?"); -NetFS::Client::ResourcePtr -NetFS::FuseApp::configureFromUri(const std::string & uriString) const -{ - AdHoc::Uri uri(uriString); + Client::ResourcePtr + FuseApp::configureFromUri(const std::string & uriString) + { + const AdHoc::Uri uri(uriString); - auto r = std::make_shared<NetFS::Client::Resource>(); - r->ExportName = uri.path->string(); - r->Endpoints.push_back(IceEndpointFmt::get(uri.scheme, uri.host, uri.port ? *uri.port : 4000)); - if (uri.password) { - r->AuthToken = *uri.password; + auto resource = std::make_shared<NetFS::Client::Resource>(); + resource->ExportName = uri.path->string(); + resource->Endpoints.emplace_back( + IceEndpointFmt::get(uri.scheme, uri.host, uri.port ? *uri.port : DEFAULT_PORT)); + if (uri.password) { + resource->AuthToken = *uri.password; + } + auto set = [&uri](const auto & param, auto & setting) { + if (auto value = uri.query.find(param); value != uri.query.end()) { + setting = boost::lexical_cast<std::remove_reference_t<decltype(setting)>>(value->second); + } + }; + auto setWith = [&uri](const auto & param, auto & setting, auto && func) { + if (auto value = uri.query.find(param); value != uri.query.end()) { + setting = func(value->second); + } + }; + set("async", resource->Async); + setWith("mapper", resource->Mapper, [&set, &setWith](auto && mapper) -> NetFS::Mapping::MapperPtr { + if (mapper == "hide") { + return std::make_shared<NetFS::Client::HideUnknownMapperImpl>(); + } + if (mapper == "mask") { + auto mapperImpl = std::make_shared<NetFS::Client::MaskUnknownMapperImpl>(); + set("mapper.unknownuser", mapperImpl->UnknownUser); + set("mapper.unknowngroup", mapperImpl->UnknownGroup); + setWith("mapper.usermask", mapperImpl->UserMask, Client::fromOctal); + setWith("mapper.groupmask", mapperImpl->GroupMask, Client::fromOctal); + return mapperImpl; + } + return {}; + }); + return resource; } - return r; -} -int -NetFS::FuseApp::opt_parse(void *, const char * arg, int, struct fuse_args *) -{ - if (strncmp(arg, "--Ice.", 6) == 0) { - iceArgs.push_back(arg); - return 0; - } - else if (strncmp(arg, "_netdev", 7) == 0) { - return 0; - } - else if (arg[0] == '-') { - return 1; - } - else if (!configurator) { - if (strstr(arg, "://")) { - configurator = std::bind(&NetFS::FuseApp::configureFromUri, this, arg); + int + FuseApp::opt_parse(void * data, const char * arg, int, struct fuse_args *) + { + auto & iceArgs = *static_cast<Ice::StringSeq *>(data); + std::string_view argV {arg}; + if (argV.starts_with("--Ice.")) { + iceArgs.emplace_back(arg); + return 0; } - else { - const char * colon = strchr(arg, ':'); - configurator = std::bind(&NetFS::FuseApp::configureFromFile, this, std::string(arg, colon), colon + 1); + if (argV == "_netdev") { + return 0; + } + if (argV.front() == '-') { + return 1; + } + if (iceArgs.empty() || iceArgs.front().starts_with("--Ice.")) { + iceArgs.emplace(iceArgs.begin(), arg); + return 0; } - return 0; - } - else if (mountPoint.empty()) { - mountPoint = arg; return 1; } - return 1; -} -void -NetFS::FuseApp::connectSession() -{ - if (!sessionOpened && ic->getDefaultRouter()) { - Lock(_lock); - auto router = Ice::checkedCast<Glacier2::RouterPrx>(ic->getDefaultRouter()); - session = router->createSession("", ""); - if (int acmTimeout = router->getACMTimeout() > 0) { - Ice::ConnectionPtr conn = router->ice_getCachedConnection(); - conn->setACM(acmTimeout, IceUtil::None, Ice::ACMHeartbeat::HeartbeatAlways); + void + FuseApp::connectSession() + { + Lock(mutex); + if (!sessionOpened && ic->getDefaultRouter()) { + auto router = Ice::checkedCast<Glacier2::RouterPrx>(ic->getDefaultRouter()); + session = router->createSession("", ""); + if (int acmTimeout = router->getACMTimeout() > 0) { + Ice::ConnectionPtr conn = router->ice_getCachedConnection(); + conn->setACM(acmTimeout, IceUtil::None, Ice::ACMHeartbeat::HeartbeatAlways); + } + sessionOpened = true; } - sessionOpened = true; } -} -void -NetFS::FuseApp::connectToService() -{ - if (!service) { - Lock(_lock); - auto proxyAddr = fcr->ServiceIdentity; - for (const auto & ep : fcr->Endpoints) { - proxyAddr += ":" + ep; - } - service = Ice::checkedCast<NetFS::ServicePrx>(ic->stringToProxy(proxyAddr)); - if (!service) { - throw std::runtime_error("Invalid service proxy: " + proxyAddr); - } + FuseApp::CombinedSettings + FuseApp::combineSettings(const Settings & daemon, const Settings & client) + { + return {safe {std::min(daemon.MessageSizeMax.value_or(DEFAULT_MAX_MESSAGE_SIZE), + client.MessageSizeMax.value_or(DEFAULT_MAX_MESSAGE_SIZE)) + * KILO_MULTIPLIER}}; } -} -void -NetFS::FuseApp::connectToVolume() -{ - if (!volume) { - Lock(_lock); - volume = service->connect(fcr->ExportName, fcr->AuthToken); - if (!volume) { - throw std::runtime_error("Invalid filesystem proxy"); + void + FuseApp::connectToService() + { + Lock(mutex); + if (!service) { + auto proxyAddr = fcr->ServiceIdentity; + for (const auto & endpoint : fcr->Endpoints) { + proxyAddr += ":" + endpoint; + } + service = Ice::checkedCast<NetFS::ServicePrx>(ic->stringToProxy(proxyAddr)); + if (!service) { + throw std::runtime_error("Invalid service proxy: " + proxyAddr); + } + daemonSettings = service->getSettings(); + combinedSettings = combineSettings(*daemonSettings, clientSettings); } } -} -void -NetFS::FuseApp::connectHandles() -{ - for (const OpenFiles::value_type & of : openFiles) { - try { - of.second->remote->ice_ping(); - } - catch (const Ice::ObjectNotExistException &) { - of.second->remote = volume->open(reqEnv(), of.second->path, of.second->flags & !O_CREAT); - } - } - for (const OpenDirs::value_type & of : openDirs) { - try { - of.second->remote->ice_ping(); - } - catch (const Ice::ObjectNotExistException &) { - of.second->remote = volume->opendir(reqEnv(), of.second->path); + void + FuseApp::connectToVolume(bool force) + { + Lock(mutex); + if (force || !volume) { + volume = service->connect(fcr->ExportName, fcr->AuthToken); + if (!volume) { + throw std::runtime_error("Invalid filesystem proxy"); + } } } -} -void -NetFS::FuseApp::verifyConnection() -{ - Lock(_lock); - if (session) { - try { - session->ice_ping(); + void + FuseApp::connectHandles() + { + for (const OpenFiles::value_type & openFile : openFiles) { + try { + openFile.second->remote->ice_ping(); + } + catch (const Ice::ObjectNotExistException &) { + openFile.second->remote + = volume->open(reqEnv(), openFile.second->path, openFile.second->flags & !O_CREAT); + } } - catch (const Ice::Exception &) { - session = NULL; - sessionOpened = false; + for (const OpenDirs::value_type & openDir : openDirs) { + try { + openDir.second->remote->ice_ping(); + } + catch (const Ice::ObjectNotExistException &) { + openDir.second->remote = volume->opendir(reqEnv(), openDir.second->path); + } } } - if (service) { - try { - service->ice_ping(); - } - catch (const Ice::Exception &) { - service = NULL; + + void + FuseApp::verifyConnection() + { + Lock(mutex); + if (session) { + try { + session->ice_ping(); + } + catch (const Ice::Exception &) { + session = nullptr; + sessionOpened = false; + } } - } - if (volume) { - try { - volume->ice_ping(); + if (service) { + try { + service->ice_ping(); + } + catch (const Ice::Exception &) { + service = nullptr; + } } - catch (const Ice::Exception &) { - volume = NULL; + if (volume) { + try { + volume->ice_ping(); + } + catch (const Ice::Exception &) { + volume = nullptr; + } } } -} -int -NetFS::FuseApp::onError(const std::exception & e) throw() -{ - if (dynamic_cast<const Ice::SocketException *>(&e) || dynamic_cast<const Ice::TimeoutException *>(&e)) { - log(LOG_ERR, e.what()); - try { - verifyConnection(); - connectSession(); - connectToService(); - connectToVolume(); - connectHandles(); - return 0; + int + FuseApp::onError(const std::exception & error) noexcept + { + if (dynamic_cast<const Ice::SocketException *>(&error) || dynamic_cast<const Ice::TimeoutException *>(&error)) { + log(LOG_ERR, error.what()); + try { + verifyConnection(); + connectSession(); + connectToService(); + connectToVolume(false); + connectHandles(); + return 0; + } + catch (...) { + return -EIO; + } } - catch (...) { - return -EIO; + if (dynamic_cast<const Ice::RequestFailedException *>(&error)) { + try { + connectToVolume(true); + connectHandles(); + return 0; + } + catch (...) { + return -EIO; + } } - } - if (dynamic_cast<const Ice::RequestFailedException *>(&e)) { - try { - volume = NULL; - connectToVolume(); - connectHandles(); - return 0; + if (dynamic_cast<const NetFS::AuthError *>(&error)) { + return -EPERM; } - catch (...) { - return -EIO; - } - } - if (dynamic_cast<const NetFS::AuthError *>(&e)) { - return -EPERM; + return FuseAppBase::onError(error); } - return FuseAppBase::onError(e); -} -void -NetFS::FuseApp::beforeOperation() -{ - connectSession(); - connectToService(); - connectToVolume(); -} + void + FuseApp::beforeOperation() + { + connectSession(); + connectToService(); + connectToVolume(false); + } -NetFS::ReqEnv -NetFS::FuseApp::reqEnv() -{ - struct fuse_context * c = fuse_get_context(); - NetFS::ReqEnv re; - userLookup.getName(c->uid, &re.user); - groupLookup.getName(c->gid, &re.grp); - return re; + ReqEnv + FuseApp::reqEnv() + { + struct fuse_context * context = fuseGetContext(); + auto transportContext = converter.mapper->mapFileSystem(safe {context->uid}, safe {context->gid}); + return {.user = std::move(transportContext.username), .grp = std::move(transportContext.groupname)}; + } } - diff --git a/netfs/fuse/fuseApp.h b/netfs/fuse/fuseApp.h index 23738b3..8dcc2ec 100644 --- a/netfs/fuse/fuseApp.h +++ b/netfs/fuse/fuseApp.h @@ -1,149 +1,127 @@ -#ifndef NETFS_FUSE_H -#define NETFS_FUSE_H +#pragma once -#include <shared_mutex> -#include <functional> -#include <filesystem> -#include <Ice/Ice.h> +#include "cache.h" +#include "fuseAppBase.h" +#include "fuseConfig.h" #include <Glacier2/Session.h> -#include <service.h> +#include <Ice/Ice.h> +#include <c++11Helpers.h> #include <entCache.h> +#include <filesystem> +#include <service.h> +#include <shared_mutex> #include <typeConverter.h> -#include "fuseAppBase.h" -#include "fuseConfig.h" -#include "cache.h" #include <visibility.h> -#include <boost/icl/interval_map.hpp> namespace NetFS { - class DLL_PUBLIC FuseApp : public FuseAppBase { - private: - class OpenDir { - public: - OpenDir(DirectoryPrxPtr remote, const std::string & path); - - DirectoryPrxPtr remote; - DirectoryV2PrxPtr remoteV2; - const std::string path; - }; - typedef std::shared_ptr<OpenDir> OpenDirPtr; - typedef std::map<int, OpenDirPtr> OpenDirs; - - class OpenFile { - public: - OpenFile(FilePrxPtr remote, const std::string & path, int flags); - - void flush(); - void wait() const; - - FilePrxPtr remote; - const std::string path; - const int flags; - class WriteState; - typedef boost::icl::interval_map<Ice::Long, std::shared_ptr<WriteState>> BGs; - static BGs::interval_type range(off_t, size_t); - BGs bg; - mutable std::shared_mutex _lock; - }; - typedef std::shared_ptr<OpenFile> OpenFilePtr; - typedef std::map<int, OpenFilePtr> OpenFiles; - - public: - FuseApp(const Ice::StringSeq &); - ~FuseApp(); - - private: - void * init (struct fuse_conn_info * info) override; - int opt_parse(void *, const char * arg, int key, struct fuse_args *) override; - - void connectSession(); - void connectToService(); - void connectToVolume(); - void connectHandles(); - void verifyConnection(); - - public: - // misc - int access(const char * p, int a) override; - int getattr(const char * p, struct stat * s) override; - int fgetattr(const char *, struct stat *, struct fuse_file_info *) override; - int chmod(const char *, mode_t) override; - int chown(const char *, uid_t, gid_t) override; - int link(const char *, const char *) override; - int readlink(const char *, char *, size_t) override; - int rename(const char *, const char *) override; - int symlink(const char *, const char *) override; - int unlink(const char *) override; - int utimens(const char *, const struct timespec tv[2]) override; - int mknod(const char *, mode_t, dev_t) override; - // dirs - int opendir(const char * p, struct fuse_file_info * fi) override; - int releasedir(const char *, struct fuse_file_info * fi) override; - int readdir(const char *, void * buf, fuse_fill_dir_t filler, off_t, struct fuse_file_info * fi) override; - int mkdir(const char *, mode_t) override; - int rmdir(const char *) override; - // files - int open(const char * p, struct fuse_file_info * fi) override; - int create(const char *, mode_t, struct fuse_file_info *) override; - int flush(const char *, struct fuse_file_info * fi) override; - int release(const char *, struct fuse_file_info * fi) override; - int read(const char *, char * buf, size_t s, off_t o, struct fuse_file_info * fi) override; - int write(const char *, const char * buf, size_t s, off_t o, struct fuse_file_info * fi) override; - int truncate(const char *, off_t) override; - int ftruncate(const char *, off_t, struct fuse_file_info *) override; - // fs - int statfs(const char *, struct statvfs *) override; - // stuff - int onError(const std::exception & err) throw() override; - void beforeOperation() override; - - virtual struct fuse_context * fuse_get_context() = 0; - - protected: - typedef std::function<Client::ResourcePtr()> Configurator; - Configurator configurator; - virtual NetFS::Client::ConfigurationPtr ReadConfiguration(const std::filesystem::path &) const; - virtual NetFS::Client::ResourcePtr configureFromFile(const std::string &, const std::string &) const; - virtual NetFS::Client::ResourcePtr configureFromUri(const std::string &) const; - - private: - template<typename Handle, typename ... Params> - void setProxy(uint64_t & fh, const Params & ...); - template<typename Handle> - Handle getProxy(uint64_t localID); - template<typename Handle> - void clearProxy(uint64_t localID); - template<typename Handle> - std::map<int, Handle> & getMap(); - - template<typename Rtn, typename F> static inline Rtn - waitOnWriteRangeAndThen(size_t s, off_t o, const OpenFilePtr & of, const F & f); - - ReqEnv reqEnv(); - - Ice::StringSeq iceArgs; - Ice::CommunicatorPtr ic; - Client::ResourcePtr fcr; - mutable std::shared_mutex _proxymaplock; - - NetFS::VolumePrxPtr volume; - NetFS::ServicePrxPtr service; - Glacier2::SessionPrxPtr session; - bool sessionOpened; - - std::string mountPoint; - - OpenDirs openDirs; - OpenFiles openFiles; - int openHandleId; - - EntCache<User> userLookup; - EntCache<Group> groupLookup; - EntryTypeConverter converter; - - typedef AdHoc::Cache<struct stat, std::string> StatCache; - StatCache statCache; + class DLL_PUBLIC FuseApp : public FuseAppBaseT<FuseApp> { + public: + using FuseHandleTypeId = decltype(fuse_file_info::fh); + class OpenDir; + using OpenDirPtr = std::shared_ptr<OpenDir>; + using OpenDirs = std::map<FuseHandleTypeId, OpenDirPtr>; + + class OpenFile; + using OpenFilePtr = std::shared_ptr<OpenFile>; + using OpenFiles = std::map<FuseHandleTypeId, OpenFilePtr>; + + explicit FuseApp(Ice::StringSeq); + SPECIAL_MEMBERS_DELETE(FuseApp); + ~FuseApp() override; + + // lifecycle + void * init(struct fuse_conn_info * info, struct fuse_config * cfg) override; + // NOLINTNEXTLINE(readability-identifier-naming) + static int opt_parse(void * data, const char * arg, int, struct fuse_args *); + + protected: + void connectSession(); + virtual void connectToService(); + void connectToVolume(bool force); + void connectHandles(); + void verifyConnection(); + + struct CombinedSettings { + size_t messageSizeMax {}; + }; + + public: + // misc + int access(const char * path, int accessMode) override; + int getattr(const char * path, struct stat *, struct fuse_file_info *) override; + int chmod(const char * path, mode_t, struct fuse_file_info *) override; + int chown(const char * path, uid_t, gid_t, struct fuse_file_info *) override; + int link(const char * path1, const char * path2) override; + int readlink(const char * path, char *, size_t) override; + int rename(const char * path1, const char * path2, unsigned int) override; + int symlink(const char * path1, const char * path2) override; + int unlink(const char * path) override; + // NOLINTNEXTLINE(*-c-arrays) + int utimens(const char * path, const struct timespec times[2], struct fuse_file_info *) override; + int mknod(const char * path, mode_t, dev_t) override; + // dirs + int opendir(const char * path, struct fuse_file_info *) override; + int releasedir(const char * path, struct fuse_file_info *) override; + int readdir(const char * path, void * buf, fuse_fill_dir_t filler, off_t, struct fuse_file_info *, + enum fuse_readdir_flags) override; + int mkdir(const char * path, mode_t) override; + int rmdir(const char * path) override; + // files + int open(const char * path, struct fuse_file_info *) override; + int create(const char * path, mode_t, struct fuse_file_info *) override; + int flush(const char * path, struct fuse_file_info *) override; + int release(const char * path, struct fuse_file_info *) override; + int read(const char * path, char * buf, size_t size, off_t offset, struct fuse_file_info *) override; + int write(const char * path, const char * buf, size_t size, off_t offset, struct fuse_file_info *) override; + ssize_t copy_file_range(const char * path, struct fuse_file_info *, off_t, const char *, + struct fuse_file_info *, off_t, size_t, int) override; + int truncate(const char * path, off_t, struct fuse_file_info *) override; + // fs + int statfs(const char * path, struct statvfs *) override; + // stuff + int onError(const std::exception & err) noexcept override; + void beforeOperation() override; + + virtual struct fuse_context * fuseGetContext() = 0; + + static NetFS::Client::ResourcePtr configureFromFile(const std::filesystem::path &, const std::string &); + static NetFS::Client::ResourcePtr configureFromUri(const std::string &); + static CombinedSettings combineSettings(const Settings & daemon, const Settings & client); + + protected: + template<typename Handle, typename... Params> void setProxy(FuseHandleTypeId &, const Params &...); + template<typename Handle> Handle getProxy(FuseHandleTypeId localID); + template<typename Handle> void clearProxy(FuseHandleTypeId localID); + template<typename Handle> std::map<FuseHandleTypeId, Handle> & getMap(); + + template<typename Callback> + static inline auto waitOnWriteRangeAndThen( + size_t size, off_t offset, const OpenFilePtr &, const Callback & callback); + + ReqEnv reqEnv(); + + Ice::StringSeq iceArgs; + Ice::CommunicatorPtr ic; + Client::ResourcePtr fcr; + mutable std::shared_mutex proxyMapMutex; + + NetFS::VolumePrxPtr volume; + NetFS::ServicePrxPtr service; + Glacier2::SessionPrxPtr session; + NetFS::SettingsPtr daemonSettings; + NetFS::Settings clientSettings; + CombinedSettings combinedSettings; + bool sessionOpened {false}; + + std::string mountPoint; + + OpenDirs openDirs; + OpenFiles openFiles; + FuseHandleTypeId openHandleId {}; + + EntryTypeConverter converter; + + using StatCache = AdHoc::Cache<struct stat, std::size_t>; + StatCache statCache; }; } - -#endif diff --git a/netfs/fuse/fuseApp.impl.h b/netfs/fuse/fuseApp.impl.h index 8dd9f1e..322ed46 100644 --- a/netfs/fuse/fuseApp.impl.h +++ b/netfs/fuse/fuseApp.impl.h @@ -1,43 +1,38 @@ -#ifndef NETFS_FUSE_COMMON_H -#define NETFS_FUSE_COMMON_H +#pragma once #include "fuseApp.h" #include <lockHelpers.h> #include <safeMapFind.h> namespace NetFS { - template<typename Handle, typename ... Params> + template<typename Handle, typename... Params> void - FuseApp::setProxy(uint64_t & fh, const Params & ... params) + FuseApp::setProxy(uint64_t & fileHandle, const Params &... params) { auto & map = getMap<Handle>(); - Lock(_proxymaplock); - while (map.find(fh = ++openHandleId) != map.end()) ; - map.emplace(fh, std::make_shared<typename Handle::element_type>(params...)); + Lock(proxyMapMutex); + while (map.find(fileHandle = ++openHandleId) != map.end()) { } + map.emplace(fileHandle, std::make_shared<typename Handle::element_type>(params...)); } template<typename Handle> Handle - FuseApp::getProxy(uint64_t localID) + FuseApp::getProxy(uint64_t localId) { const auto & map = getMap<Handle>(); - SharedLock(_proxymaplock); - auto i = map.find(localID); - if (i != map.end()) { - return i->second; + SharedLock(proxyMapMutex); + if (auto proxyIter = map.find(localId); proxyIter != map.end()) { + return proxyIter->second; } throw NetFS::SystemError(EBADF); } template<typename Handle> void - FuseApp::clearProxy(uint64_t localID) + FuseApp::clearProxy(uint64_t localId) { auto & map = getMap<Handle>(); - Lock(_proxymaplock); - map.erase(localID); + Lock(proxyMapMutex); + map.erase(localId); } } - -#endif - diff --git a/netfs/fuse/fuseAppBase.cpp b/netfs/fuse/fuseAppBase.cpp index 3eadd8f..838f1d9 100644 --- a/netfs/fuse/fuseAppBase.cpp +++ b/netfs/fuse/fuseAppBase.cpp @@ -1,198 +1,288 @@ #include "fuseAppBase.h" -#include <errno.h> -#include <assert.h> -#include <stdio.h> +#include <boost/assert.hpp> +#include <cassert> +#include <cerrno> +#include <cstdio> +#include <cstdlib> #include <unistd.h> -#include <stdlib.h> -#include <typeinfo> FuseAppBase * FuseAppBase::fuseApp; FuseAppBase::FuseAppBase() { + BOOST_ASSERT(!fuseApp); + BOOST_ASSERT(this); + fuseApp = this; } + FuseAppBase::~FuseAppBase() { + BOOST_ASSERT(fuseApp); + fuseApp = nullptr; } + // LCOV_EXCL_START // These are all excluded from coverage because it is impossible // to call them in a realistic manner. They exist only as the default // implementation of the function which is never passed to libfuse // unless it is overridden. -void * FuseAppBase::init(fuse_conn_info*) -{ - return NULL; -} -int FuseAppBase::opt_parse(void*, const char *, int, fuse_args*) +void * +FuseAppBase::init(fuse_conn_info *, fuse_config *) { - return 1; + return nullptr; } -int FuseAppBase::access(const char *, int) + +int +FuseAppBase::access(const char *, int) { return -ENOSYS; } -int FuseAppBase::chmod(const char *, mode_t) + +int +FuseAppBase::chmod(const char *, mode_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::chown(const char *, uid_t, gid_t) + +int +FuseAppBase::chown(const char *, uid_t, gid_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::create(const char *, mode_t, struct fuse_file_info *) + +int +FuseAppBase::create(const char *, mode_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::fgetattr(const char *, struct stat *, struct fuse_file_info *) + +int +FuseAppBase::getattr(const char *, struct stat *, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::flush(const char *, struct fuse_file_info *) + +int +FuseAppBase::flush(const char *, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::fsync(const char *, int, struct fuse_file_info *) + +int +FuseAppBase::fsync(const char *, int, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::fsyncdir(const char *, int, struct fuse_file_info *) + +int +FuseAppBase::fsyncdir(const char *, int, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::ftruncate(const char *, off_t, struct fuse_file_info *) + +int +FuseAppBase::truncate(const char *, off_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::getattr(const char *, struct stat *) + +int +FuseAppBase::getxattr(const char *, const char *, char *, size_t) { return -ENOSYS; } -int FuseAppBase::getxattr(const char *, const char *, char *, size_t) + +int +FuseAppBase::link(const char *, const char *) { return -ENOSYS; } -int FuseAppBase::link(const char *, const char *) + +int +FuseAppBase::listxattr(const char *, char *, size_t) { return -ENOSYS; } -int FuseAppBase::listxattr(const char *, char *, size_t) + +int +FuseAppBase::mkdir(const char *, mode_t) { return -ENOSYS; } -int FuseAppBase::mkdir(const char *, mode_t) + +int +FuseAppBase::mknod(const char *, mode_t, dev_t) { return -ENOSYS; } -int FuseAppBase::mknod(const char *, mode_t, dev_t) + +int +FuseAppBase::open(const char *, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::open(const char *, struct fuse_file_info *) + +int +FuseAppBase::opendir(const char *, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::opendir(const char *, struct fuse_file_info *) + +int +FuseAppBase::read(const char *, char *, size_t, off_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::read(const char *, char *, size_t, off_t, struct fuse_file_info *) + +int +FuseAppBase::readdir(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags) { return -ENOSYS; } -int FuseAppBase::readdir(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *) + +int +FuseAppBase::readlink(const char *, char *, size_t) { return -ENOSYS; } -int FuseAppBase::readlink(const char *, char *, size_t) + +int +FuseAppBase::release(const char *, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::release(const char *, struct fuse_file_info *) + +int +FuseAppBase::releasedir(const char *, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::releasedir(const char *, struct fuse_file_info *) + +int +FuseAppBase::removexattr(const char *, const char *) { return -ENOSYS; } -int FuseAppBase::removexattr(const char *, const char *) + +int +FuseAppBase::rename(const char *, const char *, unsigned int) { return -ENOSYS; } -int FuseAppBase::rename(const char *, const char *) + +int +FuseAppBase::rmdir(const char *) { return -ENOSYS; } -int FuseAppBase::rmdir(const char *) + +int +FuseAppBase::setxattr(const char *, const char *, const char *, size_t, int) { return -ENOSYS; } -int FuseAppBase::setxattr(const char *, const char *, const char *, size_t, int) + +int +FuseAppBase::statfs(const char *, struct statvfs *) { return -ENOSYS; } -int FuseAppBase::statfs(const char *, struct statvfs *) + +int +FuseAppBase::symlink(const char *, const char *) { return -ENOSYS; } -int FuseAppBase::symlink(const char *, const char *) + +int +FuseAppBase::unlink(const char *) { return -ENOSYS; } -int FuseAppBase::truncate(const char *, off_t) + +int +FuseAppBase::write(const char *, const char *, size_t, off_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::unlink(const char *) + +int +FuseAppBase::lock(const char *, struct fuse_file_info *, int, struct flock *) { return -ENOSYS; } -int FuseAppBase::write(const char *, const char *, size_t, off_t, struct fuse_file_info *) + +int +// NOLINTNEXTLINE(*-c-arrays) +FuseAppBase::utimens(const char *, const struct timespec[2], struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::lock(const char *, struct fuse_file_info *, int, struct flock *) + +int +FuseAppBase::bmap(const char *, size_t, uint64_t *) { return -ENOSYS; } -int FuseAppBase::utimens(const char *, const struct timespec[2]) + +int +FuseAppBase::ioctl(const char *, unsigned int, void *, struct fuse_file_info *, unsigned int, void *) { return -ENOSYS; } -int FuseAppBase::bmap(const char *, size_t, uint64_t *) + +int +FuseAppBase::poll(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *) { return -ENOSYS; } -int FuseAppBase::ioctl(const char *, int, void *, struct fuse_file_info *, unsigned int, void *) + +int +FuseAppBase::write_buf(const char *, struct fuse_bufvec *, off_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::poll(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *) + +int +FuseAppBase::read_buf(const char *, struct fuse_bufvec **, size_t, off_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::write_buf(const char *, struct fuse_bufvec *, off_t, struct fuse_file_info *) + +int +FuseAppBase::flock(const char *, struct fuse_file_info *, int) { return -ENOSYS; } -int FuseAppBase::read_buf(const char *, struct fuse_bufvec **, size_t, off_t, struct fuse_file_info *) + +int +FuseAppBase::fallocate(const char *, int, off_t, off_t, struct fuse_file_info *) { return -ENOSYS; } -int FuseAppBase::flock(const char *, struct fuse_file_info *, int) + +ssize_t +FuseAppBase::copy_file_range( + const char *, struct fuse_file_info *, off_t, const char *, struct fuse_file_info *, off_t, size_t, int) { return -ENOSYS; } -int FuseAppBase::fallocate(const char *, int, off_t, off_t, struct fuse_file_info *) + +off_t +FuseAppBase::lseek(const char *, off_t, int, struct fuse_file_info *) { return -ENOSYS; } + // LCOV_EXCL_STOP -void FuseAppBase::log(int level, const char * message) const throw() +void +FuseAppBase::log(int level, const char * message) const noexcept { logf(level, "%s", message); } -void FuseAppBase::logf(int level, const char * fmt, ...) const throw() + +void +FuseAppBase::logf(int level, const char * fmt, ...) const noexcept { va_list args; va_start(args, fmt); @@ -200,37 +290,14 @@ void FuseAppBase::logf(int level, const char * fmt, ...) const throw() va_end(args); } -int FuseAppBase::onError(const std::exception & e) throw() +int +FuseAppBase::onError(const std::exception & error) noexcept { - logf(LOG_ERR, "Unknown exception (what: %s)", e.what()); + logf(LOG_ERR, "Unknown exception (what: %s)", error.what()); return -ENOSYS; } -void FuseAppBase::beforeOperation() -{ -} - -void * FuseAppBase::fuseInit (struct fuse_conn_info *conn) +void +FuseAppBase::beforeOperation() { - return fuseApp->init(conn); } -void FuseAppBase::fuseDestroy(void *) -{ - delete fuseApp; -} - -struct fuse_args -FuseAppBase::runint(int argc, char ** argv) -{ - struct fuse_opt fuse_opts[] = { - { NULL, 0, 0 } - }; - fuseApp = this; - struct fuse_args args = FUSE_ARGS_INIT(argc, argv); - if (fuse_opt_parse(&args, fuseApp, fuse_opts, - &internalHelper<decltype(&FuseAppBase::opt_parse), &FuseAppBase::opt_parse>) == -1) { - exit(1); - } - return args; -} - diff --git a/netfs/fuse/fuseAppBase.h b/netfs/fuse/fuseAppBase.h index f0866bb..8f3bffe 100644 --- a/netfs/fuse/fuseAppBase.h +++ b/netfs/fuse/fuseAppBase.h @@ -1,186 +1,195 @@ -#ifndef FUSEAPP_H -#define FUSEAPP_H +#pragma once -#define FUSE_USE_VERSION 26 -#include <fuse.h> -#include <typeinfo> +#include <c++11Helpers.h> +#include <cerrno> +#include <cstdio> #include <exception> -#include <stdio.h> -#include <errno.h> +#include <fuse.h> +#include <lockHelpers.h> +#include <shared_mutex> #include <syslog.h> +#include <typeinfo> #include <visibility.h> -#include <buffer.h> -#include <shared_mutex> -#include <lockHelpers.h> class DLL_PUBLIC FuseAppBase { - public: - FuseAppBase(); - virtual ~FuseAppBase() = 0; - virtual void * init (struct fuse_conn_info * info); - virtual int opt_parse(void *, const char * arg, int key, struct fuse_args *); - virtual int access(const char *, int); - virtual int chmod(const char *, mode_t); - virtual int chown(const char *, uid_t, gid_t); - virtual int create(const char *, mode_t, struct fuse_file_info *); - virtual int fgetattr(const char *, struct stat *, struct fuse_file_info *); - virtual int flush(const char *, struct fuse_file_info *); - virtual int fsync(const char *, int, struct fuse_file_info *); - virtual int fsyncdir(const char *, int, struct fuse_file_info *); - virtual int ftruncate(const char *, off_t, struct fuse_file_info *); - virtual int getattr(const char *, struct stat *); - virtual int getxattr(const char *, const char *, char *, size_t); - virtual int link(const char *, const char *); - virtual int listxattr(const char *, char *, size_t); - virtual int mkdir(const char *, mode_t); - virtual int mknod(const char *, mode_t, dev_t); - virtual int open(const char *, struct fuse_file_info *); - virtual int opendir(const char *, struct fuse_file_info *); - virtual int read(const char *, char *, size_t, off_t, struct fuse_file_info *); - virtual int readdir(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *); - virtual int readlink(const char *, char *, size_t); - virtual int release(const char *, struct fuse_file_info *); - virtual int releasedir(const char *, struct fuse_file_info *); - virtual int removexattr(const char *, const char *); - virtual int rename(const char *, const char *); - virtual int rmdir(const char *); - virtual int setxattr(const char *, const char *, const char *, size_t, int); - virtual int statfs(const char *, struct statvfs *); - virtual int symlink(const char *, const char *); - virtual int truncate(const char *, off_t); - virtual int unlink(const char *); - virtual int write(const char *, const char *, size_t, off_t, struct fuse_file_info *); - virtual int lock(const char *, struct fuse_file_info *, int cmd, struct flock *); - virtual int utimens(const char *, const struct timespec tv[2]); - virtual int bmap(const char *, size_t blocksize, uint64_t *idx); - virtual int ioctl(const char *, int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void * data); - virtual int poll(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *); - virtual int write_buf(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *); - virtual int read_buf(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *); - virtual int flock(const char *, struct fuse_file_info *, int op); - virtual int fallocate(const char *, int, off_t, off_t, struct fuse_file_info *); - virtual int onError(const std::exception & err) throw(); - virtual void beforeOperation(); - void log(int level, const char * message) const throw(); - void logf(int level, const char * fmt, ...) const throw() __attribute__ ((__format__ (__printf__, 3, 4))); - virtual void vlogf(int level, const char * fmt, va_list) const throw() __attribute__ ((__format__ (__printf__, 3, 0))) = 0; +public: + FuseAppBase(); + SPECIAL_MEMBERS_DELETE(FuseAppBase); + virtual ~FuseAppBase(); + + virtual void * init(struct fuse_conn_info * info, struct fuse_config * cfg); + virtual int access(const char *, int); + virtual int chmod(const char *, mode_t, struct fuse_file_info *); + virtual int chown(const char *, uid_t, gid_t, struct fuse_file_info *); + virtual int create(const char *, mode_t, struct fuse_file_info *); + virtual int getattr(const char *, struct stat *, struct fuse_file_info *); + virtual int flush(const char *, struct fuse_file_info *); + virtual int fsync(const char *, int, struct fuse_file_info *); + virtual int fsyncdir(const char *, int, struct fuse_file_info *); + virtual int truncate(const char *, off_t, struct fuse_file_info *); + virtual int getxattr(const char *, const char *, char *, size_t); + virtual int link(const char *, const char *); + virtual int listxattr(const char *, char *, size_t); + virtual int mkdir(const char *, mode_t); + virtual int mknod(const char *, mode_t, dev_t); + virtual int open(const char *, struct fuse_file_info *); + virtual int opendir(const char *, struct fuse_file_info *); + virtual int read(const char *, char *, size_t, off_t, struct fuse_file_info *); + virtual int readdir(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags); + virtual int readlink(const char *, char *, size_t); + virtual int release(const char *, struct fuse_file_info *); + virtual int releasedir(const char *, struct fuse_file_info *); + virtual int removexattr(const char *, const char *); + virtual int rename(const char *, const char *, unsigned int); + virtual int rmdir(const char *); + virtual int setxattr(const char *, const char *, const char *, size_t, int); + virtual int statfs(const char *, struct statvfs *); + virtual int symlink(const char *, const char *); + virtual int unlink(const char *); + virtual int write(const char *, const char *, size_t, off_t, struct fuse_file_info *); + virtual int lock(const char *, struct fuse_file_info *, int cmd, struct flock *); + // NOLINTNEXTLINE(*-c-arrays) + virtual int utimens(const char *, const struct timespec times[2], struct fuse_file_info *); + virtual int bmap(const char *, size_t blocksize, uint64_t * idx); + virtual int ioctl( + const char *, unsigned int cmd, void * arg, struct fuse_file_info *, unsigned int flags, void * data); + virtual int poll(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *); + // NOLINTNEXTLINE(readability-identifier-naming) + virtual int write_buf(const char *, struct fuse_bufvec * buf, off_t off, struct fuse_file_info *); + // NOLINTNEXTLINE(readability-identifier-naming) + virtual int read_buf(const char *, struct fuse_bufvec ** bufp, size_t size, off_t off, struct fuse_file_info *); + virtual int flock(const char *, struct fuse_file_info *, int operation); + virtual int fallocate(const char *, int, off_t, off_t, struct fuse_file_info *); + // NOLINTNEXTLINE(readability-identifier-naming) + virtual ssize_t copy_file_range( + const char *, struct fuse_file_info *, off_t, const char *, struct fuse_file_info *, off_t, size_t, int); + virtual off_t lseek(const char *, off_t off, int whence, struct fuse_file_info *); + virtual int onError(const std::exception & err) noexcept; + virtual void beforeOperation(); + void log(int level, const char * message) const noexcept; + void logf(int level, const char * fmt, ...) const noexcept __attribute__((__format__(__printf__, 3, 4))); + virtual void vlogf(int level, const char * fmt, va_list) const noexcept + __attribute__((__format__(__printf__, 3, 0))) + = 0; - virtual int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc) = 0; - virtual int main(int, char **, const struct fuse_operations *) = 0; + mutable std::shared_mutex mutex; -#define GetHelper(func) getHelper<decltype(&FuseAppBase::func), decltype(&FuseApp::func), &FuseAppBase::func>(&FuseAppBase::func) - template <typename FuseApp> - static int run(int argc, char ** argv, FuseApp * fa) - { - auto args = fa->runint(argc, argv); - struct fuse_operations operations = { - GetHelper(getattr), - GetHelper(readlink), - NULL, // getdir deprecated - GetHelper(mknod), - GetHelper(mkdir), - GetHelper(unlink), - GetHelper(rmdir), - GetHelper(symlink), - GetHelper(rename), - GetHelper(link), - GetHelper(chmod), - GetHelper(chown), - GetHelper(truncate), - NULL, // utime deprecated - GetHelper(open), - GetHelper(read), - GetHelper(write), - GetHelper(statfs), - GetHelper(flush), - GetHelper(release), - GetHelper(fsync), - GetHelper(setxattr), - GetHelper(getxattr), - GetHelper(listxattr), - GetHelper(removexattr), - GetHelper(opendir), - GetHelper(readdir), - GetHelper(releasedir), - GetHelper(fsyncdir), - fuseInit, - fuseDestroy, - GetHelper(access), - GetHelper(create), - GetHelper(ftruncate), - GetHelper(fgetattr), -#if (FUSE_MINOR_VERSION >= 6) - GetHelper(lock), - GetHelper(utimens), - GetHelper(bmap), -#if (FUSE_MINOR_VERSION >= 8) - 0, // flag_nullpath_ok -#if (FUSE_MINOR_VERSION >= 9) - 0, // flag_nopath - 0, // flag_utime_omit_ok -#endif - 0, // flag_reserved - GetHelper(ioctl), - GetHelper(poll), -#if (FUSE_MINOR_VERSION >= 9) - GetHelper(write_buf), - GetHelper(read_buf), - GetHelper(flock), - GetHelper(fallocate), -#endif -#endif -#endif +protected: + static FuseAppBase * fuseApp; + + template<bool Implemented, auto BaseFunc> + constexpr static auto + getInternalHelper() + { + if constexpr (Implemented) { + return [](auto... arg) { + SharedLock(fuseApp->mutex); + return (fuseApp->*BaseFunc)(arg...); }; - return fa->main(args.argc, args.argv, &operations); } - struct fuse_args runint(int, char **); - - private: - static void * fuseInit(struct fuse_conn_info *conn); - static void fuseDestroy(void *); + else { + return nullptr; + } + } - template <typename BFunc, typename IFunc, BFunc bfunc, typename ... Args> - static int(*getHelper(int(FuseAppBase::*)(Args...)))(Args...) - { - if constexpr (!std::is_same<BFunc, IFunc>::value) { - return [](Args ... a) { - for (int t = 0; ; ++t) { - try { - fuseApp->beforeOperation(); - SharedLock(fuseApp->_lock); - return (fuseApp->*bfunc)(a...); - } - catch (const std::exception & ex) { - if (t < 10) { - if (int rtn = fuseApp->onError(ex)) { - return rtn; - } - } - else { - fuseApp->logf(LOG_ERR, "Retries expired with %s calling %s", ex.what(), typeid(bfunc).name()); - return -EIO; + template<bool Implemented, auto BaseFunc> + constexpr static auto + getHelper() + { + constexpr auto ATTEMPTS = 10; + if constexpr (Implemented) { + return [](auto... arg) { + using Return = decltype((fuseApp->*BaseFunc)(arg...)); + for (int attempt = 0;; ++attempt) { + try { + fuseApp->beforeOperation(); + SharedLock(fuseApp->mutex); + return (fuseApp->*BaseFunc)(arg...); + } + catch (const std::exception & ex) { + if (attempt < ATTEMPTS) { + if (Return rtn = fuseApp->onError(ex)) { + return rtn; } } - catch (...) { - fuseApp->logf(LOG_ERR, "Unknown exception calling %s", typeid(bfunc).name()); - return -EIO; + else { + fuseApp->logf( + LOG_ERR, "Retries expired with %s calling %s", ex.what(), typeid(BaseFunc).name()); + return static_cast<Return>(-EIO); } } - }; - } - return nullptr; + catch (...) { + fuseApp->logf(LOG_ERR, "Unknown exception calling %s", typeid(BaseFunc).name()); + return static_cast<Return>(-EIO); + } + } + }; } - template <typename Func, Func f, typename ... Args> - static int internalHelper(Args ... a) - { - return (fuseApp->*f)(a...); + else { + return nullptr; } - - static FuseAppBase * fuseApp; - - protected: - mutable std::shared_mutex _lock; + } }; -#endif +template<typename FuseApp> class FuseAppBaseT : public FuseAppBase { +private: +public: +#define GetIntHelper(func) \ + getInternalHelper<!std::is_same_v<decltype(&FuseAppBase::func), decltype(&FuseApp::func)>, &FuseAppBase::func>() +#define GetHelper(func) \ + getHelper<!std::is_same_v<decltype(&FuseAppBase::func), decltype(&FuseApp::func)>, &FuseAppBase::func>() + constexpr const static fuse_operations OPERATIONS { + GetHelper(getattr), + GetHelper(readlink), + GetHelper(mknod), + GetHelper(mkdir), + GetHelper(unlink), + GetHelper(rmdir), + GetHelper(symlink), + GetHelper(rename), + GetHelper(link), + GetHelper(chmod), + GetHelper(chown), + GetHelper(truncate), + GetHelper(open), + GetHelper(read), + GetHelper(write), + GetHelper(statfs), + GetHelper(flush), + GetHelper(release), + GetHelper(fsync), + GetHelper(setxattr), + GetHelper(getxattr), + GetHelper(listxattr), + GetHelper(removexattr), + GetHelper(opendir), + GetHelper(readdir), + GetHelper(releasedir), + GetHelper(fsyncdir), + GetIntHelper(init), + nullptr, // fuseDestroy + GetHelper(access), + GetHelper(create), + GetHelper(lock), + GetHelper(utimens), + GetHelper(bmap), + GetHelper(ioctl), + GetHelper(poll), + GetHelper(write_buf), + GetHelper(read_buf), + GetHelper(flock), + GetHelper(fallocate), + GetHelper(copy_file_range), + GetHelper(lseek), + }; +#undef GetHelper +#undef GetIntHelper +protected: + template<typename Func, Func AppFunc, typename... Args> + static int + internalHelper(Args... arg) + { + return (fuseApp->*AppFunc)(arg...); + } +}; diff --git a/netfs/fuse/fuseConfig.ice b/netfs/fuse/fuseConfig.ice index e425c21..ea489ac 100644 --- a/netfs/fuse/fuseConfig.ice +++ b/netfs/fuse/fuseConfig.ice @@ -1,4 +1,5 @@ #include <exceptions.ice> +#include <mapper.ice> module NetFS { module Client { @@ -21,8 +22,8 @@ module NetFS { ["slicer:name:async"] bool Async = false; - ["slicer:name:listdir"] - bool ListDir = true; + ["slicer:name:mapper"] + Mapping::Mapper Mapper; }; ["slicer:key:name","slicer:value:resource","slicer:item:resource"] diff --git a/netfs/fuse/fuseConfigImpl.cpp b/netfs/fuse/fuseConfigImpl.cpp index 9129bfc..351a794 100644 --- a/netfs/fuse/fuseConfigImpl.cpp +++ b/netfs/fuse/fuseConfigImpl.cpp @@ -1,10 +1,12 @@ -#include <fuseConfig.h> #include <compileTimeFormatter.h> +#include <fuseConfig.h> AdHocFormatter(ResourceNotFoundMsg, "No such resource: %?"); -void -NetFS::Client::ResourceNotFound::ice_print(std::ostream & s) const -{ - ResourceNotFoundMsg::write(s, resourceName); -} +namespace NetFS { + void + Client::ResourceNotFound::ice_print(std::ostream & stream) const + { + ResourceNotFoundMsg::write(stream, resourceName); + } +} diff --git a/netfs/fuse/fuseDirs.cpp b/netfs/fuse/fuseDirs.cpp index 3d77101..1b98048 100644 --- a/netfs/fuse/fuseDirs.cpp +++ b/netfs/fuse/fuseDirs.cpp @@ -1,98 +1,101 @@ -#include "fuseApp.impl.h" -#include <lockHelpers.h> +#include "fuseDirs.h" #include <entCache.h> +#include <lockHelpers.h> +#include <numeric.h> namespace NetFS { -FuseApp::OpenDir::OpenDir(DirectoryPrxPtr r, const std::string & p) : - remote(r), - remoteV2(r->ice_getFacet() >= "v02" ? Ice::uncheckedCast<DirectoryV2Prx>(r) : nullptr), - path(p) -{ -} - -template<> -std::map<int, FuseApp::OpenDirPtr> & -FuseApp::getMap<FuseApp::OpenDirPtr>() -{ - return openDirs; -} - -int -FuseApp::opendir(const char * p, struct fuse_file_info * fi) -{ - try { - auto remote = volume->opendir(reqEnv(), p); - setProxy<OpenDirPtr>(fi->fh, remote, p); - return 0; + FuseApp::OpenDir::OpenDir(DirectoryPrxPtr remotePrx, std::string remotePath) : + remote(std::move(remotePrx)), path(std::move(remotePath)) + { } - catch (SystemError & e) { - return -e.syserrno; + + template<> + std::map<FuseApp::FuseHandleTypeId, FuseApp::OpenDirPtr> & + FuseApp::getMap<FuseApp::OpenDirPtr>() + { + return openDirs; } -} -int -FuseApp::releasedir(const char *, struct fuse_file_info * fi) -{ - try { - auto remote = getProxy<OpenDirPtr>(fi->fh)->remote; - remote->close(); - clearProxy<OpenDirPtr>(fi->fh); - return 0; + int + FuseApp::opendir(const char * path, struct fuse_file_info * fileInfo) + { + try { + auto remote = volume->opendir(reqEnv(), path); + setProxy<OpenDirPtr>(fileInfo->fh, remote, path); + return 0; + } + catch (SystemError & e) { + return -e.syserrno; + } } - catch (SystemError & e) { - clearProxy<OpenDirPtr>(fi->fh); - return -e.syserrno; + + int + FuseApp::releasedir(const char *, struct fuse_file_info * fileInfo) + { + try { + auto remote = getProxy<OpenDirPtr>(fileInfo->fh)->remote; + remote->close(); + clearProxy<OpenDirPtr>(fileInfo->fh); + return 0; + } + catch (SystemError & e) { + clearProxy<OpenDirPtr>(fileInfo->fh); + return -e.syserrno; + } } -} -int -FuseApp::readdir(const char * p, void * buf, fuse_fill_dir_t filler, off_t, struct fuse_file_info * fi) -{ - try { - auto od = getProxy<OpenDirPtr>(fi->fh); - std::string path(p); - path += "/"; - auto expiry = time(NULL) + 2; - if (fcr->ListDir && od->remoteV2) { - for (const auto & e : od->remoteV2->listdir()) { - filler(buf, e.first.c_str(), NULL, 0); - statCache.add(path + e.first, converter.convert(e.second), expiry); + int + FuseApp::readdir(const char * path, void * buf, fuse_fill_dir_t filler, off_t, struct fuse_file_info * fileInfo, + enum fuse_readdir_flags flags) + { + try { + auto openDir = getProxy<OpenDirPtr>(fileInfo->fh); + const std::filesystem::path fspath {path}; + auto expiry = time(nullptr) + 2; + if (flags & FUSE_READDIR_PLUS) { + for (const auto & entry : openDir->remote->listdir()) { + if (auto stat = converter.convert(entry.second); stat.st_mode) { + filler(buf, entry.first.c_str(), nullptr, 0, FUSE_FILL_DIR_PLUS); + const auto key {std::filesystem::hash_value(fspath / entry.first)}; + statCache.remove(key); + statCache.add(key, stat, expiry); + } + } } - } - else { - for (const auto & e : od->remote->readdir()) { - filler(buf, e.c_str(), NULL, 0); + else { + // Standard read dir cannot know the local system cannot represent the inode + for (const auto & entry : openDir->remote->readdir()) { + filler(buf, entry.c_str(), nullptr, 0, fuse_fill_dir_flags {}); + } } + return 0; + } + catch (SystemError & e) { + return -e.syserrno; } - return 0; - } - catch (SystemError & e) { - return -e.syserrno; } -} -int -FuseApp::mkdir(const char * p, mode_t m) -{ - try { - volume->mkdir(reqEnv(), p, m); - return 0; - } - catch (SystemError & e) { - return -e.syserrno; + int + FuseApp::mkdir(const char * path, mode_t mode) + { + try { + volume->mkdir(reqEnv(), path, safe {mode}); + return 0; + } + catch (SystemError & e) { + return -e.syserrno; + } } -} -int -FuseApp::rmdir(const char * p) -{ - try { - volume->rmdir(reqEnv(), p); - return 0; - } - catch (SystemError & e) { - return -e.syserrno; + int + FuseApp::rmdir(const char * path) + { + try { + volume->rmdir(reqEnv(), path); + return 0; + } + catch (SystemError & e) { + return -e.syserrno; + } } } -} - diff --git a/netfs/fuse/fuseDirs.h b/netfs/fuse/fuseDirs.h new file mode 100644 index 0000000..4b04027 --- /dev/null +++ b/netfs/fuse/fuseDirs.h @@ -0,0 +1,13 @@ +#pragma once + +#include "fuseApp.impl.h" // IWYU pragma: keep + +namespace NetFS { + class FuseApp::OpenDir { + public: + OpenDir(DirectoryPrxPtr remote, std::string path); + + DirectoryPrxPtr remote; + const std::string path; + }; +} diff --git a/netfs/fuse/fuseFiles.cpp b/netfs/fuse/fuseFiles.cpp index b8eb014..fd313e5 100644 --- a/netfs/fuse/fuseFiles.cpp +++ b/netfs/fuse/fuseFiles.cpp @@ -1,261 +1,354 @@ -#include <string.h> -#include "fuseApp.impl.h" #include "fuseFiles.h" +#include "fuseApp.impl.h" // IWYU pragma: keep - getProxy definition #include "lockHelpers.h" +#include <Ice/BuiltinSequences.h> +#include <algorithm> +#include <cstring> #include <entCache.h> +#include <future> +#include <memory> #include <mutex> +#include <numeric.h> +#include <numeric> +#include <ranges> +#include <span> +#include <utility> +#include <vector> namespace NetFS { -FuseApp::OpenFile::WriteState::WriteState() : - future(promise.get_future().share()) -{ -} - -FuseApp::OpenFile::OpenFile(FilePrxPtr r, const std::string & p, int f) : - remote(r), - path(p), - flags(f) -{ -} + constexpr size_t MESSAGE_SIZE_HEADROOM = 1024; -template<> -std::map<int, FuseApp::OpenFilePtr> & -FuseApp::getMap<FuseApp::OpenFilePtr>() -{ - return openFiles; -} + FuseApp::OpenFile::WriteState::WriteState() : future(promise.get_future().share()) { } -void -FuseApp::OpenFile::wait() const -{ - auto cbg = [this](){ - SharedLock(_lock); - return bg; - }(); - for (const auto & w : cbg) { - w.second->future.wait(); + FuseApp::OpenFile::OpenFile(FilePrxPtr remotePrx, std::string remotePath, int openFlags, size_t messageMaxSize) : + remote(std::move(remotePrx)), path(std::move(remotePath)), flags(openFlags), + bodyMaxSize(messageMaxSize - MESSAGE_SIZE_HEADROOM) + { } -} -void -FuseApp::OpenFile::flush() -{ - auto first = [this]() { - SharedLock(_lock); - return bg.empty() ? nullptr : bg.begin()->second; - }; - while (auto w = first()) { - // background operations are void, so no need to actually get the return value - w->future.wait(); + template<> + std::map<FuseApp::FuseHandleTypeId, FuseApp::OpenFilePtr> & + FuseApp::getMap<FuseApp::OpenFilePtr>() + { + return openFiles; } -} -FuseApp::OpenFile::BGs::interval_type -FuseApp::OpenFile::range(off_t o, size_t s) -{ - return OpenFile::BGs::interval_type::right_open(o, o + s); -} - -int -FuseApp::open(const char * p, struct fuse_file_info * fi) -{ - try { - auto remote = volume->open(reqEnv(), p, fi->flags); - setProxy<OpenFilePtr>(fi->fh, remote, p, fi->flags); - return 0; - } - catch (SystemError & e) { - return -e.syserrno; + void + FuseApp::OpenFile::wait() const + { + auto cbg = [this]() { + SharedLock(mutex); + return bg; + }(); + for (const auto & operation : cbg) { + operation.second->future.get(); + } } -} -int -FuseApp::create(const char * p, mode_t m, struct fuse_file_info * fi) -{ - try { - auto remote = volume->create(reqEnv(), p, fi->flags, m); - setProxy<OpenFilePtr>(fi->fh, remote, p, fi->flags); - return 0; + void + FuseApp::OpenFile::flush() + { + auto getFirstBackgroundOp = [this]() { + SharedLock(mutex); + return bg.empty() ? nullptr : bg.begin()->second; + }; + while (auto operation = getFirstBackgroundOp()) { + operation->future.get(); + } } - catch (SystemError & e) { - return -e.syserrno; + + FuseApp::OpenFile::BGs::interval_type + FuseApp::OpenFile::range(off_t offset, size_t size) + { + return OpenFile::BGs::interval_type::right_open(safe {offset}, safe {offset} + size); } -} -int -FuseApp::release(const char *, struct fuse_file_info * fi) -{ - try { - auto of = getProxy<OpenFilePtr>(fi->fh); - auto remote = of->remote; + int + FuseApp::open(const char * path, struct fuse_file_info * fileInfo) + { try { - of->flush(); + auto remote = volume->open(reqEnv(), path, fileInfo->flags); + setProxy<OpenFilePtr>(fileInfo->fh, remote, path, fileInfo->flags, combinedSettings.messageSizeMax); + return 0; } catch (SystemError & e) { + return -e.syserrno; } - remote->close(); - clearProxy<OpenFilePtr>(fi->fh); - return 0; - } - catch (SystemError & e) { - clearProxy<OpenFilePtr>(fi->fh); - return -e.syserrno; } -} -int -FuseApp::flush(const char *, struct fuse_file_info * fi) -{ - try { - getProxy<OpenFilePtr>(fi->fh)->flush(); - return 0; + int + FuseApp::create(const char * path, mode_t mode, struct fuse_file_info * fileInfo) + { + try { + auto remote = volume->create(reqEnv(), path, fileInfo->flags, safe {mode}); + setProxy<OpenFilePtr>(fileInfo->fh, remote, path, fileInfo->flags, combinedSettings.messageSizeMax); + return 0; + } + catch (SystemError & e) { + return -e.syserrno; + } } - catch (SystemError & e) { - return -e.syserrno; + + int + FuseApp::release(const char *, struct fuse_file_info * fileInfo) + { + try { + auto openFile = getProxy<OpenFilePtr>(fileInfo->fh); + auto remote = openFile->remote; + try { + openFile->flush(); + clearProxy<OpenFilePtr>(fileInfo->fh); + remote->close(); + } + catch (SystemError & e) { + clearProxy<OpenFilePtr>(fileInfo->fh); + remote->close(); + throw; + } + return 0; + } + catch (SystemError & e) { + clearProxy<OpenFilePtr>(fileInfo->fh); + return -e.syserrno; + } } -} -template<typename Rtn, typename F> -inline -Rtn -FuseApp::waitOnWriteRangeAndThen(size_t s, off_t o, const OpenFilePtr & of, const F & f) -{ - const auto key = OpenFile::range(o, s); - while (true) { - std::unique_lock<decltype(of->_lock)> _l(of->_lock); - // Acquire operations to wait for - auto R = of->bg.equal_range(key); - if (R.first == R.second) { - // Perform operation - return f(key); + int + FuseApp::flush(const char *, struct fuse_file_info * fileInfo) + { + try { + getProxy<OpenFilePtr>(fileInfo->fh)->flush(); + return 0; } - else { + catch (SystemError & e) { + return -e.syserrno; + } + } + + template<typename Callback> + inline auto + FuseApp::waitOnWriteRangeAndThen(size_t size, off_t offset, const OpenFilePtr & openFile, const Callback & callback) + { + const auto key = OpenFile::range(offset, size); + while (true) { + std::unique_lock lock(openFile->mutex); + // Acquire operations to wait for + const auto pendingRange = openFile->bg.equal_range(key); + if (pendingRange.first == pendingRange.second) { + // Perform operation + return callback(key); + } + const auto overlap = [pendingRange]() { + std::vector<std::shared_future<void>> out; + out.reserve(safe<ptrdiff_t> {std::distance(pendingRange.first, pendingRange.second)}); + std::transform(pendingRange.first, pendingRange.second, std::back_inserter(out), [](auto && operation) { + return operation.second->future; + }); + return out; + }(); // Wait for them whilst unlocked - _l.release()->unlock(); - std::vector<std::shared_ptr<OpenFile::WriteState>> overlap; - overlap.reserve(std::distance(R.first, R.second)); - for (auto i = R.first; i != R.second; i++) { - overlap.push_back(i->second); + lock.release()->unlock(); + try { + std::for_each(overlap.begin(), overlap.end(), [](auto && operationFuture) { + operationFuture.get(); + }); + } + catch (const SystemError &) { + throw; } - for (const auto & r : overlap) { - r->future.wait(); + catch (...) { + throw SystemError {ECOMM}; } - // Cause this thread to yield so the callback can acquire _lock + // Cause this thread to yield so the callback can lock mutex usleep(0); } } -} -int -FuseApp::read(const char *, char * buf, size_t s, off_t o, struct fuse_file_info * fi) -{ - try { - auto cpy = [buf](const auto && data) { - memcpy(buf, &data.front(), data.size()); - return data.size(); - }; - auto of = getProxy<OpenFilePtr>(fi->fh); - auto remote = of->remote; - if (fcr->Async) { - auto p = waitOnWriteRangeAndThen<std::future<Buffer>>(s, o, of, [o, s, &remote](const auto &){ - return remote->readAsync(o, s); - }); - return cpy(p.get()); + int + FuseApp::read(const char *, char * buf, size_t size, off_t offset, struct fuse_file_info * fileInfo) + { + try { + const std::span out(buf, size); + using BackgroundOps = std::vector<std::promise<int>>; + const auto cpy = [out](off_t blockOffset, const auto && data) -> int { + std::ranges::copy(data, out.begin() + blockOffset); + return safe {data.size()}; + }; + const auto collateTotal = [](auto && ops) { + return std::accumulate(ops.begin(), ops.end(), 0, [](auto && total, auto & operation) { + return total += operation.get_future().get(); + }); + }; + auto openFile = getProxy<OpenFilePtr>(fileInfo->fh); + auto remote = openFile->remote; + if (fcr->Async) { + const auto blocks = out | std::views::chunk(openFile->bodyMaxSize); + BackgroundOps ops(blocks.size()); + std::ranges::for_each( + blocks, [offset, &openFile, &remote, &cpy, opIter = ops.begin(), out](auto && block) mutable { + waitOnWriteRangeAndThen(block.size(), offset, openFile, + [thisOp = opIter++, offset, &remote, &cpy, block, out](const auto &) { + const auto outPosition = block.begin() - out.begin(); + const auto position = offset + outPosition; + return remote->readAsync( + position, safe(block.size()), + [cpy, thisOp, outPosition](auto && resultBuf) { + thisOp->set_value( + cpy(outPosition, std::forward<Ice::ByteSeq>(resultBuf))); + }, + [thisOp](auto error) { + thisOp->set_exception(std::move(error)); + }); + }); + }); + return collateTotal(ops); + } + if (openFile->bodyMaxSize < size) { + const auto blocks = out | std::views::chunk(openFile->bodyMaxSize); + BackgroundOps ops(blocks.size()); + std::ranges::for_each( + blocks, [offset, &remote, out, &cpy, opIter = ops.begin()](auto && block) mutable { + auto thisOp = opIter++; + const auto outPosition = block.begin() - out.begin(); + const auto position = offset + outPosition; + remote->readAsync( + position, safe(block.size()), + [cpy, thisOp, outPosition](auto && resultBuf) { + thisOp->set_value(cpy(outPosition, std::forward<Ice::ByteSeq>(resultBuf))); + }, + [thisOp](auto error) { + thisOp->set_exception(std::move(error)); + }); + }); + return collateTotal(ops); + } + return cpy(0, remote->read(offset, safe {size})); } - else { - return cpy(remote->read(o, s)); + catch (SystemError & e) { + return -e.syserrno; } } - catch (SystemError & e) { - return -e.syserrno; - } -} -int -FuseApp::write(const char *, const char * buf, size_t s, off_t o, struct fuse_file_info * fi) -{ - try { - auto of = getProxy<OpenFilePtr>(fi->fh); - auto remote = of->remote; - if (fcr->Async) { - waitOnWriteRangeAndThen<void>(s, o, of, [o, s, buf, &of, &remote](const auto & key){ - auto p = std::make_shared<OpenFile::WriteState>(); - remote->writeAsync(o, s, Buffer(buf, buf + s), [p,of,key]() { - p->promise.set_value(); - ScopeLock(of->_lock) { - of->bg.erase(key); - } - }, [p,of,key](auto e) { - p->promise.set_exception(e); - ScopeLock(of->_lock) { - of->bg.erase(key); - } + int + FuseApp::write(const char *, const char * buf, size_t size, off_t offset, struct fuse_file_info * fileInfo) + { + static auto toBuffer = [](auto block) { + return std::make_pair(std::to_address(block.begin()), std::to_address(block.end())); + }; + try { + auto openFile = getProxy<OpenFilePtr>(fileInfo->fh); + auto remote = openFile->remote; + const std::span bytes {reinterpret_cast<const ::Ice::Byte *>(buf), size}; + if (fcr->Async) { + const auto blocks = bytes | std::views::chunk(openFile->bodyMaxSize); + std::ranges::for_each(blocks, [&remote, bytes, offset, &openFile](auto && block) { + const auto position = offset + (block.begin() - bytes.begin()); + waitOnWriteRangeAndThen( + block.size(), position, openFile, [position, block, &openFile, remote](const auto & key) { + auto pendingWrite = std::make_shared<OpenFile::WriteState>(); + openFile->bg.insert({key, pendingWrite}); + remote->writeAsync( + position, safe(block.size()), toBuffer(block), + [pendingWrite, openFile, key]() { + pendingWrite->promise.set_value(); + ScopeLock(openFile->mutex) { + openFile->bg.erase(key); + } + }, + [pendingWrite, openFile](auto error) { + pendingWrite->promise.set_exception(std::move(error)); + }); + }); }); - of->bg.insert({key, p}); - }); + } + else if (openFile->bodyMaxSize < size) { + const auto blocks = bytes | std::views::chunk(openFile->bodyMaxSize); + std::vector<std::future<void>> ops; + ops.reserve(blocks.size()); + std::ranges::transform(blocks, std::back_inserter(ops), [&remote, bytes, offset](auto && block) { + const auto position = offset + (block.begin() - bytes.begin()); + return remote->writeAsync(position, safe(block.size()), toBuffer(block)); + }); + std::ranges::for_each(ops, &std::future<void>::get); + } + else { + remote->write(offset, safe {size}, toBuffer(bytes)); + } + return safe {size}; } - else { - remote->write(o, s, Buffer(buf, buf + s)); + catch (SystemError & e) { + return -e.syserrno; } - return s; } - catch (SystemError & e) { - return -e.syserrno; - } -} -int -FuseApp::truncate(const char * p, off_t o) -{ - try { - volume->truncate(reqEnv(), p, o); - return 0; - } - catch (SystemError & e) { - return -e.syserrno; + ssize_t + FuseApp::copy_file_range(const char *, struct fuse_file_info * fileInfoIn, off_t offsetIn, const char *, + struct fuse_file_info * fileInfoOut, off_t offsetOut, size_t size, int flags) + { + try { + auto openFileIn = getProxy<OpenFilePtr>(fileInfoIn->fh); + auto openFileOut = getProxy<OpenFilePtr>(fileInfoOut->fh); + openFileIn->wait(); + openFileOut->wait(); + return openFileIn->remote->copyrange(openFileOut->remote, offsetIn, offsetOut, safe {size}, flags); + } + catch (SystemError & e) { + return -e.syserrno; + } } -} -int -FuseApp::ftruncate(const char *, off_t o, fuse_file_info * fi) -{ - try { - auto of = getProxy<OpenFilePtr>(fi->fh); - of->wait(); - auto remote = of->remote; - remote->ftruncate(reqEnv(), o); - return 0; - } - catch (SystemError & e) { - return -e.syserrno; + int + FuseApp::truncate(const char * path, off_t offset, fuse_file_info * fileInfo) + { + try { + if (fileInfo) { + auto openFile = getProxy<OpenFilePtr>(fileInfo->fh); + openFile->wait(); + openFile->remote->ftruncate(offset); + } + else { + volume->truncate(reqEnv(), path, offset); + } + return 0; + } + catch (SystemError & e) { + return -e.syserrno; + } } -} -int -FuseApp::fgetattr(const char *, struct stat * s, fuse_file_info * fi) -{ - try { - auto of = getProxy<OpenFilePtr>(fi->fh); - of->wait(); - auto remote = of->remote; - *s = converter.convert(remote->fgetattr(reqEnv())); - return 0; - } - catch (SystemError & e) { - return -e.syserrno; + int + FuseApp::getattr(const char * path, struct stat * stat, fuse_file_info * fileInfo) + { + try { + if (fileInfo) { + auto openFile = getProxy<OpenFilePtr>(fileInfo->fh); + openFile->wait(); + *stat = converter.convert(openFile->remote->fgetattr()); + } + else { + if (auto cacehedStat = statCache.get(std::filesystem::hash_value(path))) { + *stat = *cacehedStat; + } + else { + *stat = converter.convert(volume->getattr(reqEnv(), path)); + } + } + return stat->st_mode ? 0 : -ENOENT; + } + catch (SystemError & e) { + return -e.syserrno; + } } -} -int -FuseApp::unlink(const char * p) -{ - try { - volume->unlink(reqEnv(), p); - return 0; - } - catch (SystemError & e) { - return -e.syserrno; + int + FuseApp::unlink(const char * path) + { + try { + volume->unlink(reqEnv(), path); + return 0; + } + catch (SystemError & e) { + return -e.syserrno; + } } } -} - diff --git a/netfs/fuse/fuseFiles.h b/netfs/fuse/fuseFiles.h index 5e05ad2..672d30f 100644 --- a/netfs/fuse/fuseFiles.h +++ b/netfs/fuse/fuseFiles.h @@ -1,17 +1,32 @@ -#ifndef NETFS_FUSE_FILES_H -#define NETFS_FUSE_FILES_H +#pragma once #include "fuseApp.h" +#include <boost/icl/interval_map.hpp> namespace NetFS { - class FuseApp::OpenFile::WriteState { + class FuseApp::OpenFile { + public: + OpenFile(FilePrxPtr remotePrx, std::string remotePath, int openFlags, size_t messageMaxSize); + + void flush(); + void wait() const; + + FilePrxPtr remote; + const std::string path; + const int flags; + const size_t bodyMaxSize; + + class WriteState { public: WriteState(); std::promise<void> promise; std::shared_future<void> future; + }; + + using BGs = boost::icl::interval_map<size_t, std::shared_ptr<WriteState>>; + static BGs::interval_type range(off_t, size_t); + BGs bg; + mutable std::shared_mutex mutex; }; } - -#endif - diff --git a/netfs/fuse/fuseMappers.ice b/netfs/fuse/fuseMappers.ice new file mode 100644 index 0000000..c404472 --- /dev/null +++ b/netfs/fuse/fuseMappers.ice @@ -0,0 +1,23 @@ +#include <mapper.ice> + +["slicer:include:fuseMappersImpl.h"] +module NetFS { + module Client { + ["slicer:implementation:NetFS.Client.HideUnknownMapperImpl","slicer:typename:hide"] + local class HideUnknownMapper extends Mapping::Mapper { + + }; + ["slicer:implementation:NetFS.Client.MaskUnknownMapperImpl","slicer:typename:mask"] + local class MaskUnknownMapper extends Mapping::Mapper { + ["slicer:name:unknownuser"] + string UnknownUser = "nobody"; + ["slicer:name:unknowngroup"] + string UnknownGroup = "nogroup"; + ["slicer:name:usermask","slicer:conversion:std.string:NetFS.Client.fromOctal:NetFS.Client.toOctal:nodeclare"] + int UserMask = 0700; + ["slicer:name:groupmask","slicer:conversion:std.string:NetFS.Client.fromOctal:NetFS.Client.toOctal:nodeclare"] + int GroupMask = 0070; + }; + }; +}; + diff --git a/netfs/fuse/fuseMappersImpl.cpp b/netfs/fuse/fuseMappersImpl.cpp new file mode 100644 index 0000000..a6ac148 --- /dev/null +++ b/netfs/fuse/fuseMappersImpl.cpp @@ -0,0 +1,74 @@ +#include "fuseMappersImpl.h" +#include <entCache.h> +#include <format> +#include <numeric.h> + +namespace NetFS::Client { + constexpr int MASK_EVERYTHING = ~0; + + Mapping::FileSystem + HideUnknownMapperImpl::mapTransport(const std::string & userName, const std::string & groupName) + { + auto user = users->getEntry(userName); + auto group = groups->getEntry(groupName); + if (!user || !group) { + return {.uid = 0, .gid = 0, .mask = MASK_EVERYTHING}; + } + return {.uid = safe(user->id), .gid = safe(group->id), .mask = 0}; + } + + Mapping::Transport + HideUnknownMapperImpl::mapFileSystem(int uid, int gid) + { + auto user = users->getEntry(safe {uid}); + auto group = groups->getEntry(safe {gid}); + if (!user || !group) { + throw NetFS::SystemError(EPERM); + } + return {.username = user->name, .groupname = group->name, .mask = 0}; + } + + Mapping::FileSystem + MaskUnknownMapperImpl::mapTransport(const std::string & userName, const std::string & groupName) + { + int mask = 0; + auto apply = [&mask](const auto & resolver, const auto & entry, const auto & fallbackentry, auto entrymask) { + auto resolvedEntry = resolver->getEntry(entry); + if (!resolvedEntry) { + resolvedEntry = resolver->getEntry(fallbackentry); + if (!resolvedEntry) { + throw NetFS::SystemError(EPERM); + } + mask |= entrymask; + } + return resolvedEntry; + }; + auto user = apply(users, userName, UnknownUser, UserMask); + auto group = apply(groups, groupName, UnknownGroup, GroupMask); + return {.uid = safe(user->id), .gid = safe(group->id), .mask = mask}; + } + + Mapping::Transport + MaskUnknownMapperImpl::mapFileSystem(int uid, int gid) + { + auto user = users->getEntry(safe {uid}); + auto group = groups->getEntry(safe {gid}); + if (!user || !group) { + throw NetFS::SystemError(EPERM); + } + return {.username = user->name, .groupname = group->name, .mask = 0}; + } + + Ice::Int + fromOctal(const std::string & input) + { + static constexpr int OCTAL_BASE = 8; + return std::stoi(input, nullptr, OCTAL_BASE); + } + + std::string + toOctal(const Ice::Int & input) + { + return std::format("{:o}", input); + } +} diff --git a/netfs/fuse/fuseMappersImpl.h b/netfs/fuse/fuseMappersImpl.h new file mode 100644 index 0000000..f97acb3 --- /dev/null +++ b/netfs/fuse/fuseMappersImpl.h @@ -0,0 +1,26 @@ +#pragma once + +#include "baseMapper.h" +#include <fuseMappers.h> +#include <visibility.h> + +namespace NetFS::Client { + std::string toOctal(const Ice::Int &); + Ice::Int fromOctal(const std::string &); + + class DLL_PUBLIC HideUnknownMapperImpl : public HideUnknownMapper, Mapping::BaseMapper { + public: + using BaseMapper::BaseMapper; + + Mapping::Transport mapFileSystem(int uid, int gid) override; + Mapping::FileSystem mapTransport(const std::string & userName, const std::string & groupName) override; + }; + + class DLL_PUBLIC MaskUnknownMapperImpl : public MaskUnknownMapper, Mapping::BaseMapper { + public: + using BaseMapper::BaseMapper; + + Mapping::Transport mapFileSystem(int uid, int gid) override; + Mapping::FileSystem mapTransport(const std::string & userName, const std::string & groupName) override; + }; +} diff --git a/netfs/fuse/fuseMisc.cpp b/netfs/fuse/fuseMisc.cpp index 5ece9a1..f5d5f67 100644 --- a/netfs/fuse/fuseMisc.cpp +++ b/netfs/fuse/fuseMisc.cpp @@ -1,127 +1,116 @@ #include "fuseApp.h" -#include <string.h> +#include <cstring> #include <entCache.h> +#include <numeric.h> +#include <span> -int -NetFS::FuseApp::access(const char * p, int a) -{ - return -volume->access(reqEnv(), p, a); -} - -int -NetFS::FuseApp::getattr(const char * p, struct stat * s) -{ - try { - auto cacehedStat = statCache.get(p); - if (cacehedStat) { - *s = *cacehedStat; - } - else { - *s = converter.convert(volume->getattr(reqEnv(), p)); - } - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; +namespace NetFS { + int + FuseApp::access(const char * path, int accessMode) + { + return -volume->access(reqEnv(), path, accessMode); } -} -int -NetFS::FuseApp::chmod(const char * p, mode_t m) -{ - try { - volume->chmod(reqEnv(), p, m); - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; + int + FuseApp::chmod(const char * path, mode_t mode, fuse_file_info *) + { + try { + volume->chmod(reqEnv(), path, safe {mode}); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } -} -int -NetFS::FuseApp::chown(const char * p, uid_t u, gid_t g) -{ - try { - volume->chown(reqEnv(), p, u, g); - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; + int + FuseApp::chown(const char * path, uid_t uid, gid_t gid, fuse_file_info *) + { + try { + volume->chown(reqEnv(), path, safe {uid}, safe {gid}); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } -} -int -NetFS::FuseApp::link(const char * p1, const char * p2) -{ - try { - volume->link(reqEnv(), p1, p2); - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; + int + FuseApp::link(const char * path1, const char * path2) + { + try { + volume->link(reqEnv(), path1, path2); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } -} -int -NetFS::FuseApp::mknod(const char * p, mode_t mode, dev_t dev) -{ - try { - volume->mknod(reqEnv(), p, mode, dev); - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; + int + FuseApp::mknod(const char * path, mode_t mode, dev_t dev) + { + try { + volume->mknod(reqEnv(), path, safe {mode}, safe {dev}); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } -} -int -NetFS::FuseApp::symlink(const char * p1, const char * p2) -{ - try { - volume->symlink(reqEnv(), p1, p2); - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; + int + FuseApp::symlink(const char * path1, const char * path2) + { + try { + volume->symlink(reqEnv(), path1, path2); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } -} -int -NetFS::FuseApp::readlink(const char * p, char * p2, size_t s) -{ - try { - std::string l = volume->readlink(reqEnv(), p); - l.copy(p2, s); - p2[l.length()] = '\0'; - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; + int + FuseApp::readlink(const char * path, char * path2, size_t size) + { + try { + const auto link = volume->readlink(reqEnv(), path); + if (size <= link.length()) { + return -ENAMETOOLONG; + } + link.copy(path2, size); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } -} -int -NetFS::FuseApp::rename(const char * p1, const char * p2) -{ - try { - volume->rename(reqEnv(), p1, p2); - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; + int + FuseApp::rename(const char * path1, const char * path2, unsigned int flags) + { + try { + volume->rename(reqEnv(), path1, path2, safe {flags}); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } -} -int -NetFS::FuseApp::utimens(const char * path, const struct timespec times[2]) -{ - try { - volume->utimens(reqEnv(), path, - times[0].tv_sec, times[0].tv_nsec, times[1].tv_sec, times[1].tv_nsec); - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; + int + // NOLINTNEXTLINE(*-c-arrays) + FuseApp::utimens(const char * path, const struct timespec times[2], fuse_file_info *) + { + try { + std::span<const struct timespec, 2> timesSpan(times, 2); + volume->utimens(reqEnv(), path, timesSpan.front().tv_sec, timesSpan.front().tv_nsec, + timesSpan.back().tv_sec, timesSpan.back().tv_nsec); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } } - diff --git a/netfs/fuse/fuseSystem.cpp b/netfs/fuse/fuseSystem.cpp index ac87d14..3af1b36 100644 --- a/netfs/fuse/fuseSystem.cpp +++ b/netfs/fuse/fuseSystem.cpp @@ -1,14 +1,15 @@ #include "fuseApp.h" -int -NetFS::FuseApp::statfs(const char * p, struct statvfs * vfs) -{ - try { - *vfs = converter.TypeConverter::convert(volume->statfs(reqEnv(), p)); - return 0; - } - catch (NetFS::SystemError & e) { - return -e.syserrno; +namespace NetFS { + int + FuseApp::statfs(const char * path, struct statvfs * vfs) + { + try { + *vfs = converter.TypeConverter::convert(volume->statfs(reqEnv(), path)); + return 0; + } + catch (NetFS::SystemError & e) { + return -e.syserrno; + } } } - diff --git a/netfs/fuse/netfs.cpp b/netfs/fuse/netfs.cpp index 2039eed..a28ab16 100644 --- a/netfs/fuse/netfs.cpp +++ b/netfs/fuse/netfs.cpp @@ -1,43 +1,53 @@ #include "fuseApp.h" +#include <c++11Helpers.h> #include <syslog.h> -class FuseImpl : public NetFS::FuseApp { - public: - FuseImpl(const Ice::StringSeq & a) : - NetFS::FuseApp(a) - { - openlog("netfs", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_USER); - } - - ~FuseImpl() - { - closelog(); - } - - struct fuse_context * fuse_get_context() override - { - return ::fuse_get_context(); - } - - int fuse_opt_parse(struct fuse_args * args, void * data, const struct fuse_opt opts[], fuse_opt_proc_t proc) override - { - return ::fuse_opt_parse(args, data, opts, proc); - } - - int main(int argc, char ** argv, const struct fuse_operations * ops) override - { - return ::fuse_main(argc, argv, ops, this); - } - - void vlogf(int priority, const char * fmt, va_list args) const throw() override - { - vsyslog(priority, fmt, args); - } +class FuseImpl : public fuse_args, public NetFS::FuseApp { +public: + FuseImpl(int argcIn, char ** argvIn) : + fuse_args(FUSE_ARGS_INIT(argcIn, argvIn)), NetFS::FuseApp([this]() { + Ice::StringSeq rtn; + if (fuse_opt_parse(this, &rtn, nullptr, opt_parse) == -1) { + exit(-1); + } + return rtn; + }()) + { + openlog("netfs", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_USER); + } + + ~FuseImpl() override + { + closelog(); + } + + SPECIAL_MEMBERS_DELETE(FuseImpl); + + struct fuse_context * + fuseGetContext() override + { + return ::fuse_get_context(); + } + + int + run() + { + return ::fuse_main(argc, argv, &OPERATIONS, this); + } + + void + vlogf(int priority, const char * fmt, va_list args) const noexcept override + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + // NOLINTNEXTLINE(clang-diagnostic-format-nonliteral) + vsyslog(priority, fmt, args); +#pragma GCC diagnostic push + } }; int -main(int argc, char* argv[]) +main(int argc, char * argv[]) { - return FuseAppBase::run(argc, argv, new FuseImpl(Ice::argsToStringSeq(argc, argv))); + return FuseImpl(argc, argv).run(); } - diff --git a/netfs/ice/Jamfile.jam b/netfs/ice/Jamfile.jam index 6f5036f..a695712 100644 --- a/netfs/ice/Jamfile.jam +++ b/netfs/ice/Jamfile.jam @@ -1,14 +1,28 @@ import package ; +obj directory : directory.ice : <toolset>tidy:<checker>none ; +obj exceptions : exceptions.ice : <toolset>tidy:<checker>none ; +obj file : file.ice : <toolset>tidy:<checker>none ; +obj service : service.ice : <toolset>tidy:<checker>none ; +obj types : types.ice : <toolset>tidy:<checker>none ; +obj volume : volume.ice : <toolset>tidy:<checker>none ; +obj mapper : mapper.ice : <toolset>tidy:<checker>none ; +alias gen : directory exceptions file service types volume mapper ; lib netfs-api : - [ glob *.cpp *.ice ] : + gen + mapper.ice + [ glob *.cpp ] : <library>..//Ice <library>..//pthread <library>..//adhocutil + <library>..//slicer + <implicit-dependency>gen + <slicer>pure : : <include>. <library>..//Ice <library>..//pthread + <implicit-dependency>gen ; explicit install ; diff --git a/netfs/ice/directory.ice b/netfs/ice/directory.ice index 3718f69..4f5ce79 100644 --- a/netfs/ice/directory.ice +++ b/netfs/ice/directory.ice @@ -1,5 +1,4 @@ -#ifndef _DIRECTORY -#define _DIRECTORY +#pragma once #include "exceptions.ice" #include "types.ice" @@ -9,11 +8,6 @@ module NetFS { void close() throws AuthError, SystemError; idempotent NameList readdir() throws AuthError, SystemError; - }; - interface DirectoryV2 extends Directory { idempotent DirectoryContents listdir() throws AuthError, SystemError; }; }; - -#endif - diff --git a/netfs/ice/entryResolver.h b/netfs/ice/entryResolver.h deleted file mode 100644 index 1a20ec3..0000000 --- a/netfs/ice/entryResolver.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef NETFS_ENTRYRESOLVER_H -#define NETFS_ENTRYRESOLVER_H - -#include <string> -#include <stdlib.h> - -template <class entry_t> -class EntryResolver { - public: - typedef std::string name_t; - virtual void getID(const name_t &, id_t *) const = 0; - virtual void getName(const id_t &, name_t *) const = 0; -}; - -#endif - diff --git a/netfs/ice/exceptions.ice b/netfs/ice/exceptions.ice index 02a42e0..4f12321 100644 --- a/netfs/ice/exceptions.ice +++ b/netfs/ice/exceptions.ice @@ -1,5 +1,4 @@ -#ifndef _EXCEPTIONS -#define _EXCEPTIONS +#pragma once module NetFS { // Exceptions @@ -18,6 +17,3 @@ module NetFS { string exportName; }; }; - -#endif - diff --git a/netfs/ice/exceptionsImpl.cpp b/netfs/ice/exceptionsImpl.cpp index 93f5930..0c68630 100644 --- a/netfs/ice/exceptionsImpl.cpp +++ b/netfs/ice/exceptionsImpl.cpp @@ -1,10 +1,12 @@ -#include <exceptions.h> #include <compileTimeFormatter.h> +#include <exceptions.h> AdHocFormatter(ExportNotFoundMsg, "Export [%?] not found on remote host"); -void -NetFS::ExportNotFound::ice_print(std::ostream & s) const -{ - ExportNotFoundMsg::write(s, exportName); -} +namespace NetFS { + void + ExportNotFound::ice_print(std::ostream & stream) const + { + ExportNotFoundMsg::write(stream, exportName); + } +} diff --git a/netfs/ice/file.ice b/netfs/ice/file.ice index a622745..9fc2bf8 100644 --- a/netfs/ice/file.ice +++ b/netfs/ice/file.ice @@ -1,5 +1,4 @@ -#ifndef _FILES -#define _FILES +#pragma once #include "exceptions.ice" #include "types.ice" @@ -8,12 +7,10 @@ module NetFS { interface File { void close() throws AuthError, SystemError; - idempotent Attr fgetattr(ReqEnv env) throws AuthError, SystemError; + idempotent Attr fgetattr() throws AuthError, SystemError; idempotent Buffer read(long offset, long size) throws AuthError, SystemError; - idempotent void ftruncate(ReqEnv env, long size) throws AuthError, SystemError; - idempotent void write(long offset, long size, Buffer data) throws AuthError, SystemError; + idempotent void ftruncate(long size) throws AuthError, SystemError; + idempotent void write(long offset, long size, ["cpp:array"] Buffer data) throws AuthError, SystemError; + idempotent long copyrange(File * to, long offsetsrc, long offsetdest, long size, int flags) throws AuthError, SystemError; }; }; - -#endif - diff --git a/netfs/ice/mapper.ice b/netfs/ice/mapper.ice new file mode 100644 index 0000000..ce2ecb2 --- /dev/null +++ b/netfs/ice/mapper.ice @@ -0,0 +1,28 @@ +#pragma once + +#include "exceptions.ice" + +module NetFS { + module Mapping { + ["slicer:ignore"] + local struct Transport { + string username; + string groupname; + int mask; + }; + + ["slicer:ignore"] + local struct FileSystem { + int uid; + int gid; + int mask; + }; + + ["slicer:typename:default","slicer:typeid:type"] + local class Mapper { + Transport mapFileSystem(int uid, int gid) throws SystemError; + FileSystem mapTransport(string un, string gn) throws SystemError; + }; + }; + +} diff --git a/netfs/ice/numeric.cpp b/netfs/ice/numeric.cpp new file mode 100644 index 0000000..3d86f28 --- /dev/null +++ b/netfs/ice/numeric.cpp @@ -0,0 +1,21 @@ +#include "numeric.h" +#include <stdexcept> +#include <version> + +#ifdef __cpp_lib_source_location +# include <string> + +void +safe_base::out_of_range(std::string desc) +{ + throw std::out_of_range {std::move(desc)}; +} + +#else + +void +safe_base::out_of_range(const char * const function) +{ + throw std::out_of_range {function}; +} +#endif diff --git a/netfs/ice/numeric.h b/netfs/ice/numeric.h new file mode 100644 index 0000000..e47dc27 --- /dev/null +++ b/netfs/ice/numeric.h @@ -0,0 +1,113 @@ +#pragma once + +#include <Ice/Optional.h> +#include <concepts> +#include <limits> +#include <optional> +#include <utility> +#include <version> +#include <visibility.h> + +#ifdef __cpp_lib_source_location +# include <compileTimeFormatter.h> +# include <source_location> +# include <string> +AdHocFormatter(ConvPos, "%?:%?(value: %?)"); +#endif + +struct DLL_PUBLIC safe_base { +protected: +#ifdef __cpp_lib_source_location + [[noreturn]] static void out_of_range(std::string); +#else + [[noreturn]] static void out_of_range(const char * const); +#endif +}; + +template<std::integral T> struct safe : safe_base { +#ifdef __cpp_lib_source_location + // cppcheck-suppress noExplicitConstructor; NOLINTNEXTLINE(hicpp-explicit-conversions) + constexpr safe(T v, const std::source_location w = std::source_location::current()) noexcept : value {v}, where {w} + { + } +#else + // cppcheck-suppress noExplicitConstructor; NOLINTNEXTLINE(hicpp-explicit-conversions) + constexpr safe(T v) noexcept : value {v} { } +#endif + + // NOLINTNEXTLINE(hicpp-explicit-conversions) + template<std::integral R> constexpr inline operator R() const + { + if constexpr (std::cmp_less_equal(std::numeric_limits<R>::min(), min) + && std::cmp_greater_equal(std::numeric_limits<R>::max(), max)) { + return value; + } + else { + if (!std::in_range<R>(value)) { + [[unlikely]] +#ifdef __cpp_lib_source_location + out_of_range(ConvPos::get(where.function_name(), where.line(), value)); +#else + out_of_range(__PRETTY_FUNCTION__); +#endif + } + return static_cast<R>(value); + } + } + + template<std::integral R> constexpr inline operator std::optional<R>() const + { + return this->operator R(); + } + + template<std::integral R> constexpr inline operator Ice::optional<R>() const + { + return this->operator R(); + } + +private: + T value; +#ifdef __cpp_lib_source_location + const std::source_location where; +#endif + constexpr static T min {std::numeric_limits<T>::min()}; + constexpr static T max {std::numeric_limits<T>::max()}; +}; + +template<std::integral Left, std::integral Right> +auto +operator+(const Left left, const safe<Right> right) +{ + return left + static_cast<Left>(right); +} + +template<std::integral Left, std::integral Right> +auto +operator+=(Left & left, const safe<Right> right) +{ + return left += static_cast<Left>(right); +} + +template<typename T> +concept pointer = std::is_pointer_v<T>; + +template<pointer Left, std::integral Right> +auto +operator+=(Left & left, const safe<Right> right) +{ + return left += static_cast<Right>(right); +} + +template<pointer Left, std::integral Right> +auto +operator+(const Left left, const safe<Right> right) +{ + return left + static_cast<Right>(right); +} + +template<std::integral Left, std::integral Right> +auto +operator+(const safe<Left> left, const Right right) +{ + return static_cast<Right>(left) + right; +} diff --git a/netfs/ice/service.ice b/netfs/ice/service.ice index d691ae2..1d13e8d 100644 --- a/netfs/ice/service.ice +++ b/netfs/ice/service.ice @@ -1,5 +1,4 @@ -#ifndef _SERVICE -#define _SERVICE +#pragma once #include "exceptions.ice" #include "types.ice" @@ -7,10 +6,7 @@ module NetFS { interface Service { - // NameList volumes() throws ConfigError; Volume * connect(string volume, string auth) throws AuthError, ConfigError; + Settings getSettings(); }; }; - -#endif - diff --git a/netfs/ice/typeConverter.cpp b/netfs/ice/typeConverter.cpp index 46e8ff1..5db7315 100644 --- a/netfs/ice/typeConverter.cpp +++ b/netfs/ice/typeConverter.cpp @@ -1,90 +1,84 @@ #include "typeConverter.h" +#include "numeric.h" -EntryTypeConverter::EntryTypeConverter(const EntryResolver<uid_t> & u, const EntryResolver<gid_t> & g) : - userLookup(u), - groupLookup(g) -{ -} +namespace NetFS { + EntryTypeConverter::EntryTypeConverter(NetFS::Mapping::MapperPtr mapperImpl) : mapper(std::move(mapperImpl)) { } -struct stat -EntryTypeConverter::convert(const NetFS::Attr & a) const -{ - struct stat s; - s.st_dev = a.dev; - s.st_ino = a.inode; - s.st_mode = a.mode; - s.st_nlink = a.links; - userLookup.getID(a.uid, &s.st_uid); - groupLookup.getID(a.gid, &s.st_gid); - s.st_rdev = a.rdev; - s.st_size = a.size; - s.st_blksize = a.blockSize; - s.st_blocks = a.blocks; - s.st_atime = a.atime; - s.st_atim.tv_sec = a.atime; - s.st_atim.tv_nsec = 0; - s.st_mtime = a.mtime; - s.st_mtim.tv_sec = a.mtime; - s.st_mtim.tv_nsec = 0; - s.st_ctime = a.ctime; - s.st_ctim.tv_sec = a.ctime; - s.st_ctim.tv_nsec = 0; - return s; -} + struct stat + EntryTypeConverter::convert(const NetFS::Attr & attr) const + { + auto map = mapper->mapTransport(attr.uid, attr.gid); + struct stat stat {}; + static_assert(sizeof(stat.st_ino) == sizeof(attr.inode)); + stat.st_dev = safe {attr.dev}; + std::memcpy(&stat.st_ino, &attr.inode, sizeof(stat.st_ino)); + stat.st_mode = safe {attr.mode & ~map.mask}; + stat.st_nlink = safe {attr.links}; + stat.st_uid = safe {map.uid}; + stat.st_gid = safe {map.gid}; + stat.st_rdev = safe {attr.rdev}; + stat.st_size = safe {attr.size}; + stat.st_blksize = safe {attr.blockSize}; + stat.st_blocks = safe {attr.blocks}; + stat.st_atime = safe {attr.atime}; + stat.st_mtime = safe {attr.mtime}; + stat.st_ctime = safe {attr.ctime}; + return stat; + } -struct statvfs -TypeConverter::convert(const NetFS::VFS & v) const -{ - struct statvfs vfs; - vfs.f_bsize = v.blockSize; - vfs.f_frsize = v.fragmentSize; - vfs.f_blocks = v.blocks; - vfs.f_bfree = v.freeBlocks; - vfs.f_bavail = v.availBlocks; - vfs.f_files = v.files; - vfs.f_ffree = v.freeFiles; - vfs.f_favail = v.availFiles; - vfs.f_fsid = v.FSID; - vfs.f_flag = v.flags; - vfs.f_namemax = v.maxNameLen; - return vfs; -} + struct statvfs + TypeConverter::convert(const NetFS::VFS & vfs) + { + struct statvfs statvfs {}; + statvfs.f_bsize = safe {vfs.blockSize}; + statvfs.f_frsize = safe {vfs.fragmentSize}; + statvfs.f_blocks = safe {vfs.blocks}; + statvfs.f_bfree = safe {vfs.freeBlocks}; + statvfs.f_bavail = safe {vfs.availBlocks}; + statvfs.f_files = safe {vfs.files}; + statvfs.f_ffree = safe {vfs.freeFiles}; + statvfs.f_favail = safe {vfs.availFiles}; + statvfs.f_flag = safe {vfs.flags}; + statvfs.f_namemax = safe {vfs.maxNameLen}; + return statvfs; + } -NetFS::Attr -EntryTypeConverter::convert(const struct stat & s) const -{ - NetFS::Attr a; - a.dev = s.st_dev; - a.inode = s.st_ino; - a.mode = s.st_mode; - a.links = s.st_nlink; - userLookup.getName(s.st_uid, &a.uid); - groupLookup.getName(s.st_gid, &a.gid); - a.rdev = s.st_rdev; - a.size = s.st_size; - a.blockSize = s.st_blksize; - a.blocks = s.st_blocks; - a.atime = s.st_atime; - a.mtime = s.st_mtime; - a.ctime = s.st_ctime; - return a; -} + NetFS::Attr + EntryTypeConverter::convert(const struct stat & stat) const + { + auto map = mapper->mapFileSystem(safe {stat.st_uid}, safe {stat.st_gid}); + NetFS::Attr attr {}; + static_assert(sizeof(stat.st_ino) == sizeof(attr.inode)); + attr.dev = safe {stat.st_dev}; + std::memcpy(&attr.inode, &stat.st_ino, sizeof(attr.inode)); + attr.mode = safe {stat.st_mode & static_cast<decltype(stat.st_mode)>(~map.mask)}; + attr.links = safe {stat.st_nlink}; + attr.uid = std::move(map.username); + attr.gid = std::move(map.groupname); + attr.rdev = safe {stat.st_rdev}; + attr.size = safe {stat.st_size}; + attr.blockSize = safe {stat.st_blksize}; + attr.blocks = safe {stat.st_blocks}; + attr.atime = safe {stat.st_atime}; + attr.mtime = safe {stat.st_mtime}; + attr.ctime = safe {stat.st_ctime}; + return attr; + } -NetFS::VFS -TypeConverter::convert(const struct statvfs & s) const -{ - NetFS::VFS t; - t.blockSize = s.f_bsize; - t.fragmentSize = s.f_frsize; - t.blocks = s.f_blocks; - t.freeBlocks = s.f_bfree; - t.availBlocks = s.f_bavail; - t.files = s.f_files; - t.freeFiles = s.f_ffree; - t.availFiles = s.f_favail; - t.FSID = s.f_fsid; - t.flags = s.f_flag; - t.maxNameLen = s.f_namemax; - return t; + NetFS::VFS + TypeConverter::convert(const struct statvfs & statvfs) + { + NetFS::VFS vfs {}; + vfs.blockSize = safe {statvfs.f_bsize}; + vfs.fragmentSize = safe {statvfs.f_frsize}; + vfs.blocks = safe {statvfs.f_blocks}; + vfs.freeBlocks = safe {statvfs.f_bfree}; + vfs.availBlocks = safe {statvfs.f_bavail}; + vfs.files = safe {statvfs.f_files}; + vfs.freeFiles = safe {statvfs.f_ffree}; + vfs.availFiles = safe {statvfs.f_favail}; + vfs.flags = safe {statvfs.f_flag}; + vfs.maxNameLen = safe {statvfs.f_namemax}; + return vfs; + } } - diff --git a/netfs/ice/typeConverter.h b/netfs/ice/typeConverter.h index b8d6fcd..ef8a35a 100644 --- a/netfs/ice/typeConverter.h +++ b/netfs/ice/typeConverter.h @@ -1,31 +1,28 @@ -#ifndef NETFS_TYPECONVERT_H -#define NETFS_TYPECONVERT_H +#pragma once -#include <types.h> +#include <mapper.h> #include <sys/stat.h> #include <sys/statvfs.h> +#include <types.h> #include <visibility.h> -#include "entryResolver.h" -class DLL_PUBLIC TypeConverter { +namespace NetFS { + class DLL_PUBLIC TypeConverter { public: // VFS - struct statvfs convert(const NetFS::VFS &) const; - NetFS::VFS convert(const struct statvfs &) const; -}; + [[nodiscard]] static struct statvfs convert(const NetFS::VFS &); + [[nodiscard]] static NetFS::VFS convert(const struct statvfs &); + }; -class DLL_PUBLIC EntryTypeConverter : public TypeConverter { + class DLL_PUBLIC EntryTypeConverter : public TypeConverter { public: - EntryTypeConverter(const EntryResolver<uid_t> &, const EntryResolver<gid_t> &); + EntryTypeConverter() = default; + explicit EntryTypeConverter(NetFS::Mapping::MapperPtr); // Attributes - struct stat convert(const NetFS::Attr &) const; - NetFS::Attr convert(const struct stat &) const; - - protected: - const EntryResolver<uid_t> & userLookup; - const EntryResolver<gid_t> & groupLookup; -}; - -#endif + [[nodiscard]] struct stat convert(const NetFS::Attr &) const; + [[nodiscard]] NetFS::Attr convert(const struct stat &) const; + NetFS::Mapping::MapperPtr mapper; + }; +} diff --git a/netfs/ice/types.ice b/netfs/ice/types.ice index de83f6f..3ca6b07 100644 --- a/netfs/ice/types.ice +++ b/netfs/ice/types.ice @@ -1,5 +1,4 @@ -#ifndef _TYPES -#define _TYPES +#pragma once module NetFS { struct VFS { @@ -37,12 +36,13 @@ module NetFS { string grp; }; + class Settings { + optional(0) int MessageSizeMax; + }; + sequence<byte> Buffer; sequence<string> NameList; dictionary<string, Attr> DirectoryContents; }; - -#endif - diff --git a/netfs/ice/volume.ice b/netfs/ice/volume.ice index 8d5d9a7..a41953a 100644 --- a/netfs/ice/volume.ice +++ b/netfs/ice/volume.ice @@ -1,5 +1,4 @@ -#ifndef _VOLUME -#define _VOLUME +#pragma once #include "exceptions.ice" #include "types.ice" @@ -28,12 +27,9 @@ module NetFS { void mknod(ReqEnv env, string path, int mode, int dev) throws AuthError, SystemError; void symlink(ReqEnv env, string path1, string path2) throws AuthError, SystemError; void link(ReqEnv env, string path1, string path2) throws AuthError, SystemError; - void rename(ReqEnv env, string from, string to) throws AuthError, SystemError; + void rename(ReqEnv env, string from, string to, optional(0) int flags) throws AuthError, SystemError; idempotent void chmod(ReqEnv env, string path, int mode) throws AuthError, SystemError; idempotent void chown(ReqEnv env, string path, int uid, int gid) throws AuthError, SystemError; idempotent void utimens(ReqEnv env, string path, long atime, long atimens, long mtime, long mtimens) throws AuthError, SystemError; }; }; - -#endif - diff --git a/netfs/lib/Jamfile.jam b/netfs/lib/Jamfile.jam index a2d3454..b3a3285 100644 --- a/netfs/lib/Jamfile.jam +++ b/netfs/lib/Jamfile.jam @@ -2,10 +2,14 @@ lib netfs-common : [ glob *.cpp ] : <include>../ice - <library>../ice//netfs-api - <library>..//adhocutil - <implicit-dependency>../ice//netfs-api + <link>static + <use>..//adhocutil/<link>shared + <implicit-dependency>../ice//netfs-api/<link>shared + <toolset>gcc:<cxxflags>-fPIC + <toolset>clang:<cxxflags>-fPIC : : <include>. + <library>../ice//netfs-api/<link>shared + <library>..//adhocutil/<link>shared ; diff --git a/netfs/lib/baseMapper.cpp b/netfs/lib/baseMapper.cpp new file mode 100644 index 0000000..4c2b3f0 --- /dev/null +++ b/netfs/lib/baseMapper.cpp @@ -0,0 +1,13 @@ +#include "baseMapper.h" +#include "entCache.h" + +namespace NetFS::Mapping { + BaseMapper::BaseMapper() : BaseMapper(std::make_shared<UserEntCache>()) { } + + BaseMapper::BaseMapper(const EntryResolverPtr<User> & u) : BaseMapper(u, std::make_shared<GroupEntCache>(u)) { } + + BaseMapper::BaseMapper(EntryResolverPtr<User> u, EntryResolverPtr<Group> g) : + users(std::move(u)), groups(std::move(g)) + { + } +} diff --git a/netfs/lib/baseMapper.h b/netfs/lib/baseMapper.h new file mode 100644 index 0000000..b5bb900 --- /dev/null +++ b/netfs/lib/baseMapper.h @@ -0,0 +1,20 @@ +#pragma once + +#include "entries.h" +#include "entryResolver.h" +#include <mapper.h> + +namespace NetFS { + namespace Mapping { + class BaseMapper { + public: + BaseMapper(); + explicit BaseMapper(const EntryResolverPtr<User> & users); + BaseMapper(EntryResolverPtr<User> users, EntryResolverPtr<Group> groups); + + protected: + EntryResolverPtr<User> users; + EntryResolverPtr<Group> groups; + }; + } +} diff --git a/netfs/lib/defaultMapper.cpp b/netfs/lib/defaultMapper.cpp new file mode 100644 index 0000000..1845933 --- /dev/null +++ b/netfs/lib/defaultMapper.cpp @@ -0,0 +1,27 @@ +#include "defaultMapper.h" +#include <exceptions.h> +#include <numeric.h> + +namespace NetFS::Mapping { + FileSystem + DefaultMapper::mapTransport(const std::string & un, const std::string & gn) + { + auto u = users->getEntry(un); + auto g = groups->getEntry(gn); + if (!u || !g) { + throw NetFS::SystemError(EPERM); + } + return {safe {u->id}, safe {g->id}, 0}; + } + + Transport + DefaultMapper::mapFileSystem(int uid, int gid) + { + auto u = users->getEntry(safe {uid}); + auto g = groups->getEntry(safe {gid}); + if (!u || !g) { + throw NetFS::SystemError(EPERM); + } + return {u->name, g->name, 0}; + } +} diff --git a/netfs/lib/defaultMapper.h b/netfs/lib/defaultMapper.h new file mode 100644 index 0000000..d4fb77f --- /dev/null +++ b/netfs/lib/defaultMapper.h @@ -0,0 +1,18 @@ +#pragma once + +#include "baseMapper.h" +#include "entries.h" +#include "entryResolver.h" +#include <mapper.h> + +namespace NetFS { + namespace Mapping { + class DefaultMapper : public Mapper, BaseMapper { + public: + using BaseMapper::BaseMapper; + + Transport mapFileSystem(int uid, int gid) override; + FileSystem mapTransport(const std::string & un, const std::string & gn) override; + }; + } +} diff --git a/netfs/lib/entCache.cpp b/netfs/lib/entCache.cpp index fbdba3e..a2ea685 100644 --- a/netfs/lib/entCache.cpp +++ b/netfs/lib/entCache.cpp @@ -1,143 +1,46 @@ -#include "entCache.h" -#include <exceptions.h> -#include <lockHelpers.h> -#include <pwd.h> -#include <grp.h> -#include <visibility.h> +#include "entCache.impl.h" +#include "entries.h" -template<class entry_t> -EntCache<entry_t>::EntCache() : - fillTime(0) -{ -} - -template<class entry_t> -EntCache<entry_t>::~EntCache() -{ -} - -template<class entry_t> -void -EntCache<entry_t>::getID(const EntCache<entry_t>::name_t & u, EntCache<entry_t>::id_t * target) const -{ - auto e = getEntry(u); - *target = e->id; -} - -template<class entry_t> -void -EntCache<entry_t>::getName(const EntCache<entry_t>::id_t & u, EntCache<entry_t>::name_t * target) const -{ - auto e = getEntry(u); - *target = e->name; -} - -template<class entry_t> -template<class key_t> -typename EntCache<entry_t>::entry_ptr -EntCache<entry_t>::getEntry(const key_t & key) const -{ - typename EntCache<entry_t>::entry_ptr ent; - if (fillTime + 60 > time(NULL)) { - ent = getEntryNoFill<key_t>(key); - } - if (!ent) { - fillCache(); - ent = getEntryNoFill<key_t>(key); - } - if (!ent) { - throw NetFS::SystemError(EPERM); - } - return ent; -} - -template<class entry_t> -template<class key_t> -typename EntCache<entry_t>::entry_ptr -EntCache<entry_t>::getEntryNoFill(const key_t & key) const -{ - SharedLock(lock); - auto & collection = idcache.template get<key_t>(); - auto i = collection.find(key); - if (i != collection.end()) { - return *i; - } - return NULL; -} - -User::User(uid_t u, const std::string & n, gid_t g) : - id(u), - name(n), - group(g) -{ -} +template class EntCache<User>; +template class EntCache<Group>; const int BUFLEN = 8196; -template<> void -EntCache<User>::fillCache() const +UserEntCache::fillCache() const noexcept { - Lock(lock); setpwent(); - idcache.clear(); - char buf[BUFLEN]; - struct passwd pwbuf, * pwp; - while (getpwent_r(&pwbuf, buf, BUFLEN, &pwp) == 0) { - idcache.insert(std::make_shared<User>(pwp->pw_uid, pwp->pw_name, pwp->pw_gid)); + idcache->clear(); + std::array<char, BUFLEN> buf {}; + struct passwd pwbuf { + }, *pwp; + while (getpwent_r(&pwbuf, buf.data(), buf.size(), &pwp) == 0) { + idcache->insert(std::make_shared<User>(pwp->pw_uid, pwp->pw_name, pwp->pw_gid)); } endpwent(); time(&fillTime); } -Group::Group(gid_t g, const std::string & n) : - id(g), - name(n) -{ -} +GroupEntCache::GroupEntCache(EntryResolverPtr<User> u) : users(std::move(u)) { } -template<> void -EntCache<Group>::fillCache() const +GroupEntCache::fillCache() const noexcept { - Lock(lock); setgrent(); - char buf[BUFLEN]; - idcache.clear(); - struct group grpbuf, * grp; - EntCache<User> instance; - while (getgrent_r(&grpbuf, buf, BUFLEN, &grp) == 0) { + std::array<char, BUFLEN> buf {}; + idcache->clear(); + struct group grpbuf { + }, *grp; + while (getgrent_r(&grpbuf, buf.data(), buf.size(), &grp) == 0) { auto g = std::make_shared<Group>(grp->gr_gid, grp->gr_name); for (auto member = grp->gr_mem; *member; member++) { - try { - g->members.insert(instance.getEntry((const name_t &)*member)->id); - } - catch (const NetFS::SystemError &) { + if (auto ent = users->getEntry(*member)) { + g->members.insert(ent->id); } } - idcache.insert(g); + g->members.insert(g->id); + idcache->insert(std::move(g)); } endgrent(); time(&fillTime); } - -bool -DLL_PUBLIC Group::hasMember(uid_t u) const -{ - return (members.find(u) != members.end()); -} - -template DLL_PUBLIC EntCache<User>::EntCache(); -template DLL_PUBLIC EntCache<User>::~EntCache(); -template std::shared_ptr<User> DLL_PUBLIC EntCache<User>::getEntry<std::string>(const std::string &) const; -template std::shared_ptr<User> DLL_PUBLIC EntCache<User>::getEntry<uid_t>(const uid_t &) const; -template DLL_PUBLIC void EntCache<User>::getName(const gid_t &, std::string *) const; -template DLL_PUBLIC void EntCache<User>::getID(const std::string &, gid_t *) const; - -template DLL_PUBLIC EntCache<Group>::EntCache(); -template DLL_PUBLIC EntCache<Group>::~EntCache(); -template std::shared_ptr<Group> DLL_PUBLIC EntCache<Group>::getEntry<std::string>(const std::string &) const; -template std::shared_ptr<Group> DLL_PUBLIC EntCache<Group>::getEntry<gid_t>(const gid_t &) const; -template DLL_PUBLIC void EntCache<Group>::getName(const gid_t &, std::string *) const; -template DLL_PUBLIC void EntCache<Group>::getID(const std::string &, gid_t *) const; - diff --git a/netfs/lib/entCache.h b/netfs/lib/entCache.h index 4c34a32..2d6d37d 100644 --- a/netfs/lib/entCache.h +++ b/netfs/lib/entCache.h @@ -1,67 +1,51 @@ -#ifndef ENTCACHE_H -#define ENTCACHE_H +#pragma once -#include <string> -#include <set> -#include <boost/multi_index_container.hpp> -#include <boost/multi_index/member.hpp> -#include <boost/multi_index/ordered_index.hpp> +#include "entries.h" +#include "entryResolver.h" +#include <c++11Helpers.h> +#include <memory> #include <shared_mutex> -#include <entryResolver.h> -class User { - public: - User(uid_t, const std::string &, gid_t); - uid_t id; - std::string name; - gid_t group; +template<class entry_t> class EntCache : public EntryResolver<entry_t> { +public: + EntCache(); + ~EntCache() noexcept override; + + SPECIAL_MEMBERS_DEFAULT_MOVE_NO_COPY(EntCache); + + using id_t = decltype(entry_t::id); + using name_t = decltype(entry_t::name); + using entry_ptr = std::shared_ptr<entry_t>; + + [[nodiscard]] entry_ptr inline getEntry(const id_t & i) const noexcept override + { + return getEntryInternal<id_t>(i); + } + [[nodiscard]] entry_ptr inline getEntry(const name_t & n) const noexcept override + { + return getEntryInternal<name_t>(n); + }; + +protected: + virtual void fillCache() const noexcept = 0; + template<class key_t> [[nodiscard]] entry_ptr getEntryInternal(const key_t &) const noexcept; + template<class key_t> [[nodiscard]] entry_ptr getEntryNoFill(const key_t &) const noexcept; + + class Ids; + std::unique_ptr<Ids> idcache; + mutable std::shared_mutex lock; + mutable time_t fillTime {0}; }; -class Group { - public: - Group(gid_t, const std::string &); - - bool hasMember(uid_t) const; - - gid_t id; - std::string name; - std::set<uid_t> members; +class UserEntCache : public EntCache<User> { + void fillCache() const noexcept override; }; -template<class entry_t> -class EntCache : public EntryResolver<decltype(entry_t::id)> { - public: - typedef decltype(entry_t::id) id_t; - typedef decltype(entry_t::name) name_t; - typedef std::shared_ptr<entry_t> entry_ptr; - - EntCache(); - virtual ~EntCache(); - - void getID(const name_t &, id_t *) const override; - void getName(const id_t &, name_t *) const override; - template<class key_t> - entry_ptr getEntry(const key_t &) const; +class GroupEntCache : public EntCache<Group> { +public: + explicit GroupEntCache(EntryResolverPtr<User>); - protected: - void fillCache() const; - template<class key_t> - entry_ptr getEntryNoFill(const key_t &) const; - - typedef boost::multi_index::multi_index_container<std::shared_ptr<entry_t>, - boost::multi_index::indexed_by< - boost::multi_index::ordered_unique< - boost::multi_index::tag<id_t>, BOOST_MULTI_INDEX_MEMBER(entry_t, const id_t, id), std::less<>>, - boost::multi_index::ordered_unique< - boost::multi_index::tag<std::string>, BOOST_MULTI_INDEX_MEMBER(entry_t, const std::string, name), std::less<>> - > > IDs; - mutable IDs idcache; - mutable std::shared_mutex lock; - mutable time_t fillTime; +private: + void fillCache() const noexcept override; + EntryResolverPtr<User> users; }; - -typedef EntCache<User> UserEntCache; -typedef EntCache<Group> GroupEntCache; - -#endif - diff --git a/netfs/lib/entCache.impl.h b/netfs/lib/entCache.impl.h new file mode 100644 index 0000000..64d5025 --- /dev/null +++ b/netfs/lib/entCache.impl.h @@ -0,0 +1,58 @@ +#pragma once + +#include "entCache.h" +#include "entries.h" +#include <boost/multi_index/member.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/multi_index_container.hpp> +#include <grp.h> +#include <lockHelpers.h> +#include <mutex> +#include <pwd.h> +#include <type_traits> + +template<class entry_t> +class EntCache<entry_t>::Ids : + public boost::multi_index::multi_index_container<std::shared_ptr<entry_t>, + boost::multi_index::indexed_by<boost::multi_index::ordered_unique<boost::multi_index::tag<id_t>, + BOOST_MULTI_INDEX_MEMBER(entry_t, const id_t, id), std::less<>>, + boost::multi_index::ordered_unique<boost::multi_index::tag<std::string>, + BOOST_MULTI_INDEX_MEMBER(entry_t, const std::string, name), std::less<>>>> { +}; + +template<class entry_t> EntCache<entry_t>::EntCache() : idcache(std::make_unique<Ids>()) { } +template<class entry_t> EntCache<entry_t>::~EntCache() noexcept = default; + +template<class entry_t> +template<class key_t> +typename EntCache<entry_t>::entry_ptr +EntCache<entry_t>::getEntryInternal(const key_t & key) const noexcept +{ + SharedScopeLock(lock) { + if (fillTime + 60 > time(nullptr)) { + if (auto ent = getEntryNoFill<key_t>(key)) { + return ent; + } + } + } + ScopeLock(lock) { + fillCache(); + if (auto ent = getEntryNoFill<key_t>(key)) { + return ent; + } + } + return getEntryNoFill<key_t>(key); +} + +template<class entry_t> +template<class key_t> +typename EntCache<entry_t>::entry_ptr +EntCache<entry_t>::getEntryNoFill(const key_t & key) const noexcept +{ + auto & collection = idcache->template get<key_t>(); + auto i = collection.find(key); + if (i != collection.end()) { + return *i; + } + return nullptr; +} diff --git a/netfs/lib/entries.cpp b/netfs/lib/entries.cpp new file mode 100644 index 0000000..bde15e6 --- /dev/null +++ b/netfs/lib/entries.cpp @@ -0,0 +1,16 @@ +#include "entries.h" + +static_assert(std::is_nothrow_move_constructible_v<User>); +static_assert(std::is_nothrow_move_assignable_v<User>); +static_assert(std::is_nothrow_move_constructible_v<Group>); +static_assert(std::is_nothrow_move_assignable_v<Group>); + +User::User(uid_t u, std::string n, gid_t g) : id(u), name(std::move(n)), group(g) { } + +Group::Group(gid_t g, std::string n) : id(g), name(std::move(n)) { } + +bool +Group::hasMember(uid_t u) const noexcept +{ + return (members.find(u) != members.end()); +} diff --git a/netfs/lib/entries.h b/netfs/lib/entries.h new file mode 100644 index 0000000..7da8b99 --- /dev/null +++ b/netfs/lib/entries.h @@ -0,0 +1,24 @@ +#pragma once + +#include <set> +#include <string> +#include <unistd.h> + +class User { +public: + User(uid_t, std::string, gid_t); + uid_t id; + std::string name; + gid_t group; +}; + +class Group { +public: + Group(gid_t, std::string); + + bool hasMember(uid_t) const noexcept; + + gid_t id; + std::string name; + std::set<uid_t> members; +}; diff --git a/netfs/lib/entryResolver.h b/netfs/lib/entryResolver.h new file mode 100644 index 0000000..bb5baa3 --- /dev/null +++ b/netfs/lib/entryResolver.h @@ -0,0 +1,14 @@ +#pragma once + +#include <memory> + +template<typename entry_t> class EntryResolver { +public: + virtual ~EntryResolver() noexcept = default; + using EntryPtr = std::shared_ptr<entry_t>; + + virtual EntryPtr getEntry(const decltype(entry_t::id) &) const noexcept = 0; + virtual EntryPtr getEntry(const decltype(entry_t::name) &) const noexcept = 0; +}; + +template<typename entry_t> using EntryResolverPtr = std::shared_ptr<EntryResolver<entry_t>>; diff --git a/netfs/unittests/Jamfile.jam b/netfs/unittests/Jamfile.jam index a5dfbdf..53d600f 100644 --- a/netfs/unittests/Jamfile.jam +++ b/netfs/unittests/Jamfile.jam @@ -1,6 +1,18 @@ -import testing ; - lib boost_utf : : <name>boost_unit_test_framework ; +lib benchmark ; + +path-constant leak-san-suppressions : leak-suppressions.txt ; +path-constant thread-san-suppressions : thread-suppressions.txt ; + +project + : requirements + <toolset>tidy:<xcheckxx>misc-non-private-member-variables-in-classes + <toolset>tidy:<xcheckxx>hicpp-special-member-functions + <toolset>tidy:<xcheckxx>hicpp-explicit-conversions + <toolset>tidy:<xcheckxx>hicpp-member-init + <leak-sanitizer>on:<testing.launcher>LSAN_OPTIONS=suppressions=$(leak-san-suppressions) + <thread-sanitizer>on:<testing.launcher>TSAN_OPTIONS=suppressions=$(thread-san-suppressions) + ; path-constant me : . ; @@ -13,7 +25,7 @@ lib testMocks : <library>../fuse//netfs-client <library>../ice//netfs-api <library>..//adhocutil - <define>BOOST_TEST_DYN_LINK + <define>BOOST_TEST_NO_MAIN <library>boost_utf <define>ROOT=\"$(me)\" : : @@ -26,33 +38,72 @@ lib testMocks : run testCore.cpp - : : : - <dependency>defaultDaemon.xml - <dependency>defaultFuse.xml - <dependency>altDaemon.xml - <dependency>altFuse.xml - <dependency>secureDaemon.xml - <dependency>secureFuse.xml + : -- : + altDaemon.xml + defaultDaemon.xml + defaultFuse.xml + secureDaemon.xml + secureFuse.xml + : <define>BOOST_TEST_DYN_LINK <library>boost_utf <library>testMocks : testCore ; run testGlacier.cpp - : : : - <dependency>defaultDaemon.xml - <dependency>defaultFuse.xml + : -- : + defaultDaemon.xml + defaultFuse.xml + : <define>BOOST_TEST_DYN_LINK <library>boost_utf <library>testMocks : testGlacier ; run testEdgeCases.cpp - : --log_level=all : : - <dependency>defaultDaemon.xml - <dependency>defaultFuse.xml + : -- : + defaultDaemon.xml + defaultFuse.xml + : <define>BOOST_TEST_DYN_LINK <library>boost_utf <library>testMocks : testEdgeCases ; +run testLib.cpp + : -- : + defaultDaemon.xml + defaultFuse.xml + : + <define>BOOST_TEST_DYN_LINK + <library>boost_utf + <library>testMocks + ; + +run testFuse.cpp + : -- : + defaultDaemon.xml + defaultFuseHide.xml + defaultFuseMask.xml + : + <define>BOOST_TEST_DYN_LINK + <library>boost_utf + <library>testMocks + <dependency>testCore + ; + +run testDaemon.cpp ../daemon//modeCheck + : -- : : + <define>BOOST_TEST_DYN_LINK + <library>boost_utf + <library>testMocks + <dependency>testCore + ; + +explicit testPerf ; +run testPerf.cpp + : : : + <library>benchmark + <library>testMocks + ; + diff --git a/netfs/unittests/altFuse.xml b/netfs/unittests/altFuse.xml deleted file mode 100644 index 22b791a..0000000 --- a/netfs/unittests/altFuse.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="ascii"?> -<config> - <resources> - <resource> - <name>testvol</name> - <resource> - <export>testvol</export> - <listdir>false</listdir> - <endpoints> - <endpoint>overridden</endpoint> - </endpoints> - </resource> - </resource> - </resources> -</config> - diff --git a/netfs/unittests/defaultFuseHide.xml b/netfs/unittests/defaultFuseHide.xml new file mode 100644 index 0000000..098a597 --- /dev/null +++ b/netfs/unittests/defaultFuseHide.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="ascii"?> +<config> + <resources> + <resource> + <name>testvol</name> + <resource> + <export>testvol</export> + <endpoints> + <endpoint>overridden</endpoint> + </endpoints> + <mapper type="hide"/> + </resource> + </resource> + </resources> +</config> diff --git a/netfs/unittests/defaultFuseMask.xml b/netfs/unittests/defaultFuseMask.xml new file mode 100644 index 0000000..5d45a53 --- /dev/null +++ b/netfs/unittests/defaultFuseMask.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="ascii"?> +<config> + <resources> + <resource> + <name>testvol</name> + <resource> + <export>testvol</export> + <endpoints> + <endpoint>overridden</endpoint> + </endpoints> + <mapper type="mask"> + <unknownuser>uu</unknownuser> + <unknowngroup>ug</unknowngroup> + <usermask>0100</usermask> + <groupmask>0010</groupmask> + </mapper> + </resource> + </resource> + </resources> +</config> diff --git a/netfs/unittests/defaultFuseNoAsync.xml b/netfs/unittests/defaultFuseNoAsync.xml new file mode 100644 index 0000000..5350180 --- /dev/null +++ b/netfs/unittests/defaultFuseNoAsync.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="ascii"?> +<config> + <resources> + <resource> + <name>testvol</name> + <resource> + <export>testvol</export> + <async>false</async> + <endpoints> + <endpoint>overridden</endpoint> + </endpoints> + </resource> + </resource> + </resources> +</config> diff --git a/netfs/unittests/leak-suppressions.txt b/netfs/unittests/leak-suppressions.txt new file mode 100644 index 0000000..42eae90 --- /dev/null +++ b/netfs/unittests/leak-suppressions.txt @@ -0,0 +1,3 @@ +# OpenLDAP leaks when calling getpwent/getgrent +leak:ber_memalloc_x +leak:ber_memcalloc_x diff --git a/netfs/unittests/mockDaemon.cpp b/netfs/unittests/mockDaemon.cpp index 14b843f..dd8d3fd 100644 --- a/netfs/unittests/mockDaemon.cpp +++ b/netfs/unittests/mockDaemon.cpp @@ -1,31 +1,25 @@ #include "mockDaemon.h" -#include <definedDirs.h> #include <buffer.h> +#include <definedDirs.h> -const std::filesystem::path TestExportRoot(binDir / - UniqueExport::get(getpid())); +const std::filesystem::path MockDaemonHost::TestExportRoot { + getenv("XDG_RUNTIME_DIR") / selfExe.filename() / UniqueExport::get(getpid())}; -MockDaemon::MockDaemon(const std::string & ep) : - NetFSDaemon(), - testEndpoint(ep) -{ -} +MockDaemon::MockDaemon(std::string ep) : NetFSDaemon(), testEndpoint(std::move(ep)) { } NetFS::Daemon::ConfigurationPtr MockDaemon::ReadConfiguration(const std::filesystem::path & path) const { auto c = NetFSDaemon::ReadConfiguration(path); - for(auto e : c->Exports) { - e.second->RootPath = TestExportRoot.string(); + for (const auto & e : c->Exports) { + e.second->RootPath = MockDaemonHost::TestExportRoot.string(); } - c->Hosts.insert({ "unittest", std::make_shared<NetFS::Daemon::Host>(testEndpoint) }); + c->Hosts.insert({"unittest", std::make_shared<NetFS::Daemon::Host>(testEndpoint)}); return c; } -MockDaemonHost::MockDaemonHost(const std::string & ep, const Ice::StringSeq & ps) : - testEndpoint(ep), - params(ps), - daemon(nullptr) +MockDaemonHost::MockDaemonHost(std::string ep, Ice::StringSeq ps) : + testEndpoint(std::move(ep)), params(std::move(ps)), daemon(nullptr) { std::filesystem::remove_all(TestExportRoot); std::filesystem::create_directories(TestExportRoot); @@ -43,7 +37,6 @@ MockDaemonHost::stop() { if (daemon) { daemon->stop(); - delete daemon; } if (ic) { ic->destroy(); @@ -58,7 +51,7 @@ MockDaemonHost::start() id.properties->parseCommandLineOptions("", params); ic = Ice::initialize(id); ic->getProperties()->setProperty("NetFSD.HostNameOverride", "unittest"); - daemon = new MockDaemon(testEndpoint); + daemon = std::make_unique<MockDaemon>(testEndpoint); daemon->start("NetFSDaemonAdapter", ic, {}); } @@ -68,4 +61,3 @@ MockDaemonHost::restart() stop(); start(); } - diff --git a/netfs/unittests/mockDaemon.h b/netfs/unittests/mockDaemon.h index dd0a0ad..ab33c72 100644 --- a/netfs/unittests/mockDaemon.h +++ b/netfs/unittests/mockDaemon.h @@ -1,39 +1,36 @@ -#ifndef MOCKDAEMON_H -#define MOCKDAEMON_H +#pragma once +#include <compileTimeFormatter.h> #include <daemon.h> #include <visibility.h> -#include <compileTimeFormatter.h> AdHocFormatter(UniqueExport, "testExport-%?"); class DLL_PUBLIC MockDaemon : public NetFSDaemon { - public: - MockDaemon(const std::string & ep); +public: + explicit MockDaemon(std::string ep); - const std::string testEndpoint; + const std::string testEndpoint; - protected: - virtual NetFS::Daemon::ConfigurationPtr ReadConfiguration(const std::filesystem::path &) const override; +protected: + NetFS::Daemon::ConfigurationPtr ReadConfiguration(const std::filesystem::path &) const override; }; class DLL_PUBLIC MockDaemonHost { - public: - MockDaemonHost(const std::string & ep, const Ice::StringSeq & ps = {}); - ~MockDaemonHost(); +public: + explicit MockDaemonHost(std::string ep, Ice::StringSeq ps = {}); + ~MockDaemonHost(); - void restart(); + void restart(); - Ice::CommunicatorPtr ic; + Ice::CommunicatorPtr ic; + static const std::filesystem::path TestExportRoot; - private: - void start(); - void stop(); +private: + void start(); + void stop(); - const std::string testEndpoint; - Ice::StringSeq params; - NetFSDaemon * daemon; + const std::string testEndpoint; + Ice::StringSeq params; + std::unique_ptr<NetFSDaemon> daemon; }; - -#endif - diff --git a/netfs/unittests/mockFuse.cpp b/netfs/unittests/mockFuse.cpp index d7b66a6..36a5aed 100644 --- a/netfs/unittests/mockFuse.cpp +++ b/netfs/unittests/mockFuse.cpp @@ -1,9 +1,10 @@ #include "mockFuse.h" #include <boost/test/test_tools.hpp> +#include <buffer.h> +#include <random> -FuseMock::FuseMock(const std::string & ep, const Ice::StringSeq & a) : - NetFS::FuseApp(a), - testEndpoint(ep) +FuseMock::FuseMock(std::string ep, Ice::StringSeq a) : + NetFS::FuseApp(std::move(a)), testEndpoint(std::move(ep)), context({}) { ::memset(&context, 0, sizeof(fuse_context)); context.pid = getpid(); @@ -12,59 +13,57 @@ FuseMock::FuseMock(const std::string & ep, const Ice::StringSeq & a) : } struct fuse_context * -FuseMock::fuse_get_context() +FuseMock::fuseGetContext() { return &context; } -int -FuseMock::fuse_opt_parse(struct fuse_args * args, void * data, const struct fuse_opt [], fuse_opt_proc_t proc) +void +FuseMock::connectToService() { - for (int n = 0; n < args->argc; n += 1) { - proc(data, args->argv[n], n, args); + if (testEndpoint.empty()) { + return FuseApp::connectToService(); } - return 0; -} - -int -FuseMock::main(int, char **, const struct fuse_operations * o) -{ - o->init(NULL); - ops = *o; - return 0; -} -NetFS::Client::ConfigurationPtr -FuseMock::ReadConfiguration(const std::filesystem::path & path) const -{ - auto c = FuseApp::ReadConfiguration(path); - for(auto r : c->Resources) { - for(auto & e : r.second->Endpoints) { - e = testEndpoint; + if (!service) { + Lock(mutex); + std::string addr = fcr->ServiceIdentity + ":" + testEndpoint; + service = Ice::checkedCast<NetFS::ServicePrx>(ic->stringToProxy(addr)); + if (!service) { + throw std::runtime_error("Invalid service proxy: " + testEndpoint); } + daemonSettings = service->getSettings(); + combinedSettings = combineSettings(*daemonSettings, clientSettings); } - return c; } -void -FuseMock::vlogf(int, const char * fmt, va_list args) const throw() +std::vector<char> +FuseMock::genRandomData(size_t size) { - BOOST_TEST_MESSAGE(vstringf(fmt, args)); + std::vector<char> buf(size); + std::generate(begin(buf), end(buf), + [dist = std::uniform_int_distribution<char>( + std::numeric_limits<char>::min(), std::numeric_limits<char>::max()), + mersenne_engine = std::mt19937(std::random_device()())]() mutable { + return dist(mersenne_engine); + }); + return buf; } -FuseMockHost::FuseMockHost(const std::string & ep, const Ice::StringSeq & a) : - app(new FuseMock(ep, a)), - fuse(&app->ops) +void +FuseMock::vlogf(int, const char * fmt, va_list args) const noexcept { - std::vector<char *> argv; - for (auto & arg : a) { - argv.push_back(const_cast<char *>(arg.c_str())); + static std::mutex btm; + + ScopeLock(btm) { + BOOST_TEST_MESSAGE(vstringf(fmt, args)); } - FuseAppBase::run(a.size(), &argv.front(), app); } -FuseMockHost::~FuseMockHost() +FuseMockHost::FuseMockHost(std::string ep, const Ice::StringSeq & a) : + app(std::make_unique<FuseMock>(std::move(ep), a)), fuse(&app->OPERATIONS) { - delete app; + if (app->OPERATIONS.init) { + app->OPERATIONS.init(nullptr, nullptr); + } } - diff --git a/netfs/unittests/mockFuse.h b/netfs/unittests/mockFuse.h index 9b3d2e0..cd15cad 100644 --- a/netfs/unittests/mockFuse.h +++ b/netfs/unittests/mockFuse.h @@ -1,40 +1,31 @@ -#ifndef MOCKFUSE_H -#define MOCKFUSE_H +#pragma once #include <fuseApp.h> +#include <vector> #include <visibility.h> class DLL_PUBLIC FuseMock : public NetFS::FuseApp { - public: - FuseMock(const std::string &, const Ice::StringSeq &); +public: + FuseMock(std::string, Ice::StringSeq); - struct fuse_context * fuse_get_context() override; - int fuse_opt_parse(struct fuse_args * args, void * data, const struct fuse_opt [], fuse_opt_proc_t proc) override; - int main(int, char **, const struct fuse_operations * o) override; - void vlogf(int, const char *, va_list) const throw() override; + struct fuse_context * fuseGetContext() override; + void vlogf(int, const char *, va_list) const throw() override; - fuse_operations ops; + void connectToService() override; + [[nodiscard]] static std::vector<char> genRandomData(size_t); - protected: - virtual NetFS::Client::ConfigurationPtr ReadConfiguration(const std::filesystem::path &) const override; - - private: - const std::string testEndpoint; - fuse_context context; +private: + const std::string testEndpoint; + fuse_context context; }; class DLL_PUBLIC FuseMockHost { - public: - FuseMockHost(const std::string &, const Ice::StringSeq &); - ~FuseMockHost(); +public: + FuseMockHost(std::string, const Ice::StringSeq &); - private: - FuseMock * app; +private: + std::unique_ptr<FuseMock> app; - public: - const fuse_operations * fuse; +public: + const fuse_operations * fuse; }; - - -#endif - diff --git a/netfs/unittests/mockGlacier.cpp b/netfs/unittests/mockGlacier.cpp new file mode 100644 index 0000000..f1e8e72 --- /dev/null +++ b/netfs/unittests/mockGlacier.cpp @@ -0,0 +1,29 @@ +#include "mockGlacier.h" +#include <boost/test/test_tools.hpp> +#include <csignal> +#include <sys/file.h> +#include <sys/prctl.h> +#include <sys/wait.h> + +Glacier::Glacier() : lockdir(open("/tmp", O_DIRECTORY)) +{ + BOOST_REQUIRE_GE(lockdir, 0); + BOOST_REQUIRE_EQUAL(0, flock(lockdir, LOCK_EX)); + if (glacier = fork(); glacier == 0) { + prctl(PR_SET_PDEATHSIG, SIGINT); + execl("/usr/bin/glacier2router", "--Glacier2.Client.Endpoints=tcp -p 14063", + "--Glacier2.PermissionsVerifier=Glacier2/NullPermissionsVerifier", nullptr); + BOOST_FAIL("Should never get here"); + } + BOOST_REQUIRE_GT(glacier, 0); + sleep(1); +} + +Glacier::~Glacier() +{ + BOOST_CHECK_EQUAL(0, kill(glacier, SIGINT)); + int status {}; + BOOST_CHECK_EQUAL(glacier, waitpid(glacier, &status, 0)); + BOOST_CHECK_EQUAL(0, status); + BOOST_CHECK_EQUAL(0, close(lockdir)); +} diff --git a/netfs/unittests/mockGlacier.h b/netfs/unittests/mockGlacier.h new file mode 100644 index 0000000..61adcee --- /dev/null +++ b/netfs/unittests/mockGlacier.h @@ -0,0 +1,16 @@ +#pragma once + +#include <c++11Helpers.h> +#include <sys/types.h> +#include <visibility.h> + +class DLL_PUBLIC Glacier { +public: + Glacier(); + ~Glacier(); + SPECIAL_MEMBERS_DELETE(Glacier); + +private: + const int lockdir; + pid_t glacier; +}; diff --git a/netfs/unittests/mockMount.cpp b/netfs/unittests/mockMount.cpp new file mode 100644 index 0000000..81ba0d2 --- /dev/null +++ b/netfs/unittests/mockMount.cpp @@ -0,0 +1,76 @@ +#include "mockMount.h" +#include <boost/test/included/unit_test.hpp> +#include <filesystem> + +const std::string testEndpoint("tcp -h localhost -p 12015"); +const std::string testUri("tcp://localhost:12015/testvol"); + +MockFuseApp::MockFuseApp() : NetFS::FuseApp({::testUri}), fargs {} +{ + ::fuse_opt_add_arg(&fargs, selfExe.c_str()); + ::fuse_opt_add_arg(&fargs, "-onoauto_cache"); + ::fuse_opt_add_arg(&fargs, "-oattr_timeout=0"); + ::fuse_opt_add_arg(&fargs, "-oac_attr_timeout=0"); + fs = ::fuse_new(&fargs, &OPERATIONS, sizeof(fuse_operations), this); + BOOST_REQUIRE(fs); +} + +FuseMountPoint::FuseMountPoint() : + MockDaemonHost(::testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}) +{ + std::filesystem::remove_all(mntpnt); + std::filesystem::create_directory(mntpnt); + BOOST_REQUIRE_EQUAL(0, ::fuse_mount(fuseApp.fs, mntpnt.c_str())); + th.emplace( + [](auto fs) { + ::fuse_set_signal_handlers(fuse_get_session(fs)); + ::fuse_loop(fs); + }, + fuseApp.fs); + BOOST_REQUIRE(th); +} + +FuseMountPoint::~FuseMountPoint() +{ + ::fuse_exit(fuseApp.fs); + pthread_sigqueue(th->native_handle(), SIGINT, {}); + th->join(); + ::fuse_unmount(fuseApp.fs); + std::filesystem::remove(mntpnt); +} + +MockFuseApp::~MockFuseApp() +{ + ::fuse_destroy(fs); + ::fuse_opt_free_args(&fargs); +} + +struct fuse_context * +MockFuseApp::fuseGetContext() +{ + return ::fuse_get_context(); +} + +char * +MockFuseApp::vstrdupf(const char * fmt, va_list args) +{ + char * out {}; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + // NOLINTNEXTLINE(clang-diagnostic-format-nonliteral) + BOOST_REQUIRE_GE(vasprintf(&out, fmt, args), 0); +#pragma GCC diagnostic push + BOOST_REQUIRE(out); + return out; +} + +void +MockFuseApp::vlogf(int, const char * fmt, va_list args) const noexcept +{ + std::unique_ptr<char, void (*)(void *)> msg(vstrdupf(fmt, args), std::free); + static std::mutex btm; + + ScopeLock(btm) { + BOOST_TEST_MESSAGE(msg.get()); + } +} diff --git a/netfs/unittests/mockMount.h b/netfs/unittests/mockMount.h new file mode 100644 index 0000000..2fec87a --- /dev/null +++ b/netfs/unittests/mockMount.h @@ -0,0 +1,45 @@ +#pragma once + +#include "mockDaemon.h" +#include <definedDirs.h> +#include <fuseApp.h> +#include <memory> +#include <thread> +#include <visibility.h> + +inline const std::filesystem::path mntpnt {binDir / "mnt"}; + +class DLL_PUBLIC MockFuseApp : public NetFS::FuseApp { +public: + MockFuseApp(); + ~MockFuseApp() override; + SPECIAL_MEMBERS_DELETE(MockFuseApp); + + struct fuse_context * fuseGetContext() override; + + void vlogf(int, const char * fmt, va_list args) const noexcept override; + + const auto & + getStatCache() const + { + return statCache; + } + +private: + friend class FuseMountPoint; + static char * vstrdupf(const char * fmt, va_list args); + struct fuse * fs; + struct fuse_args fargs; +}; + +class DLL_PUBLIC FuseMountPoint : public MockDaemonHost { +public: + FuseMountPoint(); + ~FuseMountPoint(); + SPECIAL_MEMBERS_DELETE(FuseMountPoint); + + MockFuseApp fuseApp; + +private: + std::optional<std::thread> th; +}; diff --git a/netfs/unittests/testCore.cpp b/netfs/unittests/testCore.cpp index b56b3d5..6f4511f 100644 --- a/netfs/unittests/testCore.cpp +++ b/netfs/unittests/testCore.cpp @@ -1,20 +1,20 @@ #define BOOST_TEST_MODULE TestNetFSCore #include <boost/test/unit_test.hpp> -#define private public + #include "mockDaemon.h" #include "mockFuse.h" +#include <algorithm> +#include <climits> #include <definedDirs.h> #include <filesystem> -#include <ostream> -#include <algorithm> #include <fuseFiles.h> +#include <ostream> +const std::filesystem::path tmpDir {getenv("XDG_RUNTIME_DIR") / selfExe.filename()}; const auto testExport = UniqueExport::get(getpid()); const std::string testEndpoint("tcp -h localhost -p 12012"); const std::string testUri("tcp://localhost:12012/testvol"); -BOOST_TEST_DONT_PRINT_LOG_VALUE(NetFS::FuseApp::OpenFile::BGs::iterator); - bool operator==(const struct stat & a, const struct stat & b) { @@ -22,73 +22,85 @@ operator==(const struct stat & a, const struct stat & b) } namespace std { + template<size_t l> + bool + operator==(const std::array<char, l> & a, const char * b) + { + return strncmp(a.data(), b, l) == 0; + } + AdHocFormatter(StatDebug, "dev: %? inode: %?"); + // LCOV_EXCL_START - ostream & operator<<(ostream & s, const struct stat & ss) + template<typename T, size_t l> + ostream & + operator<<(ostream & s, const std::array<T, l> & a) + { + std::copy(a.begin(), a.end(), std::ostream_iterator<T>(s)); + return s; + } + + ostream & + operator<<(ostream & s, const struct stat & ss) { return StatDebug::write(s, ss.st_dev, ss.st_ino); } + // LCOV_EXCL_STOP } class Core { - public: - Core(const std::string & daemonConfig = "defaultDaemon.xml", const std::string & fuseConfig = "defaultFuse.xml") : - daemonHost(testEndpoint, { - "--Ice.ThreadPool.Client.Size=4", - "--Ice.ThreadPool.Client.SizeMax=20", - "--Ice.ThreadPool.Server.Size=5", - "--Ice.ThreadPool.Server.SizeMax=20", - "--NetFSD.ConfigPath=" + (rootDir / daemonConfig).string() - }), - fuseHost(testEndpoint, { - "--Ice.ThreadPool.Client.Size=6", - "--Ice.ThreadPool.Client.SizeMax=20", - (rootDir / fuseConfig).string() + ":testvol", - (binDir / "test").string() - }), - ic(daemonHost.ic), - fuse(fuseHost.fuse) - { - } - - static - int - nameListAdd(void *buf, const char *name, const struct stat *, off_t) - { - static_cast<NetFS::NameList *>(buf)->push_back(name); - return 0; - } +public: + Core(const std::string & daemonConfig = "defaultDaemon.xml", const std::string & fuseConfig = "defaultFuse.xml") : + daemonHost(testEndpoint, + {"--Ice.ThreadPool.Client.Size=4", "--Ice.ThreadPool.Client.SizeMax=20", + "--Ice.ThreadPool.Server.Size=5", "--Ice.ThreadPool.Server.SizeMax=20", + "--NetFSD.ConfigPath=" + (rootDir / daemonConfig).string()}), + fuseHost(testEndpoint, + {"--Ice.ThreadPool.Client.Size=6", "--Ice.ThreadPool.Client.SizeMax=20", + (rootDir / fuseConfig).string() + ":testvol", (tmpDir / "test").string()}), + ic(daemonHost.ic), fuse(fuseHost.fuse) + { + } + static int + nameListAdd(void * buf, const char * name, const struct stat *, off_t, fuse_fill_dir_flags) + { + static_cast<NetFS::NameList *>(buf)->push_back(name); + return 0; + } - protected: - MockDaemonHost daemonHost; - FuseMockHost fuseHost; +protected: + MockDaemonHost daemonHost; + FuseMockHost fuseHost; - Ice::CommunicatorPtr ic; + Ice::CommunicatorPtr ic; - public: - const fuse_operations * fuse; +public: + const fuse_operations * fuse; }; class AltCore : public Core { - public: - AltCore() : Core("altDaemon.xml", "altFuse.xml") { } +public: + AltCore() : Core("altDaemon.xml", "defaultFuse.xml") { } }; -BOOST_FIXTURE_TEST_SUITE( NetfsCore, Core ); +BOOST_FIXTURE_TEST_SUITE(NetfsCore, Core); BOOST_AUTO_TEST_CASE(fuse_operations_correct) { + BOOST_CHECK(NetFS::FuseApp::OPERATIONS.access); + BOOST_CHECK(!NetFS::FuseApp::OPERATIONS.destroy); + // open should be defined BOOST_REQUIRE(fuse->open); - // getdir should not be defined (Not supported) - BOOST_REQUIRE(!fuse->getdir); + // bmap should not be defined (Not supported) + BOOST_REQUIRE(!fuse->bmap); // fsync should not be defined (Not implemented) BOOST_REQUIRE(!fuse->fsync); } -BOOST_AUTO_TEST_CASE ( daemonInitialised ) +BOOST_AUTO_TEST_CASE(daemonInitialised) { auto service = Ice::checkedCast<NetFS::ServicePrx>(ic->stringToProxy("Service")); BOOST_REQUIRE(service); @@ -99,123 +111,124 @@ BOOST_AUTO_TEST_CASE ( daemonInitialised ) volume->disconnect(); } -BOOST_AUTO_TEST_CASE ( clientInitialised ) +BOOST_AUTO_TEST_CASE(daemonSettings) +{ + auto service = Ice::checkedCast<NetFS::ServicePrx>(ic->stringToProxy("Service")); + auto settings = service->getSettings(); + BOOST_REQUIRE(settings); + BOOST_REQUIRE(settings->MessageSizeMax); + BOOST_CHECK_EQUAL(1024, *settings->MessageSizeMax); +} + +BOOST_AUTO_TEST_CASE(clientInitialised) { struct statvfs s; - BOOST_REQUIRE_EQUAL(0, fuse->statfs("/", &s)); - BOOST_REQUIRE_EQUAL(-ENOENT, fuse->statfs("/missing", &s)); + BOOST_REQUIRE_EQUAL(0, fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(s.f_fsid, 0); + BOOST_REQUIRE_EQUAL(-ENOENT, fuse->statfs("/missing", &s)); } -BOOST_AUTO_TEST_CASE( testNavigation ) +BOOST_AUTO_TEST_CASE(testNavigation) { - struct stat attr, rootattr, insideattr; - memset(&attr, 0, sizeof(attr)); - memset(&rootattr, 0, sizeof(rootattr)); - memset(&insideattr, 0, sizeof(insideattr)); - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); + struct stat attr {}, rootattr {}, insideattr {}; + struct fuse_file_info fi {}; int fd = fuse->create("/inside", 0666, &fi); BOOST_REQUIRE(fd >= 0); fuse->release("/inside", &fi); - BOOST_REQUIRE(std::filesystem::exists(binDir / testExport / "inside")); - BOOST_REQUIRE_EQUAL(lstat((binDir / testExport).c_str(), &rootattr), 0); - BOOST_REQUIRE_EQUAL(lstat((binDir / testExport / "inside").c_str(), &insideattr), 0); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("/", &attr)); + BOOST_REQUIRE(std::filesystem::exists(tmpDir / testExport / "inside")); + BOOST_REQUIRE_EQUAL(lstat((tmpDir / testExport).c_str(), &rootattr), 0); + BOOST_REQUIRE_EQUAL(lstat((tmpDir / testExport / "inside").c_str(), &insideattr), 0); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("/", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, rootattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr(".", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr(".", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, rootattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, rootattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("/.", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("/.", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, rootattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("/inside/..", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("/inside/..", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, rootattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("//inside/./..", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("//inside/./..", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, rootattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("/inside", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("/inside", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, insideattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("/inside/", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("/inside/", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, insideattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("/anything/../inside", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("/anything/../inside", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, insideattr); - BOOST_REQUIRE_EQUAL(0, fuse->getattr("/inside/../inside", &attr)); + BOOST_REQUIRE_EQUAL(0, fuse->getattr("/inside/../inside", &attr, nullptr)); BOOST_REQUIRE_EQUAL(attr, insideattr); } -BOOST_AUTO_TEST_CASE( testSandboxing ) +BOOST_AUTO_TEST_CASE(testSandboxing) { // A previous (bad) run might create one or more of these: - std::filesystem::remove(binDir / "outside"); - std::filesystem::remove(binDir / "sub" / "outside"); - std::filesystem::remove(binDir / "sub"); - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); + std::filesystem::remove(tmpDir / "outside"); + std::filesystem::remove(tmpDir / "sub" / "outside"); + std::filesystem::remove(tmpDir / "sub"); + struct fuse_file_info fi {}; BOOST_REQUIRE_EQUAL(fuse->create("../outside", 0666, &fi), -EPERM); - BOOST_REQUIRE(!std::filesystem::exists(binDir / "outside")); + BOOST_REQUIRE(!std::filesystem::exists(tmpDir / "outside")); BOOST_REQUIRE_EQUAL(fuse->create("/../outside", 0666, &fi), -EPERM); - BOOST_REQUIRE(!std::filesystem::exists(binDir / "outside")); + BOOST_REQUIRE(!std::filesystem::exists(tmpDir / "outside")); BOOST_REQUIRE_EQUAL(fuse->create("../sub/outside", 0666, &fi), -EPERM); - BOOST_REQUIRE(!std::filesystem::exists(binDir / "sub" / "outside")); + BOOST_REQUIRE(!std::filesystem::exists(tmpDir / "sub" / "outside")); BOOST_REQUIRE_EQUAL(fuse->create("/../sub/outside", 0666, &fi), -EPERM); - BOOST_REQUIRE(!std::filesystem::exists(binDir / "sub" / "outside")); + BOOST_REQUIRE(!std::filesystem::exists(tmpDir / "sub" / "outside")); BOOST_REQUIRE_EQUAL(fuse->create("../sub/../outside", 0666, &fi), -EPERM); - BOOST_REQUIRE(!std::filesystem::exists(binDir / "outside")); + BOOST_REQUIRE(!std::filesystem::exists(tmpDir / "outside")); BOOST_REQUIRE_EQUAL(fuse->create("/../sub/../outside", 0666, &fi), -EPERM); - BOOST_REQUIRE(!std::filesystem::exists(binDir / "outside")); + BOOST_REQUIRE(!std::filesystem::exists(tmpDir / "outside")); BOOST_REQUIRE_EQUAL(fuse->create("/inside", 0666, &fi), 0); BOOST_REQUIRE_EQUAL(fuse->release("/inside", &fi), 0); - BOOST_REQUIRE(std::filesystem::exists(binDir / testExport / "inside")); + BOOST_REQUIRE(std::filesystem::exists(tmpDir / testExport / "inside")); BOOST_REQUIRE_EQUAL(fuse->create("inside2", 0666, &fi), 0); BOOST_REQUIRE_EQUAL(fuse->release("inside2", &fi), 0); - BOOST_REQUIRE(std::filesystem::exists(binDir / testExport / "inside2")); + BOOST_REQUIRE(std::filesystem::exists(tmpDir / testExport / "inside2")); } void enableWriteOnDir(const fuse_operations * fuse, const char * dir) { - struct stat st; - memset(&st, 0, sizeof(st)); - BOOST_REQUIRE_EQUAL(fuse->chmod(dir, 0700), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr(dir, &st), 0); + struct stat st {}; + BOOST_REQUIRE_EQUAL(fuse->chmod(dir, 0700, nullptr), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr(dir, &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_mode, 0700 | S_IFDIR); } void disableWriteOnDir(const fuse_operations * fuse, const char * dir) { - struct stat st; - memset(&st, 0, sizeof(st)); - BOOST_REQUIRE_EQUAL(fuse->chmod(dir, 0500), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr(dir, &st), 0); + struct stat st {}; + BOOST_REQUIRE_EQUAL(fuse->chmod(dir, 0500, nullptr), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr(dir, &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_mode, 0500 | S_IFDIR); } -BOOST_AUTO_TEST_CASE( directories ) +BOOST_AUTO_TEST_CASE(directories) { - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); - struct stat st; - memset(&st, 0, sizeof(st)); + struct fuse_file_info fi {}; + struct stat st {}; BOOST_REQUIRE_EQUAL(fuse->mkdir("/test", 0700), 0); - BOOST_REQUIRE(std::filesystem::is_directory(binDir / testExport / "test")); + BOOST_REQUIRE(std::filesystem::is_directory(tmpDir / testExport / "test")); BOOST_REQUIRE_EQUAL(fuse->mkdir("/test", 0700), -EEXIST); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(fuse->opendir("/test", &fi), 0); NetFS::NameList nl; - BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi, FUSE_READDIR_PLUS), 0); BOOST_REQUIRE_EQUAL(nl.size(), 2); std::sort(nl.begin(), nl.end()); BOOST_REQUIRE_EQUAL(nl[0], "."); BOOST_REQUIRE_EQUAL(nl[1], ".."); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test/.", &st), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test/..", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test/.", &st, nullptr), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test/..", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(fuse->releasedir("/test", &fi), 0); nl.clear(); BOOST_REQUIRE_EQUAL(fuse->mkdir("/test/sub", 0700), 0); BOOST_REQUIRE_EQUAL(fuse->opendir("/test", &fi), 0); - BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi, FUSE_READDIR_PLUS), 0); BOOST_REQUIRE_EQUAL(nl.size(), 3); std::sort(nl.begin(), nl.end()); BOOST_REQUIRE_EQUAL(nl[0], "."); @@ -224,25 +237,22 @@ BOOST_AUTO_TEST_CASE( directories ) BOOST_REQUIRE_EQUAL(fuse->releasedir("/test", &fi), 0); nl.clear(); - BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi), -EBADF); + BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi, FUSE_READDIR_PLUS), -EBADF); BOOST_REQUIRE_EQUAL(fuse->releasedir("/test", &fi), -EBADF); BOOST_REQUIRE_EQUAL(fuse->rmdir("/test"), -ENOTEMPTY); BOOST_REQUIRE_EQUAL(fuse->rmdir("/test/sub"), 0); BOOST_REQUIRE_EQUAL(fuse->rmdir("/test"), 0); - BOOST_REQUIRE(!std::filesystem::is_directory(binDir / testExport / "test")); + BOOST_REQUIRE(!std::filesystem::is_directory(tmpDir / testExport / "test")); BOOST_REQUIRE_EQUAL(fuse->rmdir("/test"), -ENOENT); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, nullptr), -ENOENT); BOOST_REQUIRE_EQUAL(fuse->opendir("/test", &fi), -ENOENT); } -BOOST_AUTO_TEST_CASE( files ) +BOOST_AUTO_TEST_CASE(files) { - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); - struct stat st, st2; - memset(&st, 0, sizeof(st)); - memset(&st2, 0, sizeof(st2)); + struct fuse_file_info fi {}; + struct stat st {}, st2 {}; fi.flags = O_RDWR; BOOST_REQUIRE_EQUAL(fuse->open("/test", &fi), -ENOENT); BOOST_REQUIRE_EQUAL(fuse->create("/test", 0600, &fi), 0); @@ -250,43 +260,44 @@ BOOST_AUTO_TEST_CASE( files ) BOOST_REQUIRE_EQUAL(fuse->release("/test", &fi), -EBADF); BOOST_REQUIRE_EQUAL(fuse->create("/test", 0600, &fi), -EEXIST); + std::array<char, 11> buf {}; BOOST_REQUIRE_EQUAL(fuse->open("/test", &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->read("/test", buf.data(), 0, 0, &fi), 0); BOOST_REQUIRE_EQUAL(fuse->write("/test", "some test buffer", 16, 0, &fi), 16); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->flush("/test", &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_size, 16); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st), -ENOENT); - BOOST_REQUIRE_EQUAL(fuse->rename("/test", "/test2"), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st), -ENOENT); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st2), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st, nullptr), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->rename("/test", "/test2", RENAME_EXCHANGE), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, nullptr), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st2, nullptr), 0); BOOST_REQUIRE_EQUAL(st, st2); - BOOST_REQUIRE_EQUAL(fuse->fgetattr("/test2", &st2, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st2, &fi), 0); BOOST_REQUIRE_EQUAL(st, st2); BOOST_REQUIRE_EQUAL(st2.st_size, 16); BOOST_REQUIRE_EQUAL(fuse->write("/test2", "BUFFER some", 11, 10, &fi), 11); - BOOST_REQUIRE_EQUAL(fuse->fgetattr("/test2", &st2, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st2, &fi), 0); BOOST_REQUIRE_EQUAL(st2.st_size, 21); - char buf[11]; - memset(&buf, 0, sizeof(buf)); - BOOST_REQUIRE_EQUAL(fuse->read("/test2", buf, 10, 5, &fi), 10); + BOOST_REQUIRE_EQUAL(fuse->read("/test2", buf.data(), 10, 5, &fi), 10); BOOST_REQUIRE_EQUAL(buf, "test BUFFE"); - BOOST_REQUIRE_EQUAL(fuse->ftruncate("/test2", 11, &fi), 0); - BOOST_REQUIRE_EQUAL(fuse->fgetattr("/test2", &st2, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->truncate("/test2", 11, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st2, &fi), 0); BOOST_REQUIRE_EQUAL(st2.st_size, 11); - memset(&buf, 0, sizeof(buf)); - BOOST_REQUIRE_EQUAL(fuse->read("/test2", buf, 10, 5, &fi), 6); + buf = {}; + BOOST_REQUIRE_EQUAL(fuse->read("/test2", buf.data(), 10, 5, &fi), 6); BOOST_REQUIRE_EQUAL(buf, "test B"); - BOOST_REQUIRE_EQUAL(fuse->truncate("/test2", 7), 0); - BOOST_REQUIRE_EQUAL(fuse->fgetattr("/test2", &st2, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->truncate("/test2", 7, nullptr), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st2, &fi), 0); BOOST_REQUIRE_EQUAL(st2.st_size, 7); - memset(&buf, 0, sizeof(buf)); - BOOST_REQUIRE_EQUAL(fuse->read("/test2", buf, 10, 5, &fi), 2); + buf = {}; + BOOST_REQUIRE_EQUAL(fuse->read("/test2", buf.data(), 10, 5, &fi), 2); BOOST_REQUIRE_EQUAL(buf, "te"); BOOST_REQUIRE_EQUAL(fuse->release("/test2", &fi), 0); BOOST_REQUIRE_EQUAL(fuse->link("/test3", "/test2"), -ENOENT); BOOST_REQUIRE_EQUAL(fuse->link("/test2", "/test3"), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test3", &st2), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test2", &st, nullptr), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test3", &st2, nullptr), 0); BOOST_REQUIRE_EQUAL(st, st2); BOOST_REQUIRE_EQUAL(fuse->unlink("/test2"), 0); BOOST_REQUIRE_EQUAL(fuse->unlink("/test2"), -ENOENT); @@ -294,60 +305,72 @@ BOOST_AUTO_TEST_CASE( files ) BOOST_REQUIRE_EQUAL(fuse->unlink("/test3"), -ENOENT); } -BOOST_AUTO_TEST_CASE( bgwriteOverlapped, * boost::unit_test::timeout(2) ) +BOOST_AUTO_TEST_CASE(files_copy_range) +{ + struct fuse_file_info fiin {}, fiout {}; + fiin.flags = O_RDWR; + fiout.flags = O_RDWR; + BOOST_REQUIRE_EQUAL(fuse->create("/src", 0600, &fiin), 0); + BOOST_REQUIRE_EQUAL(fuse->create("/dst", 0600, &fiout), 0); + BOOST_REQUIRE_EQUAL(fuse->write("/src", "hello world.", 12, 0, &fiin), 12); + BOOST_REQUIRE_EQUAL(fuse->copy_file_range("/src", &fiin, 6, "/dst", &fiout, 0, 5, 0), 5); + std::array<char, 5> buf {}; + BOOST_REQUIRE_EQUAL(fuse->read("/dst", buf.data(), 5, 0, &fiout), 5); + BOOST_REQUIRE_EQUAL(memcmp("world", buf.data(), buf.size()), 0); + BOOST_REQUIRE_EQUAL(fuse->release("/src", &fiin), 0); + BOOST_REQUIRE_EQUAL(fuse->release("/dst", &fiout), 0); +} + +BOOST_AUTO_TEST_CASE(bgwriteOverlapped, *boost::unit_test::timeout(2)) { - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); - struct stat st; - memset(&st, 0, sizeof(st)); + struct fuse_file_info fi {}; + struct stat st {}; fi.flags = O_RDWR; - auto s = sizeof(int32_t); - auto N = 20; + constexpr auto s = sizeof(int32_t); + constexpr auto N = 20U; BOOST_REQUIRE_EQUAL(fuse->create("/test", 0600, &fi), 0); - for (int32_t n = 0; n < N; n += 1) { - BOOST_REQUIRE_EQUAL(fuse->write("/test", (const char *)(&n), s, n, &fi), s); + for (auto n = 0U; n < N; n += 1) { + BOOST_REQUIRE_EQUAL(fuse->write("/test", reinterpret_cast<const char *>(&n), s, n, &fi), s); } - BOOST_REQUIRE_EQUAL(fuse->fgetattr("/test", &st, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, &fi), 0); BOOST_REQUIRE_EQUAL(st.st_size, N + s - 1); - char buf[s]; - BOOST_REQUIRE_EQUAL(fuse->read("/test", buf, s, (N - 1), &fi), s); - BOOST_REQUIRE_EQUAL(*(int*)buf, N - 1); + std::array<char, s> buf; + BOOST_REQUIRE_EQUAL(fuse->read("/test", buf.data(), s, (N - 1), &fi), s); + BOOST_REQUIRE_EQUAL(*reinterpret_cast<unsigned int *>(buf.data()), N - 1); BOOST_REQUIRE_EQUAL(fuse->release("/test", &fi), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_size, N + s - 1); } -BOOST_AUTO_TEST_CASE( bgwriteDistinct, * boost::unit_test::timeout(2) ) +BOOST_AUTO_TEST_CASE(bgwriteDistinct, *boost::unit_test::timeout(2)) { - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); - struct stat st; - memset(&st, 0, sizeof(st)); + struct fuse_file_info fi {}; + struct stat st {}; fi.flags = O_RDWR; - auto s = sizeof(int32_t); - auto N = 20; + constexpr auto s = sizeof(int32_t); + constexpr auto N = 20U; BOOST_REQUIRE_EQUAL(fuse->create("/test", 0600, &fi), 0); - for (int32_t n = 0; n < N; n += 1) { - BOOST_REQUIRE_EQUAL(fuse->write("/test", (const char *)(&n), s, n * s, &fi), s); + for (auto n = 0U; n < N; n += 1) { + BOOST_REQUIRE_EQUAL(fuse->write("/test", reinterpret_cast<const char *>(&n), s, n * s, &fi), s); } - BOOST_REQUIRE_EQUAL(fuse->fgetattr("/test", &st, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, &fi), 0); BOOST_REQUIRE_EQUAL(st.st_size, N * s); - char buf[s]; - BOOST_REQUIRE_EQUAL(fuse->read("/test", buf, s, (N - 1) * s, &fi), s); - BOOST_REQUIRE_EQUAL(*(int*)buf, N - 1); + std::array<char, s> buf; + BOOST_REQUIRE_EQUAL(fuse->read("/test", buf.data(), s, (N - 1) * s, &fi), s); + BOOST_REQUIRE_EQUAL(*reinterpret_cast<unsigned int *>(buf.data()), N - 1); BOOST_REQUIRE_EQUAL(fuse->release("/test", &fi), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_size, N * s); } -BOOST_AUTO_TEST_CASE( symlinks ) +BOOST_AUTO_TEST_CASE(symlinks) { - char buf[BUFSIZ]; + std::array<char, PATH_MAX> buf; BOOST_REQUIRE_EQUAL(fuse->mkdir("/dir", 0700), 0); - BOOST_REQUIRE_EQUAL(fuse->readlink("/dir", buf, BUFSIZ), -EINVAL); + BOOST_REQUIRE_EQUAL(fuse->readlink("/dir", buf.data(), buf.size()), -EINVAL); BOOST_REQUIRE_EQUAL(fuse->symlink("/test2", "/test3"), 0); BOOST_REQUIRE_EQUAL(fuse->symlink("/test2", "/test3"), -EEXIST); - BOOST_REQUIRE_EQUAL(fuse->readlink("/test3", buf, BUFSIZ), 0); + BOOST_REQUIRE_EQUAL(fuse->readlink("/test3", buf.data(), buf.size()), 0); BOOST_REQUIRE_EQUAL("/test2", buf); BOOST_REQUIRE_EQUAL(fuse->unlink("/test3"), 0); BOOST_REQUIRE_EQUAL(fuse->unlink("/test3"), -ENOENT); @@ -356,23 +379,22 @@ BOOST_AUTO_TEST_CASE( symlinks ) enableWriteOnDir(fuse, "/"); } -BOOST_AUTO_TEST_CASE( access ) +BOOST_AUTO_TEST_CASE(access) { - struct stat st; - memset(&st, 0, sizeof(st)); + struct stat st {}; BOOST_REQUIRE_EQUAL(fuse->access("/", F_OK), 0); BOOST_REQUIRE_EQUAL(fuse->access("/missing", F_OK), -ENOENT); // Basic assertions BOOST_REQUIRE_EQUAL(fuse->mkdir("/dir", 0700), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_mode, 0700 | S_IFDIR); BOOST_REQUIRE_EQUAL(fuse->access("/dir", F_OK), 0); BOOST_REQUIRE_EQUAL(fuse->access("/dir", R_OK), 0); BOOST_REQUIRE_EQUAL(fuse->access("/dir", W_OK), 0); BOOST_REQUIRE_EQUAL(fuse->access("/dir", X_OK), 0); - BOOST_REQUIRE_EQUAL(fuse->chmod("/missing", 0000), -ENOENT); - BOOST_REQUIRE_EQUAL(fuse->chmod("/dir", 0000), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->chmod("/missing", 0000, nullptr), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->chmod("/dir", 0000, nullptr), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_mode, 0000 | S_IFDIR); BOOST_REQUIRE_EQUAL(fuse->access("/dir", F_OK), 0); BOOST_REQUIRE_EQUAL(fuse->access("/dir", R_OK), -EACCES); @@ -380,54 +402,50 @@ BOOST_AUTO_TEST_CASE( access ) BOOST_REQUIRE_EQUAL(fuse->access("/dir", X_OK), -EACCES); enableWriteOnDir(fuse, "/dir"); BOOST_REQUIRE_EQUAL(fuse->rmdir("/dir"), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir", &st), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir", &st, nullptr), -ENOENT); } -BOOST_AUTO_TEST_CASE( permissionsDirs ) +BOOST_AUTO_TEST_CASE(permissionsDirs) { - struct stat st; - memset(&st, 0, sizeof(st)); + struct stat st {}; BOOST_REQUIRE_EQUAL(fuse->mkdir("/dir", 0700), 0); BOOST_REQUIRE_EQUAL(fuse->mkdir("/dir/yes", 0000), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/yes", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/yes", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_mode, 0000 | S_IFDIR); disableWriteOnDir(fuse, "/dir"); BOOST_REQUIRE_EQUAL(fuse->mkdir("/dir/no", 0700), -EACCES); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/no", &st), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/no", &st, nullptr), -ENOENT); BOOST_REQUIRE_EQUAL(fuse->rmdir("/dir/yes"), -EACCES); enableWriteOnDir(fuse, "/dir"); BOOST_REQUIRE_EQUAL(fuse->rmdir("/dir/yes"), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/yes", &st), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/yes", &st, nullptr), -ENOENT); } -BOOST_AUTO_TEST_CASE( permissionsFiles ) +BOOST_AUTO_TEST_CASE(permissionsFiles) { - struct stat st; - memset(&st, 0, sizeof(st)); - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); + struct stat st {}; + struct fuse_file_info fi {}; fi.flags = O_RDWR; BOOST_REQUIRE_EQUAL(fuse->mkdir("/dir", 0700), 0); BOOST_REQUIRE_NE(fuse->create("/dir/yes", 0000, &fi), -1); BOOST_REQUIRE_EQUAL(fuse->release("/dir/yes", &fi), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/yes", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/yes", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_mode, 0000 | S_IFREG); disableWriteOnDir(fuse, "/dir"); BOOST_REQUIRE_EQUAL(fuse->create("/dir/no", 0600, &fi), -EACCES); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/no", &st), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/no", &st, nullptr), -ENOENT); BOOST_REQUIRE_EQUAL(fuse->unlink("/dir/yes"), -EACCES); enableWriteOnDir(fuse, "/dir"); BOOST_REQUIRE_EQUAL(fuse->unlink("/dir/yes"), 0); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/yes", &st), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir/yes", &st, nullptr), -ENOENT); } -BOOST_AUTO_TEST_CASE( mknod ) +BOOST_AUTO_TEST_CASE(mknod) { BOOST_REQUIRE_EQUAL(fuse->mknod("/nod", 0600 | S_IFIFO, 0), 0); BOOST_REQUIRE_EQUAL(fuse->mknod("/nod", 0600 | S_IFIFO, 0), -EEXIST); - struct stat st; - memset(&st, 0, sizeof(st)); - BOOST_REQUIRE_EQUAL(fuse->getattr("/nod", &st), 0); + struct stat st {}; + BOOST_REQUIRE_EQUAL(fuse->getattr("/nod", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_mode, 0600 | S_IFIFO); BOOST_REQUIRE_EQUAL(fuse->unlink("/nod"), 0); BOOST_REQUIRE_EQUAL(fuse->unlink("/nod"), -ENOENT); @@ -436,53 +454,63 @@ BOOST_AUTO_TEST_CASE( mknod ) enableWriteOnDir(fuse, "/"); } -BOOST_AUTO_TEST_CASE( renameToDir ) +BOOST_AUTO_TEST_CASE(renameToDir) { - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); + struct fuse_file_info fi {}; BOOST_REQUIRE_EQUAL(fuse->create("/file", 0600, &fi), 0); BOOST_REQUIRE_EQUAL(fuse->release("/file", &fi), 0); BOOST_REQUIRE_EQUAL(fuse->mkdir("/dir", 0700), 0); - BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/dir"), -EISDIR); + BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/dir", RENAME_EXCHANGE), -EISDIR); disableWriteOnDir(fuse, "/dir"); - BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/dir/file"), -EACCES); + BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/dir/file", RENAME_EXCHANGE), -EACCES); enableWriteOnDir(fuse, "/dir"); disableWriteOnDir(fuse, "/"); - BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/dir/file"), -EACCES); + BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/dir/file", RENAME_EXCHANGE), -EACCES); enableWriteOnDir(fuse, "/"); - BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/dir/file"), 0); + BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/dir/file", RENAME_EXCHANGE), 0); BOOST_REQUIRE_EQUAL(fuse->unlink("/file"), -ENOENT); BOOST_REQUIRE_EQUAL(fuse->unlink("/dir/file"), 0); BOOST_REQUIRE_EQUAL(fuse->rmdir("/dir"), 0); } -BOOST_AUTO_TEST_CASE( chown ) +BOOST_AUTO_TEST_CASE(renameFlags) +{ + struct fuse_file_info fi {}; + BOOST_REQUIRE_EQUAL(fuse->create("/file", 0600, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->release("/file", &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->create("/file2", 0600, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->release("/file2", &fi), 0); + + BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/file2", RENAME_NOREPLACE), -EEXIST); + BOOST_REQUIRE_EQUAL(fuse->access("/file", F_OK), 0); + BOOST_REQUIRE_EQUAL(fuse->access("/file2", F_OK), 0); + + BOOST_REQUIRE_EQUAL(fuse->rename("/file", "/file2", RENAME_EXCHANGE), 0); + BOOST_REQUIRE_EQUAL(fuse->access("/file", F_OK), -ENOENT); + BOOST_REQUIRE_EQUAL(fuse->access("/file2", F_OK), 0); +} + +BOOST_AUTO_TEST_CASE(chown) { BOOST_REQUIRE_EQUAL(fuse->mkdir("/dir", 0777), 0); - BOOST_REQUIRE_EQUAL(fuse->chown("/dir", getuid(), getgid()), 0); - struct stat st; - memset(&st, 0, sizeof(st)); - BOOST_REQUIRE_EQUAL(fuse->getattr("/dir", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->chown("/dir", getuid(), getgid(), nullptr), 0); + struct stat st {}; + BOOST_REQUIRE_EQUAL(fuse->getattr("/dir", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_uid, getuid()); BOOST_REQUIRE_EQUAL(st.st_gid, getgid()); - BOOST_REQUIRE_EQUAL(fuse->chown("/dir", -2, getgid()), -EPERM); + BOOST_REQUIRE_EQUAL(fuse->chown("/dir", 99999999, getgid(), nullptr), -EPERM); BOOST_REQUIRE_EQUAL(fuse->rmdir("/dir"), 0); } -BOOST_AUTO_TEST_CASE( utimens ) +BOOST_AUTO_TEST_CASE(utimens) { BOOST_REQUIRE_EQUAL(fuse->mknod("/file", 0600, 0), 0); - struct timespec times[2]; - times[0].tv_sec = 1; - times[0].tv_nsec = 100; - times[1].tv_sec = 2; - times[1].tv_nsec = 200; - BOOST_REQUIRE_EQUAL(fuse->utimens("/file", times), 0); + std::array<timespec, 2> times {{{1, 100}, {2, 200}}}; + BOOST_REQUIRE_EQUAL(fuse->utimens("/file", times.data(), nullptr), 0); times[1].tv_nsec = -200; - BOOST_REQUIRE_EQUAL(fuse->utimens("/file", times), -EINVAL); - struct stat st; - memset(&st, 0, sizeof(st)); - BOOST_REQUIRE_EQUAL(fuse->getattr("/file", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->utimens("/file", times.data(), nullptr), -EINVAL); + struct stat st {}; + BOOST_REQUIRE_EQUAL(fuse->getattr("/file", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_atime, 1); BOOST_REQUIRE_EQUAL(st.st_atim.tv_sec, 1); BOOST_REQUIRE_EQUAL(st.st_mtime, 2); @@ -492,7 +520,7 @@ BOOST_AUTO_TEST_CASE( utimens ) BOOST_REQUIRE_EQUAL(st.st_mtim.tv_nsec, 0); BOOST_REQUIRE_EQUAL(st.st_ctim.tv_nsec, 0); // Real file doesn't - BOOST_REQUIRE_EQUAL(lstat((binDir / testExport / "file").c_str(), &st), 0); + BOOST_REQUIRE_EQUAL(lstat((tmpDir / testExport / "file").c_str(), &st), 0); BOOST_REQUIRE_EQUAL(st.st_atim.tv_sec, 1); BOOST_REQUIRE_EQUAL(st.st_atime, 1); BOOST_REQUIRE_EQUAL(st.st_mtim.tv_sec, 2); @@ -503,19 +531,18 @@ BOOST_AUTO_TEST_CASE( utimens ) BOOST_AUTO_TEST_SUITE_END(); -BOOST_FIXTURE_TEST_SUITE( NetfsAltCore, AltCore ); +BOOST_FIXTURE_TEST_SUITE(NetfsAltCore, AltCore); -BOOST_AUTO_TEST_CASE( noListDir ) +BOOST_AUTO_TEST_CASE(noListDir) { - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); + struct fuse_file_info fi {}; BOOST_REQUIRE_EQUAL(fuse->mkdir("/test", 0700), 0); - BOOST_REQUIRE(std::filesystem::is_directory(binDir / testExport / "test")); + BOOST_REQUIRE(std::filesystem::is_directory(tmpDir / testExport / "test")); BOOST_REQUIRE_EQUAL(fuse->mkdir("/test", 0700), -EEXIST); BOOST_REQUIRE_EQUAL(fuse->opendir("/test", &fi), 0); NetFS::NameList nl; - BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi, fuse_readdir_flags {}), 0); BOOST_REQUIRE_EQUAL(nl.size(), 2); std::sort(nl.begin(), nl.end()); BOOST_REQUIRE_EQUAL(nl[0], "."); @@ -525,7 +552,7 @@ BOOST_AUTO_TEST_CASE( noListDir ) BOOST_REQUIRE_EQUAL(fuse->mkdir("/test/sub", 0700), 0); BOOST_REQUIRE_EQUAL(fuse->opendir("/test", &fi), 0); - BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi), 0); + BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi, fuse_readdir_flags {}), 0); BOOST_REQUIRE_EQUAL(nl.size(), 3); std::sort(nl.begin(), nl.end()); BOOST_REQUIRE_EQUAL(nl[0], "."); @@ -534,80 +561,76 @@ BOOST_AUTO_TEST_CASE( noListDir ) BOOST_REQUIRE_EQUAL(fuse->releasedir("/test", &fi), 0); nl.clear(); - BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi), -EBADF); + BOOST_REQUIRE_EQUAL(fuse->readdir("/test", &nl, &nameListAdd, 0, &fi, fuse_readdir_flags {}), -EBADF); BOOST_REQUIRE_EQUAL(fuse->releasedir("/test", &fi), -EBADF); BOOST_REQUIRE_EQUAL(fuse->rmdir("/test"), -ENOTEMPTY); BOOST_REQUIRE_EQUAL(fuse->rmdir("/test/sub"), 0); BOOST_REQUIRE_EQUAL(fuse->rmdir("/test"), 0); - BOOST_REQUIRE(!std::filesystem::is_directory(binDir / testExport / "test")); + BOOST_REQUIRE(!std::filesystem::is_directory(tmpDir / testExport / "test")); BOOST_REQUIRE_EQUAL(fuse->rmdir("/test"), -ENOENT); BOOST_REQUIRE_EQUAL(fuse->opendir("/test", &fi), -ENOENT); } -BOOST_AUTO_TEST_CASE( testFGWrites ) +BOOST_AUTO_TEST_CASE(testFGWrites) { - struct fuse_file_info fi; - memset(&fi, 0, sizeof(fi)); - struct stat st; - memset(&st, 0, sizeof(st)); + struct fuse_file_info fi {}; + struct stat st {}; fi.flags = O_RDWR; BOOST_REQUIRE_EQUAL(fuse->create("/test", 0600, &fi), 0); BOOST_REQUIRE_EQUAL(fuse->write("/test", "some test buffer", 16, 0, &fi), 16); - BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st), 0); + BOOST_REQUIRE_EQUAL(fuse->getattr("/test", &st, nullptr), 0); BOOST_REQUIRE_EQUAL(st.st_size, 16); - char buf[11]; - memset(&buf, 0, sizeof(buf)); - BOOST_REQUIRE_EQUAL(fuse->read("/test", buf, 10, 5, &fi), 10); + std::array<char, 11> buf {}; + BOOST_REQUIRE_EQUAL(fuse->read("/test", buf.data(), 10, 5, &fi), 10); BOOST_REQUIRE_EQUAL(buf, "test buffe"); BOOST_REQUIRE_EQUAL(fuse->release("/test", &fi), 0); } BOOST_AUTO_TEST_SUITE_END(); -BOOST_AUTO_TEST_CASE( testNoAuthNoPass ) +BOOST_AUTO_TEST_CASE(testNoAuthNoPass) { Core c("defaultDaemon.xml", "defaultFuse.xml"); struct statvfs s; - BOOST_REQUIRE_EQUAL(0, c.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(0, c.fuse->statfs("/", &s)); } -BOOST_AUTO_TEST_CASE( testWithAuthNoPass ) +BOOST_AUTO_TEST_CASE(testWithAuthNoPass) { Core c("secureDaemon.xml", "defaultFuse.xml"); struct statvfs s; - BOOST_REQUIRE_EQUAL(-EPERM, c.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(-EPERM, c.fuse->statfs("/", &s)); } -BOOST_AUTO_TEST_CASE( testWithAuthWithPass ) +BOOST_AUTO_TEST_CASE(testWithAuthWithPass) { Core c("secureDaemon.xml", "secureFuse.xml"); struct statvfs s; - BOOST_REQUIRE_EQUAL(0, c.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(0, c.fuse->statfs("/", &s)); } -BOOST_AUTO_TEST_CASE( testNoAuthWithPass ) +BOOST_AUTO_TEST_CASE(testNoAuthWithPass) { Core c("defaultDaemon.xml", "secureFuse.xml"); struct statvfs s; - BOOST_REQUIRE_EQUAL(0, c.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(0, c.fuse->statfs("/", &s)); } -BOOST_AUTO_TEST_CASE( uriConnect ) +BOOST_AUTO_TEST_CASE(uriConnect) { - MockDaemonHost daemon(testEndpoint, { - "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string() - }); - FuseMockHost fuse(std::string(), { - testUri, - (binDir / "test").string(), + MockDaemonHost daemon(testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}); + FuseMockHost fuse(std::string(), + { + testUri, + (tmpDir / "test").string(), }); struct statvfs s; - BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); } -BOOST_AUTO_TEST_CASE( interval_map_works_how_i_think ) +BOOST_AUTO_TEST_CASE(interval_map_works_how_i_think) { // Used as proof that setting and selecting ranges given an // attempt to write to [offset, offset + size) will find @@ -618,7 +641,7 @@ BOOST_AUTO_TEST_CASE( interval_map_works_how_i_think ) auto check = [&](off_t o, size_t s, int c) { BOOST_TEST_CONTEXT("offset: " << o << ", size: " << s << ", count: " << c) { const auto ol = map.equal_range(r(o, s)); - BOOST_TEST_CONTEXT("range: " << ol.first->first.lower() << " to " << ol.first->first.upper()) { + BOOST_TEST_CONTEXT("range start: " << ol.first->first.lower()) { BOOST_REQUIRE_EQUAL(std::distance(ol.first, ol.second), c); } } @@ -626,9 +649,9 @@ BOOST_AUTO_TEST_CASE( interval_map_works_how_i_think ) BOOST_REQUIRE(map.empty()); // Pretend we have 3 writes in progress - map.insert({r(5, 10), std::make_shared<NetFS::FuseApp::OpenFile::WriteState>() }); // [5, 15) - map.insert({r(15, 15), std::make_shared<NetFS::FuseApp::OpenFile::WriteState>() }); // [15, 30) - map.insert({r(35, 10), std::make_shared<NetFS::FuseApp::OpenFile::WriteState>() }); // [35, 45) + map.insert({r(5, 10), std::make_shared<NetFS::FuseApp::OpenFile::WriteState>()}); // [5, 15) + map.insert({r(15, 15), std::make_shared<NetFS::FuseApp::OpenFile::WriteState>()}); // [15, 30) + map.insert({r(35, 10), std::make_shared<NetFS::FuseApp::OpenFile::WriteState>()}); // [35, 45) // Then the following writes to [O, O+S) should wait on N pending writes check(0, 1, 0); // Clear before check(100, 10, 0); // Clear after @@ -650,4 +673,3 @@ BOOST_AUTO_TEST_CASE( interval_map_works_how_i_think ) map.erase(r(35, 10)); BOOST_REQUIRE(map.empty()); } - diff --git a/netfs/unittests/testDaemon.cpp b/netfs/unittests/testDaemon.cpp new file mode 100644 index 0000000..5c6fbc9 --- /dev/null +++ b/netfs/unittests/testDaemon.cpp @@ -0,0 +1,28 @@ +#define BOOST_TEST_MODULE TestNetFSDaemon +#include <boost/test/data/test_case.hpp> +#include <boost/test/unit_test.hpp> + +#include <entCache.h> +#include <exceptions.h> +#include <modeCheck.h> + +struct ModeCheckHelper { + EntryResolverPtr<User> eru = std::make_shared<UserEntCache>(); + EntryResolverPtr<Group> erg = std::make_shared<GroupEntCache>(eru); +}; + +BOOST_FIXTURE_TEST_SUITE(mc, ModeCheckHelper) + +BOOST_AUTO_TEST_CASE(mode_check_valid) +{ + ModeCheck mc {{"root", "root"}, "/tmp", *eru, *erg}; +} + +BOOST_AUTO_TEST_CASE(mode_check_invalid) +{ + BOOST_CHECK_THROW({ ModeCheck({"root", ""}, "/tmp", *eru, *erg); }, NetFS::SystemError); + BOOST_CHECK_THROW({ ModeCheck({"", "root"}, "/tmp", *eru, *erg); }, NetFS::SystemError); + BOOST_CHECK_THROW({ ModeCheck({"", ""}, "/tmp", *eru, *erg); }, NetFS::SystemError); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/netfs/unittests/testEdgeCases.cpp b/netfs/unittests/testEdgeCases.cpp index 0cfb888..5eb8d5d 100644 --- a/netfs/unittests/testEdgeCases.cpp +++ b/netfs/unittests/testEdgeCases.cpp @@ -1,47 +1,51 @@ #define BOOST_TEST_MODULE TestNetFSEdgeCases -#include <boost/test/unit_test.hpp> + +#include "fuseFiles.h" #include "mockDaemon.h" #include "mockFuse.h" +#include <boost/test/data/test_case.hpp> +#include <boost/test/unit_test.hpp> #include <definedDirs.h> +#include <fcntl.h> +#include <fileUtils.h> const std::string testEndpoint("tcp -h localhost -p 12014"); -BOOST_AUTO_TEST_CASE ( createAndDaemonRestart ) +BOOST_AUTO_TEST_CASE(createAndDaemonRestart) { - MockDaemonHost daemon(testEndpoint, { - "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string() - }); - FuseMockHost fuse(testEndpoint, { - (rootDir / "defaultFuse.xml:testvol").string(), - (rootDir / "test").string() - }); + MockDaemonHost daemon(testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}); + FuseMockHost fuse(testEndpoint, {(rootDir / "defaultFuse.xml:testvol").string(), (rootDir / "test").string()}); - struct statvfs s; - BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); + struct statvfs s {}; + BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); const char * fileName = "/createMe"; BOOST_TEST_CHECKPOINT("Create a new file"); - struct fuse_file_info fh; + struct fuse_file_info fh {}; memset(&fh, 0, sizeof(fh)); fh.flags = O_WRONLY | O_CREAT | O_APPEND; BOOST_REQUIRE_EQUAL(0, fuse.fuse->create(fileName, 0100644, &fh)); BOOST_REQUIRE(fh.fh); BOOST_TEST_CHECKPOINT("Fetch file attributes"); - struct stat st; - BOOST_REQUIRE_EQUAL(0, fuse.fuse->fgetattr(fileName, &st, &fh)); + struct stat st {}; + BOOST_REQUIRE_EQUAL(0, fuse.fuse->getattr(fileName, &st, &fh)); BOOST_REQUIRE_EQUAL(st.st_size, 0); BOOST_REQUIRE_EQUAL(st.st_uid, getuid()); BOOST_REQUIRE_EQUAL(st.st_gid, getgid()); BOOST_TEST_CHECKPOINT("Write some data"); - char someData[890]; - for (auto & d : someData) { - d = rand(); - } - BOOST_REQUIRE_EQUAL(sizeof(someData), fuse.fuse->write(fileName, someData, sizeof(someData), 0, &fh)); + std::array<char, 890> someData {}; + std::generate(begin(someData), end(someData), + [dist = std::uniform_int_distribution<char>( + std::numeric_limits<char>::min(), std::numeric_limits<char>::max()), + mersenne_engine = std::mt19937(std::random_device()())]() mutable { + return dist(mersenne_engine); + }); - BOOST_REQUIRE_EQUAL(0, fuse.fuse->fgetattr(fileName, &st, &fh)); + BOOST_REQUIRE_EQUAL(sizeof(someData), fuse.fuse->write(fileName, someData.data(), someData.size(), 0, &fh)); + + BOOST_REQUIRE_EQUAL(0, fuse.fuse->getattr(fileName, &st, &fh)); BOOST_REQUIRE_EQUAL(st.st_size, sizeof(someData)); BOOST_REQUIRE_EQUAL(0, fuse.fuse->flush(fileName, &fh)); @@ -49,75 +53,64 @@ BOOST_AUTO_TEST_CASE ( createAndDaemonRestart ) daemon.restart(); BOOST_TEST_CHECKPOINT("Fetch file attributes again"); - BOOST_REQUIRE_EQUAL(0, fuse.fuse->fgetattr(fileName, &st, &fh)); + BOOST_REQUIRE_EQUAL(0, fuse.fuse->getattr(fileName, &st, &fh)); BOOST_TEST_CHECKPOINT("Close file"); BOOST_REQUIRE_EQUAL(0, fuse.fuse->release(fileName, &fh)); } -BOOST_AUTO_TEST_CASE( noDaemonAtStartUp ) +BOOST_AUTO_TEST_CASE(noDaemonAtStartUp) { - FuseMockHost fuse(testEndpoint, { - (rootDir / "defaultFuse.xml:testvol").string(), - (rootDir / "test").string() - }); + FuseMockHost fuse(testEndpoint, {(rootDir / "defaultFuse.xml:testvol").string(), (rootDir / "test").string()}); - struct statvfs s; - BOOST_REQUIRE_EQUAL(-EIO, fuse.fuse->statfs("/", &s)); - MockDaemonHost daemon(testEndpoint, { - "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string() - }); + struct statvfs s {}; + BOOST_REQUIRE_EQUAL(-EIO, fuse.fuse->statfs("/", &s)); + MockDaemonHost daemon(testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}); - BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); } -BOOST_AUTO_TEST_CASE ( daemonUnavailableAfterUse ) +BOOST_AUTO_TEST_CASE(daemonUnavailableAfterUse) { - FuseMockHost fuse(testEndpoint, { - (rootDir / "defaultFuse.xml:testvol").string(), - (rootDir / "test").string() - }); - struct statvfs s; + FuseMockHost fuse(testEndpoint, {(rootDir / "defaultFuse.xml:testvol").string(), (rootDir / "test").string()}); + + struct statvfs s {}; { - MockDaemonHost daemon(testEndpoint, { - "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string() - }); + MockDaemonHost daemon(testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}); - BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); } - BOOST_REQUIRE_EQUAL(-EIO, fuse.fuse->statfs("/", &s)); + + BOOST_REQUIRE_EQUAL(-EIO, fuse.fuse->statfs("/", &s)); { - MockDaemonHost daemon(testEndpoint, { - "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string() - }); + MockDaemonHost daemon(testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}); - BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); } - BOOST_REQUIRE_EQUAL(-EIO, fuse.fuse->statfs("/", &s)); + BOOST_REQUIRE_EQUAL(-EIO, fuse.fuse->statfs("/", &s)); } -BOOST_AUTO_TEST_CASE( manyThreads ) +BOOST_AUTO_TEST_CASE(manyThreads) { - MockDaemonHost daemon(testEndpoint, { - "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string() - }); - FuseMockHost fuse(testEndpoint, { - (rootDir / "defaultFuse.xml:testvol").string(), - (rootDir / "test").string() - }); + MockDaemonHost daemon(testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}); + FuseMockHost fuse(testEndpoint, {(rootDir / "defaultFuse.xml:testvol").string(), (rootDir / "test").string()}); - bool running = true; + std::atomic<bool> running {true}; std::vector<std::thread> ths; - std::atomic_uint success = 0; + ths.reserve(20); + std::atomic<unsigned int> success {0}, failure {0}; for (int x = 0; x < 20; x++) { ths.emplace_back([&] { - struct statvfs s; - while(running) { + struct statvfs s {}; + while (running) { if (fuse.fuse->statfs("/", &s) == 0) { success++; } + else { + failure++; + } + usleep(10000); } - usleep(10000); }); } @@ -125,11 +118,54 @@ BOOST_AUTO_TEST_CASE( manyThreads ) daemon.restart(); sleep(1); running = false; - + for (auto & th : ths) { th.join(); } BOOST_CHECK_GT(success, 800); + BOOST_CHECK_EQUAL(failure, 0); } +BOOST_DATA_TEST_CASE(bigWritesAndReads, + boost::unit_test::data::make({"defaultFuseNoAsync.xml", "defaultFuse.xml"}) + * boost::unit_test::data::xrange<size_t>(1024UL * 100UL, 4096UL * 1024UL, 512UL * 1024UL) + * boost::unit_test::data::make<off_t>(0, 317, 4096UL * 1024UL), + config, size, offset) +{ + MockDaemonHost daemon(testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}); + FuseMockHost fuse(testEndpoint, {(rootDir / config).string() + ":testvol", (rootDir / "test").string()}); + + { + const auto buf = FuseMock::genRandomData(size); + { + fuse_file_info fi {}; + fi.flags = O_RDWR; + auto fd = fuse.fuse->create("/big", 0600, &fi); + BOOST_REQUIRE_GE(fd, 0); + BOOST_REQUIRE_NE(fi.fh, 0); + BOOST_REQUIRE_EQUAL(buf.size(), fuse.fuse->write("/big", buf.data(), buf.size(), offset, &fi)); + BOOST_REQUIRE_EQUAL(0, fuse.fuse->release("/big", &fi)); + } + + AdHoc::FileUtils::MemMap mm(daemon.TestExportRoot / "big"); + const auto file {mm.sv()}; + BOOST_CHECK_EQUAL_COLLECTIONS(buf.begin(), buf.end(), file.begin() + offset, file.end()); + } + { + auto buf = FuseMock::genRandomData(size); + { + fuse_file_info fi {}; + fi.flags = O_RDONLY; + auto fd = fuse.fuse->open("/big", &fi); + BOOST_REQUIRE_GE(fd, 0); + BOOST_REQUIRE_NE(fi.fh, 0); + BOOST_REQUIRE_EQUAL(buf.size(), fuse.fuse->read("/big", buf.data(), buf.size(), offset, &fi)); + BOOST_REQUIRE_EQUAL(0, fuse.fuse->release("/big", &fi)); + } + + AdHoc::FileUtils::MemMap mm(daemon.TestExportRoot / "big"); + const auto file {mm.sv()}; + BOOST_CHECK_EQUAL_COLLECTIONS(buf.begin(), buf.end(), file.begin() + offset, file.end()); + } +} diff --git a/netfs/unittests/testFuse.cpp b/netfs/unittests/testFuse.cpp new file mode 100644 index 0000000..c04c52c --- /dev/null +++ b/netfs/unittests/testFuse.cpp @@ -0,0 +1,143 @@ +#define BOOST_TEST_MODULE TestNetFSFuse +#include "mockDaemon.h" +#include "mockMount.h" +#include <boost/test/unit_test.hpp> +#include <c++11Helpers.h> +#include <cache.impl.h> +#include <definedDirs.h> +#include <filesystem> +#include <fuse.h> +#include <fuseApp.h> +#include <fuseMappersImpl.h> +#include <lockHelpers.h> +#include <ostream> +#include <thread> + +#define BOOST_CHECK_EQUAL_FIELD(left, right, field) BOOST_CHECK_EQUAL((left).field, (right).field); + +#define BOOST_CHECK_EQUAL_STAT(left, right) \ + BOOST_CHECK_NE(&(left), &(right)); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_atim.tv_sec); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_mtim.tv_sec); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_ctim.tv_sec); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_mode); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_nlink); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_uid); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_gid); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_rdev); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_size); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_blksize); \ + BOOST_CHECK_EQUAL_FIELD(left, right, st_blocks); + +static auto +get_lstat(const std::filesystem::path & p) +{ + struct stat st { }; + BOOST_TEST_INFO(p); + BOOST_REQUIRE_EQUAL(::lstat(p.c_str(), &st), 0); + return st; +} + +BOOST_FIXTURE_TEST_SUITE(fmp, FuseMountPoint); + +BOOST_AUTO_TEST_CASE(fuse, *boost::unit_test::timeout(5)) +{ + BOOST_REQUIRE(std::filesystem::is_directory(mntpnt)); +} + +BOOST_AUTO_TEST_CASE(fuse_ls, *boost::unit_test::timeout(5)) +{ + static const auto ME_HASH {std::filesystem::hash_value("/me")}; + + BOOST_REQUIRE(std::filesystem::is_directory(mntpnt)); + BOOST_REQUIRE(std::filesystem::is_empty(mntpnt)); + const auto rpath = mntpnt / "me"; + const auto lpath = MockDaemonHost::TestExportRoot / "me"; + BOOST_REQUIRE_NE(lpath, rpath); + + std::filesystem::create_symlink(selfExe, rpath); + + const auto st_local = get_lstat(lpath); + + BOOST_CHECK(!fuseApp.getStatCache().get(ME_HASH)); + + std::set<std::filesystem::path> paths(std::filesystem::directory_iterator(mntpnt), {}); + BOOST_REQUIRE_EQUAL(paths.size(), 1); + BOOST_CHECK_EQUAL(paths.begin()->filename(), "me"); + + auto cached = fuseApp.getStatCache().get(ME_HASH); + BOOST_REQUIRE(cached); + const auto & st_cache = (*cached); + BOOST_CHECK_EQUAL_STAT(st_cache, st_local); + + const auto st_remote = get_lstat(rpath); + BOOST_CHECK_EQUAL_STAT(st_remote, st_local); +} + +BOOST_AUTO_TEST_SUITE_END(); + +BOOST_AUTO_TEST_CASE(url_params_non) +{ + auto fcs = NetFS::FuseApp::configureFromUri("tcp://localhost/foo"); + BOOST_CHECK_EQUAL(fcs->Async, false); +} + +BOOST_AUTO_TEST_CASE(url_params_1) +{ + auto fcs = NetFS::FuseApp::configureFromUri("tcp://localhost/foo?async=1"); + BOOST_CHECK_EQUAL(fcs->Async, true); +} + +BOOST_AUTO_TEST_CASE(url_params_2) +{ + auto fcs = NetFS::FuseApp::configureFromUri("tcp://localhost/foo?0&async=0"); + BOOST_CHECK_EQUAL(fcs->Async, false); +} + +BOOST_AUTO_TEST_CASE(url_params_hide) +{ + auto fcs = NetFS::FuseApp::configureFromUri("tcp://localhost/foo?mapper=hide"); + BOOST_REQUIRE(fcs->Mapper); + BOOST_REQUIRE(std::dynamic_pointer_cast<NetFS::Client::HideUnknownMapperImpl>(fcs->Mapper)); +} + +BOOST_AUTO_TEST_CASE(url_params_mask_default) +{ + auto fcs = NetFS::FuseApp::configureFromUri("tcp://localhost/foo?mapper=mask"); + BOOST_REQUIRE(fcs->Mapper); + auto mapper = std::dynamic_pointer_cast<NetFS::Client::MaskUnknownMapperImpl>(fcs->Mapper); + BOOST_REQUIRE(mapper); +} + +BOOST_AUTO_TEST_CASE(url_params_mask_nondefault) +{ + auto fcs = NetFS::FuseApp::configureFromUri( + "tcp://localhost/" + "foo?mapper=mask&mapper.unknownuser=uu&mapper.unknowngroup=ug&mapper.usermask=0100&mapper.groupmask=0010"); + BOOST_REQUIRE(fcs->Mapper); + auto mapper = std::dynamic_pointer_cast<NetFS::Client::MaskUnknownMapperImpl>(fcs->Mapper); + BOOST_REQUIRE(mapper); + BOOST_CHECK_EQUAL(mapper->UnknownUser, "uu"); + BOOST_CHECK_EQUAL(mapper->UnknownGroup, "ug"); + BOOST_CHECK_EQUAL(mapper->UserMask, 0100); + BOOST_CHECK_EQUAL(mapper->GroupMask, 0010); +} + +BOOST_AUTO_TEST_CASE(config_file_hide) +{ + auto fcs = NetFS::FuseApp::configureFromFile(rootDir / "defaultFuseHide.xml", "testvol"); + BOOST_REQUIRE(fcs->Mapper); + BOOST_REQUIRE(std::dynamic_pointer_cast<NetFS::Client::HideUnknownMapperImpl>(fcs->Mapper)); +} + +BOOST_AUTO_TEST_CASE(config_file_mask) +{ + auto fcs = NetFS::FuseApp::configureFromFile(rootDir / "defaultFuseMask.xml", "testvol"); + BOOST_REQUIRE(fcs->Mapper); + auto mapper = std::dynamic_pointer_cast<NetFS::Client::MaskUnknownMapperImpl>(fcs->Mapper); + BOOST_REQUIRE(mapper); + BOOST_CHECK_EQUAL(mapper->UnknownUser, "uu"); + BOOST_CHECK_EQUAL(mapper->UnknownGroup, "ug"); + BOOST_CHECK_EQUAL(mapper->UserMask, 0100); + BOOST_CHECK_EQUAL(mapper->GroupMask, 0010); +} diff --git a/netfs/unittests/testGlacier.cpp b/netfs/unittests/testGlacier.cpp index 900a531..afe3475 100644 --- a/netfs/unittests/testGlacier.cpp +++ b/netfs/unittests/testGlacier.cpp @@ -1,34 +1,22 @@ #define BOOST_TEST_MODULE TestNetFSGlacier #include <boost/test/unit_test.hpp> + #include "mockDaemon.h" #include "mockFuse.h" -#include <boost/scope_exit.hpp> +#include "mockGlacier.h" #include <definedDirs.h> const std::string testEndpoint("tcp -h localhost -p 12013"); -BOOST_AUTO_TEST_CASE ( withRouter ) -{ - BOOST_REQUIRE_EQUAL(0, - system("/usr/bin/glacier2router --Glacier2.Client.Endpoints='tcp -p 14063' --Glacier2.PermissionsVerifier=Glacier2/NullPermissionsVerifier --daemon --pidfile /tmp/glacier.pid")); - sleep(1); - - BOOST_SCOPE_EXIT(void) { - BOOST_REQUIRE_EQUAL(0, system("kill $(cat /tmp/glacier.pid)")); - } BOOST_SCOPE_EXIT_END; +BOOST_TEST_GLOBAL_FIXTURE(Glacier); - { - MockDaemonHost daemon(testEndpoint, { - "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string() - }); - FuseMockHost fuse(testEndpoint, { - (rootDir / "defaultFuse.xml:testvol").string(), - (rootDir / "test").string(), - "--Ice.Default.Router=Glacier2/router:tcp -h localhost -p 14063" - }); +BOOST_AUTO_TEST_CASE(withRouter) +{ + MockDaemonHost daemon(testEndpoint, {"--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}); + FuseMockHost fuse(testEndpoint, + {(rootDir / "defaultFuse.xml:testvol").string(), (rootDir / "test").string(), + "--Ice.Default.Router=Glacier2/router:tcp -h localhost -p 14063"}); - struct statvfs s; - BOOST_REQUIRE_EQUAL(0, fuse.fuse->statfs("/", &s)); - } + struct statvfs s { }; + BOOST_CHECK_EQUAL(0, fuse.fuse->statfs("/", &s)); } - diff --git a/netfs/unittests/testLib.cpp b/netfs/unittests/testLib.cpp new file mode 100644 index 0000000..b6d41f2 --- /dev/null +++ b/netfs/unittests/testLib.cpp @@ -0,0 +1,206 @@ +#define BOOST_TEST_MODULE TestNetFSLib +#include <boost/test/data/test_case.hpp> +#include <boost/test/unit_test.hpp> +#include <defaultMapper.h> +#include <entCache.impl.h> +#include <fuseMappersImpl.h> +#include <lockHelpers.h> + +struct TestEntry { + TestEntry(int i, std::string n) : id(i), name(std::move(n)) { } + + int id; + std::string name; +}; + +struct TestEntCache : public EntCache<TestEntry> { + void + fillCache() const noexcept override + { + idcache->insert(std::make_shared<TestEntry>(1, "user1")); + idcache->insert(std::make_shared<TestEntry>(2, "user2")); + idcache->insert(std::make_shared<TestEntry>(3, "user3")); + } +}; + +const auto GoodIds = boost::unit_test::data::make({1, 2, 3}); +const auto BadIds = boost::unit_test::data::make({0, -1, 10}); +const auto GoodNames = boost::unit_test::data::make({"user1", "user2", "user3"}); +const auto BadNames = boost::unit_test::data::make({"", "bad", "user4"}); + +BOOST_FIXTURE_TEST_SUITE(tec, TestEntCache); + +BOOST_DATA_TEST_CASE(notfoundid, BadIds, id) +{ + BOOST_CHECK(!getEntry(id)); +} + +BOOST_DATA_TEST_CASE(notfoundname, BadNames, name) +{ + BOOST_CHECK(!getEntry(name)); +} + +BOOST_DATA_TEST_CASE(foundid, GoodNames ^ GoodIds, name, id) +{ + BOOST_CHECK_EQUAL(getEntry(id)->name, name); +} + +BOOST_DATA_TEST_CASE(foundname, GoodNames ^ GoodIds, name, id) +{ + BOOST_CHECK_EQUAL(getEntry(name)->id, id); +} + +BOOST_AUTO_TEST_SUITE_END(); + +BOOST_AUTO_TEST_CASE(group_membership) +{ + Group g(0, "root"); + g.members.insert(0); + g.members.insert(1); + g.members.insert(5); + + BOOST_REQUIRE_EQUAL(3, g.members.size()); + BOOST_CHECK(g.hasMember(0)); + BOOST_CHECK(g.hasMember(1)); + BOOST_CHECK(g.hasMember(5)); + BOOST_CHECK(!g.hasMember(3)); + BOOST_CHECK(!g.hasMember(6)); +} + +// These tests make some assumptions about local system users +BOOST_FIXTURE_TEST_CASE(usercache, UserEntCache) +{ + auto root = getEntry(0); + BOOST_REQUIRE(root); + BOOST_CHECK_EQUAL(root->id, 0); + BOOST_CHECK_EQUAL(root->name, "root"); + BOOST_CHECK_EQUAL(root->group, 0); + + auto rootByName = getEntry(root->name); + BOOST_REQUIRE_EQUAL(root, rootByName); +} + +struct TestGroupEntCache : public GroupEntCache { + TestGroupEntCache() : GroupEntCache(std::make_shared<UserEntCache>()) { } +}; + +BOOST_FIXTURE_TEST_CASE(groupcache, TestGroupEntCache) +{ + auto root = getEntry(0); + BOOST_REQUIRE(root); + BOOST_CHECK_EQUAL(root->id, 0); + BOOST_CHECK_EQUAL(root->name, "root"); + BOOST_CHECK_EQUAL(root->members.size(), 1); + BOOST_CHECK(root->hasMember(0)); + + auto rootByName = getEntry(root->name); + BOOST_REQUIRE_EQUAL(root, rootByName); +} + +BOOST_FIXTURE_TEST_SUITE(dm, NetFS::Mapping::DefaultMapper); + +BOOST_AUTO_TEST_CASE(good_maptransport) +{ + auto fs = mapTransport("root", "root"); + BOOST_CHECK_EQUAL(0, fs.uid); + BOOST_CHECK_EQUAL(0, fs.gid); + BOOST_CHECK_EQUAL(0, fs.mask); +} + +BOOST_AUTO_TEST_CASE(good_mapfs) +{ + auto tn = mapFileSystem(0, 0); + BOOST_CHECK_EQUAL("root", tn.username); + BOOST_CHECK_EQUAL("root", tn.groupname); +} + +BOOST_AUTO_TEST_CASE(bad_maptransport) +{ + BOOST_REQUIRE_THROW(mapTransport("root", ""), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapTransport("", "root"), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapTransport("", ""), NetFS::SystemError); +} + +BOOST_AUTO_TEST_CASE(bad_mapfilesystem) +{ + BOOST_REQUIRE_THROW(mapFileSystem(0, 99999999), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapFileSystem(99999999, 0), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapFileSystem(99999999, 99999999), NetFS::SystemError); +} + +BOOST_AUTO_TEST_SUITE_END(); + +BOOST_FIXTURE_TEST_SUITE(hm, NetFS::Client::HideUnknownMapperImpl); + +BOOST_AUTO_TEST_CASE(good_maptransport) +{ + auto fs = mapTransport("root", "root"); + BOOST_CHECK_EQUAL(0, fs.uid); + BOOST_CHECK_EQUAL(0, fs.gid); + BOOST_CHECK_EQUAL(0, fs.mask); +} + +BOOST_AUTO_TEST_CASE(good_mapfs) +{ + auto tn = mapFileSystem(0, 0); + BOOST_CHECK_EQUAL("root", tn.username); + BOOST_CHECK_EQUAL("root", tn.groupname); +} + +BOOST_AUTO_TEST_CASE(bad_maptransport) +{ + BOOST_CHECK_EQUAL(mapTransport("root", "").mask, ~0); + BOOST_CHECK_EQUAL(mapTransport("", "root").mask, ~0); + BOOST_CHECK_EQUAL(mapTransport("", "").mask, ~0); +} + +BOOST_AUTO_TEST_CASE(bad_mapfilesystem) +{ + BOOST_REQUIRE_THROW(mapFileSystem(0, 99999999), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapFileSystem(99999999, 0), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapFileSystem(99999999, 99999999), NetFS::SystemError); +} + +BOOST_AUTO_TEST_SUITE_END(); + +BOOST_FIXTURE_TEST_SUITE(mm, NetFS::Client::MaskUnknownMapperImpl); + +BOOST_AUTO_TEST_CASE(good_maptransport) +{ + auto fs = mapTransport("root", "root"); + BOOST_CHECK_EQUAL(0, fs.uid); + BOOST_CHECK_EQUAL(0, fs.gid); + BOOST_CHECK_EQUAL(0, fs.mask); +} + +BOOST_AUTO_TEST_CASE(good_mapfs) +{ + auto tn = mapFileSystem(0, 0); + BOOST_CHECK_EQUAL("root", tn.username); + BOOST_CHECK_EQUAL("root", tn.groupname); +} + +BOOST_AUTO_TEST_CASE(bad_maptransport) +{ + BOOST_CHECK_EQUAL(mapTransport("root", "").mask, 0070); + BOOST_CHECK_EQUAL(mapTransport("", "root").mask, 0700); + BOOST_CHECK_EQUAL(mapTransport("", "").mask, 0770); +} + +BOOST_AUTO_TEST_CASE(bad_maptransport_badfallback) +{ + UnknownUser = "not existing"; + UnknownGroup = "not existing"; + BOOST_REQUIRE_THROW(mapTransport("root", ""), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapTransport("", "root"), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapTransport("", ""), NetFS::SystemError); +} + +BOOST_AUTO_TEST_CASE(bad_mapfilesystem) +{ + BOOST_REQUIRE_THROW(mapFileSystem(0, 99999999), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapFileSystem(99999999, 0), NetFS::SystemError); + BOOST_REQUIRE_THROW(mapFileSystem(99999999, 99999999), NetFS::SystemError); +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/netfs/unittests/testPerf.cpp b/netfs/unittests/testPerf.cpp new file mode 100644 index 0000000..eff3ae4 --- /dev/null +++ b/netfs/unittests/testPerf.cpp @@ -0,0 +1,133 @@ +#include "mockDaemon.h" +#include "mockFuse.h" +#include <algorithm> +#include <benchmark/benchmark.h> +#include <climits> +#include <compileTimeFormatter.h> +#include <definedDirs.h> +#include <filesystem> +#include <fuseFiles.h> + +const std::filesystem::path tmpDir {getenv("XDG_RUNTIME_DIR") / selfExe.filename()}; +const std::string testEndpoint("tcp -h localhost -p 12016"); +const std::string testUri("tcp://localhost:12016/testvol"); + +class Core { +public: + Core() : + daemonHost(testEndpoint, + {"--Ice.ThreadPool.Client.Size=4", "--Ice.ThreadPool.Client.SizeMax=20", + "--Ice.ThreadPool.Server.Size=5", "--Ice.ThreadPool.Server.SizeMax=20", + "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()}) + { + } + +protected: + MockDaemonHost daemonHost; + +public: +}; + +Core globalCore; + +class Fuse : public FuseMockHost, public benchmark::Fixture { +public: + Fuse() : + FuseMockHost(testEndpoint, + {"--Ice.ThreadPool.Client.Size=6", "--Ice.ThreadPool.Client.SizeMax=20", + (rootDir / "defaultFuse.xml").string() + ":testvol", (tmpDir / "test").string()}) + { + } + + static int + nameListDiscard(void *, const char *, const struct stat *, off_t, fuse_fill_dir_flags) + { + return 0; + } +}; + +BENCHMARK_F(Fuse, statfs_root)(benchmark::State & state) +{ + struct statvfs st { }; + for (auto _ : state) { + benchmark::DoNotOptimize(fuse->statfs("/", &st)); + } +} + +BENCHMARK_F(Fuse, getattr_root)(benchmark::State & state) +{ + struct stat st { }; + for (auto _ : state) { + benchmark::DoNotOptimize(fuse->getattr("/", &st, nullptr)); + } +} + +BENCHMARK_F(Fuse, openclose_dir_root)(benchmark::State & state) +{ + for (auto _ : state) { + fuse_file_info fi {}; + benchmark::DoNotOptimize(fuse->opendir("/", &fi)); + benchmark::DoNotOptimize(fuse->releasedir("/", &fi)); + } +} + +AdHocFormatter(numbered_filename, "/files/test-%?.txt"); +AdHocFormatter(numbered_dir, "/dirs/test-%?"); + +BENCHMARK_F(Fuse, create_files)(benchmark::State & state) +{ + fuse->mkdir("/files", 0700); + unsigned int n {}; + for (auto _ : state) { + fuse_file_info fi {}; + const std::string filename {numbered_filename::get(n++)}; + benchmark::DoNotOptimize(fuse->create(filename.c_str(), 0600, &fi)); + benchmark::DoNotOptimize(fuse->release(filename.c_str(), &fi)); + } +} + +BENCHMARK_F(Fuse, create_dirs)(benchmark::State & state) +{ + fuse->mkdir("/dirs", 0700); + unsigned int n {}; + for (auto _ : state) { + const std::string dirname {numbered_dir::get(n++)}; + benchmark::DoNotOptimize(fuse->mkdir(dirname.c_str(), 0600)); + } +} + +BENCHMARK_F(Fuse, ls_files)(benchmark::State & state) +{ + fuse_file_info fi {}; + fuse->opendir("/files", &fi); + for (auto _ : state) { + benchmark::DoNotOptimize(fuse->readdir("/files", nullptr, nameListDiscard, 0, &fi, FUSE_READDIR_PLUS)); + } + fuse->releasedir("/", &fi); +} + +BENCHMARK_F(Fuse, write_file)(benchmark::State & state) +{ + fuse_file_info fi {}; + fuse->open("/files/test-0.txt", &fi); + std::array<char, 1024> data {}; + off_t off {}; + for (auto _ : state) { + benchmark::DoNotOptimize(fuse->write("/files/test-0.txt", data.data(), data.size(), off, &fi)); + off += data.size(); + } + fuse->release("/", &fi); +} + +BENCHMARK_F(Fuse, read_file)(benchmark::State & state) +{ + fuse_file_info fi {}; + fuse->open("/files/test-0.txt", &fi); + std::array<char, 1024> data {}; + for (auto _ : state) { + benchmark::DoNotOptimize(fuse->read("/files/test-0.txt", data.data(), data.size(), 0, &fi)); + } + fuse->release("/", &fi); +} + +BENCHMARK_MAIN(); diff --git a/netfs/unittests/thread-suppressions.txt b/netfs/unittests/thread-suppressions.txt new file mode 100644 index 0000000..95c4982 --- /dev/null +++ b/netfs/unittests/thread-suppressions.txt @@ -0,0 +1,5 @@ +# Unmount Fuse filesystem while daemon is reading socket (expected) +race:fmp::Run::~Run + +# Pfft... no idea +race:IceInternal::PromiseOutgoing |
