Feel free to comment on it. You will see numerous "//TODO"s with questions 
or considerations. 

Waldek

PS. I hope that Benoit Canet will see this patch and find some time to 
comment on it.

On Wednesday, December 12, 2018 at 11:54:03 PM UTC-5, Waldek Kozaczuk wrote:
>
> This patch (even though called RFC) provides as functional 
> implementation of a read-only subset of SMB2/3 networking 
> filesystem and allows mountimg remote Windows SMB2/3 shares 
> as well as Samba shares exposed on Linux instances. Please 
> note that it does not support mounting old SMB 1 nor CIFS shares. 
>
> This patch is heavily based on the work done by Benoit 
> Canet who implemented NFS filesystem. It also uses lower 
> level 3rd-party library libsmb2. It just happens that libsmb2 
> has been implemented by the same author as libnfs uses by 
> OSv NFS implementation so the interface of libsmb2 is actually 
> quite similar. 
>
> Unlike NFS implementation which requires compiling appropriate 
> NFS-specific VFS code into kernel and linking libnfs library, 
> this one is mostly outside of kernel in form of a shared object 
> (see modules/smbfs) which dynamically links libsmb2 shared object. 
> This allows for following benefits: 
> - adding smb2 filesystem is as easy as building and adding smbfs module 
> - the smbfs object gets dynamically recognized and corresponding vfsops 
> registered 
> - it is easy to upgrade libsmb2 library to newer version and it can be 
>  also added from host filesystem 
>
> It is hoped that this implementation may become a model for future 
> optional/modular filesystems supported by OSv. Also it should not be 
> difficult to tweak NFS implementation to follow same model. 
>
> Smbfs modules uses high-level sync API of Libsmb2. However 
> libsmb2 also provides lower level async (callback based) API 
> which might be beneficial to provide more efficient implementation 
> of smbfs but given synchronous nature of most of VFS api it is 
> not clear if async API would help us. Also the fact that VFS provides 
> coarse-grained locking around individual files may also hinder any 
> performance/parallelism improvements. 
>
> In order to use it one needs to as smbfs to an image and then 
> mount desired share by using REST app API (see mount-samba.sh) 
> or add an init script to call mount-fs.so. 
>
> Signed-off-by: Waldemar Kozaczuk <[email protected]> 
> --- 
>  Makefile                                  |   3 +- 
>  fs/smbfs/smbfs_null_vfsops.cc             |  38 ++ 
>  fs/vfs/main.cc                            |  19 + 
>  fs/vfs/vfs_conf.cc                        |   3 + 
>  modules/libsmb2/Makefile                  |  19 + 
>  modules/libsmb2/usr.manifest              |   9 + 
>  modules/smbfs/.gitignore                  |   4 + 
>  modules/smbfs/Makefile                    |  49 +++ 
>  modules/smbfs/module.py                   |   3 + 
>  modules/smbfs/smbfs.cc                    |  86 +++++ 
>  modules/smbfs/smbfs.hh                    |  68 ++++ 
>  modules/smbfs/smbfs_vfsops.cc             |  66 ++++ 
>  modules/smbfs/smbfs_vnops.cc              | 416 ++++++++++++++++++++++ 
>  mount-samba.sh                            |   1 + 
>  tools/mount/{mount-nfs.cc => mount-fs.cc} |  13 +- 
>  usr.manifest.skel                         |   2 +- 
>  usr_rofs.manifest.skel                    |   3 +- 
>  17 files changed, 795 insertions(+), 7 deletions(-) 
>  create mode 100644 fs/smbfs/smbfs_null_vfsops.cc 
>  create mode 100644 modules/libsmb2/Makefile 
>  create mode 100644 modules/libsmb2/usr.manifest 
>  create mode 100644 modules/smbfs/.gitignore 
>  create mode 100644 modules/smbfs/Makefile 
>  create mode 100644 modules/smbfs/module.py 
>  create mode 100644 modules/smbfs/smbfs.cc 
>  create mode 100644 modules/smbfs/smbfs.hh 
>  create mode 100644 modules/smbfs/smbfs_vfsops.cc 
>  create mode 100644 modules/smbfs/smbfs_vnops.cc 
>  create mode 100644 mount-samba.sh 
>  rename tools/mount/{mount-nfs.cc => mount-fs.cc} (70%) 
>
> diff --git a/Makefile b/Makefile 
> index 68043485..afb43acd 100644 
> --- a/Makefile 
> +++ b/Makefile 
> @@ -396,7 +396,7 @@ tools += tools/uush/uush.so 
>  tools += tools/uush/ls.so 
>  tools += tools/uush/mkdir.so 
>   
> -tools += tools/mount/mount-nfs.so 
> +tools += tools/mount/mount-fs.so 
>  tools += tools/mount/umount.so 
>   
>  ifeq ($(arch),aarch64) 
> @@ -1853,6 +1853,7 @@ else 
>  endif 
>   
>  objects += $(addprefix fs/nfs/, $(nfs_o)) 
> +objects += fs/smbfs/smbfs_null_vfsops.o 
>   
>  # ld has a known bug (
> https://sourceware.org/bugzilla/show_bug.cgi?id=6468) 
>  # where if the executable doesn't use shared libraries, its .dynamic 
> section 
> diff --git a/fs/smbfs/smbfs_null_vfsops.cc b/fs/smbfs/smbfs_null_vfsops.cc 
> new file mode 100644 
> index 00000000..287f6388 
> --- /dev/null 
> +++ b/fs/smbfs/smbfs_null_vfsops.cc 
> @@ -0,0 +1,38 @@ 
> +/* 
> + * Copyright (C) 2018 Waldemar Kozaczuk. 
> + * 
> + * Based on ramfs code Copyright (c) 2006-2007, Kohsuke Ohtani 
> + * 
> + * This work is open source software, licensed under the terms of the 
> + * BSD license as described in the LICENSE file in the top-level 
> directory. 
> + */ 
> + 
> +#include <dlfcn.h> 
> +#include <osv/mount.h> 
> +#include <osv/debug.h> 
> + 
> +#define smbfs_mount   ((vfsop_mount_t)vfs_nullop) 
> +#define smbfs_umount  ((vfsop_umount_t)vfs_nullop) 
> +#define smbfs_sync    ((vfsop_sync_t)vfs_nullop) 
> +#define smbfs_vget    ((vfsop_vget_t)vfs_nullop) 
> +#define smbfs_statfs  ((vfsop_statfs_t)vfs_nullop) 
> + 
> +/* 
> + * File system operations 
> + * 
> + * This provides a "null" vfsops that is replaced when libsmbfs.so is 
> + * loaded upon VFS initialization. 
> + */ 
> +struct vfsops smbfs_vfsops = { 
> +    smbfs_mount,      /* mount */ 
> +    smbfs_umount,     /* umount */ 
> +    smbfs_sync,       /* sync */ 
> +    smbfs_vget,       /* vget */ 
> +    smbfs_statfs,     /* statfs */ 
> +    nullptr,          /* vnops */ 
> +}; 
> + 
> +int smbfs_init(void) 
> +{ 
> +    return 0; 
> +} 
> diff --git a/fs/vfs/main.cc b/fs/vfs/main.cc 
> index 7cc4291d..c48c52b4 100644 
> --- a/fs/vfs/main.cc 
> +++ b/fs/vfs/main.cc 
> @@ -53,6 +53,7 @@ 
>  #include <fcntl.h> 
>  #undef open 
>  #undef fcntl 
> +#include <dlfcn.h> 
>   
>  #include <osv/prex.h> 
>  #include <osv/vnode.h> 
> @@ -2267,6 +2268,24 @@ void pivot_rootfs(const char* path) 
>      if (ret) 
>          kprintf("failed to pivot root, error = %s\n", strerror(ret)); 
>   
> +    // Initialize other filesystem libraries if present 
> +    //TODO: Is this the best place on a filesystem to look for these 
> libraries 
> +    auto fs_lib_dir = opendir("/usr/lib/fs"); 
> +    if (fs_lib_dir) { 
> +        while (auto dirent = readdir(fs_lib_dir)) { 
> +            auto len = strlen(dirent->d_name); 
> +            //TODO: It would be nice to have ends_with() in std::string 
> ;-) 
> +            if (len >= 3 && strcmp(dirent->d_name + (len - 3), ".so") == 
> 0) { 
> +                auto lib_path = std::string("/usr/lib/fs/") + 
> dirent->d_name; 
> +                auto module = dlopen(lib_path.c_str(), RTLD_LAZY); 
> +                if (module) 
> +                    debugf("VFS: Initialized filesystem library: %s\n", 
> lib_path.c_str()); 
> +            } 
> +        } 
> + 
> +        closedir(fs_lib_dir); 
> +    } 
> + 
>      auto ent = setmntent("/etc/fstab", "r"); 
>      if (!ent) { 
>          return; 
> diff --git a/fs/vfs/vfs_conf.cc b/fs/vfs/vfs_conf.cc 
> index 4ac695b7..928fd5a0 100644 
> --- a/fs/vfs/vfs_conf.cc 
> +++ b/fs/vfs/vfs_conf.cc 
> @@ -51,6 +51,7 @@ extern struct vfsops devfs_vfsops; 
>  extern struct vfsops nfs_vfsops; 
>  extern struct vfsops procfs_vfsops; 
>  extern struct vfsops zfs_vfsops; 
> +extern struct vfsops smbfs_vfsops; 
>   
>  extern int ramfs_init(void); 
>  extern int rofs_init(void); 
> @@ -58,6 +59,7 @@ extern int devfs_init(void); 
>  extern int nfs_init(void); 
>  extern int procfs_init(void); 
>  extern "C" int zfs_init(void); 
> +extern int smbfs_init(void); 
>   
>  /* 
>   * VFS switch table 
> @@ -69,5 +71,6 @@ const struct vfssw vfssw[] = { 
>          {"procfs",        procfs_init,        &procfs_vfsops}, 
>          {"zfs",                zfs_init,        &zfs_vfsops}, 
>          {"rofs",         rofs_init,         &rofs_vfsops}, 
> +        {"smbfs",         smbfs_init, &smbfs_vfsops}, 
>          {nullptr,        fs_noop,        nullptr}, 
>  }; 
> diff --git a/modules/libsmb2/Makefile b/modules/libsmb2/Makefile 
> new file mode 100644 
> index 00000000..f8b87596 
> --- /dev/null 
> +++ b/modules/libsmb2/Makefile 
> @@ -0,0 +1,19 @@ 
> +src = $(shell readlink -f ../..) 
> +module-dir = $(src)/modules/libsmb2 
> + 
> +all: module 
> +module: libsmb2 
> + 
> +libsmb2: upstream/libsmb2/lib/libsmb2.so.2.0.0 
> + 
> +.PHONY: libsmb2 
> + 
> +upstream/libsmb2/.git: 
> +        mkdir -p $(module-dir)/upstream && cd $(module-dir)/upstream && \ 
> +        git clone --depth 1 https://github.com/sahlberg/libsmb2.git 
> + 
> +upstream/libsmb2/lib/libsmb2.so.2.0.0: upstream/libsmb2/.git 
> +        cd $(module-dir)/upstream/libsmb2 && cmake . && make 
> + 
> +clean: 
> +        cd $(module-dir) && rm -rf upstream 
> diff --git a/modules/libsmb2/usr.manifest b/modules/libsmb2/usr.manifest 
> new file mode 100644 
> index 00000000..64315911 
> --- /dev/null 
> +++ b/modules/libsmb2/usr.manifest 
> @@ -0,0 +1,9 @@ 
> +# 
> +# Copyright (C) 2018 Waldemar Kozaczuk 
> +# 
> +# This work is open source software, licensed under the terms of the 
> +# BSD license as described in the LICENSE file in the top-level 
> directory. 
> +# 
> + 
> +[manifest] 
> +/usr/lib/libsmb2.so.1: 
> ${MODULE_DIR}/upstream/libsmb2/lib/libsmb2.so.2.0.0 
> diff --git a/modules/smbfs/.gitignore b/modules/smbfs/.gitignore 
> new file mode 100644 
> index 00000000..35e4c593 
> --- /dev/null 
> +++ b/modules/smbfs/.gitignore 
> @@ -0,0 +1,4 @@ 
> +obj 
> +ntlm_user_file 
> +*.so 
> +usr.manifest 
> diff --git a/modules/smbfs/Makefile b/modules/smbfs/Makefile 
> new file mode 100644 
> index 00000000..b9b2bcb7 
> --- /dev/null 
> +++ b/modules/smbfs/Makefile 
> @@ -0,0 +1,49 @@ 
> +#TODO: 1. Verify if we need to set all these includes and that the 
> correct flags are set 
> +#TODO: 2. Understand why smbfs.so is almost 100K (I think it should be 
> smaller)? 
> +INCLUDES = -I. -I../libsmb2/upstream/libsmb2/include -I../../include 
> +INCLUDES += -I../../arch/$(ARCH) -I../.. 
> -I../../build/$(mode)/gen/include 
> +INCLUDES += -isystem ../../include/glibc-compat 
> +#Only for host, not external 
> +INCLUDES += $(shell $(CXX) -E -xc++ - -v </dev/null 2>&1 | awk '/^End/ 
> {exit} /^ .*c\+\+/ {print "-isystem" $$0}') 
> +# 
> +INCLUDES += -isystem ../../include/api -isystem ../../include/api/$(ARCH) 
> -isystem ../../build/$(mode)/gen/include 
> +INCLUDES += -isystem ../../bsd/sys -isystem ../../bsd/ -isystem 
> ../../bsd/$(ARCH) 
> + 
> +autodepend = -MD -MT $@ -MP 
> +CXXFLAGS  = -g -rdynamic -Wall -std=c++11 -fPIC $(INCLUDES) -D_KERNEL 
> -D_GNU_SOURCE $(autodepend) 
> + 
> +# the build target executable: 
> +TARGET = smbfs 
> +CPP_FILES := $(wildcard *.cc) 
> +OBJ_FILES := $(addprefix obj/,$(CPP_FILES:.cc=.o)) 
> +DEPS := $(OBJ_FILES:.o=.d) 
> + 
> +LIBS = -L../libsmb2/upstream/libsmb2/lib -lsmb2 
> +ifndef ARCH 
> +        ARCH = x64 
> +endif 
> +ifndef mode 
> +        mode = release 
> +endif 
> + 
> +quiet = $(if $V, $1, @echo " $2"; $1) 
> +very-quiet = $(if $V, $1, @$1) 
> + 
> +$(TARGET).so: $(OBJ_FILES) 
> +        $(call quiet, $(CXX) $(CXXFLAGS) -shared -o $(TARGET).so $^ 
> $(LIBS), LINK $@) 
> + 
> +obj/%.o: %.cc 
> +        $(call quiet, $(CXX) $(CXXFLAGS) -c -o $@ $<, CXX $@) 
> + 
> +init: 
> +        @echo "  MKDIRS" 
> +        $(call very-quiet, mkdir -p obj) 
> +.PHONY: init 
> + 
> +module: init $(TARGET).so 
> +        echo '/usr/lib/fs/smbfs.so: $${MODULE_DIR}/smbfs.so' > 
> usr.manifest 
> +        echo '/ntlm_user_file: $${MODULE_DIR}/ntlm_user_file' >> 
> usr.manifest 
> + 
> +clean: 
> +        rm -f $(TARGET)*.so usr.manifest 
> +        $(call very-quiet, $(RM) -rf obj) 
> diff --git a/modules/smbfs/module.py b/modules/smbfs/module.py 
> new file mode 100644 
> index 00000000..70d017fe 
> --- /dev/null 
> +++ b/modules/smbfs/module.py 
> @@ -0,0 +1,3 @@ 
> +from osv.modules import api 
> + 
> +api.require('libsmb2') 
> diff --git a/modules/smbfs/smbfs.cc b/modules/smbfs/smbfs.cc 
> new file mode 100644 
> index 00000000..60d38ef2 
> --- /dev/null 
> +++ b/modules/smbfs/smbfs.cc 
> @@ -0,0 +1,86 @@ 
> +/* 
> + * Copyright (C) 2018 Waldemar Kozaczuk 
> + * Based on NFS implementation by Benoit Canet 
> + * 
> + * This work is open source software, licensed under the terms of the 
> + * BSD license as described in the LICENSE file in the top-level 
> directory. 
> + */ 
> + 
> +#include <osv/debug.hh> 
> + 
> +#include "smbfs.hh" 
> + 
> +namespace smbfs { 
> + 
> +mount_context::mount_context(const char *url) 
> +        : _valid(false), 
> +          _errno(0), 
> +          _raw_url(url), 
> +          _smbfs(nullptr, smb2_destroy_context), 
> +          _url(nullptr, smb2_destroy_url) { 
> + 
> +    /* Create SMB context */ 
> +    _smbfs.reset(smb2_init_context()); 
> +    if (!_smbfs) { 
> +        debug("mount_context(): failed to create SMB2/3 context\n"); 
> +        _errno = ENOMEM; 
> +        return; 
> +    } 
> + 
> +    // parse the url while taking care of freeing it when needed 
> +    _url.reset(smb2_parse_url(_smbfs.get(), url)); 
> +    if (!_url) { 
> +        debug(std::string("mount_context(): failed to parse URL: ") + 
> +              std::string(url) + ":" + 
> +              smb2_get_error(_smbfs.get()) + "\n"); 
> +        _errno = EINVAL; 
> +        return; 
> +    } 
> + 
> +    smb2_set_security_mode(_smbfs.get(), SMB2_NEGOTIATE_SIGNING_ENABLED); 
> + 
> +    _errno = -smb2_connect_share(_smbfs.get(), _url->server, _url->share, 
> _url->user); 
> +    if (_errno) { 
> +        debug(std::string("mount_context(): failed to mount share:g: ") + 
> +              smb2_get_error(_smbfs.get()) + "\n"); 
> +        return; 
> +    } 
> + 
> +    _valid = true; 
> +} 
> + 
> +// __thread is not used for the following because it would give the error 
> +// non-local variable ‘_lock’ declared ‘__thread’ has a non-trivial 
> destructor 
> +thread_local mutex _lock; 
> +thread_local std::unordered_map <std::string, 
> +std::unique_ptr<mount_context>> _map; 
> + 
> +struct mount_context *get_mount_context(struct mount *mp, int &err_no) { 
> +    auto m_path = static_cast<const char *>(mp->m_path); 
> +    std::string mount_point(m_path); 
> +    err_no = 0; //TODO Check when err_no is set as not zero 
> + 
> +    SCOPE_LOCK(_lock); 
> + 
> +    // if not mounted at all mount it 
> +    if (!_map.count(mount_point)) { 
> +        _map[mount_point] = 
> +                std::unique_ptr<mount_context>(new 
> mount_context(mp->m_special)); 
> +    } 
> + 
> +    // if we remounted with a different url change the mount point 
> +    if (!_map[mount_point]->same_url(mp->m_special)) { 
> +        _map.erase(mount_point); 
> +        _map[mount_point] = 
> +                std::unique_ptr<mount_context>(new 
> mount_context(mp->m_special)); 
> +    } 
> + 
> +    // clear the mess if the mount point is invalid 
> +    if (!_map[mount_point]->is_valid(err_no)) { 
> +        _map.erase(mount_point); 
> +        return nullptr; 
> +    } 
> + 
> +    return _map[mount_point].get(); 
> +} 
> +} 
> diff --git a/modules/smbfs/smbfs.hh b/modules/smbfs/smbfs.hh 
> new file mode 100644 
> index 00000000..0aaef722 
> --- /dev/null 
> +++ b/modules/smbfs/smbfs.hh 
> @@ -0,0 +1,68 @@ 
> +/* 
> + * Copyright (C) 2018 Waldemar Kozaczuk 
> + * Based on NFS implementation by Benoit Canet 
> + * 
> + * This work is open source software, licensed under the terms of the 
> + * BSD license as described in the LICENSE file in the top-level 
> directory. 
> + */ 
> + 
> +#ifndef __INCLUDE_SMBFS_H__ 
> +#define __INCLUDE_SMBFS_H__ 
> + 
> +#include <string.h> 
> + 
> +#include <atomic> 
> +#include <memory> 
> +#include <string> 
> +#include <thread> 
> +#include <unordered_map> 
> + 
> +#include <osv/mutex.h> 
> +#include <osv/vnode.h> 
> +#include <osv/mount.h> 
> +#include <osv/dentry.h> 
> +#include <osv/prex.h> 
> + 
> +#include "smb2/smb2.h" 
> +#include "smb2/libsmb2.h" 
> + 
> +extern struct vfsops smbfs_vfsops; 
> +extern struct vnops smbfs_vnops; 
> + 
> +namespace smbfs { 
> +    class mount_context { 
> +    public: 
> +        mount_context(const char *url); 
> +        struct smb2_context *smbfs() { 
> +            return _smbfs.get(); 
> +        }; 
> +        // Due to my particular hatred of exceptions the is_valid() 
> method 
> +        // can be used to now if something failed in the constructor. 
> +        bool is_valid(int &err_no) { 
> +            err_no = _errno; 
> +            return _valid; 
> +        }; 
> +        std::string server() { 
> +            return std::string(_url->server); 
> +        } 
> +        std::string share() { 
> +            return std::string(_url->path); 
> +        } 
> +        bool same_url(std::string url) { 
> +            return url == _raw_url; 
> +        } 
> +    private: 
> +        bool _valid; 
> +        int _errno; 
> +        std::string _raw_url; 
> +        std::unique_ptr<struct smb2_context, 
> +                void (*)(struct smb2_context *)> _smbfs; 
> +        std::unique_ptr<struct smb2_url, void (*)(struct smb2_url *)> 
> _url; 
> +    }; 
> + 
> +    struct mount_point *get_mount_point(struct mount *mp); 
> + 
> +    struct mount_context *get_mount_context(struct mount *mp, int 
> &err_no); 
> +} 
> + 
> +#endif 
> diff --git a/modules/smbfs/smbfs_vfsops.cc b/modules/smbfs/smbfs_vfsops.cc 
> new file mode 100644 
> index 00000000..af4962a9 
> --- /dev/null 
> +++ b/modules/smbfs/smbfs_vfsops.cc 
> @@ -0,0 +1,66 @@ 
> +/* 
> + * Copyright (C) 2018 Waldemar Kozaczuk 
> + * Based on NFS implementation by Benoit Canet respectively 
> + * 
> + * This work is open source software, licensed under the terms of the 
> + * BSD license as described in the LICENSE file in the top-level 
> directory. 
> + */ 
> + 
> +#include <osv/vnode.h> 
> +#include <osv/mount.h> 
> +#include <osv/dentry.h> 
> + 
> +#include "smbfs.hh" 
> + 
> +using namespace smbfs; 
> + 
> +/* 
> + * Mount a file system. 
> + */ 
> +static int 
> +smbfs_mount(struct mount *mp, const char *dev, int flags, const void 
> *data) 
> +{ 
> +    assert(mp); 
> + 
> +    // build a temporary mount context to check the SMB2/3 server is 
> alive 
> +    int err_no; 
> +    std::unique_ptr<mount_context> ctx(new mount_context(mp->m_special)); 
> + 
> +    if (!ctx->is_valid(err_no)) { 
> +        return err_no; 
> +    } 
> + 
> +    return 0; 
> +} 
> + 
> +static int 
> +smbfs_unmount(struct mount *mp, int flags) 
> +{ 
> +    assert(mp); 
> + 
> +    // Make sure nothing is used under this mount point. 
> +    if (mp->m_count > 1) { 
> +        return EBUSY; 
> +    } 
> + 
> +    return 0; 
> +} 
> + 
> +// For the following let's rely on operations on individual files 
> +#define smbfs_sync    ((vfsop_sync_t)vfs_nullop) 
> +#define smbfs_vget    ((vfsop_vget_t)vfs_nullop) 
> +#define smbfs_statfs  ((vfsop_statfs_t)vfs_nullop) 
> + 
> +// We are relying on vfsops structure defined in kernel 
> +extern struct vfsops smbfs_vfsops; 
> + 
> +// Overwrite "null" vfsops structure fields with "real" 
> +// functions upon loading smbfs.so shared object 
> +void __attribute__((constructor)) initialize_vfsops() { 
> +    smbfs_vfsops.vfs_mount = smbfs_mount; 
> +    smbfs_vfsops.vfs_unmount = smbfs_unmount; 
> +    smbfs_vfsops.vfs_sync = smbfs_sync; 
> +    smbfs_vfsops.vfs_vget = smbfs_vget; 
> +    smbfs_vfsops.vfs_statfs = smbfs_statfs; 
> +    smbfs_vfsops.vfs_vnops = &smbfs_vnops; 
> +} 
> diff --git a/modules/smbfs/smbfs_vnops.cc b/modules/smbfs/smbfs_vnops.cc 
> new file mode 100644 
> index 00000000..992a93e7 
> --- /dev/null 
> +++ b/modules/smbfs/smbfs_vnops.cc 
> @@ -0,0 +1,416 @@ 
> +/* 
> + * Copyright (C) 2018 Waldemar Kozaczuk 
> + * Based on NFS implementation Benoit Canet 
> + * 
> + * This work is open source software, licensed under the terms of the 
> + * BSD license as described in the LICENSE file in the top-level 
> directory. 
> + */ 
> + 
> +#include <sys/stat.h> 
> +#include <dirent.h> 
> +#include <sys/param.h> 
> + 
> +#include <errno.h> 
> +#include <string.h> 
> +#include <stdlib.h> 
> +#include <fcntl.h> 
> + 
> +#include <osv/prex.h> 
> +#include <osv/vnode.h> 
> +#include <osv/file.h> 
> +#include <osv/debug.h> 
> + 
> +#include <sys/types.h> 
> +#include "smbfs.hh" 
> + 
> +static inline struct smb2_context *get_smb2_context(struct vnode *node, 
> +                                                    int &err_no) 
> +{ 
> +    return smbfs::get_mount_context(node->v_mount, err_no)->smbfs(); 
> +} 
> + 
> +static inline struct smb2fh *get_file_handle(struct vnode *node) 
> +{ 
> +    return static_cast<struct smb2fh *>(node->v_data); 
> +} 
> + 
> +static inline struct smb2dir *get_dir_handle(struct vnode *node) 
> +{ 
> +    return static_cast<struct smb2dir *>(node->v_data); 
> +} 
> + 
> +static const char *get_node_name(struct vnode *node) 
> +{ 
> +    if (LIST_EMPTY(&node->v_names) == 1) { 
> +        return nullptr; 
> +    } 
> + 
> +    auto name = LIST_FIRST(&node->v_names)->d_path; 
> +    if (name && *name == '/') 
> +        return name + 1; 
> +    else 
> +        return name; 
> +} 
> + 
> +static inline std::string mkpath(struct vnode *node, const char *name) 
> +{ 
> +    std::string path(get_node_name(node)); 
> +    if (path.length() > 0) 
> +       return path + "/" + name; 
> +    else 
> +       return name; 
> +} 
> + 
> +static inline struct timespec to_timespec(uint64_t sec, uint64_t nsec) 
> +{ 
> +    struct timespec t; 
> + 
> +    t.tv_sec = sec; 
> +    t.tv_nsec = nsec; 
> + 
> +    return t; 
> +} 
> + 
> +static int smbfs_open(struct file *fp) 
> +{ 
> +    struct vnode *vp = file_dentry(fp)->d_vnode; 
> + 
> +    // already opened reuse the nfs handle 
> +    if (vp->v_data) { 
> +        return 0; 
> +    } 
> + 
> +    // clear read write flags 
> +    int flags = file_flags(fp); 
> +    flags &= ~(O_RDONLY | O_WRONLY | O_RDWR); 
> + 
> +    // check our rights 
> +    bool read  = !vn_access(vp, VREAD); 
> +    bool write = !vn_access(vp, VWRITE); 
> + 
> +    // Set updated flags 
> +    if (read && write) { 
> +        flags |= O_RDWR; 
> +    } else if (read) { 
> +        flags |= O_RDONLY; 
> +    } else if (write) { 
> +        flags |= O_WRONLY; 
> +    } 
> + 
> +    int err_no; 
> +    auto smb2 = get_smb2_context(vp, err_no); 
> +    if (err_no) { 
> +        return err_no; 
> +    } 
> + 
> +    std::string path(fp->f_dentry->d_path); 
> +    // 
> +    // It's a directory or a file. 
> +    int type = vp->v_type; 
> +    if (type == VDIR) { 
> +        struct smb2dir *handle = smb2_opendir(smb2, path.c_str() + 1); 
> +        if (handle) { 
> +            vp->v_data = handle; 
> +        } 
> +        else { 
> +            debugf("smbfs_open [%s]: smb2_opendir failed\n", 
> path.c_str()); 
> +            return EIO; 
> +        } 
> +    } else if (type == VREG) { 
> +        struct smb2fh *handle = smb2_open(smb2, path.c_str() + 1, flags); 
> +        if (handle) { 
> +            vp->v_data = handle; 
> +        } 
> +        else { 
> +            debugf("smbfs_open [%s]: smb2_open failed\n", path.c_str()); 
> +            return EIO; 
> +        } 
> +    } else { 
> +        return EIO; 
> +    } 
> + 
> +    return 0; 
> +} 
> + 
> +//TODO: Understand why nfs_close does not do anything - was it for a 
> reason? 
> +static int smbfs_close(struct vnode *vp, struct file *fp) 
> +{ 
> +    // not opened - ignore 
> +    if (!vp->v_data) { 
> +        return 0; 
> +    } 
> + 
> +    int err_no; 
> +    auto smb2 = get_smb2_context(vp, err_no); 
> +    if (err_no) { 
> +        return err_no; 
> +    } 
> + 
> +    int type = vp->v_type; 
> +    // 
> +    // It's a directory or a file. 
> +    if (type == VDIR) { 
> +        smb2_closedir(smb2, get_dir_handle(vp)); 
> +        vp->v_data = nullptr; 
> +    } else if (type == VREG) { 
> +        smb2_close(smb2, get_file_handle(vp)); 
> +        vp->v_data = nullptr; 
> +    } else { 
> +        return EIO; 
> +    } 
> + 
> +    return 0; 
> +} 
> + 
> +// 
> +// This function reads as much data as requested per uio 
> +static int smbfs_read(struct vnode *vp, struct file *fp, struct uio *uio, 
> int ioflag) 
> +{ 
> +    if (vp->v_type == VDIR) { 
> +        return EISDIR; 
> +    } 
> +    if (vp->v_type != VREG) { 
> +        return EINVAL; 
> +    } 
> +    if (uio->uio_offset < 0) { 
> +        return EINVAL; 
> +    } 
> +    if (uio->uio_resid == 0) { 
> +        return 0; 
> +    } 
> + 
> +    if (uio->uio_offset >= (off_t)vp->v_size) { 
> +        return 0; 
> +    } 
> + 
> +    size_t len; 
> +    if (vp->v_size - uio->uio_offset < uio->uio_resid) 
> +        len = vp->v_size - uio->uio_offset; 
> +    else 
> +        len = uio->uio_resid; 
> + 
> +    int err_no; 
> +    auto smb2 = get_smb2_context(vp, err_no); 
> +    if (err_no) { 
> +        return err_no; 
> +    } 
> + 
> +    auto handle = get_file_handle(vp); 
> + 
> +    // FIXME: remove this temporary buffer 
> +    //TODO: libsmb2 claims zero-copy capability so I wonder if can 
> advantage of 
> +    //it somehow and avoid this memory copy 
> +    auto buf = std::unique_ptr<unsigned char>(new unsigned char[len + 
> 1]()); 
> +    int ret = smb2_pread(smb2, handle, buf.get(), len, uio->uio_offset); 
> +    if (ret < 0) { 
> +        return -ret; 
> +    } 
> + 
> +    return uiomove(buf.get(), ret, uio); 
> +} 
> + 
> +// 
> +// This functions reads directory information (dentries) based on 
> information in memory 
> +// under rofs->dir_entries table 
> +static int smbfs_readdir(struct vnode *vp, struct file *fp, struct dirent 
> *dir) 
> +{ 
> +    if (vp->v_type != VDIR) { 
> +        return ENOTDIR; 
> +    } 
> + 
> +    int err_no; 
> +    auto smb2 = get_smb2_context(vp, err_no); 
> +    if (err_no) { 
> +        return err_no; 
> +    } 
> + 
> +    // query the SMBFS server about this directory entry. 
> +    auto handle = get_dir_handle(vp); 
> +    auto smb2dirent = smb2_readdir(smb2, handle); 
> + 
> +    //TODO: Please note that smb2_readdir adds full stat information 
> +    //for each dirent. Would it be possible to somehow cache stat info 
> +    //so that we do not have to make network trip for each smbfs_lookup? 
> + 
> +    // We finished iterating on the directory. 
> +    if (!smb2dirent) { 
> +        return ENOENT; 
> +    } 
> + 
> +    // Fill dirent infos 
> +    assert(sizeof(ino_t) == sizeof(smb2dirent->st.smb2_ino)); 
> +    dir->d_ino = smb2dirent->st.smb2_ino; 
> +    if (smb2dirent->st.smb2_type == SMB2_TYPE_DIRECTORY ) { 
> +        dir->d_type = DT_DIR; 
> +    } 
> +    else { 
> +        dir->d_type = DT_REG; 
> +    } 
> +    // FIXME: not filling dir->d_off 
> +    // FIXME: not filling dir->d_reclen 
> + 
> +    strlcpy((char *) &dir->d_name, smb2dirent->name, 
> sizeof(dir->d_name)); 
> + 
> +    // iterate 
> +    fp->f_offset++; 
> + 
> +    return 0; 
> +} 
> + 
> +// 
> +// This functions looks up directory entry 
> +static int smbfs_lookup(struct vnode *dvp, char *name, struct vnode 
> **vpp) 
> +{ 
> +    int err_no; 
> +    auto smb2 = get_smb2_context(dvp, err_no); 
> +    if (err_no) { 
> +        return err_no; 
> +    } 
> + 
> +    std::string path = mkpath(dvp, name); 
> +    struct vnode *vp; 
> + 
> +    // Make sure we don't accidentally return garbage. 
> +    *vpp = nullptr; 
> + 
> +    // Following 4 checks inspired by ZFS code 
> +    if (!path.size()) 
> +        return ENOENT; 
> + 
> +    if (dvp->v_type != VDIR) 
> +        return ENOTDIR; 
> + 
> +    assert(path != "."); 
> +    assert(path != ".."); 
> + 
> +    // We must get the inode number so we query the NFS server. 
> +    struct smb2_stat_64 st; 
> +    int ret = smb2_stat(smb2, path.c_str(), &st); 
> +    if (ret) { 
> +        debugf("smbfs_lookup [%s]: smb2_stat failed with %d\n", name, 
> ret); 
> +        return -ret; 
> +    } 
> + 
> +    // Filter by inode type: only keep files and directories 
> +    // Symbolic links for now do not seem to be supported by smb2/3 or 
> this library 
> +    if (st.smb2_type != SMB2_TYPE_DIRECTORY && st.smb2_type != 
> SMB2_TYPE_FILE) { 
> +        debugf("smbfs_lookup [%s]: wrong type\n", name); 
> +        // FIXME: Not sure it's the right error code. 
> +        return EINVAL; 
> +    } 
> + 
> +    // Create the new vnode or get it from the cache. 
> +    if (vget(dvp->v_mount, st.smb2_ino, &vp)) { 
> +        // Present in the cache 
> +        *vpp = vp; 
> +        return 0; 
> +    } 
> + 
> +    if (!vp) { 
> +        return ENOMEM; 
> +    } 
> + 
> +    // Fill in the new vnode informations. 
> +    vp->v_size = st.smb2_size; 
> +    vp->v_mount = dvp->v_mount; 
> +    vp->v_data = nullptr; 
> + 
> +    // Get the entry type. 
> +    if (st.smb2_type == SMB2_TYPE_DIRECTORY) { 
> +        vp->v_type = VDIR; 
> +    } else if (st.smb2_type == SMB2_TYPE_FILE) { 
> +        vp->v_type = VREG; 
> +    } 
> + 
> +    //TODO: vp->v_mode -> It looks like high level API does not have 
> +    // a way to retrieve flags or any other security info 
> +    // May need to use raw API 
> + 
> +    *vpp = vp; 
> + 
> +    return 0; 
> +} 
> + 
> +static int smbfs_getattr(struct vnode *vp, struct vattr *attr) 
> +{ 
> +    int err_no; 
> +    auto smb2 = get_smb2_context(vp, err_no); 
> +    if (err_no) { 
> +        return err_no; 
> +    } 
> + 
> +    auto path = get_node_name(vp); 
> +    if (!path) { 
> +        return ENOENT; 
> +    } 
> + 
> +    // Get the file infos. 
> +    struct smb2_stat_64 st; 
> +    int ret = smb2_stat(smb2, path, &st); 
> +    if (ret) { 
> +        debugf("smbfs_getattr [%s]: smb2_stat failed with %d\n", path, 
> ret); 
> +        return -ret; 
> +    } 
> + 
> +    if (st.smb2_type == SMB2_TYPE_DIRECTORY) { 
> +        attr->va_type = VDIR; 
> +    } else if (st.smb2_type == SMB2_TYPE_FILE) { 
> +        attr->va_type = VREG; 
> +    } 
> + 
> +    //TODO: attr->va_mode -> It looks like high level API does not have a 
> way to rerieve flags or any other security info 
> +    // needs to use raw API 
> +    attr->va_mode    = 0777; 
> +    attr->va_nlink   = st.smb2_nlink; 
> +    attr->va_nodeid  = st.smb2_ino; 
> +    attr->va_atime   = to_timespec(st.smb2_atime, st.smb2_atime_nsec); 
> +    attr->va_mtime   = to_timespec(st.smb2_mtime, st.smb2_mtime_nsec); 
> +    attr->va_ctime   = to_timespec(st.smb2_ctime, st.smb2_ctime_nsec); 
> +    attr->va_size    = st.smb2_size; 
> + 
> +    return 0; 
> +} 
> + 
> +#define smbfs_write       ((vnop_write_t)vop_erofs) 
> +#define smbfs_seek        ((vnop_seek_t)vop_nullop) 
> +#define smbfs_ioctl       ((vnop_ioctl_t)vop_nullop) 
> +#define smbfs_create      ((vnop_create_t)vop_erofs) 
> +#define smbfs_remove      ((vnop_remove_t)vop_erofs) 
> +#define smbfs_rename      ((vnop_rename_t)vop_erofs) 
> +#define smbfs_mkdir       ((vnop_mkdir_t)vop_erofs) 
> +#define smbfs_rmdir       ((vnop_rmdir_t)vop_erofs) 
> +#define smbfs_setattr     ((vnop_setattr_t)vop_erofs) 
> +#define smbfs_inactive    ((vnop_inactive_t)vop_nullop) 
> +#define smbfs_truncate    ((vnop_truncate_t)vop_erofs) 
> +#define smbfs_link        ((vnop_link_t)vop_erofs) 
> +#define smbfs_arc         ((vnop_cache_t) nullptr) 
> +#define smbfs_fallocate   ((vnop_fallocate_t)vop_erofs) 
> +#define smbfs_fsync       ((vnop_fsync_t)vop_nullop) 
> +#define smbfs_symlink     ((vnop_symlink_t)vop_erofs) 
> +#define smbfs_readlink    ((vnop_readlink_t)vop_nullop) 
> + 
> +struct vnops smbfs_vnops = { 
> +    smbfs_open,       /* open */ 
> +    smbfs_close,      /* close */ 
> +    smbfs_read,       /* read */ 
> +    smbfs_write,      /* write - returns error when called */ 
> +    smbfs_seek,       /* seek */ 
> +    smbfs_ioctl,      /* ioctl */ 
> +    smbfs_fsync,      /* fsync */ 
> +    smbfs_readdir,    /* readdir */ 
> +    smbfs_lookup,     /* lookup */ 
> +    smbfs_create,     /* create - returns error when called */ 
> +    smbfs_remove,     /* remove - returns error when called */ 
> +    smbfs_rename,     /* rename - returns error when called */ 
> +    smbfs_mkdir,      /* mkdir - returns error when called */ 
> +    smbfs_rmdir,      /* rmdir - returns error when called */ 
> +    smbfs_getattr,    /* getattr */ 
> +    smbfs_setattr,    /* setattr - returns error when called */ 
> +    smbfs_inactive,   /* inactive */ 
> +    smbfs_truncate,   /* truncate - returns error when called*/ 
> +    smbfs_link,       /* link - returns error when called*/ 
> +    smbfs_arc,        /* arc */ 
> +    smbfs_fallocate,  /* fallocate - returns error when called*/ 
> +    smbfs_readlink,   /* read link */ 
> +    smbfs_symlink     /* symbolic link - returns error when called*/ 
> +}; 
> diff --git a/mount-samba.sh b/mount-samba.sh 
> new file mode 100644 
> index 00000000..181d30ba 
> --- /dev/null 
> +++ b/mount-samba.sh 
> @@ -0,0 +1 @@ 
> +curl -X PUT --data-urlencode "command=/tools/mount-fs.so smbfs 
> smb://<user>@<host_name>/<share_name> /samba" http://localhost:8000/app/ 
> diff --git a/tools/mount/mount-nfs.cc b/tools/mount/mount-fs.cc 
> similarity index 70% 
> rename from tools/mount/mount-nfs.cc 
> rename to tools/mount/mount-fs.cc 
> index 9c3da84a..6e6324c0 100644 
> --- a/tools/mount/mount-nfs.cc 
> +++ b/tools/mount/mount-fs.cc 
> @@ -9,28 +9,33 @@ 
>  int main(int argc, char **argv) 
>  { 
>      // Check number of arguments 
> -    if (argc != 3) { 
> +    if (argc != 4) { 
>          std::cout << "Usage:" << std::endl; 
>          std::cout << "\t" << argv[0] << 
> +                     " nfs" << 
>                       " 
> nfs://<server|ipv4|ipv6>/path[?arg=val[&arg=val]*]" << 
>                       " /mount_point" << std::endl; 
>          return(1); 
>      } 
>   
>      // fetch arguments 
> -    std::string url(argv[1]); 
> -    std::string mount_point(argv[2]); 
> +    std::string fs_type(argv[1]); 
> +    std::string url(argv[2]); 
> +    std::string mount_point(argv[3]); 
>   
>      // create the mount point as a convenience if it does not already 
> exists 
>      mkdir(mount_point.c_str(), 0777); 
>      // Mount and process error 
> -    int ret = mount(url.c_str(), mount_point.c_str(), "nfs", 0, nullptr); 
> +    int ret = mount(url.c_str(), mount_point.c_str(), fs_type.c_str(), 0, 
> nullptr); 
>      if (ret) { 
>          int my_errno = errno; 
>          std::cout << "Error in mount(): " << strerror(my_errno) << "(" << 
> my_errno << ")" 
>                    << std::endl; 
>          return(1); 
>      } 
> +    else { 
> +        std::cout << "Mounted " << url << " at " << mount_point << 
> std::endl; 
> +    } 
>   
>      return(0); 
>  } 
> diff --git a/usr.manifest.skel b/usr.manifest.skel 
> index 159b73d5..798a1451 100644 
> --- a/usr.manifest.skel 
> +++ b/usr.manifest.skel 
> @@ -7,7 +7,7 @@ 
>  /zfs.so: zfs.so 
>  /tools/mkfs.so: tools/mkfs/mkfs.so 
>  /tools/cpiod.so: tools/cpiod/cpiod.so 
> -/tools/mount-nfs.so: tools/mount/mount-nfs.so 
> +/tools/mount-fs.so: tools/mount/mount-fs.so 
>  /tools/umount.so: tools/mount/umount.so 
>  /usr/lib/libgcc_s.so.1: %(gccbase)s/lib64/libgcc_s.so.1 
>  /&/etc/hosts: ../../static/& 
> diff --git a/usr_rofs.manifest.skel b/usr_rofs.manifest.skel 
> index fde87736..bf71bd32 100644 
> --- a/usr_rofs.manifest.skel 
> +++ b/usr_rofs.manifest.skel 
> @@ -1,7 +1,7 @@ 
>  [manifest] 
>  /libenviron.so: libenviron.so 
>  /libvdso.so: libvdso.so 
> -/tools/mount-nfs.so: tools/mount/mount-nfs.so 
> +/tools/mount-fs.so: tools/mount/mount-fs.so 
>  /tools/umount.so: tools/mount/umount.so 
>  /usr/lib/libgcc_s.so.1: %(gccbase)s/lib64/libgcc_s.so.1 
>  /&/etc/hosts: ../../static/& 
> @@ -11,3 +11,4 @@ 
>  /dev: ../../static 
>  /proc: ../../static 
>  /tmp: ../../static 
> +/samba: ../../static 
> -- 
> 2.19.1 
>
>

-- 
You received this message because you are subscribed to the Google Groups "OSv 
Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to