From 3b6e19653c0436bcaba4a8b38a2893249b8c6687 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 12 Nov 2022 11:37:20 +0100 Subject: [PATCH 01/91] fixed typo --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef77469..8e76287 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Install APT dependencies - run: sudo apt install libfuse3-dev libwecksockets-dev + run: sudo apt install libfuse3-dev libwebsockets-dev - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} From 6c96e8c6f84b2515c036bd3ef473ecfb0052e214 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 12 Nov 2022 12:29:30 +0100 Subject: [PATCH 02/91] added unit tests --- .github/workflows/build.yml | 3 +++ CMakeLists.txt | 27 +++++++++++++++++++++++++++ src/main.cpp | 5 +++-- src/webfuse/webfuse.cpp | 27 +++++++++++++++++++++++++++ src/webfuse/webfuse.hpp | 24 ++++++++++++++++++++++++ test/webfuse/test_app.cpp | 9 +++++++++ 6 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/webfuse/webfuse.cpp create mode 100644 src/webfuse/webfuse.hpp create mode 100644 test/webfuse/test_app.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e76287..b3e3084 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,3 +25,6 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Unit Test + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target test diff --git a/CMakeLists.txt b/CMakeLists.txt index 03299b0..44433ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,32 @@ cmake_minimum_required(VERSION 3.10) project(webfuse VERSION 2.0.0) +find_package(PkgConfig REQUIRED) +pkg_check_modules(FUSE REQUIRED IMPORTED_TARGET fuse3) +pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets) + +add_library(webfuse_static STATIC + src/webfuse/webfuse.cpp) + +target_include_directories(webfuse_static PUBLIC src) +target_link_libraries(webfuse_static PUBLIC PkgConfig::FUSE PkgConfig::LWS) + add_executable(webfuse src/main.cpp) + +target_link_libraries(webfuse PRIVATE webfuse_static) + +if(NOT(WITHOUT_TEST)) + + pkg_check_modules(GTEST REQUIRED IMPORTED_TARGET gtest_main) + + add_executable(alltests + test/webfuse/test_app.cpp + ) + + target_link_libraries(alltests PRIVATE webfuse_static PkgConfig::GTEST) + + enable_testing() + add_test(NAME alltests COMMAND alltests) + +endif() diff --git a/src/main.cpp b/src/main.cpp index 4112f94..702f132 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ -#include +#include "webfuse/webfuse.hpp" int main(int argc, char* argv[]) { - return EXIT_SUCCESS; + webfuse::app app(argc, argv); + return app.run(); } \ No newline at end of file diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp new file mode 100644 index 0000000..2d69d06 --- /dev/null +++ b/src/webfuse/webfuse.cpp @@ -0,0 +1,27 @@ +#include "webfuse/webfuse.hpp" + +namespace webfuse +{ + +class app::detail +{ + int dummy; +}; + +app::app(int argc, char * argv[]) +: d(new detail) +{ + +} + +app::~app() +{ + delete d; +} + +int app::run() +{ + return 0; +} + +} \ No newline at end of file diff --git a/src/webfuse/webfuse.hpp b/src/webfuse/webfuse.hpp new file mode 100644 index 0000000..60c5989 --- /dev/null +++ b/src/webfuse/webfuse.hpp @@ -0,0 +1,24 @@ +#ifndef WEBFUSE_HPP +#define WEBFUSE_HPP + +namespace webfuse +{ + +class app +{ + app(app const &) = delete; + app& operator=(app const &) = delete; + app(app &&) = delete; + app& operator=(app &&) = delete; +public: + app(int argc, char * argv[]); + ~app(); + int run(); +private: + class detail; + detail * d; +}; + +} + +#endif \ No newline at end of file diff --git a/test/webfuse/test_app.cpp b/test/webfuse/test_app.cpp new file mode 100644 index 0000000..ff08344 --- /dev/null +++ b/test/webfuse/test_app.cpp @@ -0,0 +1,9 @@ +#include "webfuse/webfuse.hpp" +#include + +TEST(app, init) +{ + char args0[] = "webfuse"; + char * args[] = { args0, nullptr }; + webfuse::app(1, args); +} \ No newline at end of file From d63993682e9b58907cbb62570e5fb10dd4abd0ab Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 12 Nov 2022 12:32:00 +0100 Subject: [PATCH 03/91] fix: added gtest as dependency --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3e3084..7cf35e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Install APT dependencies - run: sudo apt install libfuse3-dev libwebsockets-dev + run: sudo apt install libfuse3-dev libwebsockets-dev libgtest-dev libgmock-dev - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} From 16d9ce7971d92d2199e6db33a5b8d00f993de16f Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 12 Nov 2022 13:32:13 +0100 Subject: [PATCH 04/91] added memcheck target --- .github/workflows/build.yml | 5 ++++- CMakeLists.txt | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7cf35e3..5cccfa6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Install APT dependencies - run: sudo apt install libfuse3-dev libwebsockets-dev libgtest-dev libgmock-dev + run: sudo apt install libfuse3-dev libwebsockets-dev libgtest-dev libgmock-dev valgrind - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} @@ -28,3 +28,6 @@ jobs: - name: Unit Test run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target test + + - name: Memcheck + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target memcheck diff --git a/CMakeLists.txt b/CMakeLists.txt index 44433ba..5d6503e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,15 +18,26 @@ target_link_libraries(webfuse PRIVATE webfuse_static) if(NOT(WITHOUT_TEST)) - pkg_check_modules(GTEST REQUIRED IMPORTED_TARGET gtest_main) + pkg_check_modules(GTEST REQUIRED gtest_main) + pkg_check_modules(GMOCK REQUIRED gmock) add_executable(alltests test/webfuse/test_app.cpp ) - target_link_libraries(alltests PRIVATE webfuse_static PkgConfig::GTEST) + target_include_directories(alltests PRIVATE ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) + target_compile_options(alltests PRIVATE + ${GTEST_CFLAGS} ${GTEST_CFLAGS_OTHER} + ${GMOCK_CFLAGS} ${GMOCK_CFLAGS_OTHER} + ) + target_link_libraries(alltests PRIVATE webfuse_static ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) enable_testing() add_test(NAME alltests COMMAND alltests) -endif() + find_program(VALGRIND valgrind REQUIRED) + if(VALGRIND) + add_custom_target(memcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./alltests) + endif() + +endif() \ No newline at end of file From fc4111e215e9a9aae6a32cdedd367fa4f117b40f Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 12 Nov 2022 13:34:19 +0100 Subject: [PATCH 05/91] added build status badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 19b5895..2d68498 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![build](https://github.com/falk-werner/webfuse/actions/workflows/build.yml/badge.svg)](https://github.com/falk-werner/webfuse/actions/workflows/build.yml) + # webfuse2 Reimplementation of webfuse. From 9e60323429446bbcfabe467363cf92e01ddab066 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 13 Nov 2022 14:17:24 +0100 Subject: [PATCH 06/91] ignore vscode settings --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f3d6549..8745738 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/build/ \ No newline at end of file +/build/ +/.vscode/ \ No newline at end of file From 9f300ec2d4e8f6f135968ac7a9b31593b5525ab4 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 13 Nov 2022 14:17:47 +0100 Subject: [PATCH 07/91] added filesystem interface --- CMakeLists.txt | 20 ++- src/webfuse/filesystem/accessmode.cpp | 41 ++++++ src/webfuse/filesystem/accessmode.hpp | 28 ++++ src/webfuse/filesystem/fileattributes.cpp | 46 +++++++ src/webfuse/filesystem/fileattributes.hpp | 37 ++++++ src/webfuse/filesystem/filehandle.hpp | 16 +++ src/webfuse/filesystem/filemode.cpp | 50 ++++++++ src/webfuse/filesystem/filemode.hpp | 75 +++++++++++ src/webfuse/filesystem/filesystem_i.hpp | 56 ++++++++ .../filesystem/filesystem_statistics.hpp | 31 +++++ src/webfuse/filesystem/filetime.cpp | 43 +++++++ src/webfuse/filesystem/filetime.hpp | 25 ++++ src/webfuse/filesystem/groupid.cpp | 27 ++++ src/webfuse/filesystem/groupid.hpp | 25 ++++ src/webfuse/filesystem/openflags.cpp | 69 ++++++++++ src/webfuse/filesystem/openflags.hpp | 43 +++++++ src/webfuse/filesystem/status.cpp | 120 ++++++++++++++++++ src/webfuse/filesystem/status.hpp | 62 +++++++++ src/webfuse/filesystem/userid.cpp | 28 ++++ src/webfuse/filesystem/userid.hpp | 25 ++++ src/webfuse/fuse.hpp | 26 ++++ .../webfuse/filesystem/test_accessmode.cpp | 26 ++++ .../filesystem/test_fileattributes.cpp | 101 +++++++++++++++ test-src/webfuse/filesystem/test_filemode.cpp | 25 ++++ test-src/webfuse/filesystem/test_groupid.cpp | 27 ++++ .../webfuse/filesystem/test_openflags.cpp | 24 ++++ test-src/webfuse/filesystem/test_status.cpp | 28 ++++ test-src/webfuse/filesystem/test_userid.cpp | 27 ++++ {test => test-src}/webfuse/test_app.cpp | 0 29 files changed, 1149 insertions(+), 2 deletions(-) create mode 100644 src/webfuse/filesystem/accessmode.cpp create mode 100644 src/webfuse/filesystem/accessmode.hpp create mode 100644 src/webfuse/filesystem/fileattributes.cpp create mode 100644 src/webfuse/filesystem/fileattributes.hpp create mode 100644 src/webfuse/filesystem/filehandle.hpp create mode 100644 src/webfuse/filesystem/filemode.cpp create mode 100644 src/webfuse/filesystem/filemode.hpp create mode 100644 src/webfuse/filesystem/filesystem_i.hpp create mode 100644 src/webfuse/filesystem/filesystem_statistics.hpp create mode 100644 src/webfuse/filesystem/filetime.cpp create mode 100644 src/webfuse/filesystem/filetime.hpp create mode 100644 src/webfuse/filesystem/groupid.cpp create mode 100644 src/webfuse/filesystem/groupid.hpp create mode 100644 src/webfuse/filesystem/openflags.cpp create mode 100644 src/webfuse/filesystem/openflags.hpp create mode 100644 src/webfuse/filesystem/status.cpp create mode 100644 src/webfuse/filesystem/status.hpp create mode 100644 src/webfuse/filesystem/userid.cpp create mode 100644 src/webfuse/filesystem/userid.hpp create mode 100644 src/webfuse/fuse.hpp create mode 100644 test-src/webfuse/filesystem/test_accessmode.cpp create mode 100644 test-src/webfuse/filesystem/test_fileattributes.cpp create mode 100644 test-src/webfuse/filesystem/test_filemode.cpp create mode 100644 test-src/webfuse/filesystem/test_groupid.cpp create mode 100644 test-src/webfuse/filesystem/test_openflags.cpp create mode 100644 test-src/webfuse/filesystem/test_status.cpp create mode 100644 test-src/webfuse/filesystem/test_userid.cpp rename {test => test-src}/webfuse/test_app.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d6503e..8c5bf3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,16 @@ pkg_check_modules(FUSE REQUIRED IMPORTED_TARGET fuse3) pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets) add_library(webfuse_static STATIC - src/webfuse/webfuse.cpp) + src/webfuse/webfuse.cpp + src/webfuse/filesystem/status.cpp + src/webfuse/filesystem/accessmode.cpp + src/webfuse/filesystem/openflags.cpp + src/webfuse/filesystem/userid.cpp + src/webfuse/filesystem/groupid.cpp + src/webfuse/filesystem/filemode.cpp + src/webfuse/filesystem/filetime.cpp + src/webfuse/filesystem/fileattributes.cpp +) target_include_directories(webfuse_static PUBLIC src) target_link_libraries(webfuse_static PUBLIC PkgConfig::FUSE PkgConfig::LWS) @@ -22,7 +31,14 @@ if(NOT(WITHOUT_TEST)) pkg_check_modules(GMOCK REQUIRED gmock) add_executable(alltests - test/webfuse/test_app.cpp + test-src/webfuse/test_app.cpp + test-src/webfuse/filesystem/test_status.cpp + test-src/webfuse/filesystem/test_accessmode.cpp + test-src/webfuse/filesystem/test_openflags.cpp + test-src/webfuse/filesystem/test_filemode.cpp + test-src/webfuse/filesystem/test_userid.cpp + test-src/webfuse/filesystem/test_groupid.cpp + test-src/webfuse/filesystem/test_fileattributes.cpp ) target_include_directories(alltests PRIVATE ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/filesystem/accessmode.cpp b/src/webfuse/filesystem/accessmode.cpp new file mode 100644 index 0000000..6753a5c --- /dev/null +++ b/src/webfuse/filesystem/accessmode.cpp @@ -0,0 +1,41 @@ +#include "webfuse/filesystem/accessmode.hpp" +#include + +namespace webfuse +{ + +access_mode::access_mode(int8_t value) +: value_(value) +{ + +} + +access_mode::operator int8_t() const +{ + return value_; +} + +access_mode access_mode::from_int(int value) +{ + int8_t result = 0; + + if (0 != (value & R_OK)) { result |= r_ok; } + if (0 != (value & W_OK)) { result |= w_ok; } + if (0 != (value & X_OK)) { result |= x_ok; } + + return access_mode(result); +} + +int access_mode::to_int() const +{ + int result = 0; + + if (0 != (value_ & r_ok)) { result |= R_OK; } + if (0 != (value_ & w_ok)) { result |= W_OK; } + if (0 != (value_ & x_ok)) { result |= X_OK; } + + return result; +} + + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/accessmode.hpp b/src/webfuse/filesystem/accessmode.hpp new file mode 100644 index 0000000..57b8139 --- /dev/null +++ b/src/webfuse/filesystem/accessmode.hpp @@ -0,0 +1,28 @@ +#ifndef WEBFUSE_ACCESSMODE_HPP +#define WEBFUSE_ACCESSMODE_HPP + +#include + +namespace webfuse +{ + +class access_mode +{ +public: + static constexpr int8_t const f_ok = 0; // F_OK + static constexpr int8_t const r_ok = 4; // R_OK + static constexpr int8_t const w_ok = 2; // W_OK + static constexpr int8_t const x_ok = 1; // X_OK + + access_mode(int8_t value = f_ok); + operator int8_t() const; + + static access_mode from_int(int value); + int to_int() const; +private: + int8_t value_; +}; + +} + +#endif diff --git a/src/webfuse/filesystem/fileattributes.cpp b/src/webfuse/filesystem/fileattributes.cpp new file mode 100644 index 0000000..d6fb3b7 --- /dev/null +++ b/src/webfuse/filesystem/fileattributes.cpp @@ -0,0 +1,46 @@ +#include "webfuse/filesystem/fileattributes.hpp" + +namespace webfuse +{ + +file_attributes::file_attributes() +: inode(0) +, nlink(0) +, rdev(0) +, size(0) +, blocks(0) +{ + +} + +file_attributes::file_attributes(struct stat const & other) +{ + inode = static_cast(other.st_ino); + nlink = static_cast(other.st_nlink); + mode = filemode::from_mode(other.st_mode); + uid = user_id::from_uid(other.st_uid); + gid = group_id::from_gid(other.st_gid); + rdev = static_cast(other.st_rdev); + size = static_cast(other.st_size); + blocks = static_cast(other.st_blocks); + atime = filetime::from_timespec(other.st_atim); + mtime = filetime::from_timespec(other.st_mtim); + ctime = filetime::from_timespec(other.st_ctim); +} + +void file_attributes::to_stat(struct stat & other) const +{ + other.st_ino = inode; + other.st_nlink = nlink; + other.st_mode = mode.to_mode(); + other.st_uid = uid.to_uid(); + other.st_gid = gid.to_gid(); + other.st_rdev = rdev; + other.st_size = size; + other.st_blocks = blocks; + atime.to_timespec(other.st_atim); + mtime.to_timespec(other.st_mtim); + ctime.to_timespec(other.st_ctim); +} + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/fileattributes.hpp b/src/webfuse/filesystem/fileattributes.hpp new file mode 100644 index 0000000..ba8f7fe --- /dev/null +++ b/src/webfuse/filesystem/fileattributes.hpp @@ -0,0 +1,37 @@ +#ifndef WEBFUSE_FILEATTRIBUTES_HPP +#define WEBFUSE_FILEATTRIBUTES_HPP + +#include "webfuse/filesystem/filemode.hpp" +#include "webfuse/filesystem/filetime.hpp" +#include "webfuse/filesystem/userid.hpp" +#include "webfuse/filesystem/groupid.hpp" +#include +#include + +namespace webfuse +{ + +class file_attributes +{ +public: + file_attributes(); + + explicit file_attributes(struct stat const & other); + void to_stat(struct stat & other) const; + + uint64_t inode; + uint64_t nlink; + filemode mode; + user_id uid; + group_id gid; + uint64_t rdev; + uint64_t size; + uint64_t blocks; + filetime atime; + filetime mtime; + filetime ctime; +}; + +} + +#endif diff --git a/src/webfuse/filesystem/filehandle.hpp b/src/webfuse/filesystem/filehandle.hpp new file mode 100644 index 0000000..620074e --- /dev/null +++ b/src/webfuse/filesystem/filehandle.hpp @@ -0,0 +1,16 @@ +#ifndef WEBFUSE_FILEHANDLE_HPP +#define WEBFUSE_FILEHANDLE_HPP + +#include + +namespace webfuse +{ + +using filehandle = uint64_t; + +constexpr filehandle const invalid_handle = (filehandle) -1; + + +} + +#endif diff --git a/src/webfuse/filesystem/filemode.cpp b/src/webfuse/filesystem/filemode.cpp new file mode 100644 index 0000000..6d1213f --- /dev/null +++ b/src/webfuse/filesystem/filemode.cpp @@ -0,0 +1,50 @@ +#include "webfuse/filesystem/filemode.hpp" +#include + +namespace webfuse +{ + +filemode::filemode(uint32_t value) +: value_(value) +{ + +} + +filemode::operator uint32_t() const +{ + return value_; +} + + +filemode filemode::from_mode(mode_t value) +{ + uint32_t result = value & 07777; + + if (S_ISREG(value) ) { result |= filemode::reg; } + if (S_ISDIR(value) ) { result |= filemode::dir; } + if (S_ISCHR(value) ) { result |= filemode::chr; } + if (S_ISBLK(value) ) { result |= filemode::blk; } + if (S_ISFIFO(value)) { result |= filemode::fifo; } + if (S_ISLNK(value) ) { result |= filemode::lnk; } + if (S_ISSOCK(value)) { result |= filemode::sock; } + + return filemode(result); +} + +mode_t filemode::to_mode() const +{ + mode_t result = value_ & 07777; + + if (is_reg() ) { result |= S_IFREG; } + if (is_dir() ) { result |= S_IFDIR; } + if (is_chr() ) { result |= S_IFCHR; } + if (is_blk() ) { result |= S_IFBLK; } + if (is_fifo()) { result |= S_IFIFO; } + if (is_lnk() ) { result |= S_IFLNK; } + if (is_sock()) { result |= S_IFSOCK; } + + return result; +} + + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/filemode.hpp b/src/webfuse/filesystem/filemode.hpp new file mode 100644 index 0000000..64af918 --- /dev/null +++ b/src/webfuse/filesystem/filemode.hpp @@ -0,0 +1,75 @@ +#ifndef WEBFUSE_FILEMODE_HPP +#define WEBFUSE_FILEMODE_HPP + +#include +#include + +namespace webfuse +{ + +class filemode +{ +public: + static constexpr uint32_t const protection_mask = 0000777; + static constexpr uint32_t const sticky_mask = 0007000; + static constexpr uint32_t const filetype_mask = 0170000; + + static constexpr uint32_t const suid = 04000; // S_ISUID + static constexpr uint32_t const sgid = 02000; // S_ISGID + static constexpr uint32_t const svtx = 01000; // S_ISVTX + + static constexpr uint32_t const reg = 0100000; // S_IFREG + static constexpr uint32_t const dir = 0040000; // S_IFDIR + static constexpr uint32_t const chr = 0020000; // S_IFCHR + static constexpr uint32_t const blk = 0060000; // S_IFBLK + static constexpr uint32_t const fifo = 0010000; // S_IFIFO + static constexpr uint32_t const lnk = 0120000; // S_IFLNK + static constexpr uint32_t const sock = 0140000; // S_IFSOCK + + explicit filemode(uint32_t value = 0); + operator uint32_t() const; + static filemode from_mode(mode_t value); + mode_t to_mode() const; + + inline bool is_reg() const + { + return (filemode::reg == (value_ & filemode::filetype_mask)); + } + + inline bool is_dir() const + { + return (filemode::dir == (value_ & filemode::filetype_mask)); + } + + inline bool is_chr() const + { + return (filemode::chr == (value_ & filemode::filetype_mask)); + } + + inline bool is_blk() const + { + return (filemode::blk == (value_ & filemode::filetype_mask)); + } + + inline bool is_fifo() const + { + return (filemode::fifo == (value_ & filemode::filetype_mask)); + } + + inline bool is_lnk() const + { + return (filemode::lnk == (value_ & filemode::filetype_mask)); + } + + inline bool is_sock() const + { + return (filemode::sock == (value_ & filemode::filetype_mask)); + } + +private: + uint32_t value_; +}; + +} + +#endif diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp new file mode 100644 index 0000000..03f2e58 --- /dev/null +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -0,0 +1,56 @@ +#ifndef WEBFUSE_FILESYSTEM_I_HPP +#define WEBFUSE_FILESYSTEM_I_HPP + +#include "webfuse/filesystem/filehandle.hpp" +#include "webfuse/filesystem/accessmode.hpp" +#include "webfuse/filesystem/filemode.hpp" +#include "webfuse/filesystem/fileattributes.hpp" +#include "webfuse/filesystem/openflags.hpp" +#include "webfuse/filesystem/userid.hpp" +#include "webfuse/filesystem/groupid.hpp" +#include "webfuse/filesystem/status.hpp" +#include "webfuse/filesystem/filesystem_statistics.hpp" + +#include +#include +#include + +namespace webfuse +{ + +class filesystem_i +{ +public: + virtual ~filesystem_i() = default; + + virtual status access(std::string const & path, access_mode mode) = 0; + virtual status getattr(std::string const & path, file_attributes & attr) = 0; + + virtual status readlink(std::string const & path, std::string & out) = 0; + virtual status symlink(std::string const & target, std::string const & linkpath) = 0; + virtual status link(std::string const & old_path, std::string const & new_path) = 0; + + virtual status rename(std::string const & old_path, std::string const & new_path) = 0; + virtual status chmod(std::string const & path, filemode mode) = 0; + virtual status chown(std::string const & path, user_id uid, group_id gid); + virtual status truncate(std::string const & path, uint64_t offset, filehandle handle) = 0; + virtual status fsync(std::string const & path, bool is_datasync, filehandle handle) = 0; + + virtual status open(std::string const & path, openflags flags, filehandle & handle) = 0; + virtual status mknod(std::string const & path, filemode mode, uint64_t rdev) = 0; + virtual status create(std::string const & path, filemode mode, filehandle & handle) = 0; + virtual status release(std::string const & path, filehandle handle) = 0; + virtual status unlink(std::string const & path) = 0; + + virtual status read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) = 0; + virtual status write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) = 0; + + virtual status readdir(std::string const & path, std::vector entries, filehandle handle) = 0; + virtual status rmdir(std::string const & path) = 0; + + virtual status statfs(std::string const & path, filesystem_statistics & statistics) = 0; +}; + +} + +#endif diff --git a/src/webfuse/filesystem/filesystem_statistics.hpp b/src/webfuse/filesystem/filesystem_statistics.hpp new file mode 100644 index 0000000..2534a09 --- /dev/null +++ b/src/webfuse/filesystem/filesystem_statistics.hpp @@ -0,0 +1,31 @@ +#ifndef WEBFUSE_FILESYSTEMSTATISTICS_HPP +#define WEBFUSE_FILESYSTEMSTATISTICS_HPP + +#include +#include + +namespace webfuse +{ + +class filesystem_statistics +{ +public: + filesystem_statistics(); + explicit filesystem_statistics(struct statvfs const & other); + void copy_to(struct statvfs & other) const; + + uint64_t bsize; + uint64_t frsize; + uint64_t blocks; + uint64_t bfree; + uint64_t bavail; + uint64_t files; + uint64_t ffree; + uint64_t f_namemax; +}; + + + +} + +#endif diff --git a/src/webfuse/filesystem/filetime.cpp b/src/webfuse/filesystem/filetime.cpp new file mode 100644 index 0000000..a10f14e --- /dev/null +++ b/src/webfuse/filesystem/filetime.cpp @@ -0,0 +1,43 @@ +#include "webfuse/filesystem/filetime.hpp" + +namespace webfuse +{ + +filetime::filetime() +: seconds(0) +, nsec(0) +{ + +} + +filetime filetime::from_timespec(timespec const & other) +{ + filetime result; + result.seconds = static_cast(other.tv_sec); + result.nsec = static_cast(other.tv_nsec); + + return result; +} + +filetime filetime::from_time(time_t const & other) +{ + filetime result; + result.seconds = static_cast(other); + result.nsec = 0; + + return result; +} + +void filetime::to_timespec(timespec & other) const +{ + other.tv_sec = seconds; + other.tv_nsec = static_cast(nsec); +} + +time_t filetime::to_time() const +{ + return static_cast(seconds); +} + + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/filetime.hpp b/src/webfuse/filesystem/filetime.hpp new file mode 100644 index 0000000..8f77ff8 --- /dev/null +++ b/src/webfuse/filesystem/filetime.hpp @@ -0,0 +1,25 @@ +#ifndef WEBFUSE_FILETIME_HPP +#define WEBFUSE_FILETIME_HPP + +#include +#include + +namespace webfuse +{ + +class filetime +{ +public: + filetime(); + static filetime from_timespec(timespec const & other); + static filetime from_time(time_t const & other); + void to_timespec(timespec & other) const; + time_t to_time() const; + + uint64_t seconds; + uint32_t nsec; +}; + +} + +#endif diff --git a/src/webfuse/filesystem/groupid.cpp b/src/webfuse/filesystem/groupid.cpp new file mode 100644 index 0000000..84583e5 --- /dev/null +++ b/src/webfuse/filesystem/groupid.cpp @@ -0,0 +1,27 @@ +#include "webfuse/filesystem/groupid.hpp" + +namespace webfuse +{ + +group_id::group_id(uint32_t value) +: value_(value) +{ + +} + +group_id::operator uint32_t() const +{ + return value_; +} + +group_id group_id::from_gid(gid_t value) +{ + return group_id(static_cast(value)); +} + +gid_t group_id::to_gid() const +{ + return static_cast(value_); +} + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/groupid.hpp b/src/webfuse/filesystem/groupid.hpp new file mode 100644 index 0000000..e3b8ac0 --- /dev/null +++ b/src/webfuse/filesystem/groupid.hpp @@ -0,0 +1,25 @@ +#ifndef WEBFUSE_GROUPID_HPP +#define WEBFUSE_GROUPID_HPP + +#include +#include + +namespace webfuse +{ + +class group_id +{ +public: + static constexpr uint32_t const invalid = (uint32_t) -1; + + explicit group_id(uint32_t value = invalid); + operator uint32_t() const; + static group_id from_gid(gid_t value); + gid_t to_gid() const; +private: + uint32_t value_; +}; + +} + +#endif diff --git a/src/webfuse/filesystem/openflags.cpp b/src/webfuse/filesystem/openflags.cpp new file mode 100644 index 0000000..7a0502c --- /dev/null +++ b/src/webfuse/filesystem/openflags.cpp @@ -0,0 +1,69 @@ +#include "webfuse/filesystem/openflags.hpp" +#include + +namespace webfuse +{ + +openflags::openflags(int32_t value) +: value_(value) +{ + +} + +openflags::operator int32_t() const +{ + return value_; +} + +openflags openflags::from_int(int value) +{ + int32_t result = 0; + + if (O_RDONLY == (value & O_RDONLY )) { result |= openflags::rdonly; } + if (O_WRONLY == (value & O_WRONLY )) { result |= openflags::wronly; } + if (O_RDWR == (value & O_RDWR )) { result |= openflags::rdwr; } + if (O_CLOEXEC == (value & O_CLOEXEC )) { result |= openflags::cloexec; } + if (O_CREAT == (value & O_CREAT )) { result |= openflags::creat; } + if (O_DIRECT == (value & O_DIRECT )) { result |= openflags::direct; } + if (O_DIRECTORY == (value & O_DIRECTORY)) { result |= openflags::directory; } + if (O_EXCL == (value & O_EXCL )) { result |= openflags::excl; } + if (O_NOCTTY == (value & O_NOCTTY )) { result |= openflags::noctty; } + if (O_NOFOLLOW == (value & O_NOFOLLOW )) { result |= openflags::nofollow; } + if (O_TRUNC == (value & O_TRUNC )) { result |= openflags::trunc; } + if (O_ASYNC == (value & O_ASYNC )) { result |= openflags::async; } + if (O_LARGEFILE == (value & O_LARGEFILE)) { result |= openflags::largefile; } + if (O_NOATIME == (value & O_NOATIME )) { result |= openflags::noatime; } + if (O_NONBLOCK == (value & O_NONBLOCK )) { result |= openflags::nonblock; } + if (O_NDELAY == (value & O_NDELAY )) { result |= openflags::ndelay; } + if (O_SYNC == (value & O_SYNC )) { result |= openflags::sync; } + + return openflags(result); +} + +int openflags::to_int() const +{ + int result = 0; + + if (openflags::rdonly == (value_ & openflags::rdonly )) { result |= O_RDONLY; } + if (openflags::wronly == (value_ & openflags::wronly )) { result |= O_WRONLY; } + if (openflags::rdwr == (value_ & openflags::rdwr )) { result |= O_RDWR; } + if (openflags::cloexec == (value_ & openflags::cloexec )) { result |= O_CLOEXEC; } + if (openflags::creat == (value_ & openflags::creat )) { result |= O_CREAT; } + if (openflags::direct == (value_ & openflags::direct )) { result |= O_DIRECT; } + if (openflags::directory == (value_ & openflags::directory)) { result |= O_DIRECTORY; } + if (openflags::excl == (value_ & openflags::excl )) { result |= O_EXCL; } + if (openflags::noctty == (value_ & openflags::noctty )) { result |= O_NOCTTY; } + if (openflags::nofollow == (value_ & openflags::nofollow )) { result |= O_NOFOLLOW; } + if (openflags::trunc == (value_ & openflags::trunc )) { result |= O_TRUNC; } + if (openflags::async == (value_ & openflags::async )) { result |= O_ASYNC; } + if (openflags::largefile == (value_ & openflags::largefile)) { result |= O_LARGEFILE; } + if (openflags::noatime == (value_ & openflags::noatime )) { result |= O_NOATIME; } + if (openflags::nonblock == (value_ & openflags::nonblock )) { result |= O_NONBLOCK; } + if (openflags::ndelay == (value_ & openflags::ndelay )) { result |= O_NDELAY; } + if (openflags::sync == (value_ & openflags::sync )) { result |= O_SYNC; } + + return result; +} + + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/openflags.hpp b/src/webfuse/filesystem/openflags.hpp new file mode 100644 index 0000000..44541f1 --- /dev/null +++ b/src/webfuse/filesystem/openflags.hpp @@ -0,0 +1,43 @@ +#ifndef WEBFUSE_OPENFLAGS_HPP +#define WEBFUSE_OPENFLAGS_HPP + +#include + +namespace webfuse +{ + +class openflags +{ +public: + static constexpr int32_t const accessmode_mask = 3; // O_ACCMODE + + static constexpr int32_t const rdonly = 00000000; // O_RDONLY + static constexpr int32_t const wronly = 00000001; // O_WRONLY + static constexpr int32_t const rdwr = 00000002; // O_RDWR + static constexpr int32_t const cloexec = 02000000; // O_CLOEXEC + static constexpr int32_t const creat = 00000100; // O_CREAT + static constexpr int32_t const direct = 00040000; // O_DIRECT + static constexpr int32_t const directory = 00200000; // O_DIRECTORY + static constexpr int32_t const excl = 00000200; // O_EXCL + static constexpr int32_t const noctty = 00000400; // O_NOCTTY + static constexpr int32_t const nofollow = 00400000; // O_NOFOLLOW + static constexpr int32_t const trunc = 00001000; // O_TRUNC + static constexpr int32_t const async = 00002000; // O_ASYNC + static constexpr int32_t const largefile = 00000000; // O_LARGEFILE + static constexpr int32_t const noatime = 01000000; // O_NOATIME + static constexpr int32_t const nonblock = 00004000; // O_NONBLOCK + static constexpr int32_t const ndelay = 00004000; // O_NDELAY + static constexpr int32_t const sync = 04010000; // O_SYNC + + explicit openflags(int32_t value = 0); + operator int32_t() const; + + static openflags from_int(int value); + int to_int() const; +private: + int32_t value_; +}; + +} + +#endif diff --git a/src/webfuse/filesystem/status.cpp b/src/webfuse/filesystem/status.cpp new file mode 100644 index 0000000..cda71cc --- /dev/null +++ b/src/webfuse/filesystem/status.cpp @@ -0,0 +1,120 @@ +#include "webfuse/filesystem/status.hpp" +#include + +namespace webfuse +{ + +status::status(int32_t value) +: value_(value) +{ + +} + +status::operator int32_t() const +{ + return value_; +} + +status status::from_fusestatus(int value) +{ + if (value >= 0) + { + return static_cast(value); + } + else + { + switch(value) + { + case -E2BIG: return status::bad_e2big; + case -EACCES: return status::bad_eacces; + case -EAGAIN: return status::bad_eagain; + case -EBADF: return status::bad_ebadf; + case -EBUSY: return status::bad_ebusy; + case -EDESTADDRREQ: return status::bad_edestaddrreq; + case -EDQUOT: return status::bad_edquot; + case -EEXIST: return status::bad_eexist; + case -EFAULT: return status::bad_efault; + case -EFBIG: return status::bad_efbig; + case -EINTR: return status::bad_eintr; + case -EINVAL: return status::bad_einval; + case -EIO: return status::bad_eio; + case -EISDIR: return status::bad_eisdir; + case -ELOOP: return status::bad_eloop; + case -EMFILE: return status::bad_emfile; + case -EMLINK: return status::bad_emlink; + case -ENAMETOOLONG: return status::bad_enametoolong; + case -ENFILE: return status::bad_enfile; + case -ENODATA: return status::bad_enodata; + case -ENODEV: return status::bad_enodev; + case -ENOENT: return status::bad_enoent; + case -ENOMEM: return status::bad_enomem; + case -ENOSPC: return status::bad_enospc; + case -ENOSYS: return status::bad_enosys; + case -ENOTDIR: return status::bad_enotdir; + case -ENOTEMPTY: return status::bad_enotempty; + case -ENOTSUP: return status::bad_enotsup; + case -ENXIO: return status::bad_enxio; + case -EOVERFLOW: return status::bad_eoverflow; + case -EPERM: return status ::bad_eperm; + case -EPIPE: return status::bad_epipe; + case -ERANGE: return status::bad_erange; + case -EROFS: return status::bad_erofs; + case -ETXTBSY: return status::bad_etxtbsy; + case -EXDEV: return status::bad_exdev; + default: return static_cast(value); + } + } +} + +int status::to_fusestatus() const +{ + if (value_ >= 0) + { + return static_cast(value_); + } + else + { + switch(value_) + { + case status::bad_e2big: return -E2BIG; + case status::bad_eacces: return -EACCES; + case status::bad_eagain: return -EAGAIN; + case status::bad_ebadf: return -EBADF; + case status::bad_ebusy: return -EBUSY; + case status::bad_edestaddrreq: return -EDESTADDRREQ; + case status::bad_edquot: return -EDQUOT; + case status::bad_eexist: return -EEXIST; + case status::bad_efault: return -EFAULT; + case status::bad_efbig: return -EFBIG; + case status::bad_eintr: return -EINTR; + case status::bad_einval: return -EINVAL; + case status::bad_eio: return -EIO; + case status::bad_eisdir: return -EISDIR; + case status::bad_eloop: return -ELOOP; + case status::bad_emfile: return -EMFILE; + case status::bad_emlink: return -EMLINK; + case status::bad_enametoolong: return -ENAMETOOLONG; + case status::bad_enfile: return -ENFILE; + case status::bad_enodata: return -ENODATA; + case status::bad_enodev: return -ENODEV; + case status::bad_enoent: return -ENOENT; + case status::bad_enomem: return -ENOMEM; + case status::bad_enospc: return -ENOSPC; + case status::bad_enosys: return -ENOSYS; + case status::bad_enotdir: return -ENOTDIR; + case status::bad_enotempty: return -ENOTEMPTY; + case status::bad_enotsup: return -ENOTSUP; + case status::bad_enxio: return -ENXIO; + case status::bad_eoverflow: return -EOVERFLOW; + case status::bad_eperm: return -EPERM; + case status::bad_epipe: return -EPIPE; + case status::bad_erange: return -ERANGE; + case status::bad_erofs: return -EROFS; + case status::bad_etxtbsy: return -ETXTBSY; + case status::bad_exdev: return -EXDEV; + default: return static_cast(value_); + } + } +} + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/status.hpp b/src/webfuse/filesystem/status.hpp new file mode 100644 index 0000000..ee322a1 --- /dev/null +++ b/src/webfuse/filesystem/status.hpp @@ -0,0 +1,62 @@ +#ifndef WEBFUSE_STATUS_HPP +#define WEBFUSE_STATUS_HPP + +#include + +namespace webfuse +{ + +class status +{ +public: + static constexpr int32_t const good = 0; + static constexpr int32_t const bad_e2big = -7; // -E2BIG + static constexpr int32_t const bad_eacces = -13; // -EACCES + static constexpr int32_t const bad_eagain = -11; // -EAGAIN + static constexpr int32_t const bad_ebadf = -9; // -EBADF + static constexpr int32_t const bad_ebusy = -16; // -EBUSY + static constexpr int32_t const bad_edestaddrreq = -89; // -EDESTADDRREQ + static constexpr int32_t const bad_edquot = -122; // -EDQUOT + static constexpr int32_t const bad_eexist = -17; // -EEXIST + static constexpr int32_t const bad_efault = -14; // -EFAULT + static constexpr int32_t const bad_efbig = -27; // -EFBIG + static constexpr int32_t const bad_eintr = -4; // -EINTR + static constexpr int32_t const bad_einval = -22; // -EINVAL + static constexpr int32_t const bad_eio = -5; // -EIO + static constexpr int32_t const bad_eisdir = -21; // -EISDIR + static constexpr int32_t const bad_eloop = -40; // -ELOOP + static constexpr int32_t const bad_emfile = -24; // -EMFILE + static constexpr int32_t const bad_emlink = -31; // -EMLINK + static constexpr int32_t const bad_enametoolong = -36; // -ENAMETOOLONG + static constexpr int32_t const bad_enfile = -23; // -ENFILE + static constexpr int32_t const bad_enodata = -61; // -ENODATA + static constexpr int32_t const bad_enodev = -19; // -ENODEV + static constexpr int32_t const bad_enoent = -2; // -ENOENT + static constexpr int32_t const bad_enomem = -12; // -ENOMEM + static constexpr int32_t const bad_enospc = -28; // -ENOSPC + static constexpr int32_t const bad_enosys = -38; // -ENOSYS + static constexpr int32_t const bad_enotdir = -20; // -ENOTDIR + static constexpr int32_t const bad_enotempty = -39; // -ENOTEMPTY + static constexpr int32_t const bad_enotsup = -95; // -ENOTSUP + static constexpr int32_t const bad_enxio = -6; // -ENXIO + static constexpr int32_t const bad_eoverflow = -75; // -EOVERFLOW + static constexpr int32_t const bad_eperm = -1; // -EPERM + static constexpr int32_t const bad_epipe = -32; // -EPIPE + static constexpr int32_t const bad_erange = -34; // -ERANGE + static constexpr int32_t const bad_erofs = -30; // -EROFS + static constexpr int32_t const bad_etxtbsy = -26; // -ETXTBSY + static constexpr int32_t const bad_exdev = -18; // -EXDEV + static constexpr int32_t const bad_ewouldblock = -11; // -EWOULDBLOCK + + status(int32_t value = status::good); + operator int32_t() const; + + static status from_fusestatus(int value); + int to_fusestatus() const; +private: + int32_t value_; +}; + +} + +#endif \ No newline at end of file diff --git a/src/webfuse/filesystem/userid.cpp b/src/webfuse/filesystem/userid.cpp new file mode 100644 index 0000000..36f37f0 --- /dev/null +++ b/src/webfuse/filesystem/userid.cpp @@ -0,0 +1,28 @@ +#include "webfuse/filesystem/userid.hpp" + +namespace webfuse +{ + +user_id::user_id(uint32_t value) +: value_(value) +{ + +} + +user_id::operator uint32_t() const +{ + return value_; +} + +user_id user_id::from_uid(uid_t value) +{ + return user_id(static_cast(value)); +} + +uid_t user_id::to_uid() const +{ + return static_cast(value_); +} + + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/userid.hpp b/src/webfuse/filesystem/userid.hpp new file mode 100644 index 0000000..dc43673 --- /dev/null +++ b/src/webfuse/filesystem/userid.hpp @@ -0,0 +1,25 @@ +#ifndef WEBFUSE_USERID_HPP +#define WEBFUSE_USERID_HPP + +#include +#include + +namespace webfuse +{ + +class user_id +{ +public: + static constexpr uint32_t const invalid = (uint32_t) -1; + + explicit user_id(uint32_t value = invalid); + operator uint32_t() const; + static user_id from_uid(uid_t value); + uid_t to_uid() const; +private: + uint32_t value_; +}; + +} + +#endif diff --git a/src/webfuse/fuse.hpp b/src/webfuse/fuse.hpp new file mode 100644 index 0000000..39acb9e --- /dev/null +++ b/src/webfuse/fuse.hpp @@ -0,0 +1,26 @@ +#ifndef WEBFUSE_FUSE_HPP +#define WEBFUSE_FUSE_HPP + +#include "webfuse/filesystem/filesystem_i.hpp" + +namespace webfuse +{ + +class fuse +{ + fuse (fuse const &) = delete; + fuse& operator=(fuse const &) = delete; +public: + explicit fuse(filesystem_i & filesystem); + ~fuse(); + fuse (fuse &&) = delete; + fuse& operator=(fuse &&) = delete; + void run(int argc, char * argv[]); +private: + class detail; + detail * d; +}; + +} + +#endif diff --git a/test-src/webfuse/filesystem/test_accessmode.cpp b/test-src/webfuse/filesystem/test_accessmode.cpp new file mode 100644 index 0000000..1f265cc --- /dev/null +++ b/test-src/webfuse/filesystem/test_accessmode.cpp @@ -0,0 +1,26 @@ +#include "webfuse/filesystem/accessmode.hpp" +#include + +using webfuse::access_mode; + +TEST(accessmode, f_ok) +{ + ASSERT_EQ(0, access_mode::f_ok); + ASSERT_EQ(F_OK, access_mode::f_ok); +} + +class accessmode_test: public testing::TestWithParam { }; + +TEST_P(accessmode_test, conversion) +{ + int const expected = GetParam(); + auto mode = access_mode::from_int(expected); + ASSERT_EQ(expected, mode.to_int()); +} + +INSTANTIATE_TEST_CASE_P(accesmode_values, accessmode_test, + testing::Values( + F_OK, R_OK, W_OK, X_OK, + R_OK | W_OK, R_OK | X_OK, W_OK | X_OK, + R_OK | W_OK | X_OK) +); diff --git a/test-src/webfuse/filesystem/test_fileattributes.cpp b/test-src/webfuse/filesystem/test_fileattributes.cpp new file mode 100644 index 0000000..f0ecd2e --- /dev/null +++ b/test-src/webfuse/filesystem/test_fileattributes.cpp @@ -0,0 +1,101 @@ +#include "webfuse/filesystem/fileattributes.hpp" + +#include + +using webfuse::file_attributes; +using webfuse::user_id; +using webfuse::group_id; +using webfuse::filemode; + +TEST(file_attibutes, create_empty) +{ + file_attributes attributes; + + ASSERT_EQ(0, attributes.inode); + ASSERT_EQ(0, attributes.nlink); + ASSERT_EQ(0, attributes.mode); + ASSERT_EQ(user_id::invalid, attributes.uid); + ASSERT_EQ(group_id::invalid, attributes.gid); + ASSERT_EQ(0, attributes.rdev); + ASSERT_EQ(0, attributes.size); + ASSERT_EQ(0, attributes.blocks); + ASSERT_EQ(0, attributes.atime.seconds); + ASSERT_EQ(0, attributes.atime.nsec); + ASSERT_EQ(0, attributes.mtime.seconds); + ASSERT_EQ(0, attributes.mtime.nsec); + ASSERT_EQ(0, attributes.ctime.seconds); + ASSERT_EQ(0, attributes.ctime.nsec); +} + +TEST(file_attibutes, from_stat) +{ + struct stat info; + info.st_ino = 1; + info.st_nlink = 2; + info.st_mode = S_IFREG | 0644; + info.st_uid = 1000; + info.st_gid = 1234; + info.st_rdev = 0; + info.st_size = 21 * 1024; + info.st_blocks = 42; + info.st_atim.tv_sec = 1; + info.st_atim.tv_nsec = 2; + info.st_mtim.tv_sec = 3; + info.st_mtim.tv_nsec = 4; + info.st_ctim.tv_sec = 5; + info.st_ctim.tv_nsec = 6; + + file_attributes attributes(info); + + ASSERT_EQ(info.st_ino, attributes.inode); + ASSERT_EQ(info.st_nlink, attributes.nlink); + ASSERT_EQ(info.st_mode, attributes.mode.to_mode()); + ASSERT_EQ(info.st_uid, attributes.uid.to_uid()); + ASSERT_EQ(info.st_gid, attributes.gid.to_gid()); + ASSERT_EQ(info.st_rdev, attributes.rdev); + ASSERT_EQ(info.st_size, attributes.size); + ASSERT_EQ(info.st_blocks, attributes.blocks); + ASSERT_EQ(info.st_atim.tv_sec, attributes.atime.seconds); + ASSERT_EQ(info.st_atim.tv_nsec, attributes.atime.nsec); + ASSERT_EQ(info.st_mtim.tv_sec, attributes.mtime.seconds); + ASSERT_EQ(info.st_mtim.tv_nsec, attributes.mtime.nsec); + ASSERT_EQ(info.st_ctim.tv_sec, attributes.ctime.seconds); + ASSERT_EQ(info.st_ctim.tv_nsec, attributes.ctime.nsec); +} + +TEST(file_attibutes, to_stat) +{ + file_attributes attributes; + attributes.inode = 1; + attributes.nlink = 2; + attributes.mode = filemode(S_IFREG | 0644); + attributes.uid = user_id(1000); + attributes.gid = group_id(1234); + attributes.rdev = 0; + attributes.size = 21 * 1024; + attributes.blocks = 42; + attributes.atime.seconds = 1; + attributes.atime.nsec = 2; + attributes.mtime.seconds = 3; + attributes.mtime.nsec = 4; + attributes.ctime.seconds = 5; + attributes.ctime.nsec = 6; + + struct stat info; + attributes.to_stat(info); + + ASSERT_EQ(attributes.inode, info.st_ino); + ASSERT_EQ(attributes.nlink, info.st_nlink); + ASSERT_EQ(attributes.mode.to_mode(), info.st_mode); + ASSERT_EQ(attributes.uid.to_uid(), info.st_uid); + ASSERT_EQ(attributes.gid.to_gid(), info.st_gid); + ASSERT_EQ(attributes.rdev, info.st_rdev); + ASSERT_EQ(attributes.size, info.st_size); + ASSERT_EQ(attributes.blocks, info.st_blocks); + ASSERT_EQ(attributes.atime.seconds, info.st_atim.tv_sec); + ASSERT_EQ(attributes.atime.nsec, info.st_atim.tv_nsec); + ASSERT_EQ(attributes.mtime.seconds, info.st_mtim.tv_sec); + ASSERT_EQ(attributes.mtime.nsec, info.st_mtim.tv_nsec); + ASSERT_EQ(attributes.ctime.seconds, info.st_ctim.tv_sec); + ASSERT_EQ(attributes.ctime.nsec, info.st_ctim.tv_nsec); +} \ No newline at end of file diff --git a/test-src/webfuse/filesystem/test_filemode.cpp b/test-src/webfuse/filesystem/test_filemode.cpp new file mode 100644 index 0000000..7c72af8 --- /dev/null +++ b/test-src/webfuse/filesystem/test_filemode.cpp @@ -0,0 +1,25 @@ +#include "webfuse/filesystem/filemode.hpp" +#include + +using webfuse::filemode; + +class filemode_test: public testing::TestWithParam { }; + +TEST_P(filemode_test, conversion) +{ + mode_t const expected = GetParam(); + auto value = filemode::from_mode(expected); + ASSERT_EQ(expected, value.to_mode()); +} + +INSTANTIATE_TEST_CASE_P(filemode_value, filemode_test, + testing::Values( + S_IROTH, S_IWOTH, S_IXOTH, + S_IRGRP, S_IWGRP, S_IXGRP, + S_IRUSR, S_IWUSR, S_IXUSR, + S_ISUID, S_ISGID, S_ISVTX, + S_IFREG, S_IFCHR, S_IFBLK, S_IFDIR, S_IFIFO, S_IFLNK, S_IFSOCK, + S_IFREG | 0644, + S_IFDIR | 0755 + ) +); \ No newline at end of file diff --git a/test-src/webfuse/filesystem/test_groupid.cpp b/test-src/webfuse/filesystem/test_groupid.cpp new file mode 100644 index 0000000..0b091f5 --- /dev/null +++ b/test-src/webfuse/filesystem/test_groupid.cpp @@ -0,0 +1,27 @@ +#include "webfuse/filesystem/groupid.hpp" +#include + +using webfuse::group_id; + +TEST(group_id, invalid) +{ + group_id invalid_group; + + ASSERT_EQ(group_id::invalid, invalid_group); +} + +TEST(group_id, to_gid) +{ + group_id group(69); + gid_t id = group.to_gid(); + + ASSERT_EQ(69, id); +} + +TEST(group_id, from_gid) +{ + gid_t id = 99; + auto group = group_id::from_gid(id); + + ASSERT_EQ(99, group); +} diff --git a/test-src/webfuse/filesystem/test_openflags.cpp b/test-src/webfuse/filesystem/test_openflags.cpp new file mode 100644 index 0000000..f36d9e1 --- /dev/null +++ b/test-src/webfuse/filesystem/test_openflags.cpp @@ -0,0 +1,24 @@ +#include "webfuse/filesystem/openflags.hpp" +#include +#include + +using webfuse::openflags; + +class openflags_test: public testing::TestWithParam { }; + +TEST_P(openflags_test, conversion) +{ + int const expected = GetParam(); + auto flags = openflags::from_int(expected); + ASSERT_EQ(expected, flags.to_int()); +} + +INSTANTIATE_TEST_CASE_P(openflags_values, openflags_test, + testing::Values<>( + O_RDONLY, O_WRONLY, O_RDWR, O_CLOEXEC, O_CREAT, + O_DIRECT, O_DIRECTORY, O_EXCL, O_NOCTTY, O_NOFOLLOW, + O_TRUNC, O_ASYNC, O_LARGEFILE, O_NOATIME, O_NONBLOCK, + O_NDELAY, O_SYNC, + O_WRONLY | O_CREAT | O_TRUNC + ) +); \ No newline at end of file diff --git a/test-src/webfuse/filesystem/test_status.cpp b/test-src/webfuse/filesystem/test_status.cpp new file mode 100644 index 0000000..52dcc7b --- /dev/null +++ b/test-src/webfuse/filesystem/test_status.cpp @@ -0,0 +1,28 @@ +#include "webfuse/filesystem/status.hpp" +#include +#include + +using webfuse::status; + +class status_test: public testing::TestWithParam { }; + +TEST_P(status_test, conversion) +{ + int const expected = GetParam(); + auto status = status::from_fusestatus(expected); + ASSERT_EQ(expected, status.to_fusestatus()); +} + +INSTANTIATE_TEST_CASE_P(status_values, status_test, + testing::Values( + 0, 1, 2, 3, 42, + -E2BIG, -EACCES, -EBADF, -EBUSY, -EDESTADDRREQ, + -EDQUOT, -EEXIST, -EFAULT, -EFBIG, -EINTR, + -EINVAL, -EIO, -EISDIR, -ELOOP, -EMFILE, + -ENAMETOOLONG, -ENFILE, -ENODATA, -ENODEV, + -ENOENT, -ENOMEM, -ENOSPC, -ENOSYS, -ENOTDIR, + -ENOTEMPTY, -ENOTSUP, -ENXIO, -EOVERFLOW, -EPERM, + -EPIPE, -ERANGE, -EROFS, -EXDEV, -EWOULDBLOCK, + -EAGAIN, -12345 + ) +); diff --git a/test-src/webfuse/filesystem/test_userid.cpp b/test-src/webfuse/filesystem/test_userid.cpp new file mode 100644 index 0000000..cb555d4 --- /dev/null +++ b/test-src/webfuse/filesystem/test_userid.cpp @@ -0,0 +1,27 @@ +#include "webfuse/filesystem/userid.hpp" +#include + +using webfuse::user_id; + +TEST(user_id, invalid) +{ + user_id invalid_user; + + ASSERT_EQ(user_id::invalid, invalid_user); +} + +TEST(user_id, to_uid) +{ + user_id user(42); + uid_t id = user.to_uid(); + + ASSERT_EQ(42, id); +} + +TEST(user_id, from_uid) +{ + uid_t id = 23; + auto user = user_id::from_uid(id); + + ASSERT_EQ(23, user); +} diff --git a/test/webfuse/test_app.cpp b/test-src/webfuse/test_app.cpp similarity index 100% rename from test/webfuse/test_app.cpp rename to test-src/webfuse/test_app.cpp From aa3cbd28f4110fa0fea45f3015094a0e15a315f7 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 13 Nov 2022 14:22:11 +0100 Subject: [PATCH 08/91] set c++ standard --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c5bf3b..123d8ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.10) project(webfuse VERSION 2.0.0) +set (CMAKE_CXX_STANDARD 17) + find_package(PkgConfig REQUIRED) pkg_check_modules(FUSE REQUIRED IMPORTED_TARGET fuse3) pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets) From eddec21bbd83495a02185bb8a45fbd38b9ec8b99 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 13 Nov 2022 18:18:44 +0100 Subject: [PATCH 09/91] provide empty filesystem --- CMakeLists.txt | 3 + src/main.cpp | 4 +- src/webfuse/filesystem/empty_filesystem.cpp | 135 ++++++++ src/webfuse/filesystem/empty_filesystem.hpp | 45 +++ src/webfuse/filesystem/filesystem_i.hpp | 5 +- .../filesystem/filesystem_statistics.cpp | 43 +++ src/webfuse/filesystem/status.cpp | 6 + src/webfuse/filesystem/status.hpp | 2 + src/webfuse/fuse.cpp | 319 ++++++++++++++++++ src/webfuse/fuse.hpp | 6 +- src/webfuse/webfuse.cpp | 23 +- src/webfuse/webfuse.hpp | 11 +- test-src/webfuse/test_app.cpp | 4 +- 13 files changed, 569 insertions(+), 37 deletions(-) create mode 100644 src/webfuse/filesystem/empty_filesystem.cpp create mode 100644 src/webfuse/filesystem/empty_filesystem.hpp create mode 100644 src/webfuse/filesystem/filesystem_statistics.cpp create mode 100644 src/webfuse/fuse.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 123d8ec..f54bb8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets) add_library(webfuse_static STATIC src/webfuse/webfuse.cpp + src/webfuse/fuse.cpp src/webfuse/filesystem/status.cpp src/webfuse/filesystem/accessmode.cpp src/webfuse/filesystem/openflags.cpp @@ -17,6 +18,8 @@ add_library(webfuse_static STATIC src/webfuse/filesystem/filemode.cpp src/webfuse/filesystem/filetime.cpp src/webfuse/filesystem/fileattributes.cpp + src/webfuse/filesystem/filesystem_statistics.cpp + src/webfuse/filesystem/empty_filesystem.cpp ) target_include_directories(webfuse_static PUBLIC src) diff --git a/src/main.cpp b/src/main.cpp index 702f132..f582bd2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,6 @@ int main(int argc, char* argv[]) { - webfuse::app app(argc, argv); - return app.run(); + webfuse::app app; + return app.run(argc, argv); } \ No newline at end of file diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp new file mode 100644 index 0000000..9993cda --- /dev/null +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -0,0 +1,135 @@ +#include "webfuse/filesystem/empty_filesystem.hpp" + +namespace webfuse +{ + +status empty_filesystem::access(std::string const & path, access_mode mode) +{ + if (path == "/") + { + return status::good; + } + else + { + return status::bad_enoent; + } +} + +status empty_filesystem::getattr(std::string const & path, file_attributes & attr) +{ + if (path == "/") + { + attr.inode = 1; + attr.nlink = 1; + attr.mode = filemode(filemode::dir | 0x444); + return status::good; + } + else + { + return status::bad_enoent; + } +} + +status empty_filesystem::readlink(std::string const & path, std::string & out) +{ + return status::bad_enoent; +} + +status empty_filesystem::symlink(std::string const & target, std::string const & linkpath) +{ + return status::bad_enoent; +} + +status empty_filesystem::link(std::string const & old_path, std::string const & new_path) +{ + return status::bad_enoent; +} + +status empty_filesystem::rename(std::string const & old_path, std::string const & new_path) +{ + return status::bad_enoent; +} + +status empty_filesystem::chmod(std::string const & path, filemode mode) +{ + return status::bad_eperm; +} + +status empty_filesystem::chown(std::string const & path, user_id uid, group_id gid) +{ + return status::bad_eperm; +} + +status empty_filesystem::truncate(std::string const & path, uint64_t offset, filehandle handle) +{ + return status::bad_eperm; +} + +status empty_filesystem::fsync(std::string const & path, bool is_datasync, filehandle handle) +{ + return status::good; +} + +status empty_filesystem::open(std::string const & path, openflags flags, filehandle & handle) +{ + return status::bad_enoent; +} + +status empty_filesystem::mknod(std::string const & path, filemode mode, uint64_t rdev) +{ + return status::bad_eperm; + +} +status empty_filesystem::create(std::string const & path, filemode mode, filehandle & handle) +{ + return status::bad_eperm; +} + +status empty_filesystem::release(std::string const & path, filehandle handle) +{ + return status::good; +} + +status empty_filesystem::unlink(std::string const & path) +{ + return status::bad_eperm; +} + +status empty_filesystem::read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) +{ + return status::bad_ebadf; +} +status empty_filesystem::write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) +{ + return status::bad_ebadf; +} + +status empty_filesystem::mkdir(std::string const & path, filemode mode) +{ + return status::bad_eperm; +} + +status empty_filesystem::readdir(std::string const & path, std::vector & entries, filehandle handle) +{ + if (path == "/") + { + return status::good; + } + else + { + return status::bad_enoent; + } +} + +status empty_filesystem::rmdir(std::string const & path) +{ + return status::bad_eperm; +} + +status empty_filesystem::statfs(std::string const & path, filesystem_statistics & statistics) +{ + return status::bad_enosys; +} + + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/empty_filesystem.hpp b/src/webfuse/filesystem/empty_filesystem.hpp new file mode 100644 index 0000000..b020f03 --- /dev/null +++ b/src/webfuse/filesystem/empty_filesystem.hpp @@ -0,0 +1,45 @@ +#ifndef WEBFUSE_EMPTYFILESYSTEM_HPP +#define WEBFUSE_EMPTYFILESYSTEM_HPP + +#include "webfuse/filesystem/filesystem_i.hpp" + +namespace webfuse +{ + +class empty_filesystem: public filesystem_i +{ +public: + ~empty_filesystem() override = default; + + status access(std::string const & path, access_mode mode) override; + status getattr(std::string const & path, file_attributes & attr) override; + + status readlink(std::string const & path, std::string & out) override; + status symlink(std::string const & target, std::string const & linkpath) override; + status link(std::string const & old_path, std::string const & new_path) override; + + status rename(std::string const & old_path, std::string const & new_path) override; + status chmod(std::string const & path, filemode mode) override; + status chown(std::string const & path, user_id uid, group_id gid) override; + status truncate(std::string const & path, uint64_t offset, filehandle handle) override; + status fsync(std::string const & path, bool is_datasync, filehandle handle) override; + + status open(std::string const & path, openflags flags, filehandle & handle) override; + status mknod(std::string const & path, filemode mode, uint64_t rdev) override; + status create(std::string const & path, filemode mode, filehandle & handle) override; + status release(std::string const & path, filehandle handle) override; + status unlink(std::string const & path) override; + + status read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) override; + status write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) override; + + status mkdir(std::string const & path, filemode mode) override; + status readdir(std::string const & path, std::vector & entries, filehandle handle) override; + status rmdir(std::string const & path) override; + + status statfs(std::string const & path, filesystem_statistics & statistics) override; +}; + +} + +#endif diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp index 03f2e58..84b585e 100644 --- a/src/webfuse/filesystem/filesystem_i.hpp +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -32,7 +32,7 @@ public: virtual status rename(std::string const & old_path, std::string const & new_path) = 0; virtual status chmod(std::string const & path, filemode mode) = 0; - virtual status chown(std::string const & path, user_id uid, group_id gid); + virtual status chown(std::string const & path, user_id uid, group_id gid) = 0; virtual status truncate(std::string const & path, uint64_t offset, filehandle handle) = 0; virtual status fsync(std::string const & path, bool is_datasync, filehandle handle) = 0; @@ -45,7 +45,8 @@ public: virtual status read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) = 0; virtual status write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) = 0; - virtual status readdir(std::string const & path, std::vector entries, filehandle handle) = 0; + virtual status mkdir(std::string const & path, filemode mode) = 0; + virtual status readdir(std::string const & path, std::vector & entries, filehandle handle) = 0; virtual status rmdir(std::string const & path) = 0; virtual status statfs(std::string const & path, filesystem_statistics & statistics) = 0; diff --git a/src/webfuse/filesystem/filesystem_statistics.cpp b/src/webfuse/filesystem/filesystem_statistics.cpp new file mode 100644 index 0000000..5422b80 --- /dev/null +++ b/src/webfuse/filesystem/filesystem_statistics.cpp @@ -0,0 +1,43 @@ +#include "webfuse/filesystem/filesystem_statistics.hpp" + +namespace webfuse +{ + +filesystem_statistics::filesystem_statistics() +: bsize(0) +, frsize(0) +, blocks(0) +, bfree(0) +, bavail(0) +, files(0) +, ffree(0) +, f_namemax(0) +{ + +} + +filesystem_statistics::filesystem_statistics(struct statvfs const & other) +{ + bsize = other.f_bsize; + frsize = other.f_frsize; + blocks = other.f_blocks; + bfree = other.f_bfree; + bavail = other.f_bavail; + files = other.f_files; + ffree = other.f_ffree; + f_namemax = other.f_namemax; +} + +void filesystem_statistics::copy_to(struct statvfs & other) const +{ + other.f_bsize = bsize; + other.f_frsize = frsize; + other.f_blocks = blocks; + other.f_bfree = bfree; + other.f_bavail = bavail; + other.f_files = files; + other.f_ffree = ffree; + other.f_namemax = f_namemax; +} + +} \ No newline at end of file diff --git a/src/webfuse/filesystem/status.cpp b/src/webfuse/filesystem/status.cpp index cda71cc..b3b3977 100644 --- a/src/webfuse/filesystem/status.cpp +++ b/src/webfuse/filesystem/status.cpp @@ -117,4 +117,10 @@ int status::to_fusestatus() const } } +bool status::is_good() const +{ + return (value_ == status::good); +} + + } \ No newline at end of file diff --git a/src/webfuse/filesystem/status.hpp b/src/webfuse/filesystem/status.hpp index ee322a1..5b3f6af 100644 --- a/src/webfuse/filesystem/status.hpp +++ b/src/webfuse/filesystem/status.hpp @@ -53,6 +53,8 @@ public: static status from_fusestatus(int value); int to_fusestatus() const; + + bool is_good() const; private: int32_t value_; }; diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp new file mode 100644 index 0000000..4ed7a49 --- /dev/null +++ b/src/webfuse/fuse.cpp @@ -0,0 +1,319 @@ +#define FUSE_USE_VERSION 31 + +#include "webfuse/fuse.hpp" +#include +#include +#include + +extern "C" +{ + +static webfuse::filesystem_i * fs_get_filesystem() +{ + struct fuse_context * context = fuse_get_context(); + void * private_data = context->private_data; + return reinterpret_cast(private_data); +} + +static webfuse::filehandle fs_get_handle(fuse_file_info * info) +{ + return (nullptr != info) ? info->fh : webfuse::invalid_handle; +} + +static void * fs_init(fuse_conn_info * connection, fuse_config * config) +{ + (void) connection; + config->use_ino = 1; + config->entry_timeout = 0; + config->attr_timeout = 0; + config->negative_timeout = 0; + + struct fuse_context * context = fuse_get_context(); + return context->private_data; +} + + +static int fs_access(char const * path, int raw_mode) +{ + auto * const fs = fs_get_filesystem(); + auto const mode = webfuse::access_mode::from_int(raw_mode); + auto const result = fs->access(path, mode); + + return result.to_fusestatus(); +} + +static int fs_getattr(char const * path, struct stat * buffer, fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + webfuse::file_attributes attributes(*buffer); + + auto const result = fs->getattr(path, attributes); + attributes.to_stat(*buffer); + + return result.to_fusestatus(); +} + +static int fs_readlink(char const * path, char * buffer, size_t buffer_size) +{ + auto * const fs = fs_get_filesystem(); + + std::string out; + auto result = fs->readlink(path, out); + if (webfuse::status::good == result) + { + snprintf(buffer, buffer_size, "%s", out.c_str()); + result = strlen(buffer); + } + + return result.to_fusestatus(); +} + +static int fs_symlink(char const * target, char const * linkpath) +{ + auto * const fs = fs_get_filesystem(); + auto const result = fs->symlink(target, linkpath); + return result.to_fusestatus(); +} + +static int fs_link(char const * old_path, char const * new_path) +{ + auto * const fs = fs_get_filesystem(); + auto const result = fs->link(old_path, new_path); + return result.to_fusestatus(); +} + +static int fs_rename(char const * from, char const * to, unsigned int flags) +{ + // ToDo: provide flags + auto * const fs = fs_get_filesystem(); + auto const result = fs->rename(from, to); + return result.to_fusestatus(); +} + +static int fs_chmod(char const * path, mode_t raw_mode, fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto const mode = webfuse::filemode::from_mode(raw_mode); + auto const result = fs->chmod(path, mode); + return result.to_fusestatus(); +} + +static int fs_chown(char const * path, uid_t raw_uid, + gid_t raw_gid, fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto const uid = webfuse::user_id::from_uid(raw_uid); + auto const gid = webfuse::group_id::from_gid(raw_gid); + auto const result = fs->chown(path, uid, gid); + return result.to_fusestatus(); +} + +static int fs_truncate(char const * path, off_t raw_size, fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto const size = static_cast(raw_size); + auto const handle = fs_get_handle(info); + + auto const result = fs->truncate(path, size, handle); + return result.to_fusestatus(); +} + +static int fs_fsync(char const * path, int isdatasync, fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + bool const is_datasync = (is_datasync != 0); + auto const handle = fs_get_handle(info); + + auto const result = fs->fsync(path, is_datasync, handle); + return result.to_fusestatus(); +} + +static int fs_open(char const * path, fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto const flags = webfuse::openflags::from_int(info->flags); + + auto const result = fs->open(path, flags, info->fh); + return result.to_fusestatus(); +} + +static int fs_mknod(char const * path, mode_t raw_mode, dev_t raw_rdev) +{ + auto * const fs = fs_get_filesystem(); + auto const mode = webfuse::filemode::from_mode(raw_mode); + auto const rdev = static_cast(raw_rdev); + + auto const result = fs->mknod(path, mode, rdev); + return result.to_fusestatus(); +} + +static int fs_create(char const * path, mode_t raw_mode, fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto const mode = webfuse::filemode::from_mode(raw_mode); + + auto const result = fs->create(path, mode, info->fh); + return result.to_fusestatus(); +} + +static int fs_release(char const * path, fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto const handle = fs_get_handle(info); + + auto const result = fs->release(path, handle); + return result.to_fusestatus(); +} + +static int fs_unlink(char const * path) +{ + auto * const fs = fs_get_filesystem(); + auto const result = fs->unlink(path); + return result.to_fusestatus(); +} + +static int fs_read(char const * path, char * buffer, + size_t buffer_size, off_t raw_offset, + fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto const offset = static_cast(raw_offset); + auto const handle = fs_get_handle(info); + + auto const result = fs->read(path, buffer, buffer_size, offset, handle); + return result.to_fusestatus(); +} + +static int fs_write(char const * path, char const * buffer, + size_t buffer_size, off_t raw_offset, + fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto const offset = static_cast(raw_offset); + auto const handle = fs_get_handle(info); + + auto const result = fs->write(path, buffer, buffer_size, offset, handle); + return result.to_fusestatus(); +} + +static int fs_mkdir(char const * path, mode_t raw_mode) +{ + auto * const fs = fs_get_filesystem(); + auto const mode = webfuse::filemode::from_mode(raw_mode); + + auto const result = fs->mkdir(path, mode); + return result.to_fusestatus(); +} + +static int fs_readdir(char const * path, void * buffer, + fuse_fill_dir_t filler, off_t offset, fuse_file_info * info, + fuse_readdir_flags flags) +{ + auto * const fs = fs_get_filesystem(); + auto handle = fs_get_handle(info); + std::vector names; + auto const result = fs->readdir(path, names, handle); + if (result.is_good()) + { + filler(buffer, ".", nullptr, 0, static_cast(0)); + filler(buffer, "..", nullptr, 0, static_cast(0)); + for (auto const & name: names) + { + filler(buffer, name.c_str(), nullptr, 0, static_cast(0)); + } + } + + return result.to_fusestatus(); +} + +static int fs_rmdir(char const * path) +{ + auto * const fs = fs_get_filesystem(); + auto const result = fs->rmdir(path); + return result.to_fusestatus(); +} + +static int fs_statfs(char const * path, struct statvfs * buffer) +{ + auto * const fs = fs_get_filesystem(); + webfuse::filesystem_statistics statistics(*buffer); + + auto const result = fs->statfs(path, statistics); + statistics.copy_to(*buffer); + + return result.to_fusestatus(); + +} + +} + +namespace webfuse +{ + +class fuse::detail +{ +public: + filesystem_i & filesystem; +}; + +fuse::fuse(filesystem_i & filesystem) +: d(new detail{filesystem}) +{ + +} + +fuse::~fuse() +{ + delete d; +} + +fuse::fuse(fuse && other) +{ + this->d = other.d; + other.d = nullptr; +} + +fuse& fuse::operator=(fuse && other) +{ + if (this != &other) + { + delete d; + this->d = other.d; + other.d = nullptr; + } + + return *this; +} + +int fuse::run(int argc, char * argv[]) +{ + void * context = reinterpret_cast(&d->filesystem); + struct fuse_operations operations; + memset(reinterpret_cast(&operations), 0, sizeof(operations)); + operations.init = fs_init; + operations.access = fs_access; + operations.getattr = fs_getattr; + operations.readlink = fs_readlink; + operations.symlink = fs_symlink; + operations.link = fs_link; + operations.rename = fs_rename; + operations.chmod = fs_chmod; + operations.chown = fs_chown; + operations.truncate = fs_truncate; + operations.fsync = fs_fsync; + operations.open = fs_open; + operations.mknod = fs_mknod; + operations.create = fs_create; + operations.release = fs_release; + operations.unlink = fs_unlink; + operations.read = fs_read; + operations.write = fs_write; + operations.mkdir = fs_mkdir; + operations.readdir = fs_readdir; + operations.rmdir = fs_rmdir; + operations.statfs = fs_statfs; + + return fuse_main(argc, argv, &operations, context); +} + +} \ No newline at end of file diff --git a/src/webfuse/fuse.hpp b/src/webfuse/fuse.hpp index 39acb9e..aa5bbbb 100644 --- a/src/webfuse/fuse.hpp +++ b/src/webfuse/fuse.hpp @@ -13,9 +13,9 @@ class fuse public: explicit fuse(filesystem_i & filesystem); ~fuse(); - fuse (fuse &&) = delete; - fuse& operator=(fuse &&) = delete; - void run(int argc, char * argv[]); + fuse (fuse && other); + fuse& operator=(fuse && other); + int run(int argc, char * argv[]); private: class detail; detail * d; diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index 2d69d06..6ebb5b2 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -1,27 +1,16 @@ #include "webfuse/webfuse.hpp" +#include "webfuse/fuse.hpp" +#include "webfuse/filesystem/empty_filesystem.hpp" namespace webfuse { -class app::detail +int app::run(int argc, char * argv[]) { - int dummy; -}; + empty_filesystem filesystem; + fuse fuse_fs(filesystem); -app::app(int argc, char * argv[]) -: d(new detail) -{ - -} - -app::~app() -{ - delete d; -} - -int app::run() -{ - return 0; + return fuse_fs.run(argc, argv); } } \ No newline at end of file diff --git a/src/webfuse/webfuse.hpp b/src/webfuse/webfuse.hpp index 60c5989..6e76c1e 100644 --- a/src/webfuse/webfuse.hpp +++ b/src/webfuse/webfuse.hpp @@ -6,17 +6,8 @@ namespace webfuse class app { - app(app const &) = delete; - app& operator=(app const &) = delete; - app(app &&) = delete; - app& operator=(app &&) = delete; public: - app(int argc, char * argv[]); - ~app(); - int run(); -private: - class detail; - detail * d; + int run(int argc, char * argv[]); }; } diff --git a/test-src/webfuse/test_app.cpp b/test-src/webfuse/test_app.cpp index ff08344..d15cfde 100644 --- a/test-src/webfuse/test_app.cpp +++ b/test-src/webfuse/test_app.cpp @@ -3,7 +3,5 @@ TEST(app, init) { - char args0[] = "webfuse"; - char * args[] = { args0, nullptr }; - webfuse::app(1, args); + webfuse::app app; } \ No newline at end of file From a5f1cdc7e7362bb08a3f717eb66b49a0b6acefdb Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 13 Nov 2022 22:22:35 +0100 Subject: [PATCH 10/91] added basic websockets protocol --- CMakeLists.txt | 2 + script/provider.py | 11 ++++ script/requirements.txt | 1 + src/webfuse/webfuse.cpp | 29 +++++++++ src/webfuse/ws/config.cpp | 13 +++++ src/webfuse/ws/config.hpp | 19 ++++++ src/webfuse/ws/server.cpp | 120 ++++++++++++++++++++++++++++++++++++++ src/webfuse/ws/server.hpp | 27 +++++++++ 8 files changed, 222 insertions(+) create mode 100755 script/provider.py create mode 100644 script/requirements.txt create mode 100644 src/webfuse/ws/config.cpp create mode 100644 src/webfuse/ws/config.hpp create mode 100644 src/webfuse/ws/server.cpp create mode 100644 src/webfuse/ws/server.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f54bb8f..b74745e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(webfuse_static STATIC src/webfuse/filesystem/fileattributes.cpp src/webfuse/filesystem/filesystem_statistics.cpp src/webfuse/filesystem/empty_filesystem.cpp + src/webfuse/ws/config.cpp + src/webfuse/ws/server.cpp ) target_include_directories(webfuse_static PUBLIC src) diff --git a/script/provider.py b/script/provider.py new file mode 100755 index 0000000..f61a57e --- /dev/null +++ b/script/provider.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +import asyncio +import websockets + +async def hello(): + async with websockets.connect('ws://localhost:8081') as websocket: + await websocket.send('Hello') + await websocket.recv() + +asyncio.run(hello()) diff --git a/script/requirements.txt b/script/requirements.txt new file mode 100644 index 0000000..707d0d3 --- /dev/null +++ b/script/requirements.txt @@ -0,0 +1 @@ +websockets==10.4 \ No newline at end of file diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index 6ebb5b2..8158417 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -1,16 +1,45 @@ #include "webfuse/webfuse.hpp" #include "webfuse/fuse.hpp" #include "webfuse/filesystem/empty_filesystem.hpp" +#include "webfuse/ws/server.hpp" +#include "webfuse/ws/config.hpp" + +#include + +namespace +{ + +bool shutdown_requested = false; + +void on_shutdown_requested(int) +{ + shutdown_requested = true; +} + +} namespace webfuse { int app::run(int argc, char * argv[]) { +/* empty_filesystem filesystem; fuse fuse_fs(filesystem); return fuse_fs.run(argc, argv); +*/ + signal(SIGINT, &on_shutdown_requested); + + ws_config config; + ws_server server(config); + + while (!shutdown_requested) + { + server.service(); + } + + return 0; } } \ No newline at end of file diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp new file mode 100644 index 0000000..4acbbe6 --- /dev/null +++ b/src/webfuse/ws/config.cpp @@ -0,0 +1,13 @@ +#include "webfuse/ws/config.hpp" + +namespace webfuse +{ + +ws_config::ws_config() +: port(8081) +{ + +} + + +} \ No newline at end of file diff --git a/src/webfuse/ws/config.hpp b/src/webfuse/ws/config.hpp new file mode 100644 index 0000000..97c20e7 --- /dev/null +++ b/src/webfuse/ws/config.hpp @@ -0,0 +1,19 @@ +#ifndef WEBFUSE_WS_CONFIG_HPP +#define WEBFUSE_WS_CONFIG_HPP + +#include + +namespace webfuse +{ + +class ws_config +{ +public: + ws_config(); + + uint16_t port; +}; + +} + +#endif diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp new file mode 100644 index 0000000..4942cf8 --- /dev/null +++ b/src/webfuse/ws/server.cpp @@ -0,0 +1,120 @@ +#include "webfuse/ws/server.hpp" +#include +#include + +#include + +extern "C" +{ + +static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch(reason) + { + case LWS_CALLBACK_PROTOCOL_INIT: + std::cout << "lws: protocol init "<< std::endl; + break; + case LWS_CALLBACK_ESTABLISHED: + std::cout << "lws: established "<< std::endl; + break; + case LWS_CALLBACK_CLOSED: + std::cout << "lws: closed "<< std::endl; + break; + case LWS_CALLBACK_RECEIVE: + std::cout << "lws: receive "<< std::endl; + break; + case LWS_CALLBACK_SERVER_WRITEABLE: + std::cout << "lws: server writable "<< std::endl; + break; + default: + break; + } + + return 0; +} + + +} + +namespace webfuse +{ + +class ws_server::detail +{ + detail(detail const &) = delete; + detail& operator=(detail const &) = delete; + detail(detail &&) = delete; + detail& operator=(detail &&) = delete; +public: + detail(ws_config const & config) + { + memset(reinterpret_cast(protocols), 0, sizeof(protocols)); + protocols[0].name = "webfuse2"; + protocols[0].callback = &ws_server_callback; + protocols[0].per_session_data_size = 0; + protocols[0].user = nullptr; + + memset(reinterpret_cast(&info), 0, sizeof(info)); + info.port = config.port; + info.protocols = protocols; + info.vhost_name = "localhost"; + info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE | LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + context = lws_create_context(&info); + + lws_vhost * const vhost = lws_create_vhost(context, &info); + // port = lws_get_vhost_port(vhost); + } + + ~detail() + { + lws_context_destroy(context); + } + + lws_protocols protocols[2]; + lws_context_creation_info info; + lws_context * context; +}; + +ws_server::ws_server(ws_config const & config) +: d(new detail(config)) +{ + +} + +ws_server::~ws_server() +{ + delete d; +} + +ws_server::ws_server(ws_server && other) +{ + this->d = other.d; + other.d = nullptr; +} + +ws_server& ws_server::operator=(ws_server && other) +{ + if (this != &other) + { + delete this->d; + this->d = other.d; + other.d = nullptr; + } + + return *this; +} + +void ws_server::service() +{ + lws_service(d->context, 0); +} + +void ws_server::interrupt() +{ + lws_cancel_service(d->context); +} + + +} \ No newline at end of file diff --git a/src/webfuse/ws/server.hpp b/src/webfuse/ws/server.hpp new file mode 100644 index 0000000..fd752ac --- /dev/null +++ b/src/webfuse/ws/server.hpp @@ -0,0 +1,27 @@ +#ifndef WEBFUSE_WSSERVER_HPP +#define WEBFUSE_WSSERBER_HPP + +#include "webfuse/ws/config.hpp" + +namespace webfuse +{ + +class ws_server +{ + ws_server(ws_server const &) = delete; + ws_server& operator=(ws_server const &) = delete; +public: + ws_server(ws_config const & config); + ~ws_server(); + ws_server(ws_server && other); + ws_server& operator=(ws_server && other); + void service(); + void interrupt(); +private: + class detail; + detail * d; +}; + +} + +#endif From d9350e972511aa55b97022737072d8e598c05069 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Mon, 14 Nov 2022 17:21:47 +0100 Subject: [PATCH 11/91] put websocket server into separate thread --- src/webfuse/webfuse.cpp | 28 +--------------------------- src/webfuse/ws/server.cpp | 28 +++++++++++++++++----------- src/webfuse/ws/server.hpp | 2 -- 3 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index 8158417..64eb26b 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -2,44 +2,18 @@ #include "webfuse/fuse.hpp" #include "webfuse/filesystem/empty_filesystem.hpp" #include "webfuse/ws/server.hpp" -#include "webfuse/ws/config.hpp" - -#include - -namespace -{ - -bool shutdown_requested = false; - -void on_shutdown_requested(int) -{ - shutdown_requested = true; -} - -} namespace webfuse { int app::run(int argc, char * argv[]) { -/* empty_filesystem filesystem; fuse fuse_fs(filesystem); - - return fuse_fs.run(argc, argv); -*/ - signal(SIGINT, &on_shutdown_requested); - ws_config config; ws_server server(config); - while (!shutdown_requested) - { - server.service(); - } - - return 0; + return fuse_fs.run(argc, argv); } } \ No newline at end of file diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 4942cf8..b346cc0 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include extern "C" { @@ -48,6 +50,7 @@ class ws_server::detail detail& operator=(detail &&) = delete; public: detail(ws_config const & config) + : shutdown_requested(false) { memset(reinterpret_cast(protocols), 0, sizeof(protocols)); protocols[0].name = "webfuse2"; @@ -65,13 +68,27 @@ public: lws_vhost * const vhost = lws_create_vhost(context, &info); // port = lws_get_vhost_port(vhost); + + + thread = std::thread([this]() { + while (!shutdown_requested) + { + lws_service(context, 0); + } + + }); } ~detail() { + shutdown_requested = true; + lws_cancel_service(context); + thread.join(); lws_context_destroy(context); } + std::thread thread; + std::atomic shutdown_requested; lws_protocols protocols[2]; lws_context_creation_info info; lws_context * context; @@ -106,15 +123,4 @@ ws_server& ws_server::operator=(ws_server && other) return *this; } -void ws_server::service() -{ - lws_service(d->context, 0); -} - -void ws_server::interrupt() -{ - lws_cancel_service(d->context); -} - - } \ No newline at end of file diff --git a/src/webfuse/ws/server.hpp b/src/webfuse/ws/server.hpp index fd752ac..68492a3 100644 --- a/src/webfuse/ws/server.hpp +++ b/src/webfuse/ws/server.hpp @@ -15,8 +15,6 @@ public: ~ws_server(); ws_server(ws_server && other); ws_server& operator=(ws_server && other); - void service(); - void interrupt(); private: class detail; detail * d; From bc024481f9986da9e6c57e87f4b078af65bbe008 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Mon, 14 Nov 2022 19:02:46 +0100 Subject: [PATCH 12/91] added dummy filesystem implementation --- CMakeLists.txt | 1 + src/webfuse/filesystem.cpp | 311 +++++++++++++++++++++++++++++++++++++ src/webfuse/filesystem.hpp | 57 +++++++ src/webfuse/webfuse.cpp | 6 +- src/webfuse/ws/server.cpp | 18 +++ src/webfuse/ws/server.hpp | 6 +- 6 files changed, 394 insertions(+), 5 deletions(-) create mode 100644 src/webfuse/filesystem.cpp create mode 100644 src/webfuse/filesystem.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b74745e..bd16718 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets) add_library(webfuse_static STATIC src/webfuse/webfuse.cpp src/webfuse/fuse.cpp + src/webfuse/filesystem.cpp src/webfuse/filesystem/status.cpp src/webfuse/filesystem/accessmode.cpp src/webfuse/filesystem/openflags.cpp diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp new file mode 100644 index 0000000..fbeb13c --- /dev/null +++ b/src/webfuse/filesystem.cpp @@ -0,0 +1,311 @@ +#include "webfuse/filesystem.hpp" + +namespace webfuse +{ + +filesystem::filesystem(ws_server& server) +: proxy(server) +{ + +} + +filesystem::~filesystem() +{ + +} + +status filesystem::access(std::string const & path, access_mode mode) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.access(path, mode); + } +} + +status filesystem::getattr(std::string const & path, file_attributes & attr) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.getattr(path, attr); + } +} + +status filesystem::readlink(std::string const & path, std::string & out) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.readlink(path, out); + } +} + +status filesystem::symlink(std::string const & target, std::string const & linkpath) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.symlink(target, linkpath); + } +} + +status filesystem::link(std::string const & old_path, std::string const & new_path) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.link(old_path, new_path); + } +} + +status filesystem::rename(std::string const & old_path, std::string const & new_path) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.rename(old_path, new_path); + } +} + +status filesystem::chmod(std::string const & path, filemode mode) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.chmod(path, mode); + } +} + +status filesystem::chown(std::string const & path, user_id uid, group_id gid) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.chown(path, uid, gid); + } +} + +status filesystem::truncate(std::string const & path, uint64_t offset, filehandle handle) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.truncate(path, offset, handle); + } +} + +status filesystem::fsync(std::string const & path, bool is_datasync, filehandle handle) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.fsync(path, is_datasync, handle); + } +} + +status filesystem::open(std::string const & path, openflags flags, filehandle & handle) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.open(path, flags, handle); + } +} + +status filesystem::mknod(std::string const & path, filemode mode, uint64_t rdev) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.mknod(path, mode, rdev); + } +} + +status filesystem::create(std::string const & path, filemode mode, filehandle & handle) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.create(path, mode, handle); + } +} + +status filesystem::release(std::string const & path, filehandle handle) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.release(path, handle); + } +} + +status filesystem::unlink(std::string const & path) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.unlink(path); + } +} + +status filesystem::read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.read(path, buffer, buffer_size, offset, handle); + } +} + +status filesystem::write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.write(path, buffer, buffer_size, offset, handle); + } +} + +status filesystem::mkdir(std::string const & path, filemode mode) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.mkdir(path, mode); + } +} + +status filesystem::readdir(std::string const & path, std::vector & entries, filehandle handle) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.readdir(path, entries, handle); + } +} + +status filesystem::rmdir(std::string const & path) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.rmdir(path); + } +} + +status filesystem::statfs(std::string const & path, filesystem_statistics & statistics) +{ + try + { + std::string req; + auto resp = proxy.perform(req).get(); + return status::bad_enoent; + } + catch(...) + { + return fallback.statfs(path, statistics); + } +} + +} \ No newline at end of file diff --git a/src/webfuse/filesystem.hpp b/src/webfuse/filesystem.hpp new file mode 100644 index 0000000..6f86944 --- /dev/null +++ b/src/webfuse/filesystem.hpp @@ -0,0 +1,57 @@ +#ifndef WEBFUSE_FILESYSTEM_HPP +#define WEBFUSE_FILESYSTEM_HPP + +#include "webfuse/filesystem/filesystem_i.hpp" +#include "webfuse/filesystem/empty_filesystem.hpp" +#include "webfuse/ws/server.hpp" + +namespace webfuse +{ + +class filesystem: public filesystem_i +{ + filesystem(filesystem const &) = delete; + filesystem& operator=(filesystem const &) = delete; + filesystem(filesystem &&) = delete; + filesystem& operator=(filesystem &&) = delete; +public: + explicit filesystem(ws_server& server); + ~filesystem() override; + + status access(std::string const & path, access_mode mode) override; + status getattr(std::string const & path, file_attributes & attr) override; + + status readlink(std::string const & path, std::string & out) override; + status symlink(std::string const & target, std::string const & linkpath) override; + status link(std::string const & old_path, std::string const & new_path) override; + + status rename(std::string const & old_path, std::string const & new_path) override; + status chmod(std::string const & path, filemode mode) override; + status chown(std::string const & path, user_id uid, group_id gid) override; + status truncate(std::string const & path, uint64_t offset, filehandle handle) override; + status fsync(std::string const & path, bool is_datasync, filehandle handle) override; + + status open(std::string const & path, openflags flags, filehandle & handle) override; + status mknod(std::string const & path, filemode mode, uint64_t rdev) override; + status create(std::string const & path, filemode mode, filehandle & handle) override; + status release(std::string const & path, filehandle handle) override; + status unlink(std::string const & path) override; + + status read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) override; + status write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) override; + + status mkdir(std::string const & path, filemode mode) override; + status readdir(std::string const & path, std::vector & entries, filehandle handle) override; + status rmdir(std::string const & path) override; + + status statfs(std::string const & path, filesystem_statistics & statistics) override; + + +private: + ws_server &proxy; + empty_filesystem fallback; +}; + +} + +#endif diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index 64eb26b..e06a6ee 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -1,6 +1,6 @@ #include "webfuse/webfuse.hpp" #include "webfuse/fuse.hpp" -#include "webfuse/filesystem/empty_filesystem.hpp" +#include "webfuse/filesystem.hpp" #include "webfuse/ws/server.hpp" namespace webfuse @@ -8,10 +8,10 @@ namespace webfuse int app::run(int argc, char * argv[]) { - empty_filesystem filesystem; - fuse fuse_fs(filesystem); ws_config config; ws_server server(config); + filesystem filesystem(server); + fuse fuse_fs(filesystem); return fuse_fs.run(argc, argv); } diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index b346cc0..b13ee7a 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -5,6 +5,7 @@ #include #include #include +#include extern "C" { @@ -123,4 +124,21 @@ ws_server& ws_server::operator=(ws_server && other) return *this; } +std::future ws_server::perform(std::string const & req) +{ + std::promise resp; + + try + { + throw std::runtime_error("not implemented"); + } + catch (std::exception const & ex) + { + resp.set_exception(std::current_exception()); + } + + return resp.get_future(); +} + + } \ No newline at end of file diff --git a/src/webfuse/ws/server.hpp b/src/webfuse/ws/server.hpp index 68492a3..b681248 100644 --- a/src/webfuse/ws/server.hpp +++ b/src/webfuse/ws/server.hpp @@ -1,8 +1,9 @@ #ifndef WEBFUSE_WSSERVER_HPP -#define WEBFUSE_WSSERBER_HPP +#define WEBFUSE_WSSERVER_HPP #include "webfuse/ws/config.hpp" - +#include +#include namespace webfuse { @@ -15,6 +16,7 @@ public: ~ws_server(); ws_server(ws_server && other); ws_server& operator=(ws_server && other); + std::future perform(std::string const & req); private: class detail; detail * d; From 7924fa11915c20ec6f71d97f46541f93a3f94349 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 19 Nov 2022 22:57:32 +0100 Subject: [PATCH 13/91] implemented basic request --- CMakeLists.txt | 1 + doc/protocol.md | 36 +++++++ script/provider.py | 156 ++++++++++++++++++++++++++++++- src/webfuse/filesystem.cpp | 86 ++++++++--------- src/webfuse/message_type.hpp | 37 ++++++++ src/webfuse/ws/message.cpp | 129 +++++++++++++++++++++++++ src/webfuse/ws/message.hpp | 45 +++++++++ src/webfuse/ws/messagereader.hpp | 23 +++++ src/webfuse/ws/server.cpp | 119 ++++++++++++++++++++--- src/webfuse/ws/server.hpp | 9 +- 10 files changed, 578 insertions(+), 63 deletions(-) create mode 100644 doc/protocol.md create mode 100644 src/webfuse/message_type.hpp create mode 100644 src/webfuse/ws/message.cpp create mode 100644 src/webfuse/ws/message.hpp create mode 100644 src/webfuse/ws/messagereader.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bd16718..71c776f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(webfuse_static STATIC src/webfuse/filesystem/empty_filesystem.cpp src/webfuse/ws/config.cpp src/webfuse/ws/server.cpp + src/webfuse/ws/message.cpp ) target_include_directories(webfuse_static PUBLIC src) diff --git a/doc/protocol.md b/doc/protocol.md new file mode 100644 index 0000000..23763cf --- /dev/null +++ b/doc/protocol.md @@ -0,0 +1,36 @@ +# Webufse 2 Protocol + +## Endianness + +All numeric data types are transferred in [Big Endian](https://en.wikipedia.org/wiki/Endianness). +For instance, the uint32 value 1 will be transferred as + + 00 00 00 01 + +## Data Types + +### Basic data types + +| Data | Width | Description | +| ---- | ------ | ----------- | +| bool | 8 bit | Represents a boolean value | +| i32 | 32 bit | + +## Message + + + +## Methods + +### access + +| Field | Data Type | Description | +| ----- | ---------------- | ----------- | +| | uint32 | message id | +| type | uint8 | message type (0x00) | +| path | string | | +| mode | access_mode (i8) | + +#### Response + +| Field | \ No newline at end of file diff --git a/script/provider.py b/script/provider.py index f61a57e..dc76f11 100755 --- a/script/provider.py +++ b/script/provider.py @@ -1,11 +1,157 @@ #!/usr/bin/env python3 import asyncio +import os import websockets +import errno -async def hello(): - async with websockets.connect('ws://localhost:8081') as websocket: - await websocket.send('Hello') - await websocket.recv() +F_OK = 0 +R_OK = 4 +W_OK = 2 +X_OK = 1 -asyncio.run(hello()) +RESPONSE = 0x80 + +ERRNO = { + -errno.E2BIG : -7, + -errno.EACCES : -13, + -errno.EAGAIN : -11, + -errno.EBADF : -9, + -errno.EBUSY : -16, + -errno.EDESTADDRREQ : -89, + -errno.EDQUOT : -122, + -errno.EEXIST : -17, + -errno.EFAULT : -14, + -errno.EFBIG : -27, + -errno.EINTR : -4, + -errno.EINVAL : -22, + -errno.EIO : -5, + -errno.EISDIR : -21, + -errno.ELOOP : -40, + -errno.EMFILE : -24, + -errno.EMLINK : -31, + -errno.ENAMETOOLONG : -36, + -errno.ENFILE : -23, + -errno.ENODATA : -61, + -errno.ENODEV : -19, + -errno.ENOENT : -2, + -errno.ENOMEM : -12, + -errno.ENOSPC : -28, + -errno.ENOSYS : -38, + -errno.ENOTDIR : -20, + -errno.ENOTEMPTY : -39, + -errno.ENOTSUP : -95, + -errno.ENXIO : -6, + -errno.EOVERFLOW : -75, + -errno.EPERM : -1, + -errno.EPIPE : -32, + -errno.ERANGE : -34, + -errno.EROFS : -30, + -errno.ETXTBSY : -26, + -errno.EXDEV : -18 +} + +class MessageReader: + def __init__(self, buffer): + self.buffer = buffer + self.offset = 0 + + def read_u8(self): + value = self.buffer[self.offset] + self.offset += 1 + return value + + def read_u32(self): + value = (self.buffer[self.offset] << 24) + (self.buffer[self.offset + 1] << 16) + (self.buffer[self.offset + 2] << 8) + self.buffer[self.offset + 3] + self.offset += 4 + return value + + def read_str(self): + return self.read_bytes().decode() + + def read_bytes(self): + size = self.read_u32() + value = self.buffer[self.offset : self.offset + size] + self.offset += size + return value + + def read_path(self, base_path): + local_path = self.read_str().lstrip('/') + return os.path.join(base_path, local_path) + + def read_access_mode(self): + value = self.read_u8() + mode = os.F_OK if F_OK == (value & F_OK) else 0 + mode += os.R_OK if R_OK == (value & R_OK) else 0 + mode += os.W_OK if W_OK == (value & W_OK) else 0 + mode += os.X_OK if X_OK == (value & X_OK) else 0 + return mode + + +class MessageWriter: + def __init__(self, message_id, message_type): + self.buffer = [] + self.write_u32(message_id) + self.write_u8(message_type) + + def write_u8(self, value): + self.buffer.append(value) + + def write_u32(self, value): + a = (value >> 24) & 0xff + b = (value >> 16) & 0xff + c = (value >> 8) & 0xff + d = value & 0xff + self.buffer.extend([a, b, c, d]) + + def write_i32(self, value): + self.write_u32(value & 0xffffffff) + + def write_result(self, value): + if 0 > value: + if value in ERRNO: + value = ERRNO[value] + self.write_i32(value) + + def get_bytes(self): + return bytearray(self.buffer) + + +class FilesystemProvider: + def __init__(self, path, url): + self.root = os.path.abspath(path) + self.url = url + self.commands = { + 0x01: FilesystemProvider.access + } + + async def run(self): + async with websockets.connect(self.url) as connection: + while True: + request = await connection.recv() + reader = MessageReader(request) + message_id = reader.read_u32() + message_type = reader.read_u8() + writer = MessageWriter(message_id, RESPONSE + message_type) + if message_type in self.commands: + method = self.commands[message_type] + method(self, reader, writer) + else: + print("unknown message type: %d" % message_type) + response = writer.get_bytes() + await connection.send(response) + + def access(self, reader, writer): + path = reader.read_path(self.root) + mode = reader.read_access_mode() + result = -errno.EACCES + try: + if os.access(path, mode) == True: + result = 0 + except OSError as ex: + result = -ex.errno + writer.write_result(result) + +if __name__ == '__main__': + provider = FilesystemProvider('.', 'ws://localhost:8081') + asyncio.run(provider.run()) diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index fbeb13c..cf7dd60 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -18,8 +18,10 @@ status filesystem::access(std::string const & path, access_mode mode) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::access_req); + req.add_str(path); + req.add_i8(mode); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -32,8 +34,8 @@ status filesystem::getattr(std::string const & path, file_attributes & attr) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::getattr_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -46,8 +48,8 @@ status filesystem::readlink(std::string const & path, std::string & out) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::readlink_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -60,8 +62,8 @@ status filesystem::symlink(std::string const & target, std::string const & linkp { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::symlink_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -74,8 +76,8 @@ status filesystem::link(std::string const & old_path, std::string const & new_pa { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::link_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -88,8 +90,8 @@ status filesystem::rename(std::string const & old_path, std::string const & new_ { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::rename_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -102,8 +104,8 @@ status filesystem::chmod(std::string const & path, filemode mode) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::chmod_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -116,8 +118,8 @@ status filesystem::chown(std::string const & path, user_id uid, group_id gid) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::chown_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -130,8 +132,8 @@ status filesystem::truncate(std::string const & path, uint64_t offset, filehandl { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::truncate_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -144,8 +146,8 @@ status filesystem::fsync(std::string const & path, bool is_datasync, filehandle { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::fsync_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -158,8 +160,8 @@ status filesystem::open(std::string const & path, openflags flags, filehandle & { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::open_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -172,8 +174,8 @@ status filesystem::mknod(std::string const & path, filemode mode, uint64_t rdev) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::mknod_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -186,8 +188,8 @@ status filesystem::create(std::string const & path, filemode mode, filehandle & { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::create_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -200,8 +202,8 @@ status filesystem::release(std::string const & path, filehandle handle) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::release_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -214,8 +216,8 @@ status filesystem::unlink(std::string const & path) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::unlink_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -228,8 +230,8 @@ status filesystem::read(std::string const & path, char * buffer, size_t buffer_s { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::read_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -242,8 +244,8 @@ status filesystem::write(std::string const & path, char const * buffer, size_t b { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::write_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -256,8 +258,8 @@ status filesystem::mkdir(std::string const & path, filemode mode) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::mkdir_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -270,8 +272,8 @@ status filesystem::readdir(std::string const & path, std::vector & { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::readdir_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -284,8 +286,8 @@ status filesystem::rmdir(std::string const & path) { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::rmdir_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) @@ -298,8 +300,8 @@ status filesystem::statfs(std::string const & path, filesystem_statistics & stat { try { - std::string req; - auto resp = proxy.perform(req).get(); + message req(message_type::statfs_req); + proxy.perform(std::move(req)); return status::bad_enoent; } catch(...) diff --git a/src/webfuse/message_type.hpp b/src/webfuse/message_type.hpp new file mode 100644 index 0000000..b876b40 --- /dev/null +++ b/src/webfuse/message_type.hpp @@ -0,0 +1,37 @@ +#ifndef WEBFUSE_MESSAGETYPE_HPP +#define WEBFUSE_MESSAGETYPE_HPP + +#include + +namespace webfuse +{ + +enum class message_type: uint8_t +{ + access_req = 0x01, + getattr_req = 0x02, + readlink_req = 0x03, + symlink_req = 0x04, + link_req = 0x05, + rename_req = 0x06, + chmod_req = 0x07, + chown_req = 0x08, + truncate_req = 0x09, + fsync_req = 0x0a, + open_req = 0x0b, + mknod_req = 0x0c, + create_req = 0x0d, + release_req = 0x0e, + unlink_req = 0x0f, + read_req = 0x10, + write_req = 0x11, + mkdir_req = 0x12, + readdir_req = 0x13, + rmdir_req = 0x14, + statfs_req = 0x15 +}; + + +} + +#endif diff --git a/src/webfuse/ws/message.cpp b/src/webfuse/ws/message.cpp new file mode 100644 index 0000000..77e45ec --- /dev/null +++ b/src/webfuse/ws/message.cpp @@ -0,0 +1,129 @@ +#include "message.hpp" +#include + +namespace webfuse +{ + +message::message(message_type msg_type) +: id(0) +, data(LWS_PRE) +{ + add_u32(0); + add_u8(static_cast(msg_type)); +} + +message::message(message && other) +{ + this->id = other.id; + this->data = std::move(other.data); +} + +message& message::operator=(message && other) +{ + if (this != &other) + { + this->id = other.id; + this->data = std::move(other.data); + } + + return *this; +} + +void message::set_id(uint32_t value) +{ + id = id; + data[LWS_PRE ] = (id >> 24) & 0xff; + data[LWS_PRE + 1] = (id >> 16) & 0xff; + data[LWS_PRE + 2] = (id >> 8) & 0xff; + data[LWS_PRE + 3] = id & 0xff; +} + +uint32_t message::get_id() const +{ + return id; +} + +void message::add_bool(bool value) +{ + data.push_back(value ? 0x01 : 0x00); +} + +void message::add_u8(uint8_t value) +{ + data.push_back(value); +} + +void message::add_i8(int8_t value) +{ + data.push_back(static_cast(value)); +} + +void message::add_i32(int32_t value) +{ + add_u32((static_cast(value))); +} + +void message::add_u32(uint32_t value) +{ + auto const offset = data.size(); + data.resize(offset + 4); + data[offset ] = (value >> 24) & 0xff; + data[offset + 1] = (value >> 16) & 0xff; + data[offset + 2] = (value >> 8) & 0xff; + data[offset + 3] = value & 0xff; +} + +void message::add_u64(uint64_t value) +{ + auto const offset = data.size(); + data.resize(offset + 8); + data[offset ] = (value >> 56) & 0xff; + data[offset + 1] = (value >> 48) & 0xff; + data[offset + 2] = (value >> 40) & 0xff; + data[offset + 3] = (value >> 32) & 0xff; + data[offset + 4] = (value >> 24) & 0xff; + data[offset + 5] = (value >> 16) & 0xff; + data[offset + 6] = (value >> 8) & 0xff; + data[offset + 7] = value & 0xff; +} + +void message::add_str(std::string const &value) +{ + add_data(value.data(), value.size()); +} + +void message::add_data(char const * buffer, size_t size) +{ + uint32_t const effective_size = size & 0xffffffff; + add_u32(effective_size); + + if (size > 0) + { + auto const offset = data.size(); + data.resize(offset + effective_size); + void * to = reinterpret_cast(&data.data()[offset]); + void const * from = reinterpret_cast(buffer); + memcpy(to, from, effective_size); + } +} + +void message::add_strings(std::vector const & list) +{ + uint32_t const count = list.size() & 0xffffffff; + add_u32(count); + for (auto const & item: list) + { + add_str(item); + } +} + +unsigned char * message::get_data(size_t &size) +{ + size = data.size() - LWS_PRE; + void * result = reinterpret_cast(&data.data()[LWS_PRE]); + + return reinterpret_cast(result); +} + + +} \ No newline at end of file diff --git a/src/webfuse/ws/message.hpp b/src/webfuse/ws/message.hpp new file mode 100644 index 0000000..7fb4f6f --- /dev/null +++ b/src/webfuse/ws/message.hpp @@ -0,0 +1,45 @@ +#ifndef WEBFUSE_MESSAGEBUILDER_HPP +#define WEBFUSE_MESSAGEBUILDER_HPP + +#include "webfuse/message_type.hpp" + +#include +#include +#include + +namespace webfuse +{ + +class message +{ + message(message const &) = delete; + message& operator=(message const &) = delete; +public: + explicit message(message_type msg_type); + ~message() = default; + message(message && other); + message& operator=(message && other); + + void set_id(uint32_t value); + uint32_t get_id() const; + + void add_bool(bool value); + void add_u8(uint8_t value); + void add_i8(int8_t value); + void add_i32(int32_t value); + void add_u32(uint32_t value); + void add_u64(uint64_t value); + void add_str(std::string const &value); + void add_data(char const * buffer, size_t size); + void add_strings(std::vector const & list); + + unsigned char * get_data(size_t &size); + +private: + uint32_t id; + std::vector data; +}; + +} + +#endif diff --git a/src/webfuse/ws/messagereader.hpp b/src/webfuse/ws/messagereader.hpp new file mode 100644 index 0000000..c62b8b6 --- /dev/null +++ b/src/webfuse/ws/messagereader.hpp @@ -0,0 +1,23 @@ +#ifndef WEBFUSE_MESSAGEREADER_HPP +#define WEBFUSE_MESSAGEREADER_HPP + +#include + +namespace webfuse +{ + +class messagereader +{ + +public: + explicit messagereader(std::string && value); + ~messagereader() = default; + +private: + std::string data; + size_t pos; +}; + +} + +#endif diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index b13ee7a..42eb4c2 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -1,40 +1,108 @@ #include "webfuse/ws/server.hpp" +#include "webfuse/ws/message.hpp" + #include + +#include #include #include #include #include +#include +#include +#include #include +#include +#include +#include + + +namespace +{ + +struct user_data +{ + struct lws * connection = nullptr; + + std::mutex mut; + std::queue requests; + std::unordered_map> pending_responses; +}; + +} + extern "C" { static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { + auto const * protocol = lws_get_protocol(wsi); + if (nullptr == protocol) { return 0; } + if (&ws_server_callback != protocol->callback) { return 0; } + + auto * data = reinterpret_cast(protocol->user); + + int result = 0; switch(reason) { - case LWS_CALLBACK_PROTOCOL_INIT: - std::cout << "lws: protocol init "<< std::endl; - break; case LWS_CALLBACK_ESTABLISHED: std::cout << "lws: established "<< std::endl; + if (nullptr == data->connection) + { + data->connection = wsi; + } + else + { + result = -1; + } break; case LWS_CALLBACK_CLOSED: std::cout << "lws: closed "<< std::endl; + if (wsi == data->connection) + { + data->connection = nullptr; + } break; case LWS_CALLBACK_RECEIVE: std::cout << "lws: receive "<< std::endl; break; case LWS_CALLBACK_SERVER_WRITEABLE: std::cout << "lws: server writable "<< std::endl; + { + webfuse::message msg(webfuse::message_type::access_req); + bool has_msg = false; + bool has_more = false; + + { + std::lock_guard lock(data->mut); + has_msg = !(data->requests.empty()); + if (has_msg) + { + has_msg = true; + msg = std::move(data->requests.front()); + data->requests.pop(); + + has_more = !(data->requests.empty()); + } + } + + if (has_msg) + { + size_t size; + unsigned char * raw_data = msg.get_data(size); + int const rc = lws_write(data->connection, raw_data, size, LWS_WRITE_BINARY); + } + + } break; default: break; } - return 0; + return result; } @@ -57,7 +125,7 @@ public: protocols[0].name = "webfuse2"; protocols[0].callback = &ws_server_callback; protocols[0].per_session_data_size = 0; - protocols[0].user = nullptr; + protocols[0].user = reinterpret_cast(&data); memset(reinterpret_cast(&info), 0, sizeof(info)); info.port = config.port; @@ -74,6 +142,22 @@ public: thread = std::thread([this]() { while (!shutdown_requested) { + { + std::lock_guard lock(data.mut); + if (!data.requests.empty()) + { + if (nullptr != data.connection) + { + lws_callback_on_writable(data.connection); + } + else + { + data.requests = std::move(std::queue()); + data.pending_responses.clear(); + } + } + } + lws_service(context, 0); } @@ -93,6 +177,7 @@ public: lws_protocols protocols[2]; lws_context_creation_info info; lws_context * context; + user_data data; }; ws_server::ws_server(ws_config const & config) @@ -124,20 +209,26 @@ ws_server& ws_server::operator=(ws_server && other) return *this; } -std::future ws_server::perform(std::string const & req) +void ws_server::perform(message msg) { - std::promise resp; - - try + std::future f; { - throw std::runtime_error("not implemented"); + std::promise p; + f = p.get_future(); + + std::lock_guard lock(d->data.mut); + d->data.requests.emplace(std::move(msg)); + d->data.pending_responses.emplace(42, std::move(p)); } - catch (std::exception const & ex) + + lws_cancel_service(d->context); + if(std::future_status::timeout == f.wait_for(std::chrono::seconds(1))) { - resp.set_exception(std::current_exception()); + throw std::runtime_error("timeout"); } - - return resp.get_future(); + std::string resp = f.get(); + + throw std::runtime_error("not implemented"); } diff --git a/src/webfuse/ws/server.hpp b/src/webfuse/ws/server.hpp index b681248..c040fc9 100644 --- a/src/webfuse/ws/server.hpp +++ b/src/webfuse/ws/server.hpp @@ -2,8 +2,12 @@ #define WEBFUSE_WSSERVER_HPP #include "webfuse/ws/config.hpp" -#include +#include "webfuse/ws/message.hpp" + +#include #include +#include + namespace webfuse { @@ -16,7 +20,8 @@ public: ~ws_server(); ws_server(ws_server && other); ws_server& operator=(ws_server && other); - std::future perform(std::string const & req); + + void perform(message msg); private: class detail; detail * d; From 0a513322b72334d92426c8cd9b6a5d2eda46dda8 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 20 Nov 2022 13:29:34 +0100 Subject: [PATCH 14/91] added implementation for access, getattr and readdir --- CMakeLists.txt | 3 +- script/provider.py | 81 ++++++++- src/webfuse/filesystem.cpp | 154 +++++++++-------- src/webfuse/filesystem.hpp | 42 ++--- src/webfuse/filesystem/empty_filesystem.cpp | 97 +++++------ src/webfuse/filesystem/empty_filesystem.hpp | 42 ++--- src/webfuse/filesystem/filesystem_i.hpp | 55 +++--- src/webfuse/fuse.cpp | 112 +++++-------- src/webfuse/ws/message.hpp | 45 ----- src/webfuse/ws/messagereader.cpp | 158 ++++++++++++++++++ src/webfuse/ws/messagereader.hpp | 30 +++- .../ws/{message.cpp => messagewriter.cpp} | 57 ++++--- src/webfuse/ws/messagewriter.hpp | 47 ++++++ src/webfuse/ws/server.cpp | 81 +++++++-- src/webfuse/ws/server.hpp | 5 +- 15 files changed, 651 insertions(+), 358 deletions(-) delete mode 100644 src/webfuse/ws/message.hpp create mode 100644 src/webfuse/ws/messagereader.cpp rename src/webfuse/ws/{message.cpp => messagewriter.cpp} (61%) create mode 100644 src/webfuse/ws/messagewriter.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 71c776f..a30fb92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,8 @@ add_library(webfuse_static STATIC src/webfuse/filesystem/empty_filesystem.cpp src/webfuse/ws/config.cpp src/webfuse/ws/server.cpp - src/webfuse/ws/message.cpp + src/webfuse/ws/messagewriter.cpp + src/webfuse/ws/messagereader.cpp ) target_include_directories(webfuse_static PUBLIC src) diff --git a/script/provider.py b/script/provider.py index dc76f11..564bcd0 100755 --- a/script/provider.py +++ b/script/provider.py @@ -98,11 +98,24 @@ class MessageWriter: self.buffer.append(value) def write_u32(self, value): - a = (value >> 24) & 0xff - b = (value >> 16) & 0xff - c = (value >> 8) & 0xff - d = value & 0xff - self.buffer.extend([a, b, c, d]) + self.buffer.extend([ + (value >> 24) & 0xff, + (value >> 16) & 0xff, + (value >> 8) & 0xff, + value & 0xff + ]) + + def write_u64(self, value): + self.buffer.extend([ + (value >> 56) & 0xff, + (value >> 48) & 0xff, + (value >> 40) & 0xff, + (value >> 32) & 0xff, + (value >> 24) & 0xff, + (value >> 16) & 0xff, + (value >> 8) & 0xff, + value & 0xff + ]) def write_i32(self, value): self.write_u32(value & 0xffffffff) @@ -113,7 +126,23 @@ class MessageWriter: value = ERRNO[value] self.write_i32(value) + def write_str(self, value): + data = value.encode('utf-8') + self.write_bytes(data) + + def write_bytes(self, value): + size = len(value) + self.write_u32(size) + self.buffer.extend(value) + + def write_strings(self, values): + count = len(values) + self.write_u32(count) + for value in values: + self.write_str(value) + def get_bytes(self): + print(self.buffer) return bytearray(self.buffer) @@ -122,7 +151,9 @@ class FilesystemProvider: self.root = os.path.abspath(path) self.url = url self.commands = { - 0x01: FilesystemProvider.access + 0x01: FilesystemProvider.access, + 0x02: FilesystemProvider.getattr, + 0x13: FilesystemProvider.readdir } async def run(self): @@ -132,6 +163,7 @@ class FilesystemProvider: reader = MessageReader(request) message_id = reader.read_u32() message_type = reader.read_u8() + print("received message: id=%d, type=%d" % (message_id, message_type)) writer = MessageWriter(message_id, RESPONSE + message_type) if message_type in self.commands: method = self.commands[message_type] @@ -152,6 +184,43 @@ class FilesystemProvider: result = -ex.errno writer.write_result(result) + def getattr(self, reader, writer): + path = reader.read_path(self.root) + try: + attr = os.lstat(path) + except OSError as ex: + writer.write_result(-ex.errno) + return + writer.write_result(0) + writer.write_u64(attr.st_ino) + writer.write_u64(attr.st_nlink) + writer.write_u32(attr.st_mode) + writer.write_i32(attr.st_uid) + writer.write_i32(attr.st_gid) + writer.write_u64(attr.st_dev) + writer.write_u64(attr.st_size) + writer.write_u64(attr.st_blocks) + writer.write_u64(int(attr.st_atime)) + writer.write_u32(attr.st_atime_ns) + writer.write_u64(int(attr.st_mtime)) + writer.write_u32(attr.st_mtime_ns) + writer.write_u64(int(attr.st_ctime)) + writer.write_u32(attr.st_ctime_ns) + + def readdir(self, reader, writer): + path = reader.read_path(self.root) + names = [] + try: + with os.scandir(path) as it: + for entry in it: + names.append(entry.name) + except OSError as ex: + writer.write_result(-ex.errno) + return + writer.write_result(0) + writer.write_strings(names) + + if __name__ == '__main__': provider = FilesystemProvider('.', 'ws://localhost:8081') asyncio.run(provider.run()) diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index cf7dd60..d895aac 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -1,4 +1,5 @@ #include "webfuse/filesystem.hpp" +#include namespace webfuse { @@ -14,15 +15,15 @@ filesystem::~filesystem() } -status filesystem::access(std::string const & path, access_mode mode) +int filesystem::access(std::string const & path, int mode) { try { - message req(message_type::access_req); - req.add_str(path); - req.add_i8(mode); - proxy.perform(std::move(req)); - return status::bad_enoent; + messagewriter req(message_type::access_req); + req.write_str(path); + req.write_access_mode(mode); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -30,27 +31,34 @@ status filesystem::access(std::string const & path, access_mode mode) } } -status filesystem::getattr(std::string const & path, file_attributes & attr) +int filesystem::getattr(std::string const & path, struct stat * attr) { try { - message req(message_type::getattr_req); - proxy.perform(std::move(req)); - return status::bad_enoent; + messagewriter req(message_type::getattr_req); + req.write_str(path); + auto reader = proxy.perform(std::move(req)); + int const result = reader.read_result(); + if (0 == result) + { + reader.read_attr(attr); + } + return result; } catch(...) { + puts("getattr: failed"); return fallback.getattr(path, attr); } } -status filesystem::readlink(std::string const & path, std::string & out) +int filesystem::readlink(std::string const & path, std::string & out) { try { - message req(message_type::readlink_req); + messagewriter req(message_type::readlink_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -58,13 +66,13 @@ status filesystem::readlink(std::string const & path, std::string & out) } } -status filesystem::symlink(std::string const & target, std::string const & linkpath) +int filesystem::symlink(std::string const & target, std::string const & linkpath) { try { - message req(message_type::symlink_req); + messagewriter req(message_type::symlink_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -72,13 +80,13 @@ status filesystem::symlink(std::string const & target, std::string const & linkp } } -status filesystem::link(std::string const & old_path, std::string const & new_path) +int filesystem::link(std::string const & old_path, std::string const & new_path) { try { - message req(message_type::link_req); + messagewriter req(message_type::link_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -86,27 +94,27 @@ status filesystem::link(std::string const & old_path, std::string const & new_pa } } -status filesystem::rename(std::string const & old_path, std::string const & new_path) +int filesystem::rename(std::string const & old_path, std::string const & new_path, int flags) { try { - message req(message_type::rename_req); + messagewriter req(message_type::rename_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { - return fallback.rename(old_path, new_path); + return fallback.rename(old_path, new_path, flags); } } -status filesystem::chmod(std::string const & path, filemode mode) +int filesystem::chmod(std::string const & path, mode_t mode) { try { - message req(message_type::chmod_req); + messagewriter req(message_type::chmod_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -114,13 +122,13 @@ status filesystem::chmod(std::string const & path, filemode mode) } } -status filesystem::chown(std::string const & path, user_id uid, group_id gid) +int filesystem::chown(std::string const & path, uid_t uid, gid_t gid) { try { - message req(message_type::chown_req); + messagewriter req(message_type::chown_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -128,27 +136,27 @@ status filesystem::chown(std::string const & path, user_id uid, group_id gid) } } -status filesystem::truncate(std::string const & path, uint64_t offset, filehandle handle) +int filesystem::truncate(std::string const & path, uint64_t size, uint64_t handle) { try { - message req(message_type::truncate_req); + messagewriter req(message_type::truncate_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { - return fallback.truncate(path, offset, handle); + return fallback.truncate(path, size, handle); } } -status filesystem::fsync(std::string const & path, bool is_datasync, filehandle handle) +int filesystem::fsync(std::string const & path, bool is_datasync, uint64_t handle) { try { - message req(message_type::fsync_req); + messagewriter req(message_type::fsync_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -156,13 +164,13 @@ status filesystem::fsync(std::string const & path, bool is_datasync, filehandle } } -status filesystem::open(std::string const & path, openflags flags, filehandle & handle) +int filesystem::open(std::string const & path, int flags, uint64_t & handle) { try { - message req(message_type::open_req); + messagewriter req(message_type::open_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -170,13 +178,13 @@ status filesystem::open(std::string const & path, openflags flags, filehandle & } } -status filesystem::mknod(std::string const & path, filemode mode, uint64_t rdev) +int filesystem::mknod(std::string const & path, mode_t mode, dev_t rdev) { try { - message req(message_type::mknod_req); + messagewriter req(message_type::mknod_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -184,13 +192,13 @@ status filesystem::mknod(std::string const & path, filemode mode, uint64_t rdev) } } -status filesystem::create(std::string const & path, filemode mode, filehandle & handle) +int filesystem::create(std::string const & path, mode_t mode, uint64_t & handle) { try { - message req(message_type::create_req); + messagewriter req(message_type::create_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -198,13 +206,13 @@ status filesystem::create(std::string const & path, filemode mode, filehandle & } } -status filesystem::release(std::string const & path, filehandle handle) +int filesystem::release(std::string const & path, uint64_t handle) { try { - message req(message_type::release_req); + messagewriter req(message_type::release_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -212,13 +220,13 @@ status filesystem::release(std::string const & path, filehandle handle) } } -status filesystem::unlink(std::string const & path) +int filesystem::unlink(std::string const & path) { try { - message req(message_type::unlink_req); + messagewriter req(message_type::unlink_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -226,13 +234,13 @@ status filesystem::unlink(std::string const & path) } } -status filesystem::read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) +int filesystem::read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) { try { - message req(message_type::read_req); + messagewriter req(message_type::read_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -240,13 +248,13 @@ status filesystem::read(std::string const & path, char * buffer, size_t buffer_s } } -status filesystem::write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) +int filesystem::write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) { try { - message req(message_type::write_req); + messagewriter req(message_type::write_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -254,13 +262,13 @@ status filesystem::write(std::string const & path, char const * buffer, size_t b } } -status filesystem::mkdir(std::string const & path, filemode mode) +int filesystem::mkdir(std::string const & path, mode_t mode) { try { - message req(message_type::mkdir_req); + messagewriter req(message_type::mkdir_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -268,13 +276,19 @@ status filesystem::mkdir(std::string const & path, filemode mode) } } -status filesystem::readdir(std::string const & path, std::vector & entries, filehandle handle) +int filesystem::readdir(std::string const & path, std::vector & entries, uint64_t handle) { try { - message req(message_type::readdir_req); - proxy.perform(std::move(req)); - return status::bad_enoent; + messagewriter req(message_type::readdir_req); + req.write_str(path); + auto resp = proxy.perform(std::move(req)); + int result = resp.read_result(); + if (0 == result) + { + resp.read_strings(entries); + } + return result; } catch(...) { @@ -282,13 +296,13 @@ status filesystem::readdir(std::string const & path, std::vector & } } -status filesystem::rmdir(std::string const & path) +int filesystem::rmdir(std::string const & path) { try { - message req(message_type::rmdir_req); + messagewriter req(message_type::rmdir_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { @@ -296,13 +310,13 @@ status filesystem::rmdir(std::string const & path) } } -status filesystem::statfs(std::string const & path, filesystem_statistics & statistics) +int filesystem::statfs(std::string const & path, struct statvfs * statistics) { try { - message req(message_type::statfs_req); + messagewriter req(message_type::statfs_req); proxy.perform(std::move(req)); - return status::bad_enoent; + return -ENOENT; } catch(...) { diff --git a/src/webfuse/filesystem.hpp b/src/webfuse/filesystem.hpp index 6f86944..8b3c44c 100644 --- a/src/webfuse/filesystem.hpp +++ b/src/webfuse/filesystem.hpp @@ -18,33 +18,33 @@ public: explicit filesystem(ws_server& server); ~filesystem() override; - status access(std::string const & path, access_mode mode) override; - status getattr(std::string const & path, file_attributes & attr) override; + int access(std::string const & path, int mode) override; + int getattr(std::string const & path, struct stat * attr) override; - status readlink(std::string const & path, std::string & out) override; - status symlink(std::string const & target, std::string const & linkpath) override; - status link(std::string const & old_path, std::string const & new_path) override; + int readlink(std::string const & path, std::string & out) override; + int symlink(std::string const & target, std::string const & linkpath) override; + int link(std::string const & old_path, std::string const & new_path) override; - status rename(std::string const & old_path, std::string const & new_path) override; - status chmod(std::string const & path, filemode mode) override; - status chown(std::string const & path, user_id uid, group_id gid) override; - status truncate(std::string const & path, uint64_t offset, filehandle handle) override; - status fsync(std::string const & path, bool is_datasync, filehandle handle) override; + int rename(std::string const & old_path, std::string const & new_path, int flags) override; + int chmod(std::string const & path, mode_t mode) override; + int chown(std::string const & path, uid_t uid, gid_t gid) override; + int truncate(std::string const & path, uint64_t size, uint64_t handle) override; + int fsync(std::string const & path, bool is_datasync, uint64_t handle) override; - status open(std::string const & path, openflags flags, filehandle & handle) override; - status mknod(std::string const & path, filemode mode, uint64_t rdev) override; - status create(std::string const & path, filemode mode, filehandle & handle) override; - status release(std::string const & path, filehandle handle) override; - status unlink(std::string const & path) override; + int open(std::string const & path, int flags, uint64_t & handle) override; + int mknod(std::string const & path, mode_t mode, dev_t rdev) override; + int create(std::string const & path, mode_t mode, uint64_t & handle) override; + int release(std::string const & path, uint64_t handle) override; + int unlink(std::string const & path) override; - status read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) override; - status write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) override; + int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override; + int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override; - status mkdir(std::string const & path, filemode mode) override; - status readdir(std::string const & path, std::vector & entries, filehandle handle) override; - status rmdir(std::string const & path) override; + int mkdir(std::string const & path, mode_t mode) override; + int readdir(std::string const & path, std::vector & entries, uint64_t handle) override; + int rmdir(std::string const & path) override; - status statfs(std::string const & path, filesystem_statistics & statistics) override; + int statfs(std::string const & path, struct statvfs * statistics) override; private: diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index 9993cda..6552d2e 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -1,134 +1,135 @@ #include "webfuse/filesystem/empty_filesystem.hpp" +#include namespace webfuse { -status empty_filesystem::access(std::string const & path, access_mode mode) +int empty_filesystem::access(std::string const & path, int mode) { if (path == "/") { - return status::good; + return 0; } else { - return status::bad_enoent; + return -ENOENT; } } -status empty_filesystem::getattr(std::string const & path, file_attributes & attr) +int empty_filesystem::getattr(std::string const & path, struct stat * attr) { if (path == "/") { - attr.inode = 1; - attr.nlink = 1; - attr.mode = filemode(filemode::dir | 0x444); - return status::good; + attr->st_ino = 1; + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0x444; + return 0; } else { - return status::bad_enoent; + return -ENOENT; } } -status empty_filesystem::readlink(std::string const & path, std::string & out) +int empty_filesystem::readlink(std::string const & path, std::string & out) { - return status::bad_enoent; + return -ENOENT; } -status empty_filesystem::symlink(std::string const & target, std::string const & linkpath) +int empty_filesystem::symlink(std::string const & target, std::string const & linkpath) { - return status::bad_enoent; + return -ENOENT; } -status empty_filesystem::link(std::string const & old_path, std::string const & new_path) +int empty_filesystem::link(std::string const & old_path, std::string const & new_path) { - return status::bad_enoent; + return -ENOENT; } -status empty_filesystem::rename(std::string const & old_path, std::string const & new_path) +int empty_filesystem::rename(std::string const & old_path, std::string const & new_path, int flags) { - return status::bad_enoent; + return -ENOENT; } -status empty_filesystem::chmod(std::string const & path, filemode mode) +int empty_filesystem::chmod(std::string const & path, mode_t mode) { - return status::bad_eperm; + return -EPERM; } -status empty_filesystem::chown(std::string const & path, user_id uid, group_id gid) +int empty_filesystem::chown(std::string const & path, uid_t uid, gid_t gid) { - return status::bad_eperm; + return -EPERM; } -status empty_filesystem::truncate(std::string const & path, uint64_t offset, filehandle handle) +int empty_filesystem::truncate(std::string const & path, uint64_t size, uint64_t handle) { - return status::bad_eperm; + return -EPERM; } -status empty_filesystem::fsync(std::string const & path, bool is_datasync, filehandle handle) +int empty_filesystem::fsync(std::string const & path, bool is_datasync, uint64_t handle) { - return status::good; + return 0; } -status empty_filesystem::open(std::string const & path, openflags flags, filehandle & handle) +int empty_filesystem::open(std::string const & path, int flags, uint64_t & handle) { - return status::bad_enoent; + return -ENOENT; } -status empty_filesystem::mknod(std::string const & path, filemode mode, uint64_t rdev) +int empty_filesystem::mknod(std::string const & path, mode_t mode, dev_t rdev) { - return status::bad_eperm; + return -EPERM; } -status empty_filesystem::create(std::string const & path, filemode mode, filehandle & handle) +int empty_filesystem::create(std::string const & path, mode_t mode, uint64_t & handle) { - return status::bad_eperm; + return -EPERM; } -status empty_filesystem::release(std::string const & path, filehandle handle) +int empty_filesystem::release(std::string const & path, uint64_t handle) { - return status::good; + return 0; } -status empty_filesystem::unlink(std::string const & path) +int empty_filesystem::unlink(std::string const & path) { - return status::bad_eperm; + return -EPERM; } -status empty_filesystem::read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) +int empty_filesystem::read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) { - return status::bad_ebadf; + return -EBADF; } -status empty_filesystem::write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) +int empty_filesystem::write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) { - return status::bad_ebadf; + return -EBADF; } -status empty_filesystem::mkdir(std::string const & path, filemode mode) +int empty_filesystem::mkdir(std::string const & path, mode_t mode) { - return status::bad_eperm; + return -EPERM; } -status empty_filesystem::readdir(std::string const & path, std::vector & entries, filehandle handle) +int empty_filesystem::readdir(std::string const & path, std::vector & entries, uint64_t handle) { if (path == "/") { - return status::good; + return 0; } else { - return status::bad_enoent; + return -ENOENT; } } -status empty_filesystem::rmdir(std::string const & path) +int empty_filesystem::rmdir(std::string const & path) { - return status::bad_eperm; + return -EPERM; } -status empty_filesystem::statfs(std::string const & path, filesystem_statistics & statistics) +int empty_filesystem::statfs(std::string const & path, struct statvfs * statistics) { - return status::bad_enosys; + return -ENOSYS; } diff --git a/src/webfuse/filesystem/empty_filesystem.hpp b/src/webfuse/filesystem/empty_filesystem.hpp index b020f03..b26477f 100644 --- a/src/webfuse/filesystem/empty_filesystem.hpp +++ b/src/webfuse/filesystem/empty_filesystem.hpp @@ -11,33 +11,33 @@ class empty_filesystem: public filesystem_i public: ~empty_filesystem() override = default; - status access(std::string const & path, access_mode mode) override; - status getattr(std::string const & path, file_attributes & attr) override; + int access(std::string const & path, int mode) override; + int getattr(std::string const & path, struct stat * attr) override; - status readlink(std::string const & path, std::string & out) override; - status symlink(std::string const & target, std::string const & linkpath) override; - status link(std::string const & old_path, std::string const & new_path) override; + int readlink(std::string const & path, std::string & out) override; + int symlink(std::string const & target, std::string const & linkpath) override; + int link(std::string const & old_path, std::string const & new_path) override; - status rename(std::string const & old_path, std::string const & new_path) override; - status chmod(std::string const & path, filemode mode) override; - status chown(std::string const & path, user_id uid, group_id gid) override; - status truncate(std::string const & path, uint64_t offset, filehandle handle) override; - status fsync(std::string const & path, bool is_datasync, filehandle handle) override; + int rename(std::string const & old_path, std::string const & new_path, int flags) override; + int chmod(std::string const & path, mode_t mode) override; + int chown(std::string const & path, uid_t uid, gid_t gid) override; + int truncate(std::string const & path, uint64_t size, uint64_t handle) override; + int fsync(std::string const & path, bool is_datasync, uint64_t handle) override; - status open(std::string const & path, openflags flags, filehandle & handle) override; - status mknod(std::string const & path, filemode mode, uint64_t rdev) override; - status create(std::string const & path, filemode mode, filehandle & handle) override; - status release(std::string const & path, filehandle handle) override; - status unlink(std::string const & path) override; + int open(std::string const & path, int flags, uint64_t & handle) override; + int mknod(std::string const & path, mode_t mode, dev_t rdev) override; + int create(std::string const & path, mode_t mode, uint64_t & handle) override; + int release(std::string const & path, uint64_t handle) override; + int unlink(std::string const & path) override; - status read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) override; - status write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) override; + int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override; + int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override; - status mkdir(std::string const & path, filemode mode) override; - status readdir(std::string const & path, std::vector & entries, filehandle handle) override; - status rmdir(std::string const & path) override; + int mkdir(std::string const & path, mode_t mode) override; + int readdir(std::string const & path, std::vector & entries, uint64_t handle) override; + int rmdir(std::string const & path) override; - status statfs(std::string const & path, filesystem_statistics & statistics) override; + int statfs(std::string const & path, struct statvfs * statistivs) override; }; } diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp index 84b585e..d1de857 100644 --- a/src/webfuse/filesystem/filesystem_i.hpp +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -1,15 +1,10 @@ #ifndef WEBFUSE_FILESYSTEM_I_HPP #define WEBFUSE_FILESYSTEM_I_HPP -#include "webfuse/filesystem/filehandle.hpp" -#include "webfuse/filesystem/accessmode.hpp" -#include "webfuse/filesystem/filemode.hpp" -#include "webfuse/filesystem/fileattributes.hpp" -#include "webfuse/filesystem/openflags.hpp" -#include "webfuse/filesystem/userid.hpp" -#include "webfuse/filesystem/groupid.hpp" -#include "webfuse/filesystem/status.hpp" -#include "webfuse/filesystem/filesystem_statistics.hpp" +#include +#include +#include +#include #include #include @@ -23,33 +18,33 @@ class filesystem_i public: virtual ~filesystem_i() = default; - virtual status access(std::string const & path, access_mode mode) = 0; - virtual status getattr(std::string const & path, file_attributes & attr) = 0; + virtual int access(std::string const & path, int mode) = 0; + virtual int getattr(std::string const & path, struct stat * attr) = 0; - virtual status readlink(std::string const & path, std::string & out) = 0; - virtual status symlink(std::string const & target, std::string const & linkpath) = 0; - virtual status link(std::string const & old_path, std::string const & new_path) = 0; + virtual int readlink(std::string const & path, std::string & out) = 0; + virtual int symlink(std::string const & target, std::string const & linkpath) = 0; + virtual int link(std::string const & old_path, std::string const & new_path) = 0; - virtual status rename(std::string const & old_path, std::string const & new_path) = 0; - virtual status chmod(std::string const & path, filemode mode) = 0; - virtual status chown(std::string const & path, user_id uid, group_id gid) = 0; - virtual status truncate(std::string const & path, uint64_t offset, filehandle handle) = 0; - virtual status fsync(std::string const & path, bool is_datasync, filehandle handle) = 0; + virtual int rename(std::string const & old_path, std::string const & new_path, int flags) = 0; + virtual int chmod(std::string const & path, mode_t mode) = 0; + virtual int chown(std::string const & path, uid_t uid, gid_t gid) = 0; + virtual int truncate(std::string const & path, uint64_t size, uint64_t handle) = 0; + virtual int fsync(std::string const & path, bool is_datasync, uint64_t handle) = 0; - virtual status open(std::string const & path, openflags flags, filehandle & handle) = 0; - virtual status mknod(std::string const & path, filemode mode, uint64_t rdev) = 0; - virtual status create(std::string const & path, filemode mode, filehandle & handle) = 0; - virtual status release(std::string const & path, filehandle handle) = 0; - virtual status unlink(std::string const & path) = 0; + virtual int open(std::string const & path, int flags, uint64_t & handle) = 0; + virtual int mknod(std::string const & path, mode_t mode, dev_t rdev) = 0; + virtual int create(std::string const & path, mode_t mode, uint64_t & handle) = 0; + virtual int release(std::string const & path, uint64_t handle) = 0; + virtual int unlink(std::string const & path) = 0; - virtual status read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, filehandle handle) = 0; - virtual status write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, filehandle handle) = 0; + virtual int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) = 0; + virtual int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) = 0; - virtual status mkdir(std::string const & path, filemode mode) = 0; - virtual status readdir(std::string const & path, std::vector & entries, filehandle handle) = 0; - virtual status rmdir(std::string const & path) = 0; + virtual int mkdir(std::string const & path, mode_t mode) = 0; + virtual int readdir(std::string const & path, std::vector & entries, uint64_t handle) = 0; + virtual int rmdir(std::string const & path) = 0; - virtual status statfs(std::string const & path, filesystem_statistics & statistics) = 0; + virtual int statfs(std::string const & path, struct statvfs * statistics) = 0; }; } diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp index 4ed7a49..5fd8cac 100644 --- a/src/webfuse/fuse.cpp +++ b/src/webfuse/fuse.cpp @@ -15,9 +15,9 @@ static webfuse::filesystem_i * fs_get_filesystem() return reinterpret_cast(private_data); } -static webfuse::filehandle fs_get_handle(fuse_file_info * info) +static uint64_t fs_get_handle(fuse_file_info * info) { - return (nullptr != info) ? info->fh : webfuse::invalid_handle; + return (nullptr != info) ? info->fh : ((uint64_t) -1); } static void * fs_init(fuse_conn_info * connection, fuse_config * config) @@ -33,24 +33,18 @@ static void * fs_init(fuse_conn_info * connection, fuse_config * config) } -static int fs_access(char const * path, int raw_mode) +static int fs_access(char const * path, int mode) { auto * const fs = fs_get_filesystem(); - auto const mode = webfuse::access_mode::from_int(raw_mode); - auto const result = fs->access(path, mode); - - return result.to_fusestatus(); + return fs->access(path, mode); } static int fs_getattr(char const * path, struct stat * buffer, fuse_file_info * info) { - auto * const fs = fs_get_filesystem(); - webfuse::file_attributes attributes(*buffer); - - auto const result = fs->getattr(path, attributes); - attributes.to_stat(*buffer); + (void) info; - return result.to_fusestatus(); + auto * const fs = fs_get_filesystem(); + return fs->getattr(path, buffer); } static int fs_readlink(char const * path, char * buffer, size_t buffer_size) @@ -59,63 +53,55 @@ static int fs_readlink(char const * path, char * buffer, size_t buffer_size) std::string out; auto result = fs->readlink(path, out); - if (webfuse::status::good == result) + if (0 == result) { snprintf(buffer, buffer_size, "%s", out.c_str()); result = strlen(buffer); } - return result.to_fusestatus(); + return result; } static int fs_symlink(char const * target, char const * linkpath) { auto * const fs = fs_get_filesystem(); - auto const result = fs->symlink(target, linkpath); - return result.to_fusestatus(); + return fs->symlink(target, linkpath); } static int fs_link(char const * old_path, char const * new_path) { auto * const fs = fs_get_filesystem(); - auto const result = fs->link(old_path, new_path); - return result.to_fusestatus(); + return fs->link(old_path, new_path); } static int fs_rename(char const * from, char const * to, unsigned int flags) { - // ToDo: provide flags auto * const fs = fs_get_filesystem(); - auto const result = fs->rename(from, to); - return result.to_fusestatus(); + return fs->rename(from, to, flags); } -static int fs_chmod(char const * path, mode_t raw_mode, fuse_file_info * info) +static int fs_chmod(char const * path, mode_t mode, fuse_file_info * info) { + (void) info; + auto * const fs = fs_get_filesystem(); - auto const mode = webfuse::filemode::from_mode(raw_mode); - auto const result = fs->chmod(path, mode); - return result.to_fusestatus(); + return fs->chmod(path, mode); } -static int fs_chown(char const * path, uid_t raw_uid, - gid_t raw_gid, fuse_file_info * info) +static int fs_chown(char const * path, uid_t uid, gid_t gid, fuse_file_info * info) { + (void) info; + auto * const fs = fs_get_filesystem(); - auto const uid = webfuse::user_id::from_uid(raw_uid); - auto const gid = webfuse::group_id::from_gid(raw_gid); - auto const result = fs->chown(path, uid, gid); - return result.to_fusestatus(); + return fs->chown(path, uid, gid); } -static int fs_truncate(char const * path, off_t raw_size, fuse_file_info * info) +static int fs_truncate(char const * path, off_t size, fuse_file_info * info) { auto * const fs = fs_get_filesystem(); - auto const size = static_cast(raw_size); auto const handle = fs_get_handle(info); - auto const result = fs->truncate(path, size, handle); - return result.to_fusestatus(); + return fs->truncate(path, size, handle); } static int fs_fsync(char const * path, int isdatasync, fuse_file_info * info) @@ -124,36 +110,30 @@ static int fs_fsync(char const * path, int isdatasync, fuse_file_info * info) bool const is_datasync = (is_datasync != 0); auto const handle = fs_get_handle(info); - auto const result = fs->fsync(path, is_datasync, handle); - return result.to_fusestatus(); + return fs->fsync(path, is_datasync, handle); } static int fs_open(char const * path, fuse_file_info * info) { auto * const fs = fs_get_filesystem(); - auto const flags = webfuse::openflags::from_int(info->flags); + auto const flags = info->flags; - auto const result = fs->open(path, flags, info->fh); - return result.to_fusestatus(); + return fs->open(path, flags, info->fh); } -static int fs_mknod(char const * path, mode_t raw_mode, dev_t raw_rdev) +static int fs_mknod(char const * path, mode_t mode, dev_t raw_rdev) { auto * const fs = fs_get_filesystem(); - auto const mode = webfuse::filemode::from_mode(raw_mode); auto const rdev = static_cast(raw_rdev); - auto const result = fs->mknod(path, mode, rdev); - return result.to_fusestatus(); + return fs->mknod(path, mode, rdev); } -static int fs_create(char const * path, mode_t raw_mode, fuse_file_info * info) +static int fs_create(char const * path, mode_t mode, fuse_file_info * info) { auto * const fs = fs_get_filesystem(); - auto const mode = webfuse::filemode::from_mode(raw_mode); - auto const result = fs->create(path, mode, info->fh); - return result.to_fusestatus(); + return fs->create(path, mode, info->fh); } static int fs_release(char const * path, fuse_file_info * info) @@ -161,15 +141,13 @@ static int fs_release(char const * path, fuse_file_info * info) auto * const fs = fs_get_filesystem(); auto const handle = fs_get_handle(info); - auto const result = fs->release(path, handle); - return result.to_fusestatus(); + return fs->release(path, handle); } static int fs_unlink(char const * path) { auto * const fs = fs_get_filesystem(); - auto const result = fs->unlink(path); - return result.to_fusestatus(); + return fs->unlink(path); } static int fs_read(char const * path, char * buffer, @@ -180,8 +158,7 @@ static int fs_read(char const * path, char * buffer, auto const offset = static_cast(raw_offset); auto const handle = fs_get_handle(info); - auto const result = fs->read(path, buffer, buffer_size, offset, handle); - return result.to_fusestatus(); + return fs->read(path, buffer, buffer_size, offset, handle); } static int fs_write(char const * path, char const * buffer, @@ -192,17 +169,13 @@ static int fs_write(char const * path, char const * buffer, auto const offset = static_cast(raw_offset); auto const handle = fs_get_handle(info); - auto const result = fs->write(path, buffer, buffer_size, offset, handle); - return result.to_fusestatus(); + return fs->write(path, buffer, buffer_size, offset, handle); } -static int fs_mkdir(char const * path, mode_t raw_mode) +static int fs_mkdir(char const * path, mode_t mode) { auto * const fs = fs_get_filesystem(); - auto const mode = webfuse::filemode::from_mode(raw_mode); - - auto const result = fs->mkdir(path, mode); - return result.to_fusestatus(); + return fs->mkdir(path, mode); } static int fs_readdir(char const * path, void * buffer, @@ -213,7 +186,7 @@ static int fs_readdir(char const * path, void * buffer, auto handle = fs_get_handle(info); std::vector names; auto const result = fs->readdir(path, names, handle); - if (result.is_good()) + if (0 == result) { filler(buffer, ".", nullptr, 0, static_cast(0)); filler(buffer, "..", nullptr, 0, static_cast(0)); @@ -223,26 +196,19 @@ static int fs_readdir(char const * path, void * buffer, } } - return result.to_fusestatus(); + return result; } static int fs_rmdir(char const * path) { auto * const fs = fs_get_filesystem(); - auto const result = fs->rmdir(path); - return result.to_fusestatus(); + return fs->rmdir(path); } static int fs_statfs(char const * path, struct statvfs * buffer) { auto * const fs = fs_get_filesystem(); - webfuse::filesystem_statistics statistics(*buffer); - - auto const result = fs->statfs(path, statistics); - statistics.copy_to(*buffer); - - return result.to_fusestatus(); - + return fs->statfs(path, buffer); } } diff --git a/src/webfuse/ws/message.hpp b/src/webfuse/ws/message.hpp deleted file mode 100644 index 7fb4f6f..0000000 --- a/src/webfuse/ws/message.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef WEBFUSE_MESSAGEBUILDER_HPP -#define WEBFUSE_MESSAGEBUILDER_HPP - -#include "webfuse/message_type.hpp" - -#include -#include -#include - -namespace webfuse -{ - -class message -{ - message(message const &) = delete; - message& operator=(message const &) = delete; -public: - explicit message(message_type msg_type); - ~message() = default; - message(message && other); - message& operator=(message && other); - - void set_id(uint32_t value); - uint32_t get_id() const; - - void add_bool(bool value); - void add_u8(uint8_t value); - void add_i8(int8_t value); - void add_i32(int32_t value); - void add_u32(uint32_t value); - void add_u64(uint64_t value); - void add_str(std::string const &value); - void add_data(char const * buffer, size_t size); - void add_strings(std::vector const & list); - - unsigned char * get_data(size_t &size); - -private: - uint32_t id; - std::vector data; -}; - -} - -#endif diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp new file mode 100644 index 0000000..b073cb8 --- /dev/null +++ b/src/webfuse/ws/messagereader.cpp @@ -0,0 +1,158 @@ +#include "webfuse/ws/messagereader.hpp" +#include "webfuse/filesystem/status.hpp" +#include "webfuse/filesystem/fileattributes.hpp" +#include "webfuse/filesystem/filemode.hpp" + +#include + +namespace webfuse +{ + +messagereader::messagereader(std::string & value) +: data(std::move(value)) +, pos(0) +{ +} + +messagereader::messagereader(messagereader && other) +{ + this->data = std::move(other.data); + this->pos = other.pos; +} + +messagereader& messagereader::operator=(messagereader && other) +{ + if (this != &other) + { + this->data = std::move(other.data); + this->pos = other.pos; + } + + return *this; +} + + +int messagereader::read_result() +{ + status value(read_i32()); + return value.to_fusestatus(); +} + +void messagereader::read_attr(struct stat * attr) +{ + attr->st_ino = static_cast(read_u64()); + attr->st_nlink = static_cast(read_u64()); + attr->st_mode = read_mode(); + attr->st_uid = static_cast(read_u32()); + attr->st_gid = static_cast(read_u32()); + attr->st_rdev = static_cast(read_u64()); + attr->st_size = static_cast(read_u64()); + attr->st_blocks = static_cast(read_u64()); + attr->st_atim.tv_sec = static_cast(read_u64()); + attr->st_atim.tv_nsec = static_cast(read_u32()); + attr->st_mtim.tv_sec = static_cast(read_u64()); + attr->st_mtim.tv_nsec = static_cast(read_u32()); + attr->st_ctim.tv_sec = static_cast(read_u64()); + attr->st_ctim.tv_nsec = static_cast(read_u32()); +} + +mode_t messagereader::read_mode() +{ + filemode mode(read_u32()); + return mode.to_mode(); +} + + +uint8_t messagereader::read_u8() +{ + if (pos < data.size()) + { + uint8_t value = static_cast(data[pos]); + pos++; + return value; + } + else + { + throw std::runtime_error("out of bounds"); + } +} + +uint32_t messagereader::read_u32() +{ + if ((pos + 3) < data.size()) + { + uint32_t value = + ((static_cast(data[pos ]) & 0xff) << 24) | + ((static_cast(data[pos + 1]) & 0xff) << 16) | + ((static_cast(data[pos + 2]) & 0xff) << 8) | + ((static_cast(data[pos + 3]) & 0xff) ); + pos += 4; + return value; + } + else + { + throw std::runtime_error("out of bounds"); + } +} + +uint64_t messagereader::read_u64() +{ + if ((pos + 7) < data.size()) + { + uint32_t value = + (static_cast(data[pos ] & 0xff) << 56) | + (static_cast(data[pos + 1] & 0xff) << 48) | + (static_cast(data[pos + 2] & 0xff) << 40) | + (static_cast(data[pos + 3] & 0xff) << 32) | + (static_cast(data[pos + 4] & 0xff) << 24) | + (static_cast(data[pos + 5] & 0xff) << 16) | + (static_cast(data[pos + 6] & 0xff) << 8) | + (static_cast(data[pos + 7] & 0xff) ); + pos += 8; + return value; + } + else + { + throw std::runtime_error("out of bounds"); + } +} + + +int32_t messagereader::read_i32() +{ + uint32_t value = read_u32(); + return static_cast(value); +} + +std::string messagereader::read_str() +{ + return std::move(read_bytes()); +} + +std::string messagereader::read_bytes() +{ + uint32_t size = read_u32(); + if ((pos + size) <= data.size()) + { + std::string const value(&data[pos], size); + pos += size; + return std::move(value); + } + else + { + throw std::runtime_error("out of bounds"); + } +} + +void messagereader::read_strings(std::vector &entries) +{ + uint32_t const count = read_u32(); + for(uint32_t i = 0; i < count; i++) + { + std::string entry = read_str(); + entries.push_back(entry); + } +} + + +} \ No newline at end of file diff --git a/src/webfuse/ws/messagereader.hpp b/src/webfuse/ws/messagereader.hpp index c62b8b6..6cdceeb 100644 --- a/src/webfuse/ws/messagereader.hpp +++ b/src/webfuse/ws/messagereader.hpp @@ -1,17 +1,43 @@ #ifndef WEBFUSE_MESSAGEREADER_HPP #define WEBFUSE_MESSAGEREADER_HPP +#include +#include +#include + +#include #include +#include namespace webfuse { class messagereader { - + messagereader(messagereader const &) = delete; + messagereader& operator=(messagereader const &) = delete; public: - explicit messagereader(std::string && value); + explicit messagereader(std::string & value); ~messagereader() = default; + messagereader(messagereader && other); + messagereader& operator=(messagereader && other); + + int read_result(); + void read_attr(struct stat * attr); + mode_t read_mode(); + + uint8_t read_u8(); + uint32_t read_u32(); + uint64_t read_u64(); + + int32_t read_i32(); + + std::string read_str(); + std::string read_bytes(); + + void read_strings(std::vector &entries); + + private: std::string data; diff --git a/src/webfuse/ws/message.cpp b/src/webfuse/ws/messagewriter.cpp similarity index 61% rename from src/webfuse/ws/message.cpp rename to src/webfuse/ws/messagewriter.cpp index 77e45ec..86822bb 100644 --- a/src/webfuse/ws/message.cpp +++ b/src/webfuse/ws/messagewriter.cpp @@ -1,24 +1,26 @@ -#include "message.hpp" +#include "webfuse/ws/messagewriter.hpp" +#include "webfuse/filesystem/accessmode.hpp" + #include namespace webfuse { -message::message(message_type msg_type) +messagewriter::messagewriter(message_type msg_type) : id(0) , data(LWS_PRE) { - add_u32(0); - add_u8(static_cast(msg_type)); + write_u32(0); + write_u8(static_cast(msg_type)); } -message::message(message && other) +messagewriter::messagewriter(messagewriter && other) { this->id = other.id; this->data = std::move(other.data); } -message& message::operator=(message && other) +messagewriter& messagewriter::operator=(messagewriter && other) { if (this != &other) { @@ -29,41 +31,41 @@ message& message::operator=(message && other) return *this; } -void message::set_id(uint32_t value) +void messagewriter::set_id(uint32_t value) { - id = id; + id = value; data[LWS_PRE ] = (id >> 24) & 0xff; data[LWS_PRE + 1] = (id >> 16) & 0xff; data[LWS_PRE + 2] = (id >> 8) & 0xff; data[LWS_PRE + 3] = id & 0xff; } -uint32_t message::get_id() const +uint32_t messagewriter::get_id() const { return id; } -void message::add_bool(bool value) +void messagewriter::write_bool(bool value) { data.push_back(value ? 0x01 : 0x00); } -void message::add_u8(uint8_t value) +void messagewriter::write_u8(uint8_t value) { data.push_back(value); } -void message::add_i8(int8_t value) +void messagewriter::write_i8(int8_t value) { data.push_back(static_cast(value)); } -void message::add_i32(int32_t value) +void messagewriter::write_i32(int32_t value) { - add_u32((static_cast(value))); + write_u32((static_cast(value))); } -void message::add_u32(uint32_t value) +void messagewriter::write_u32(uint32_t value) { auto const offset = data.size(); data.resize(offset + 4); @@ -73,7 +75,7 @@ void message::add_u32(uint32_t value) data[offset + 3] = value & 0xff; } -void message::add_u64(uint64_t value) +void messagewriter::write_u64(uint64_t value) { auto const offset = data.size(); data.resize(offset + 8); @@ -87,15 +89,15 @@ void message::add_u64(uint64_t value) data[offset + 7] = value & 0xff; } -void message::add_str(std::string const &value) +void messagewriter::write_str(std::string const &value) { - add_data(value.data(), value.size()); + write_data(value.data(), value.size()); } -void message::add_data(char const * buffer, size_t size) +void messagewriter::write_data(char const * buffer, size_t size) { uint32_t const effective_size = size & 0xffffffff; - add_u32(effective_size); + write_u32(effective_size); if (size > 0) { @@ -107,17 +109,24 @@ void message::add_data(char const * buffer, size_t size) } } -void message::add_strings(std::vector const & list) +void messagewriter::write_strings(std::vector const & list) { uint32_t const count = list.size() & 0xffffffff; - add_u32(count); + write_u32(count); for (auto const & item: list) { - add_str(item); + write_str(item); } } -unsigned char * message::get_data(size_t &size) +void messagewriter::write_access_mode(int value) +{ + access_mode mode = access_mode::from_int(value); + write_i8(mode); +} + + +unsigned char * messagewriter::get_data(size_t &size) { size = data.size() - LWS_PRE; void * result = reinterpret_cast(&data.data()[LWS_PRE]); diff --git a/src/webfuse/ws/messagewriter.hpp b/src/webfuse/ws/messagewriter.hpp new file mode 100644 index 0000000..dc5e30a --- /dev/null +++ b/src/webfuse/ws/messagewriter.hpp @@ -0,0 +1,47 @@ +#ifndef WEBFUSE_MESSAGEWRITER_HPP +#define WEBFUSE_MESSAGEWRITER_HPP + +#include "webfuse/message_type.hpp" + +#include +#include +#include + +namespace webfuse +{ + +class messagewriter +{ + messagewriter(messagewriter const &) = delete; + messagewriter& operator=(messagewriter const &) = delete; +public: + explicit messagewriter(message_type msg_type); + ~messagewriter() = default; + messagewriter(messagewriter && other); + messagewriter& operator=(messagewriter && other); + + void set_id(uint32_t value); + uint32_t get_id() const; + + void write_bool(bool value); + void write_u8(uint8_t value); + void write_i8(int8_t value); + void write_i32(int32_t value); + void write_u32(uint32_t value); + void write_u64(uint64_t value); + void write_str(std::string const &value); + void write_data(char const * buffer, size_t size); + void write_strings(std::vector const & list); + + void write_access_mode(int value); + + unsigned char * get_data(size_t &size); + +private: + uint32_t id; + std::vector data; +}; + +} + +#endif diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 42eb4c2..499624a 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -1,5 +1,4 @@ #include "webfuse/ws/server.hpp" -#include "webfuse/ws/message.hpp" #include @@ -25,10 +24,12 @@ namespace struct user_data { struct lws * connection = nullptr; + std::string current_message; std::mutex mut; - std::queue requests; - std::unordered_map> pending_responses; + uint32_t id = 0; + std::queue requests; + std::unordered_map> pending_responses; }; } @@ -68,11 +69,49 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_RECEIVE: std::cout << "lws: receive "<< std::endl; + { + auto * fragment = reinterpret_cast(in); + data->current_message.append(fragment, len); + if (lws_is_final_fragment(wsi)) + { + try + { + webfuse::messagereader reader(data->current_message); + uint32_t id = reader.read_u32(); + uint8_t message_type = reader.read_u8(); + + std::lock_guard lock(data->mut); + auto it = data->pending_responses.find(id); + if (it != data->pending_responses.end()) + { + std::cout << "propagate message" << std::endl; + it->second.set_value(std::move(reader)); + data->pending_responses.erase(it); + } + else + { + // ToDo: log request not found + std::cout << "warning: request not found: id=" << id << std::endl; + for(auto const & entry: data->pending_responses) + { + std::cout << "\t" << entry.first << std::endl; + } + } + } + catch(...) + { + // ToDo: log invalid message + std::cout << "warning: invalid message" << std::endl; + } + + + } + } break; case LWS_CALLBACK_SERVER_WRITEABLE: std::cout << "lws: server writable "<< std::endl; { - webfuse::message msg(webfuse::message_type::access_req); + webfuse::messagewriter writer(webfuse::message_type::access_req); bool has_msg = false; bool has_more = false; @@ -82,7 +121,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, if (has_msg) { has_msg = true; - msg = std::move(data->requests.front()); + writer = std::move(data->requests.front()); data->requests.pop(); has_more = !(data->requests.empty()); @@ -92,7 +131,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, if (has_msg) { size_t size; - unsigned char * raw_data = msg.get_data(size); + unsigned char * raw_data = writer.get_data(size); int const rc = lws_write(data->connection, raw_data, size, LWS_WRITE_BINARY); } @@ -148,11 +187,12 @@ public: { if (nullptr != data.connection) { + std::cout << "request write" << std::endl; lws_callback_on_writable(data.connection); } else { - data.requests = std::move(std::queue()); + data.requests = std::move(std::queue()); data.pending_responses.clear(); } } @@ -172,6 +212,16 @@ public: lws_context_destroy(context); } + uint32_t next_id() + { + data.id++; + if (0 == data.id) + { + data.id = 1; + } + return data.id; + } + std::thread thread; std::atomic shutdown_requested; lws_protocols protocols[2]; @@ -209,26 +259,27 @@ ws_server& ws_server::operator=(ws_server && other) return *this; } -void ws_server::perform(message msg) +messagereader ws_server::perform(messagewriter writer) { - std::future f; + std::future f; { - std::promise p; + std::promise p; f = p.get_future(); std::lock_guard lock(d->data.mut); - d->data.requests.emplace(std::move(msg)); - d->data.pending_responses.emplace(42, std::move(p)); + uint32_t id = d->next_id(); + writer.set_id(id); + d->data.requests.emplace(std::move(writer)); + d->data.pending_responses.emplace(id, std::move(p)); } lws_cancel_service(d->context); - if(std::future_status::timeout == f.wait_for(std::chrono::seconds(1))) + if(std::future_status::timeout == f.wait_for(std::chrono::seconds(10))) { throw std::runtime_error("timeout"); } - std::string resp = f.get(); - throw std::runtime_error("not implemented"); + return std::move(f.get()); } diff --git a/src/webfuse/ws/server.hpp b/src/webfuse/ws/server.hpp index c040fc9..84766b2 100644 --- a/src/webfuse/ws/server.hpp +++ b/src/webfuse/ws/server.hpp @@ -2,7 +2,8 @@ #define WEBFUSE_WSSERVER_HPP #include "webfuse/ws/config.hpp" -#include "webfuse/ws/message.hpp" +#include "webfuse/ws/messagewriter.hpp" +#include "webfuse/ws/messagereader.hpp" #include #include @@ -21,7 +22,7 @@ public: ws_server(ws_server && other); ws_server& operator=(ws_server && other); - void perform(message msg); + messagereader perform(messagewriter writer); private: class detail; detail * d; From bd57b1a8404ab20f02fec5c8a14780c4201a4a28 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 20 Nov 2022 13:36:16 +0100 Subject: [PATCH 15/91] fixed file permissions --- src/webfuse/filesystem/empty_filesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index 6552d2e..c9389e2 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -22,7 +22,7 @@ int empty_filesystem::getattr(std::string const & path, struct stat * attr) { attr->st_ino = 1; attr->st_nlink = 1; - attr->st_mode = S_IFDIR | 0x444; + attr->st_mode = S_IFDIR | 0555; return 0; } else From c78acee4a2e2b24f33655b165f89a47cb2c28a8e Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 20 Nov 2022 13:36:38 +0100 Subject: [PATCH 16/91] removed obsolete files --- CMakeLists.txt | 7 -- src/webfuse/filesystem/fileattributes.cpp | 46 -------- src/webfuse/filesystem/fileattributes.hpp | 37 ------- src/webfuse/filesystem/filehandle.hpp | 16 --- src/webfuse/filesystem/filetime.cpp | 43 -------- src/webfuse/filesystem/filetime.hpp | 25 ----- src/webfuse/filesystem/groupid.cpp | 27 ----- src/webfuse/filesystem/groupid.hpp | 25 ----- src/webfuse/filesystem/userid.cpp | 28 ----- src/webfuse/filesystem/userid.hpp | 25 ----- src/webfuse/ws/messagereader.cpp | 1 - .../filesystem/test_fileattributes.cpp | 101 ------------------ test-src/webfuse/filesystem/test_groupid.cpp | 27 ----- test-src/webfuse/filesystem/test_userid.cpp | 27 ----- 14 files changed, 435 deletions(-) delete mode 100644 src/webfuse/filesystem/fileattributes.cpp delete mode 100644 src/webfuse/filesystem/fileattributes.hpp delete mode 100644 src/webfuse/filesystem/filehandle.hpp delete mode 100644 src/webfuse/filesystem/filetime.cpp delete mode 100644 src/webfuse/filesystem/filetime.hpp delete mode 100644 src/webfuse/filesystem/groupid.cpp delete mode 100644 src/webfuse/filesystem/groupid.hpp delete mode 100644 src/webfuse/filesystem/userid.cpp delete mode 100644 src/webfuse/filesystem/userid.hpp delete mode 100644 test-src/webfuse/filesystem/test_fileattributes.cpp delete mode 100644 test-src/webfuse/filesystem/test_groupid.cpp delete mode 100644 test-src/webfuse/filesystem/test_userid.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a30fb92..4298642 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,11 +14,7 @@ add_library(webfuse_static STATIC src/webfuse/filesystem/status.cpp src/webfuse/filesystem/accessmode.cpp src/webfuse/filesystem/openflags.cpp - src/webfuse/filesystem/userid.cpp - src/webfuse/filesystem/groupid.cpp src/webfuse/filesystem/filemode.cpp - src/webfuse/filesystem/filetime.cpp - src/webfuse/filesystem/fileattributes.cpp src/webfuse/filesystem/filesystem_statistics.cpp src/webfuse/filesystem/empty_filesystem.cpp src/webfuse/ws/config.cpp @@ -46,9 +42,6 @@ if(NOT(WITHOUT_TEST)) test-src/webfuse/filesystem/test_accessmode.cpp test-src/webfuse/filesystem/test_openflags.cpp test-src/webfuse/filesystem/test_filemode.cpp - test-src/webfuse/filesystem/test_userid.cpp - test-src/webfuse/filesystem/test_groupid.cpp - test-src/webfuse/filesystem/test_fileattributes.cpp ) target_include_directories(alltests PRIVATE ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/filesystem/fileattributes.cpp b/src/webfuse/filesystem/fileattributes.cpp deleted file mode 100644 index d6fb3b7..0000000 --- a/src/webfuse/filesystem/fileattributes.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "webfuse/filesystem/fileattributes.hpp" - -namespace webfuse -{ - -file_attributes::file_attributes() -: inode(0) -, nlink(0) -, rdev(0) -, size(0) -, blocks(0) -{ - -} - -file_attributes::file_attributes(struct stat const & other) -{ - inode = static_cast(other.st_ino); - nlink = static_cast(other.st_nlink); - mode = filemode::from_mode(other.st_mode); - uid = user_id::from_uid(other.st_uid); - gid = group_id::from_gid(other.st_gid); - rdev = static_cast(other.st_rdev); - size = static_cast(other.st_size); - blocks = static_cast(other.st_blocks); - atime = filetime::from_timespec(other.st_atim); - mtime = filetime::from_timespec(other.st_mtim); - ctime = filetime::from_timespec(other.st_ctim); -} - -void file_attributes::to_stat(struct stat & other) const -{ - other.st_ino = inode; - other.st_nlink = nlink; - other.st_mode = mode.to_mode(); - other.st_uid = uid.to_uid(); - other.st_gid = gid.to_gid(); - other.st_rdev = rdev; - other.st_size = size; - other.st_blocks = blocks; - atime.to_timespec(other.st_atim); - mtime.to_timespec(other.st_mtim); - ctime.to_timespec(other.st_ctim); -} - -} \ No newline at end of file diff --git a/src/webfuse/filesystem/fileattributes.hpp b/src/webfuse/filesystem/fileattributes.hpp deleted file mode 100644 index ba8f7fe..0000000 --- a/src/webfuse/filesystem/fileattributes.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef WEBFUSE_FILEATTRIBUTES_HPP -#define WEBFUSE_FILEATTRIBUTES_HPP - -#include "webfuse/filesystem/filemode.hpp" -#include "webfuse/filesystem/filetime.hpp" -#include "webfuse/filesystem/userid.hpp" -#include "webfuse/filesystem/groupid.hpp" -#include -#include - -namespace webfuse -{ - -class file_attributes -{ -public: - file_attributes(); - - explicit file_attributes(struct stat const & other); - void to_stat(struct stat & other) const; - - uint64_t inode; - uint64_t nlink; - filemode mode; - user_id uid; - group_id gid; - uint64_t rdev; - uint64_t size; - uint64_t blocks; - filetime atime; - filetime mtime; - filetime ctime; -}; - -} - -#endif diff --git a/src/webfuse/filesystem/filehandle.hpp b/src/webfuse/filesystem/filehandle.hpp deleted file mode 100644 index 620074e..0000000 --- a/src/webfuse/filesystem/filehandle.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef WEBFUSE_FILEHANDLE_HPP -#define WEBFUSE_FILEHANDLE_HPP - -#include - -namespace webfuse -{ - -using filehandle = uint64_t; - -constexpr filehandle const invalid_handle = (filehandle) -1; - - -} - -#endif diff --git a/src/webfuse/filesystem/filetime.cpp b/src/webfuse/filesystem/filetime.cpp deleted file mode 100644 index a10f14e..0000000 --- a/src/webfuse/filesystem/filetime.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "webfuse/filesystem/filetime.hpp" - -namespace webfuse -{ - -filetime::filetime() -: seconds(0) -, nsec(0) -{ - -} - -filetime filetime::from_timespec(timespec const & other) -{ - filetime result; - result.seconds = static_cast(other.tv_sec); - result.nsec = static_cast(other.tv_nsec); - - return result; -} - -filetime filetime::from_time(time_t const & other) -{ - filetime result; - result.seconds = static_cast(other); - result.nsec = 0; - - return result; -} - -void filetime::to_timespec(timespec & other) const -{ - other.tv_sec = seconds; - other.tv_nsec = static_cast(nsec); -} - -time_t filetime::to_time() const -{ - return static_cast(seconds); -} - - -} \ No newline at end of file diff --git a/src/webfuse/filesystem/filetime.hpp b/src/webfuse/filesystem/filetime.hpp deleted file mode 100644 index 8f77ff8..0000000 --- a/src/webfuse/filesystem/filetime.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef WEBFUSE_FILETIME_HPP -#define WEBFUSE_FILETIME_HPP - -#include -#include - -namespace webfuse -{ - -class filetime -{ -public: - filetime(); - static filetime from_timespec(timespec const & other); - static filetime from_time(time_t const & other); - void to_timespec(timespec & other) const; - time_t to_time() const; - - uint64_t seconds; - uint32_t nsec; -}; - -} - -#endif diff --git a/src/webfuse/filesystem/groupid.cpp b/src/webfuse/filesystem/groupid.cpp deleted file mode 100644 index 84583e5..0000000 --- a/src/webfuse/filesystem/groupid.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "webfuse/filesystem/groupid.hpp" - -namespace webfuse -{ - -group_id::group_id(uint32_t value) -: value_(value) -{ - -} - -group_id::operator uint32_t() const -{ - return value_; -} - -group_id group_id::from_gid(gid_t value) -{ - return group_id(static_cast(value)); -} - -gid_t group_id::to_gid() const -{ - return static_cast(value_); -} - -} \ No newline at end of file diff --git a/src/webfuse/filesystem/groupid.hpp b/src/webfuse/filesystem/groupid.hpp deleted file mode 100644 index e3b8ac0..0000000 --- a/src/webfuse/filesystem/groupid.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef WEBFUSE_GROUPID_HPP -#define WEBFUSE_GROUPID_HPP - -#include -#include - -namespace webfuse -{ - -class group_id -{ -public: - static constexpr uint32_t const invalid = (uint32_t) -1; - - explicit group_id(uint32_t value = invalid); - operator uint32_t() const; - static group_id from_gid(gid_t value); - gid_t to_gid() const; -private: - uint32_t value_; -}; - -} - -#endif diff --git a/src/webfuse/filesystem/userid.cpp b/src/webfuse/filesystem/userid.cpp deleted file mode 100644 index 36f37f0..0000000 --- a/src/webfuse/filesystem/userid.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "webfuse/filesystem/userid.hpp" - -namespace webfuse -{ - -user_id::user_id(uint32_t value) -: value_(value) -{ - -} - -user_id::operator uint32_t() const -{ - return value_; -} - -user_id user_id::from_uid(uid_t value) -{ - return user_id(static_cast(value)); -} - -uid_t user_id::to_uid() const -{ - return static_cast(value_); -} - - -} \ No newline at end of file diff --git a/src/webfuse/filesystem/userid.hpp b/src/webfuse/filesystem/userid.hpp deleted file mode 100644 index dc43673..0000000 --- a/src/webfuse/filesystem/userid.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef WEBFUSE_USERID_HPP -#define WEBFUSE_USERID_HPP - -#include -#include - -namespace webfuse -{ - -class user_id -{ -public: - static constexpr uint32_t const invalid = (uint32_t) -1; - - explicit user_id(uint32_t value = invalid); - operator uint32_t() const; - static user_id from_uid(uid_t value); - uid_t to_uid() const; -private: - uint32_t value_; -}; - -} - -#endif diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp index b073cb8..9f3439b 100644 --- a/src/webfuse/ws/messagereader.cpp +++ b/src/webfuse/ws/messagereader.cpp @@ -1,6 +1,5 @@ #include "webfuse/ws/messagereader.hpp" #include "webfuse/filesystem/status.hpp" -#include "webfuse/filesystem/fileattributes.hpp" #include "webfuse/filesystem/filemode.hpp" #include diff --git a/test-src/webfuse/filesystem/test_fileattributes.cpp b/test-src/webfuse/filesystem/test_fileattributes.cpp deleted file mode 100644 index f0ecd2e..0000000 --- a/test-src/webfuse/filesystem/test_fileattributes.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "webfuse/filesystem/fileattributes.hpp" - -#include - -using webfuse::file_attributes; -using webfuse::user_id; -using webfuse::group_id; -using webfuse::filemode; - -TEST(file_attibutes, create_empty) -{ - file_attributes attributes; - - ASSERT_EQ(0, attributes.inode); - ASSERT_EQ(0, attributes.nlink); - ASSERT_EQ(0, attributes.mode); - ASSERT_EQ(user_id::invalid, attributes.uid); - ASSERT_EQ(group_id::invalid, attributes.gid); - ASSERT_EQ(0, attributes.rdev); - ASSERT_EQ(0, attributes.size); - ASSERT_EQ(0, attributes.blocks); - ASSERT_EQ(0, attributes.atime.seconds); - ASSERT_EQ(0, attributes.atime.nsec); - ASSERT_EQ(0, attributes.mtime.seconds); - ASSERT_EQ(0, attributes.mtime.nsec); - ASSERT_EQ(0, attributes.ctime.seconds); - ASSERT_EQ(0, attributes.ctime.nsec); -} - -TEST(file_attibutes, from_stat) -{ - struct stat info; - info.st_ino = 1; - info.st_nlink = 2; - info.st_mode = S_IFREG | 0644; - info.st_uid = 1000; - info.st_gid = 1234; - info.st_rdev = 0; - info.st_size = 21 * 1024; - info.st_blocks = 42; - info.st_atim.tv_sec = 1; - info.st_atim.tv_nsec = 2; - info.st_mtim.tv_sec = 3; - info.st_mtim.tv_nsec = 4; - info.st_ctim.tv_sec = 5; - info.st_ctim.tv_nsec = 6; - - file_attributes attributes(info); - - ASSERT_EQ(info.st_ino, attributes.inode); - ASSERT_EQ(info.st_nlink, attributes.nlink); - ASSERT_EQ(info.st_mode, attributes.mode.to_mode()); - ASSERT_EQ(info.st_uid, attributes.uid.to_uid()); - ASSERT_EQ(info.st_gid, attributes.gid.to_gid()); - ASSERT_EQ(info.st_rdev, attributes.rdev); - ASSERT_EQ(info.st_size, attributes.size); - ASSERT_EQ(info.st_blocks, attributes.blocks); - ASSERT_EQ(info.st_atim.tv_sec, attributes.atime.seconds); - ASSERT_EQ(info.st_atim.tv_nsec, attributes.atime.nsec); - ASSERT_EQ(info.st_mtim.tv_sec, attributes.mtime.seconds); - ASSERT_EQ(info.st_mtim.tv_nsec, attributes.mtime.nsec); - ASSERT_EQ(info.st_ctim.tv_sec, attributes.ctime.seconds); - ASSERT_EQ(info.st_ctim.tv_nsec, attributes.ctime.nsec); -} - -TEST(file_attibutes, to_stat) -{ - file_attributes attributes; - attributes.inode = 1; - attributes.nlink = 2; - attributes.mode = filemode(S_IFREG | 0644); - attributes.uid = user_id(1000); - attributes.gid = group_id(1234); - attributes.rdev = 0; - attributes.size = 21 * 1024; - attributes.blocks = 42; - attributes.atime.seconds = 1; - attributes.atime.nsec = 2; - attributes.mtime.seconds = 3; - attributes.mtime.nsec = 4; - attributes.ctime.seconds = 5; - attributes.ctime.nsec = 6; - - struct stat info; - attributes.to_stat(info); - - ASSERT_EQ(attributes.inode, info.st_ino); - ASSERT_EQ(attributes.nlink, info.st_nlink); - ASSERT_EQ(attributes.mode.to_mode(), info.st_mode); - ASSERT_EQ(attributes.uid.to_uid(), info.st_uid); - ASSERT_EQ(attributes.gid.to_gid(), info.st_gid); - ASSERT_EQ(attributes.rdev, info.st_rdev); - ASSERT_EQ(attributes.size, info.st_size); - ASSERT_EQ(attributes.blocks, info.st_blocks); - ASSERT_EQ(attributes.atime.seconds, info.st_atim.tv_sec); - ASSERT_EQ(attributes.atime.nsec, info.st_atim.tv_nsec); - ASSERT_EQ(attributes.mtime.seconds, info.st_mtim.tv_sec); - ASSERT_EQ(attributes.mtime.nsec, info.st_mtim.tv_nsec); - ASSERT_EQ(attributes.ctime.seconds, info.st_ctim.tv_sec); - ASSERT_EQ(attributes.ctime.nsec, info.st_ctim.tv_nsec); -} \ No newline at end of file diff --git a/test-src/webfuse/filesystem/test_groupid.cpp b/test-src/webfuse/filesystem/test_groupid.cpp deleted file mode 100644 index 0b091f5..0000000 --- a/test-src/webfuse/filesystem/test_groupid.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "webfuse/filesystem/groupid.hpp" -#include - -using webfuse::group_id; - -TEST(group_id, invalid) -{ - group_id invalid_group; - - ASSERT_EQ(group_id::invalid, invalid_group); -} - -TEST(group_id, to_gid) -{ - group_id group(69); - gid_t id = group.to_gid(); - - ASSERT_EQ(69, id); -} - -TEST(group_id, from_gid) -{ - gid_t id = 99; - auto group = group_id::from_gid(id); - - ASSERT_EQ(99, group); -} diff --git a/test-src/webfuse/filesystem/test_userid.cpp b/test-src/webfuse/filesystem/test_userid.cpp deleted file mode 100644 index cb555d4..0000000 --- a/test-src/webfuse/filesystem/test_userid.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "webfuse/filesystem/userid.hpp" -#include - -using webfuse::user_id; - -TEST(user_id, invalid) -{ - user_id invalid_user; - - ASSERT_EQ(user_id::invalid, invalid_user); -} - -TEST(user_id, to_uid) -{ - user_id user(42); - uid_t id = user.to_uid(); - - ASSERT_EQ(42, id); -} - -TEST(user_id, from_uid) -{ - uid_t id = 23; - auto user = user_id::from_uid(id); - - ASSERT_EQ(23, user); -} From 6f6555cc90bbc3727064a84042ea4d4bf99c7164 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 20 Nov 2022 13:38:54 +0100 Subject: [PATCH 17/91] removed debug output --- script/provider.py | 1 - src/webfuse/filesystem.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/script/provider.py b/script/provider.py index 564bcd0..0bcc816 100755 --- a/script/provider.py +++ b/script/provider.py @@ -142,7 +142,6 @@ class MessageWriter: self.write_str(value) def get_bytes(self): - print(self.buffer) return bytearray(self.buffer) diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index d895aac..a8517fa 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -47,7 +47,6 @@ int filesystem::getattr(std::string const & path, struct stat * attr) } catch(...) { - puts("getattr: failed"); return fallback.getattr(path, attr); } } From 2479a6c2d6e4ea010608f87d0a9d1744173764eb Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 20 Nov 2022 14:48:56 +0100 Subject: [PATCH 18/91] completed filesystem implemenation --- src/webfuse/filesystem.cpp | 149 +++++++++++++++++++++++-------- src/webfuse/ws/messagereader.cpp | 13 +++ src/webfuse/ws/messagereader.hpp | 2 + src/webfuse/ws/messagewriter.cpp | 36 ++++++++ src/webfuse/ws/messagewriter.hpp | 5 ++ 5 files changed, 169 insertions(+), 36 deletions(-) diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index a8517fa..574f990 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -1,5 +1,8 @@ #include "webfuse/filesystem.hpp" + #include +#include +#include namespace webfuse { @@ -56,8 +59,14 @@ int filesystem::readlink(std::string const & path, std::string & out) try { messagewriter req(message_type::readlink_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + auto reader = proxy.perform(std::move(req)); + int const result = reader.read_result(); + if (0 == result) + { + out = reader.read_str(); + } + return result; } catch(...) { @@ -70,8 +79,10 @@ int filesystem::symlink(std::string const & target, std::string const & linkpath try { messagewriter req(message_type::symlink_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(target); + req.write_str(linkpath); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -84,8 +95,10 @@ int filesystem::link(std::string const & old_path, std::string const & new_path) try { messagewriter req(message_type::link_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(old_path); + req.write_str(new_path); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -98,8 +111,11 @@ int filesystem::rename(std::string const & old_path, std::string const & new_pat try { messagewriter req(message_type::rename_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(old_path); + req.write_str(new_path); + req.write_rename_flags(flags); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -112,8 +128,10 @@ int filesystem::chmod(std::string const & path, mode_t mode) try { messagewriter req(message_type::chmod_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_mode(mode); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -126,8 +144,11 @@ int filesystem::chown(std::string const & path, uid_t uid, gid_t gid) try { messagewriter req(message_type::chown_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_uid(uid); + req.write_gid(gid); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -140,8 +161,11 @@ int filesystem::truncate(std::string const & path, uint64_t size, uint64_t handl try { messagewriter req(message_type::truncate_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_u64(size); + req.write_u64(handle); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -154,8 +178,11 @@ int filesystem::fsync(std::string const & path, bool is_datasync, uint64_t handl try { messagewriter req(message_type::fsync_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_bool(is_datasync); + req.write_u64(handle); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -168,8 +195,15 @@ int filesystem::open(std::string const & path, int flags, uint64_t & handle) try { messagewriter req(message_type::open_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_openflags(flags); + auto reader = proxy.perform(std::move(req)); + int result = reader.read_result(); + if (0 == result) + { + handle = reader.read_u64(); + } + return result; } catch(...) { @@ -182,8 +216,11 @@ int filesystem::mknod(std::string const & path, mode_t mode, dev_t rdev) try { messagewriter req(message_type::mknod_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_mode(mode); + req.write_u64(rdev); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -196,8 +233,15 @@ int filesystem::create(std::string const & path, mode_t mode, uint64_t & handle) try { messagewriter req(message_type::create_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_mode(mode); + auto reader = proxy.perform(std::move(req)); + int result = reader.read_result(); + if (0 == result) + { + handle = reader.read_u64(); + } + return result; } catch(...) { @@ -210,8 +254,10 @@ int filesystem::release(std::string const & path, uint64_t handle) try { messagewriter req(message_type::release_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_u64(handle); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -224,8 +270,9 @@ int filesystem::unlink(std::string const & path) try { messagewriter req(message_type::unlink_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -238,8 +285,25 @@ int filesystem::read(std::string const & path, char * buffer, size_t buffer_size try { messagewriter req(message_type::read_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_u32(buffer_size); + req.write_u64(offset); + req.write_u64(handle); + auto reader = proxy.perform(std::move(req)); + int result = reader.read_result(); + if (result > 0) + { + std::string data = reader.read_bytes(); + if (data.size() <= buffer_size) + { + memcpy(reinterpret_cast(buffer), reinterpret_cast(data.data()), data.size()); + } + else + { + throw std::runtime_error("invalid message"); + } + } + return result; } catch(...) { @@ -252,8 +316,12 @@ int filesystem::write(std::string const & path, char const * buffer, size_t buff try { messagewriter req(message_type::write_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_data(buffer, buffer_size); + req.write_u64(offset); + req.write_u64(handle); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -266,8 +334,10 @@ int filesystem::mkdir(std::string const & path, mode_t mode) try { messagewriter req(message_type::mkdir_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + req.write_mode(mode); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -300,8 +370,9 @@ int filesystem::rmdir(std::string const & path) try { messagewriter req(message_type::rmdir_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); } catch(...) { @@ -314,8 +385,14 @@ int filesystem::statfs(std::string const & path, struct statvfs * statistics) try { messagewriter req(message_type::statfs_req); - proxy.perform(std::move(req)); - return -ENOENT; + req.write_str(path); + auto reader = proxy.perform(std::move(req)); + int result = reader.read_result(); + if (0 == result) + { + reader.read_statistics(statistics); + } + return result; } catch(...) { diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp index 9f3439b..f04c52a 100644 --- a/src/webfuse/ws/messagereader.cpp +++ b/src/webfuse/ws/messagereader.cpp @@ -55,6 +55,19 @@ void messagereader::read_attr(struct stat * attr) attr->st_ctim.tv_nsec = static_cast(read_u32()); } +void messagereader::read_statistics(struct statvfs * statistics) +{ + statistics->f_bsize = read_u64(); + statistics->f_frsize = read_u64(); + statistics->f_blocks = read_u64(); + statistics->f_bfree = read_u64(); + statistics->f_bavail = read_u64(); + statistics->f_files = read_u64(); + statistics->f_ffree = read_u64(); + statistics->f_namemax = read_u64(); +} + + mode_t messagereader::read_mode() { filemode mode(read_u32()); diff --git a/src/webfuse/ws/messagereader.hpp b/src/webfuse/ws/messagereader.hpp index 6cdceeb..9157d8c 100644 --- a/src/webfuse/ws/messagereader.hpp +++ b/src/webfuse/ws/messagereader.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -24,6 +25,7 @@ public: int read_result(); void read_attr(struct stat * attr); + void read_statistics(struct statvfs * statistics); mode_t read_mode(); uint8_t read_u8(); diff --git a/src/webfuse/ws/messagewriter.cpp b/src/webfuse/ws/messagewriter.cpp index 86822bb..7cdcbd8 100644 --- a/src/webfuse/ws/messagewriter.cpp +++ b/src/webfuse/ws/messagewriter.cpp @@ -1,11 +1,16 @@ #include "webfuse/ws/messagewriter.hpp" #include "webfuse/filesystem/accessmode.hpp" +#include "webfuse/filesystem/filemode.hpp" +#include "webfuse/filesystem/openflags.hpp" #include namespace webfuse { +constexpr uint8_t const rename_noreplace = 0x01; +constexpr uint8_t const rename_exchange = 0x02; + messagewriter::messagewriter(message_type msg_type) : id(0) , data(LWS_PRE) @@ -125,6 +130,37 @@ void messagewriter::write_access_mode(int value) write_i8(mode); } +void messagewriter::write_rename_flags(unsigned int value) +{ + uint8_t flags = 0; + if (RENAME_NOREPLACE == (value & RENAME_NOREPLACE)) { flags |= rename_noreplace; } + if (RENAME_EXCHANGE == (value & RENAME_EXCHANGE )) { flags |= rename_exchange; } + + write_u8(flags); +} + +void messagewriter::write_mode(mode_t value) +{ + filemode mode = filemode::from_mode(value); + write_u32(mode); +} + +void messagewriter::write_uid(uid_t value) +{ + write_u32(static_cast(value)); +} + +void messagewriter::write_gid(gid_t value) +{ + write_u32(static_cast(value)); +} + +void messagewriter::write_openflags(int value) +{ + openflags flags = openflags::from_int(value); + write_i32(flags); +} + unsigned char * messagewriter::get_data(size_t &size) { diff --git a/src/webfuse/ws/messagewriter.hpp b/src/webfuse/ws/messagewriter.hpp index dc5e30a..c44ab11 100644 --- a/src/webfuse/ws/messagewriter.hpp +++ b/src/webfuse/ws/messagewriter.hpp @@ -34,6 +34,11 @@ public: void write_strings(std::vector const & list); void write_access_mode(int value); + void write_rename_flags(unsigned int value); + void write_mode(mode_t value); + void write_uid(uid_t value); + void write_gid(gid_t value); + void write_openflags(int value); unsigned char * get_data(size_t &size); From da22bdf51881b8fde56f13f8ed99e1cced740bd9 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 20 Nov 2022 21:30:26 +0100 Subject: [PATCH 19/91] added some provider implementation --- script/provider.py | 174 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 2 deletions(-) diff --git a/script/provider.py b/script/provider.py index 0bcc816..4703f44 100755 --- a/script/provider.py +++ b/script/provider.py @@ -2,6 +2,7 @@ import asyncio import os +import stat import websockets import errno @@ -51,6 +52,18 @@ ERRNO = { -errno.EXDEV : -18 } +RENAME_NOREPLACE = 0x01 +RENAME_EXCHANGE = 0x02 + +MODE_REG = 0o100000 +MODE_DIR = 0o040000 +MODE_CHR = 0o020000 +MODE_BLK = 0o060000 +MODE_FIFO = 0o010000 +MODE_LNK = 0o120000 +MODE_SOCK = 0o140000 + + class MessageReader: def __init__(self, buffer): self.buffer = buffer @@ -87,6 +100,22 @@ class MessageReader: mode += os.X_OK if X_OK == (value & X_OK) else 0 return mode + def read_rename_flags(self): + return self.read_u8() + + def read_mode(self): + value = self.read_u32() + mode = value & 0o7777 + mode += stat.S_IFREG if MODE_REG == (value & MODE_REG ) else 0 + mode += stat.S_IFDIR if MODE_DIR == (value & MODE_DIR ) else 0 + mode += stat.S_IFCHR if MODE_CHR == (value & MODE_CHR ) else 0 + mode += stat.S_IFBLK if MODE_BLK == (value & MODE_BLK ) else 0 + mode += stat.S_IFFIFO if MODE_FIFO == (value & MODE_FIFO) else 0 + mode += stat.S_IFLNK if MODE_LNK == (value & MODE_LNK ) else 0 + mode += stat.S_IFSOCK if MODE_SOCK == (value & MODE_SOCK) else 0 + return mode + + class MessageWriter: def __init__(self, message_id, message_type): @@ -152,7 +181,25 @@ class FilesystemProvider: self.commands = { 0x01: FilesystemProvider.access, 0x02: FilesystemProvider.getattr, - 0x13: FilesystemProvider.readdir + 0x03: FilesystemProvider.readlink, + 0x04: FilesystemProvider.symlink, + 0x05: FilesystemProvider.link, + 0x06: FilesystemProvider.rename, + 0x07: FilesystemProvider.chmod, + 0x08: FilesystemProvider.chown, + 0x09: FilesystemProvider.truncate, + 0x0a: FilesystemProvider.fsync, + 0x0b: FilesystemProvider.open, + 0x0c: FilesystemProvider.mknod, + 0x0d: FilesystemProvider.create, + 0x0e: FilesystemProvider.release, + 0x0f: FilesystemProvider.unlink, + 0x10: FilesystemProvider.read, + 0x11: FilesystemProvider.write, + 0x12: FilesystemProvider.mkdir, + 0x13: FilesystemProvider.readdir, + 0x14: FilesystemProvider.rmdir, + 0x15: FilesystemProvider.statfs } async def run(self): @@ -205,7 +252,124 @@ class FilesystemProvider: writer.write_u32(attr.st_mtime_ns) writer.write_u64(int(attr.st_ctime)) writer.write_u32(attr.st_ctime_ns) - + + def readlink(self, reader, writer): + path = reader.read_path(self.root) + try: + link = os.readlink(path) + except OSError as ex: + writer.write_result(-ex.errno) + return + writer.write_result(0) + writer.write_str(link) + + def symlink(self, reader, writer): + source = reader.read_str() + target = reader.read_path(self.root) + result = 0 + try: + os.symlink(source, target) + except OSError as ex: + result = -ex.errno + writer.write_result(result) + + def link(self, reader, writer): + source = reader.read_path(self.root) + target = reader.read_path(self.root) + result = 0 + try: + os.link(source, target) + except OSError as ex: + result = -ex.errno + writer.write_result(result) + + def rename(self, reader, writer): + source = reader.read_path(self.root) + target = reader.read_path(self.root) + flags = reader.read_rename_flags() + result = 0 + try: + if RENAME_EXCHANGE == (flags & RENAME_EXCHANGE): + # exchange is not supported + result = -errno.EINVAL + elif RENAME_NOREPLACE == (flags & RENAME_NOREPLACE): + os.rename(source, target) + else: + os.replace(source, target) + except OSError as ex: + result = -ex.errno + writer.write_result(result) + + def chmod(self, reader, writer): + path = reader.read_path(self.root) + mode = reader.read_mode() + result = 0 + try: + os.chmod(path, mode) + except OSError as ex: + result = -ex.errno + writer.write_result(result) + + def chown(self, reader, writer): + path = reader.read_path(self.root) + uid = reader.read_u32() + gid = reader.read_u32() + result = 0 + try: + os.chown(path, uid, gid) + except OSError as ex: + result = -ex.errno + writer.write_result(result) + + def truncate(self, reader, writer): + path = reader.read_path(self.root) + size = reader.read_u64() + fd = reader.read_u64() + result = 0 + try: + if fd != 0xffffffffffffffff: + os.ftruncate(fd, size) + else: + os.truncate(path, size) + except OSError as ex: + result = -ex.errno + writer.write_result(result) + + def fsync(self, reader, writer): + path = reader.read_path(self.root) + _ = reader.read_i32() + fd = reader.read_u64() + result = 0 + try: + os.fsync(fd) + except OSError as ex: + result = -ex.errno + writer.write_result(result) + + def open(self, reader, writer): + pass + + def mknod(self, reader, writer): + pass + + def create(self, reader, writer): + pass + + def release(self, reader, writer): + pass + + def unlink(self, reader, writer): + pass + + def read(self, reader, writer): + pass + + def write(self, reader, writer): + pass + + def mkdir(self, reader, writer): + pass + def readdir(self, reader, writer): path = reader.read_path(self.root) names = [] @@ -218,6 +382,12 @@ class FilesystemProvider: return writer.write_result(0) writer.write_strings(names) + + def rmdir(self, reader, writer): + pass + + def statfs(self, reader, writer): + pass if __name__ == '__main__': From b9d8de574326e600755c28e36850f294aeb1277e Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Tue, 22 Nov 2022 19:36:26 +0100 Subject: [PATCH 20/91] added description of most data types --- doc/protocol.md | 130 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/doc/protocol.md b/doc/protocol.md index 23763cf..cd15349 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -11,10 +11,132 @@ For instance, the uint32 value 1 will be transferred as ### Basic data types -| Data | Width | Description | -| ---- | ------ | ----------- | -| bool | 8 bit | Represents a boolean value | -| i32 | 32 bit | +| Data Type | Width | Description | +| --------- | ------ | ----------- | +| bool | 8 bit | boolean value | +| u8 | 8 bit | 8 bit unsigned integer | +| u32 | 32 bit | 32 bit unsigned integer | +| u64 | 64 bit | 64 bit unsigned integer | +| i32 | 32 bit | 32 bit signed integer | + +### Derrived integral types + +| Data Type | Base Type | Description | +| --------- | --------- | ----------- | +| uid | u32 | user ID | +| gid | u32 | group ID | +| dev | u64 | device ID | +| handle | u64 | file handle | + +### Flags and Enums + +| Data Type | Base Type | Description | +| ------------ | --------- | ------------------------- | +| result | i32 | result type of methods | +| access_mode | u32 | mode of `access` method | +| mode | u32 | file type and permissions | +| open_flags | i32 | flags of `open` method | +| rename_flags | u8 | flags of `rename` method | + +#### result + +| Value Range | Description | +| -------------- | ---------------------- | +| 0 | method call succeed | +| negative value | error code (see below) | +| positive value | amount of bytes read or written (`read` and `write` only) | + +| Error Code | Value | Description | +| ------------ | ----- | ----------- | +| E2BIG | -7 | argument list too long | +| EACCES | -13 | permission denied | +| EAGAIN | -11 | resource temporarily unavailable | +| EBADF | -9 | bad file descriptor | +| EBUSY | -16 | device or resource busy | +| EDESTADDRREQ | -89 | destination address required | +| EDQUOT | -122 | disk quota exceeded | +| EEXIST | -17 | file exists | +| EFAULT | -14 | bad address | +| EFBIG | -27 | file too large | +| EINTR | -4 | interrupt function call | +| EINVAL | -22 | invalid argument | +| EIO | -5 | input / output error | +| EISDIR | -21 | is a directory +| ELOOP | -40 | too many levels of symbolic links | +| EMFILE | -24 | too many open files | +| EMLINK | -31 | too many links | +| ENAMETOOLONG | -36 | filename too long | +| ENFILE | -23 | too many open files in system | +| ENODATA | -61 | the named attribute does not exist, or the process has not access to this attribute | +| ENODEV | -19 | no such device | +| ENOENT | -2 | no such file or directory | +| ENOMEM | -12 | not enough space / cannot allocate memory | +| ENOSPC | -28 | no space left on device | +| ENOSYS | -38 | function not implemented | +| ENOTDIR | -20 | not a directory | +| ENOTEMPTY | -39 | directory not empty | +| ENOTSUP | -95 | operation not supported | +| ENXIO | -6 | no such device or address | +| EOVERFLOW | -75 | value too large to be stored in data type | +| EPERM | -1 | operation not permitted | +| EPIPE | -32 | broken pipe | +| ERANGE | -34 | result too large | +| EROFS | -30 | read-only filesystem | +| ETXTBSY | -26 | text file busy | +| EXDEV | -18 | improper link | +| EWOULDBLOCK | -11 | resource temporarily unavailable | + + + +### Complex Types + +| Data Type | Description | +| ---------- | --------------------- | +| string | UTF-8 string | +| bytes | array of bytes | +| timestamp | date and time | +| attributes | file attributes | +| statistics | filesystem statistics | + +#### string + +| Field | Data Type | Description | +| ----- | --------- | ----------------------------- | +| size | u32 | length of the string in bytes | +| data | u8[] | string data (UTF-8) | + +#### bytes + +| Field | Data Type | Description | +| ----- | --------- | ------------------------ | +| size | u32 | length of the byte array | +| data | u8[] | array data | + +#### timestamp + +| Field | Data Type | Description | +| ------- | --------- | ----------------------------- | +| seconds | u64 | seconds sind epoch (1.1.1970) | +| nsec | u32 | nano seconds | + +#### attributes + +| Field | Data Type | Description | +| ------ | ---------- | -------------------------- | +| inode | u64 | Inode value | +| nlink | u64 | Number of hard links | +| mode | mode (u32) | file mode flags | +| uid | uid (u32) | user ID | +| gid | gid (u32) | group ID | +| rdev | dev (u64) | device ID | +| size | u64 | file size | +| blocks | u64 | number 512-byte blocks | +| atime | timestamp | time of last access | +| mtime | timestamp | time of last modification | +| ctime | timestamp | time of last status change | + +#### statistics + ## Message From fde3c15817db0c312a9705abf4c69c03db7bf93b Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 27 Nov 2022 12:42:18 +0100 Subject: [PATCH 21/91] openflags: added missing flags --- src/webfuse/filesystem/openflags.cpp | 26 +++++++++++------ src/webfuse/filesystem/openflags.hpp | 42 +++++++++++++++------------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/webfuse/filesystem/openflags.cpp b/src/webfuse/filesystem/openflags.cpp index 7a0502c..3547eaa 100644 --- a/src/webfuse/filesystem/openflags.cpp +++ b/src/webfuse/filesystem/openflags.cpp @@ -22,20 +22,25 @@ openflags openflags::from_int(int value) if (O_RDONLY == (value & O_RDONLY )) { result |= openflags::rdonly; } if (O_WRONLY == (value & O_WRONLY )) { result |= openflags::wronly; } if (O_RDWR == (value & O_RDWR )) { result |= openflags::rdwr; } + + if (O_APPEND == (value & O_APPEND )) { result |= openflags::append; } + if (O_ASYNC == (value & O_ASYNC )) { result |= openflags::async; } if (O_CLOEXEC == (value & O_CLOEXEC )) { result |= openflags::cloexec; } if (O_CREAT == (value & O_CREAT )) { result |= openflags::creat; } if (O_DIRECT == (value & O_DIRECT )) { result |= openflags::direct; } if (O_DIRECTORY == (value & O_DIRECTORY)) { result |= openflags::directory; } + if (O_DSYNC == (value & O_DSYNC )) { result |= openflags::dsync; } if (O_EXCL == (value & O_EXCL )) { result |= openflags::excl; } - if (O_NOCTTY == (value & O_NOCTTY )) { result |= openflags::noctty; } - if (O_NOFOLLOW == (value & O_NOFOLLOW )) { result |= openflags::nofollow; } - if (O_TRUNC == (value & O_TRUNC )) { result |= openflags::trunc; } - if (O_ASYNC == (value & O_ASYNC )) { result |= openflags::async; } if (O_LARGEFILE == (value & O_LARGEFILE)) { result |= openflags::largefile; } if (O_NOATIME == (value & O_NOATIME )) { result |= openflags::noatime; } + if (O_NOCTTY == (value & O_NOCTTY )) { result |= openflags::noctty; } + if (O_NOFOLLOW == (value & O_NOFOLLOW )) { result |= openflags::nofollow; } if (O_NONBLOCK == (value & O_NONBLOCK )) { result |= openflags::nonblock; } if (O_NDELAY == (value & O_NDELAY )) { result |= openflags::ndelay; } + if (O_PATH == (value & O_PATH )) { result |= openflags::path; } if (O_SYNC == (value & O_SYNC )) { result |= openflags::sync; } + if (O_TMPFILE == (value & O_TMPFILE )) { result |= openflags::tmpfile; } + if (O_TRUNC == (value & O_TRUNC )) { result |= openflags::trunc; } return openflags(result); } @@ -47,20 +52,25 @@ int openflags::to_int() const if (openflags::rdonly == (value_ & openflags::rdonly )) { result |= O_RDONLY; } if (openflags::wronly == (value_ & openflags::wronly )) { result |= O_WRONLY; } if (openflags::rdwr == (value_ & openflags::rdwr )) { result |= O_RDWR; } + + if (openflags::append == (value_ & openflags::append )) { result |= O_APPEND; } + if (openflags::async == (value_ & openflags::async )) { result |= O_ASYNC; } if (openflags::cloexec == (value_ & openflags::cloexec )) { result |= O_CLOEXEC; } if (openflags::creat == (value_ & openflags::creat )) { result |= O_CREAT; } if (openflags::direct == (value_ & openflags::direct )) { result |= O_DIRECT; } if (openflags::directory == (value_ & openflags::directory)) { result |= O_DIRECTORY; } + if (openflags::dsync == (value_ & openflags::dsync )) { result |= O_DSYNC; } if (openflags::excl == (value_ & openflags::excl )) { result |= O_EXCL; } - if (openflags::noctty == (value_ & openflags::noctty )) { result |= O_NOCTTY; } - if (openflags::nofollow == (value_ & openflags::nofollow )) { result |= O_NOFOLLOW; } - if (openflags::trunc == (value_ & openflags::trunc )) { result |= O_TRUNC; } - if (openflags::async == (value_ & openflags::async )) { result |= O_ASYNC; } if (openflags::largefile == (value_ & openflags::largefile)) { result |= O_LARGEFILE; } if (openflags::noatime == (value_ & openflags::noatime )) { result |= O_NOATIME; } + if (openflags::noctty == (value_ & openflags::noctty )) { result |= O_NOCTTY; } + if (openflags::nofollow == (value_ & openflags::nofollow )) { result |= O_NOFOLLOW; } if (openflags::nonblock == (value_ & openflags::nonblock )) { result |= O_NONBLOCK; } if (openflags::ndelay == (value_ & openflags::ndelay )) { result |= O_NDELAY; } + if (openflags::path == (value_ & openflags::path )) { result |= O_PATH; } if (openflags::sync == (value_ & openflags::sync )) { result |= O_SYNC; } + if (openflags::tmpfile == (value_ & openflags::tmpfile )) { result |= O_TMPFILE; } + if (openflags::trunc == (value_ & openflags::trunc )) { result |= O_TRUNC; } return result; } diff --git a/src/webfuse/filesystem/openflags.hpp b/src/webfuse/filesystem/openflags.hpp index 44541f1..8405332 100644 --- a/src/webfuse/filesystem/openflags.hpp +++ b/src/webfuse/filesystem/openflags.hpp @@ -9,25 +9,29 @@ namespace webfuse class openflags { public: - static constexpr int32_t const accessmode_mask = 3; // O_ACCMODE - - static constexpr int32_t const rdonly = 00000000; // O_RDONLY - static constexpr int32_t const wronly = 00000001; // O_WRONLY - static constexpr int32_t const rdwr = 00000002; // O_RDWR - static constexpr int32_t const cloexec = 02000000; // O_CLOEXEC - static constexpr int32_t const creat = 00000100; // O_CREAT - static constexpr int32_t const direct = 00040000; // O_DIRECT - static constexpr int32_t const directory = 00200000; // O_DIRECTORY - static constexpr int32_t const excl = 00000200; // O_EXCL - static constexpr int32_t const noctty = 00000400; // O_NOCTTY - static constexpr int32_t const nofollow = 00400000; // O_NOFOLLOW - static constexpr int32_t const trunc = 00001000; // O_TRUNC - static constexpr int32_t const async = 00002000; // O_ASYNC - static constexpr int32_t const largefile = 00000000; // O_LARGEFILE - static constexpr int32_t const noatime = 01000000; // O_NOATIME - static constexpr int32_t const nonblock = 00004000; // O_NONBLOCK - static constexpr int32_t const ndelay = 00004000; // O_NDELAY - static constexpr int32_t const sync = 04010000; // O_SYNC + static constexpr int32_t const accessmode_mask = 03; // O_ACCMODE + static constexpr int32_t const rdonly = 0; // O_RDONLY + static constexpr int32_t const wronly = 01; // O_WRONLY + static constexpr int32_t const rdwr = 02; // O_RDWR + + static constexpr int32_t const append = 02000; // O_APPEND + static constexpr int32_t const async = 020000; // O_ASYNC + static constexpr int32_t const cloexec = 02000000; // O_CLOEXEC + static constexpr int32_t const creat = 0100; // O_CREAT + static constexpr int32_t const direct = 040000; // O_DIRECT + static constexpr int32_t const directory = 0200000; // O_DIRECTORY + static constexpr int32_t const dsync = 010000; // O_DSYNC + static constexpr int32_t const excl = 0200; // O_EXCL + static constexpr int32_t const largefile = 0100000; // O_LARGEFILE + static constexpr int32_t const noatime = 01000000; // O_NOATIME + static constexpr int32_t const noctty = 0400; // O_NOCTTY + static constexpr int32_t const nofollow = 0400000; // O_NOFOLLOW + static constexpr int32_t const nonblock = 04000; // O_NONBLOCK + static constexpr int32_t const ndelay = 04000; // O_NDELAY + static constexpr int32_t const path = 010000000; // O_PATH + static constexpr int32_t const sync = 04010000; // O_SYNC + static constexpr int32_t const tmpfile = 020200000; // O_TMPFILE + static constexpr int32_t const trunc = 01000; // O_TRUNC explicit openflags(int32_t value = 0); operator int32_t() const; From 0eca956ef70cd066ffc9737f37dc8789c5cdefc5 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 27 Nov 2022 13:24:00 +0100 Subject: [PATCH 22/91] provider: added implementation for open, mknod, create and release --- script/provider.py | 129 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 14 deletions(-) diff --git a/script/provider.py b/script/provider.py index 4703f44..09f2d07 100755 --- a/script/provider.py +++ b/script/provider.py @@ -6,6 +6,8 @@ import stat import websockets import errno +INVALID_FD = 0xffffffffffffffff + F_OK = 0 R_OK = 4 W_OK = 2 @@ -63,6 +65,28 @@ MODE_FIFO = 0o010000 MODE_LNK = 0o120000 MODE_SOCK = 0o140000 +O_RDONLY = 0o00 +O_WRONLY = 0o01 +O_RDWR = 0o02 + +O_APPEND = 0o00002000 +O_ASYNC = 0o00020000 +O_CLOEXEC = 0o02000000 +O_CREAT = 0o00000100 +O_DIRECT = 0o00040000 +O_DIRECTORY = 0o00200000 +O_DSYNC = 0o00010000 +O_EXCL = 0o00000200 +O_LARGEFILE = 0o00100000 +O_NOATIME = 0o01000000 +O_NOCTTY = 0o00000400 +O_NOFOLLOW = 0o00400000 +O_NONBLOCK = 0o00004000 +O_NDELAY = 0o00004000 +O_PATH = 0o10000000 +O_SYNC = 0o04010000 +O_TMPFILE = 0o20200000 +O_TRUNC = 0o00001000 class MessageReader: def __init__(self, buffer): @@ -79,6 +103,20 @@ class MessageReader: self.offset += 4 return value + def read_u64(self): + value = ( + (self.buffer[self.offset ] << 56) + + (self.buffer[self.offset + 1] << 48) + + (self.buffer[self.offset + 2] << 40) + + (self.buffer[self.offset + 3] << 32) + + (self.buffer[self.offset + 4] << 24) + + (self.buffer[self.offset + 5] << 16) + + (self.buffer[self.offset + 6] << 8) + + self.buffer[self.offset + 7]) + self.offset += 8 + return value + + def read_str(self): return self.read_bytes().decode() @@ -95,9 +133,9 @@ class MessageReader: def read_access_mode(self): value = self.read_u8() mode = os.F_OK if F_OK == (value & F_OK) else 0 - mode += os.R_OK if R_OK == (value & R_OK) else 0 - mode += os.W_OK if W_OK == (value & W_OK) else 0 - mode += os.X_OK if X_OK == (value & X_OK) else 0 + mode |= os.R_OK if R_OK == (value & R_OK) else 0 + mode |= os.W_OK if W_OK == (value & W_OK) else 0 + mode |= os.X_OK if X_OK == (value & X_OK) else 0 return mode def read_rename_flags(self): @@ -106,15 +144,42 @@ class MessageReader: def read_mode(self): value = self.read_u32() mode = value & 0o7777 - mode += stat.S_IFREG if MODE_REG == (value & MODE_REG ) else 0 - mode += stat.S_IFDIR if MODE_DIR == (value & MODE_DIR ) else 0 - mode += stat.S_IFCHR if MODE_CHR == (value & MODE_CHR ) else 0 - mode += stat.S_IFBLK if MODE_BLK == (value & MODE_BLK ) else 0 - mode += stat.S_IFFIFO if MODE_FIFO == (value & MODE_FIFO) else 0 - mode += stat.S_IFLNK if MODE_LNK == (value & MODE_LNK ) else 0 - mode += stat.S_IFSOCK if MODE_SOCK == (value & MODE_SOCK) else 0 + mode |= stat.S_IFREG if MODE_REG == (value & MODE_REG ) else 0 + mode |= stat.S_IFDIR if MODE_DIR == (value & MODE_DIR ) else 0 + mode |= stat.S_IFCHR if MODE_CHR == (value & MODE_CHR ) else 0 + mode |= stat.S_IFBLK if MODE_BLK == (value & MODE_BLK ) else 0 + mode |= stat.S_IFFIFO if MODE_FIFO == (value & MODE_FIFO) else 0 + mode |= stat.S_IFLNK if MODE_LNK == (value & MODE_LNK ) else 0 + mode |= stat.S_IFSOCK if MODE_SOCK == (value & MODE_SOCK) else 0 return mode + def read_openflags(self): + value = self.read_u32() + flags = 0 + # Access Mode + flags |= os.O_RDONLY if O_RDONLY == (value & O_RDONLY) else 0 + flags |= os.O_WRONLY if O_WRONLY == (value & O_WRONLY) else 0 + flags |= os.O_RDWR if O_RDWR == (value & O_RDWR ) else 0 + # Flags + flags |= os.O_APPEND if O_APPEND == (value & O_APPEND ) else 0 + flags |= os.O_ASYNC if O_ASYNC == (value & O_ASYNC ) else 0 + flags |= os.O_CLOEXEC if O_CLOEXEC == (value & O_CLOEXEC ) else 0 + flags |= os.O_CREAT if O_CREAT == (value & O_CREAT ) else 0 + flags |= os.O_DIRECT if O_DIRECT == (value & O_DIRECT ) else 0 + flags |= os.O_DIRECTORY if O_DIRECTORY == (value & O_DIRECTORY) else 0 + flags |= os.O_DSYNC if O_DSYNC == (value & O_DSYNC ) else 0 + flags |= os.O_EXCL if O_EXCL == (value & O_EXCL ) else 0 + flags |= os.O_LARGEFILE if O_LARGEFILE == (value & O_LARGEFILE) else 0 + flags |= os.O_NOCTTY if O_NOCTTY == (value & O_NOCTTY ) else 0 + flags |= os.O_NOFOLLOW if O_NOFOLLOW == (value & O_NOFOLLOW ) else 0 + flags |= os.O_NONBLOCK if O_NONBLOCK == (value & O_NONBLOCK ) else 0 + flags |= os.O_NDELAY if O_NDELAY == (value & O_NDELAY ) else 0 + flags |= os.O_PATH if O_PATH == (value & O_PATH ) else 0 + flags |= os.O_SYNC if O_SYNC == (value & O_SYNC ) else 0 + flags |= os.O_TMPFILE if O_TMPFILE == (value & O_TMPFILE ) else 0 + flags |= os.O_TRUNC if O_TRUNC == (value & O_TRUNC ) else 0 + return flags + class MessageWriter: @@ -327,7 +392,7 @@ class FilesystemProvider: fd = reader.read_u64() result = 0 try: - if fd != 0xffffffffffffffff: + if fd != INVALID_FD: os.ftruncate(fd, size) else: os.truncate(path, size) @@ -347,16 +412,52 @@ class FilesystemProvider: writer.write_result(result) def open(self, reader, writer): - pass + path = reader.read_path(self.root) + flags = reader.read_openflags() + try: + fd = os.open(path, flags) + except OSError as ex: + writer.write_result(-ex.errno) + return + writer.write_result(0) + writer.write_u64(fd) + def mknod(self, reader, writer): - pass + path = reader.read_path(self.root) + mode = reader.read_mode() + rdev = reader.read_u64() + result = 0 + try: + os.mknod(path, mode, rdev) + except OSError as ex: + result = -ex.errno + writer.write_result(result) def create(self, reader, writer): + path = reader.read_path(self.root) + mode = reader.read_mode() + try: + flags = os.O_CREAT | os.O_WRONLY | os.O_TRUNC + fd = os.open(path, flags, mode) + except OSError as ex: + writer.write_result(-ex.errno) + return + writer.write_result(0) + writer.write_u64(fd) pass def release(self, reader, writer): - pass + path = reader.read_path(self.root) + fd = reader.read_u64() + result = 0 + try: + os.close(fd) + except OSError as ex: + writer.write_result(-ex.errno) + return + print(result) + writer.write_result(result) def unlink(self, reader, writer): pass From 37f07e79b9f06dc41007c2b940b6f432a2e4ed00 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 27 Nov 2022 19:55:11 +0100 Subject: [PATCH 23/91] implemented some methods --- script/provider.py | 72 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/script/provider.py b/script/provider.py index 09f2d07..15d6d04 100755 --- a/script/provider.py +++ b/script/provider.py @@ -456,20 +456,58 @@ class FilesystemProvider: except OSError as ex: writer.write_result(-ex.errno) return - print(result) writer.write_result(result) def unlink(self, reader, writer): - pass + path = reader.read_path(self.root) + result = 0 + try: + os.unlink(path) + except OSError as ex: + result = -ex.errno + writer.write_result(result) def read(self, reader, writer): - pass + path = reader.read_path(self.root) + size = reader.read_u32() + offset = reader.read_u64() + fd = reader.read_u64() + try: + if fd != INVALID_FD: + buffer = os.pread(fd, size, offset) + else: + with os.open(path, os.O_RDONLY) as f: + buffer = os.pread(f, size, offset) + writer.write_result(len(buffer)) + writer.write_bytes(buffer) + except OSError as ex: + writer.write_result(-ex.errno) def write(self, reader, writer): - pass + path = reader.read_path(self.root) + data = reader.read_bytes() + offset = reader.read_u64() + fd = reader.read_u64() + result = 0 + try: + if fd != INVALID_FD: + result = os.pwrite(fd, data, offset) + else: + with os.open(path, os.O_WRONLY) as f: + result = os.pwrite(f, data, offset) + except OSError as ex: + result = -ex.errno + writer.write_result(result) def mkdir(self, reader, writer): - pass + path = reader.read_path(self.root) + mode = reader.read_u32() + result = 0 + try: + os.mkdir(path, mode) + except OSError as ex: + result = -ex.errno + writer.write_result(result) def readdir(self, reader, writer): path = reader.read_path(self.root) @@ -485,10 +523,30 @@ class FilesystemProvider: writer.write_strings(names) def rmdir(self, reader, writer): - pass + path = reader.read_path(self.root) + result = 0 + try: + os.rmdir(path) + except OSError as ex: + result = -ex.errno + writer.write_result(result) def statfs(self, reader, writer): - pass + path = self.get_path(reader) + try: + buffer = os.statvfs(path) + except OSError as ex: + writer.write_result(-ex.errno) + return + writer.write_result(0) + writer.write_u64(buffer.f_bsize) + writer.write_u64(buffer.f_frsize) + writer.write_u64(buffer.f_blocks) + writer.write_u64(buffer.f_bfree) + writer.write_u64(buffer.f_bavail) + writer.write_u64(buffer.f_files) + writer.write_u64(buffer.f_ffree) + writer.write_u64(buffer.f_namemax) if __name__ == '__main__': From 4a2dd3e609b8c72bfb74fb5b4c12effea9e80504 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 27 Nov 2022 20:26:45 +0100 Subject: [PATCH 24/91] added utimens --- src/webfuse/filesystem.cpp | 19 +++++++++++++++++++ src/webfuse/filesystem.hpp | 1 + src/webfuse/filesystem/empty_filesystem.cpp | 5 +++++ src/webfuse/filesystem/empty_filesystem.hpp | 1 + src/webfuse/filesystem/filesystem_i.hpp | 1 + src/webfuse/message_type.hpp | 3 ++- src/webfuse/ws/messagewriter.cpp | 6 ++++++ src/webfuse/ws/messagewriter.hpp | 1 + 8 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index 574f990..0a14376 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -190,6 +190,25 @@ int filesystem::fsync(std::string const & path, bool is_datasync, uint64_t handl } } +int filesystem::utimens(std::string const &path, struct timespec tv[2], uint64_t handle) +{ + try + { + messagewriter req(message_type::utimens_req); + req.write_str(path); + req.write_time(tv[0]); + req.write_time(tv[1]); + req.write_u64(handle); + auto reader = proxy.perform(std::move(req)); + return reader.read_result(); + } + catch(...) + { + return fallback.utimens(path, tv, handle); + } +} + + int filesystem::open(std::string const & path, int flags, uint64_t & handle) { try diff --git a/src/webfuse/filesystem.hpp b/src/webfuse/filesystem.hpp index 8b3c44c..ca1e29e 100644 --- a/src/webfuse/filesystem.hpp +++ b/src/webfuse/filesystem.hpp @@ -30,6 +30,7 @@ public: int chown(std::string const & path, uid_t uid, gid_t gid) override; int truncate(std::string const & path, uint64_t size, uint64_t handle) override; int fsync(std::string const & path, bool is_datasync, uint64_t handle) override; + int utimens(std::string const &path, struct timespec tv[2], uint64_t handle) override; int open(std::string const & path, int flags, uint64_t & handle) override; int mknod(std::string const & path, mode_t mode, dev_t rdev) override; diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index c9389e2..b0d33b3 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -71,6 +71,11 @@ int empty_filesystem::fsync(std::string const & path, bool is_datasync, uint64_t return 0; } +int empty_filesystem::utimens(std::string const &path, struct timespec tv[2], uint64_t handle) +{ + return -ENOSYS; +} + int empty_filesystem::open(std::string const & path, int flags, uint64_t & handle) { return -ENOENT; diff --git a/src/webfuse/filesystem/empty_filesystem.hpp b/src/webfuse/filesystem/empty_filesystem.hpp index b26477f..c271063 100644 --- a/src/webfuse/filesystem/empty_filesystem.hpp +++ b/src/webfuse/filesystem/empty_filesystem.hpp @@ -23,6 +23,7 @@ public: int chown(std::string const & path, uid_t uid, gid_t gid) override; int truncate(std::string const & path, uint64_t size, uint64_t handle) override; int fsync(std::string const & path, bool is_datasync, uint64_t handle) override; + int utimens(std::string const &path, struct timespec tv[2], uint64_t handle) override; int open(std::string const & path, int flags, uint64_t & handle) override; int mknod(std::string const & path, mode_t mode, dev_t rdev) override; diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp index d1de857..2d42e52 100644 --- a/src/webfuse/filesystem/filesystem_i.hpp +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -30,6 +30,7 @@ public: virtual int chown(std::string const & path, uid_t uid, gid_t gid) = 0; virtual int truncate(std::string const & path, uint64_t size, uint64_t handle) = 0; virtual int fsync(std::string const & path, bool is_datasync, uint64_t handle) = 0; + virtual int utimens(std::string const &path, struct timespec tv[2], uint64_t handle) = 0; virtual int open(std::string const & path, int flags, uint64_t & handle) = 0; virtual int mknod(std::string const & path, mode_t mode, dev_t rdev) = 0; diff --git a/src/webfuse/message_type.hpp b/src/webfuse/message_type.hpp index b876b40..871466e 100644 --- a/src/webfuse/message_type.hpp +++ b/src/webfuse/message_type.hpp @@ -28,7 +28,8 @@ enum class message_type: uint8_t mkdir_req = 0x12, readdir_req = 0x13, rmdir_req = 0x14, - statfs_req = 0x15 + statfs_req = 0x15, + utimens_req = 0x16 }; diff --git a/src/webfuse/ws/messagewriter.cpp b/src/webfuse/ws/messagewriter.cpp index 7cdcbd8..3a8d4f6 100644 --- a/src/webfuse/ws/messagewriter.cpp +++ b/src/webfuse/ws/messagewriter.cpp @@ -161,6 +161,12 @@ void messagewriter::write_openflags(int value) write_i32(flags); } +void messagewriter::write_time(timespec const & value) +{ + write_u64(static_cast(value.tv_sec)); + write_u32(static_cast(value.tv_nsec)); +} + unsigned char * messagewriter::get_data(size_t &size) { diff --git a/src/webfuse/ws/messagewriter.hpp b/src/webfuse/ws/messagewriter.hpp index c44ab11..22b58d6 100644 --- a/src/webfuse/ws/messagewriter.hpp +++ b/src/webfuse/ws/messagewriter.hpp @@ -39,6 +39,7 @@ public: void write_uid(uid_t value); void write_gid(gid_t value); void write_openflags(int value); + void write_time(timespec const & value); unsigned char * get_data(size_t &size); From bc0784acc6972de6529d2c506d1d708e461b5cb3 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 27 Nov 2022 20:27:32 +0100 Subject: [PATCH 25/91] added timens to provider --- script/provider.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/script/provider.py b/script/provider.py index 15d6d04..26083b7 100755 --- a/script/provider.py +++ b/script/provider.py @@ -263,8 +263,9 @@ class FilesystemProvider: 0x11: FilesystemProvider.write, 0x12: FilesystemProvider.mkdir, 0x13: FilesystemProvider.readdir, - 0x14: FilesystemProvider.rmdir, - 0x15: FilesystemProvider.statfs + 0x14: FilesystemProvider.rmdir, + 0x15: FilesystemProvider.statfs, + 0x15: FilesystemProvider.utimens, } async def run(self): @@ -411,6 +412,21 @@ class FilesystemProvider: result = -ex.errno writer.write_result(result) + def utimens(self, reader, writer): + path = reader.read_path(self.root) + atime = reader.read_u64() + atime_ns = reader.read_u32() + mtime = reader.read_u64() + mtime_ns = reader.read_u32() + fd = reader.read_u64() + result = 0 + try: + os.utime(path, (atime, mtime), ns = (atime_ns, mtime_ns)) + except OSError as ex: + result = -ex.errno + writer.write_result(result) + + def open(self, reader, writer): path = reader.read_path(self.root) flags = reader.read_openflags() From f87630cdd8fe035550086d1fcb7990f0356f5b53 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 30 Dec 2022 12:43:11 +0100 Subject: [PATCH 26/91] removed debug output --- src/webfuse/ws/server.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 499624a..59843b4 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -50,7 +50,6 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, switch(reason) { case LWS_CALLBACK_ESTABLISHED: - std::cout << "lws: established "<< std::endl; if (nullptr == data->connection) { data->connection = wsi; @@ -61,14 +60,12 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, } break; case LWS_CALLBACK_CLOSED: - std::cout << "lws: closed "<< std::endl; if (wsi == data->connection) { data->connection = nullptr; } break; case LWS_CALLBACK_RECEIVE: - std::cout << "lws: receive "<< std::endl; { auto * fragment = reinterpret_cast(in); data->current_message.append(fragment, len); @@ -84,7 +81,6 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, auto it = data->pending_responses.find(id); if (it != data->pending_responses.end()) { - std::cout << "propagate message" << std::endl; it->second.set_value(std::move(reader)); data->pending_responses.erase(it); } @@ -109,7 +105,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, } break; case LWS_CALLBACK_SERVER_WRITEABLE: - std::cout << "lws: server writable "<< std::endl; + << "lws: server writable "<< std::endl; { webfuse::messagewriter writer(webfuse::message_type::access_req); bool has_msg = false; @@ -187,7 +183,6 @@ public: { if (nullptr != data.connection) { - std::cout << "request write" << std::endl; lws_callback_on_writable(data.connection); } else From db88fa8c4db535e27d7d90e601cf954b3bbaae47 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 30 Dec 2022 12:43:44 +0100 Subject: [PATCH 27/91] fixed fsync --- script/provider.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/script/provider.py b/script/provider.py index 26083b7..3c22286 100755 --- a/script/provider.py +++ b/script/provider.py @@ -97,6 +97,9 @@ class MessageReader: value = self.buffer[self.offset] self.offset += 1 return value + + def read_bool(self): + return self.read_u8() == 1 def read_u32(self): value = (self.buffer[self.offset] << 24) + (self.buffer[self.offset + 1] << 16) + (self.buffer[self.offset + 2] << 8) + self.buffer[self.offset + 3] @@ -275,7 +278,6 @@ class FilesystemProvider: reader = MessageReader(request) message_id = reader.read_u32() message_type = reader.read_u8() - print("received message: id=%d, type=%d" % (message_id, message_type)) writer = MessageWriter(message_id, RESPONSE + message_type) if message_type in self.commands: method = self.commands[message_type] @@ -403,7 +405,7 @@ class FilesystemProvider: def fsync(self, reader, writer): path = reader.read_path(self.root) - _ = reader.read_i32() + _ = reader.read_bool() fd = reader.read_u64() result = 0 try: From 60594067b144e305cbec3d120d51bedb633b7c28 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 30 Dec 2022 13:01:40 +0100 Subject: [PATCH 28/91] fixed compile error --- src/webfuse/ws/server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 59843b4..4ae00b4 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -105,7 +105,6 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, } break; case LWS_CALLBACK_SERVER_WRITEABLE: - << "lws: server writable "<< std::endl; { webfuse::messagewriter writer(webfuse::message_type::access_req); bool has_msg = false; From 9036aba41b753b1aa741a27e9ea0e6b381f51795 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 30 Dec 2022 19:43:45 +0100 Subject: [PATCH 29/91] added test utilities --- test-src/webfuse/test/tempdir.cpp | 23 ++++++++++++++++++ test-src/webfuse/test/tempdir.hpp | 22 +++++++++++++++++ test-src/webfuse/test/thread.cpp | 39 +++++++++++++++++++++++++++++++ test-src/webfuse/test/thread.hpp | 22 +++++++++++++++++ test-src/webfuse/test_app.cpp | 31 ++++++++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 test-src/webfuse/test/tempdir.cpp create mode 100644 test-src/webfuse/test/tempdir.hpp create mode 100644 test-src/webfuse/test/thread.cpp create mode 100644 test-src/webfuse/test/thread.hpp diff --git a/test-src/webfuse/test/tempdir.cpp b/test-src/webfuse/test/tempdir.cpp new file mode 100644 index 0000000..9474ba5 --- /dev/null +++ b/test-src/webfuse/test/tempdir.cpp @@ -0,0 +1,23 @@ +#include "webfuse/test/tempdir.hpp" +#include + +namespace webfuse +{ + +tempdir::tempdir() +{ + char path_template[] = "/tmp/webfuse_test_XXXXXX"; + path = mkdtemp(path_template); +} + +tempdir::~tempdir() +{ + unlink(path.c_str()); +} + +std::string const tempdir::name() const +{ + return path; +} + +} \ No newline at end of file diff --git a/test-src/webfuse/test/tempdir.hpp b/test-src/webfuse/test/tempdir.hpp new file mode 100644 index 0000000..216341b --- /dev/null +++ b/test-src/webfuse/test/tempdir.hpp @@ -0,0 +1,22 @@ +#ifndef WEBFUSE_TEMPDIR_HPP +#define WEBFUSE_TEMPDIR_HPP + +#include + +namespace webfuse +{ + +class tempdir +{ +public: + tempdir(); + ~tempdir(); + std::string const name() const; +private: + std::string path; + +}; + +} + +#endif diff --git a/test-src/webfuse/test/thread.cpp b/test-src/webfuse/test/thread.cpp new file mode 100644 index 0000000..2ebef00 --- /dev/null +++ b/test-src/webfuse/test/thread.cpp @@ -0,0 +1,39 @@ +#include "webfuse/test/thread.hpp" +#include + +namespace +{ + +extern "C" void * webfuse_thread_main(void * args) +{ + auto * run = reinterpret_cast *>(args); + (*run)(); + return nullptr; +} + +} + + +namespace webfuse +{ + +thread::thread(std::function run) +{ + pthread_create(&real_thread, nullptr, + &webfuse_thread_main, + reinterpret_cast(&run)); + +} + +thread::~thread() +{ + pthread_join(real_thread, nullptr); +} + +void thread::kill(int signal_id) +{ + pthread_kill(real_thread, signal_id); +} + + +} \ No newline at end of file diff --git a/test-src/webfuse/test/thread.hpp b/test-src/webfuse/test/thread.hpp new file mode 100644 index 0000000..8dfbd67 --- /dev/null +++ b/test-src/webfuse/test/thread.hpp @@ -0,0 +1,22 @@ +#ifndef WEBFUSE_THREAD_HPP +#define WEBFUSE_THREAD_HPP + +#include +#include + +namespace webfuse +{ + +class thread +{ +public: + explicit thread(std::function run); + ~thread(); + void kill(int signal_id); +private: + pthread_t real_thread; +}; + +} + +#endif diff --git a/test-src/webfuse/test_app.cpp b/test-src/webfuse/test_app.cpp index d15cfde..0f546c6 100644 --- a/test-src/webfuse/test_app.cpp +++ b/test-src/webfuse/test_app.cpp @@ -1,7 +1,38 @@ #include "webfuse/webfuse.hpp" +#include "webfuse/test/thread.hpp" +#include "webfuse/test/tempdir.hpp" #include +#include +#include +#include +#include +#include + +extern "C" void * run(void * args) +{ + webfuse::app * app = reinterpret_cast(args); + + return nullptr; +} TEST(app, init) { webfuse::app app; +} + +TEST(app, run) +{ + webfuse::tempdir dir; + webfuse::thread thread([&dir](){ + webfuse::app app; + char arg0[] = "webfuse"; + char arg1[] = "-f"; + char* arg2 = strdup(dir.name().c_str()); + char* argv[] = { arg0, arg1, arg2, nullptr}; + int rc = app.run(3, argv); + free(arg2); + }); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + thread.kill(SIGINT); } \ No newline at end of file From 4e1c9e144c16c6f0c667ca6d7b5f74455762491c Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 30 Dec 2022 19:44:55 +0100 Subject: [PATCH 30/91] added basic C++ provider infrastructure --- CMakeLists.txt | 15 ++- src/provider_main.cpp | 162 +++++++++++++++++++++++++++++++ src/webfuse/filesystem.cpp | 44 ++++----- src/webfuse/message_type.hpp | 38 -------- src/webfuse/provider.cpp | 94 ++++++++++++++++++ src/webfuse/provider.hpp | 28 ++++++ src/webfuse/request_type.hpp | 38 ++++++++ src/webfuse/response_type.hpp | 38 ++++++++ src/webfuse/ws/client.cpp | 150 ++++++++++++++++++++++++++++ src/webfuse/ws/client.hpp | 33 +++++++ src/webfuse/ws/messagewriter.cpp | 12 ++- src/webfuse/ws/messagewriter.hpp | 6 +- src/webfuse/ws/server.cpp | 2 +- 13 files changed, 594 insertions(+), 66 deletions(-) create mode 100644 src/provider_main.cpp delete mode 100644 src/webfuse/message_type.hpp create mode 100644 src/webfuse/provider.cpp create mode 100644 src/webfuse/provider.hpp create mode 100644 src/webfuse/request_type.hpp create mode 100644 src/webfuse/response_type.hpp create mode 100644 src/webfuse/ws/client.cpp create mode 100644 src/webfuse/ws/client.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4298642..edbd14a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets) add_library(webfuse_static STATIC src/webfuse/webfuse.cpp + src/webfuse/provider.cpp src/webfuse/fuse.cpp src/webfuse/filesystem.cpp src/webfuse/filesystem/status.cpp @@ -19,6 +20,7 @@ add_library(webfuse_static STATIC src/webfuse/filesystem/empty_filesystem.cpp src/webfuse/ws/config.cpp src/webfuse/ws/server.cpp + src/webfuse/ws/client.cpp src/webfuse/ws/messagewriter.cpp src/webfuse/ws/messagereader.cpp ) @@ -31,12 +33,23 @@ add_executable(webfuse target_link_libraries(webfuse PRIVATE webfuse_static) +if(NOT(WITHOUT_PROVIDER)) + +add_executable(webfuse_provider + src/provider_main.cpp) + +target_link_libraries(webfuse_provider PRIVATE webfuse_static) + +endif() + if(NOT(WITHOUT_TEST)) pkg_check_modules(GTEST REQUIRED gtest_main) pkg_check_modules(GMOCK REQUIRED gmock) add_executable(alltests + test-src/webfuse/test/thread.cpp + test-src/webfuse/test/tempdir.cpp test-src/webfuse/test_app.cpp test-src/webfuse/filesystem/test_status.cpp test-src/webfuse/filesystem/test_accessmode.cpp @@ -44,7 +57,7 @@ if(NOT(WITHOUT_TEST)) test-src/webfuse/filesystem/test_filemode.cpp ) - target_include_directories(alltests PRIVATE ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) + target_include_directories(alltests PRIVATE test-src ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) target_compile_options(alltests PRIVATE ${GTEST_CFLAGS} ${GTEST_CFLAGS_OTHER} ${GMOCK_CFLAGS} ${GMOCK_CFLAGS_OTHER} diff --git a/src/provider_main.cpp b/src/provider_main.cpp new file mode 100644 index 0000000..ec2c9a3 --- /dev/null +++ b/src/provider_main.cpp @@ -0,0 +1,162 @@ +#include "webfuse/provider.hpp" +#include +#include + +namespace +{ + +static bool shutdown_requested = false; + +void on_signal(int _) +{ + (void) _; + shutdown_requested = true; +} + +class filesystem: public webfuse::filesystem_i +{ +public: + explicit filesystem(std::string const & base_path) + : base_path_(base_path) + { + + } + + ~filesystem() override + { + + } + + int access(std::string const & path, int mode) override + { + return -ENOENT; + } + + int getattr(std::string const & path, struct stat * attr) override + { + return -ENOENT; + } + + int readlink(std::string const & path, std::string & out) override + { + return -ENOENT; + } + + int symlink(std::string const & target, std::string const & linkpath) override + { + return -ENOENT; + } + + int link(std::string const & old_path, std::string const & new_path) override + { + return -ENOENT; + } + + int rename(std::string const & old_path, std::string const & new_path, int flags) override + { + return -ENOENT; + } + + int chmod(std::string const & path, mode_t mode) override + { + return -ENOENT; + } + + int chown(std::string const & path, uid_t uid, gid_t gid) override + { + return -ENOENT; + } + + int truncate(std::string const & path, uint64_t size, uint64_t handle) override + { + return -ENOENT; + } + + int fsync(std::string const & path, bool is_datasync, uint64_t handle) override + { + return -ENOENT; + } + + int utimens(std::string const &path, struct timespec tv[2], uint64_t handle) override + { + return -ENOENT; + } + + int open(std::string const & path, int flags, uint64_t & handle) override + { + return -ENOENT; + } + + int mknod(std::string const & path, mode_t mode, dev_t rdev) override + { + return -ENOENT; + } + + int create(std::string const & path, mode_t mode, uint64_t & handle) override + { + return -ENOENT; + } + + int release(std::string const & path, uint64_t handle) override + { + return -ENOENT; + } + + int unlink(std::string const & path) override + { + return -ENOENT; + } + + int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override + { + return -ENOENT; + } + + int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override + { + return -ENOENT; + } + + int mkdir(std::string const & path, mode_t mode) override + { + return -ENOENT; + } + + int readdir(std::string const & path, std::vector & entries, uint64_t handle) override + { + return -ENOENT; + } + + int rmdir(std::string const & path) override + { + return -ENOENT; + } + + int statfs(std::string const & path, struct statvfs * statistics) override + { + return -ENOENT; + } + + +private: + std::string base_path_; +}; + +} + + + +int main(int argc, char* argv[]) +{ + signal(SIGINT, &on_signal); + signal(SIGTERM, &on_signal); + + filesystem fs("."); + webfuse::provider provider(fs); + provider.connect("ws://localhost:8080/"); + while (!shutdown_requested) + { + provider.service(); + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index 0a14376..feadeb7 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -22,7 +22,7 @@ int filesystem::access(std::string const & path, int mode) { try { - messagewriter req(message_type::access_req); + messagewriter req(request_type::access); req.write_str(path); req.write_access_mode(mode); auto reader = proxy.perform(std::move(req)); @@ -38,7 +38,7 @@ int filesystem::getattr(std::string const & path, struct stat * attr) { try { - messagewriter req(message_type::getattr_req); + messagewriter req(request_type::getattr); req.write_str(path); auto reader = proxy.perform(std::move(req)); int const result = reader.read_result(); @@ -58,7 +58,7 @@ int filesystem::readlink(std::string const & path, std::string & out) { try { - messagewriter req(message_type::readlink_req); + messagewriter req(request_type::readlink); req.write_str(path); auto reader = proxy.perform(std::move(req)); int const result = reader.read_result(); @@ -78,7 +78,7 @@ int filesystem::symlink(std::string const & target, std::string const & linkpath { try { - messagewriter req(message_type::symlink_req); + messagewriter req(request_type::symlink); req.write_str(target); req.write_str(linkpath); auto reader = proxy.perform(std::move(req)); @@ -94,7 +94,7 @@ int filesystem::link(std::string const & old_path, std::string const & new_path) { try { - messagewriter req(message_type::link_req); + messagewriter req(request_type::link); req.write_str(old_path); req.write_str(new_path); auto reader = proxy.perform(std::move(req)); @@ -110,7 +110,7 @@ int filesystem::rename(std::string const & old_path, std::string const & new_pat { try { - messagewriter req(message_type::rename_req); + messagewriter req(request_type::rename); req.write_str(old_path); req.write_str(new_path); req.write_rename_flags(flags); @@ -127,7 +127,7 @@ int filesystem::chmod(std::string const & path, mode_t mode) { try { - messagewriter req(message_type::chmod_req); + messagewriter req(request_type::chmod); req.write_str(path); req.write_mode(mode); auto reader = proxy.perform(std::move(req)); @@ -143,7 +143,7 @@ int filesystem::chown(std::string const & path, uid_t uid, gid_t gid) { try { - messagewriter req(message_type::chown_req); + messagewriter req(request_type::chown); req.write_str(path); req.write_uid(uid); req.write_gid(gid); @@ -160,7 +160,7 @@ int filesystem::truncate(std::string const & path, uint64_t size, uint64_t handl { try { - messagewriter req(message_type::truncate_req); + messagewriter req(request_type::truncate); req.write_str(path); req.write_u64(size); req.write_u64(handle); @@ -177,7 +177,7 @@ int filesystem::fsync(std::string const & path, bool is_datasync, uint64_t handl { try { - messagewriter req(message_type::fsync_req); + messagewriter req(request_type::fsync); req.write_str(path); req.write_bool(is_datasync); req.write_u64(handle); @@ -194,7 +194,7 @@ int filesystem::utimens(std::string const &path, struct timespec tv[2], uint64_t { try { - messagewriter req(message_type::utimens_req); + messagewriter req(request_type::utimens); req.write_str(path); req.write_time(tv[0]); req.write_time(tv[1]); @@ -213,7 +213,7 @@ int filesystem::open(std::string const & path, int flags, uint64_t & handle) { try { - messagewriter req(message_type::open_req); + messagewriter req(request_type::open); req.write_str(path); req.write_openflags(flags); auto reader = proxy.perform(std::move(req)); @@ -234,7 +234,7 @@ int filesystem::mknod(std::string const & path, mode_t mode, dev_t rdev) { try { - messagewriter req(message_type::mknod_req); + messagewriter req(request_type::mknod); req.write_str(path); req.write_mode(mode); req.write_u64(rdev); @@ -251,7 +251,7 @@ int filesystem::create(std::string const & path, mode_t mode, uint64_t & handle) { try { - messagewriter req(message_type::create_req); + messagewriter req(request_type::create); req.write_str(path); req.write_mode(mode); auto reader = proxy.perform(std::move(req)); @@ -272,7 +272,7 @@ int filesystem::release(std::string const & path, uint64_t handle) { try { - messagewriter req(message_type::release_req); + messagewriter req(request_type::release); req.write_str(path); req.write_u64(handle); auto reader = proxy.perform(std::move(req)); @@ -288,7 +288,7 @@ int filesystem::unlink(std::string const & path) { try { - messagewriter req(message_type::unlink_req); + messagewriter req(request_type::unlink); req.write_str(path); auto reader = proxy.perform(std::move(req)); return reader.read_result(); @@ -303,7 +303,7 @@ int filesystem::read(std::string const & path, char * buffer, size_t buffer_size { try { - messagewriter req(message_type::read_req); + messagewriter req(request_type::read); req.write_str(path); req.write_u32(buffer_size); req.write_u64(offset); @@ -334,7 +334,7 @@ int filesystem::write(std::string const & path, char const * buffer, size_t buff { try { - messagewriter req(message_type::write_req); + messagewriter req(request_type::write); req.write_str(path); req.write_data(buffer, buffer_size); req.write_u64(offset); @@ -352,7 +352,7 @@ int filesystem::mkdir(std::string const & path, mode_t mode) { try { - messagewriter req(message_type::mkdir_req); + messagewriter req(request_type::mkdir); req.write_str(path); req.write_mode(mode); auto reader = proxy.perform(std::move(req)); @@ -368,7 +368,7 @@ int filesystem::readdir(std::string const & path, std::vector & ent { try { - messagewriter req(message_type::readdir_req); + messagewriter req(request_type::readdir); req.write_str(path); auto resp = proxy.perform(std::move(req)); int result = resp.read_result(); @@ -388,7 +388,7 @@ int filesystem::rmdir(std::string const & path) { try { - messagewriter req(message_type::rmdir_req); + messagewriter req(request_type::rmdir); req.write_str(path); auto reader = proxy.perform(std::move(req)); return reader.read_result(); @@ -403,7 +403,7 @@ int filesystem::statfs(std::string const & path, struct statvfs * statistics) { try { - messagewriter req(message_type::statfs_req); + messagewriter req(request_type::statfs); req.write_str(path); auto reader = proxy.perform(std::move(req)); int result = reader.read_result(); diff --git a/src/webfuse/message_type.hpp b/src/webfuse/message_type.hpp deleted file mode 100644 index 871466e..0000000 --- a/src/webfuse/message_type.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef WEBFUSE_MESSAGETYPE_HPP -#define WEBFUSE_MESSAGETYPE_HPP - -#include - -namespace webfuse -{ - -enum class message_type: uint8_t -{ - access_req = 0x01, - getattr_req = 0x02, - readlink_req = 0x03, - symlink_req = 0x04, - link_req = 0x05, - rename_req = 0x06, - chmod_req = 0x07, - chown_req = 0x08, - truncate_req = 0x09, - fsync_req = 0x0a, - open_req = 0x0b, - mknod_req = 0x0c, - create_req = 0x0d, - release_req = 0x0e, - unlink_req = 0x0f, - read_req = 0x10, - write_req = 0x11, - mkdir_req = 0x12, - readdir_req = 0x13, - rmdir_req = 0x14, - statfs_req = 0x15, - utimens_req = 0x16 -}; - - -} - -#endif diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp new file mode 100644 index 0000000..adf3fdf --- /dev/null +++ b/src/webfuse/provider.cpp @@ -0,0 +1,94 @@ +#include "webfuse/provider.hpp" +#include "webfuse/ws/client.hpp" + +namespace webfuse +{ + +class provider::detail +{ +public: + detail(filesystem_i & fs) + : fs_(fs) + , client([this](auto& reader) { return this->on_message(reader); }) + { + + } + + ~detail() + { + + } + + void connect(std::string const & url) + { + client.connect(url); + } + + void service() + { + client.service(); + } + + messagewriter on_message(messagereader & reader) + { + auto message_id = reader.read_u32(); + auto request_type = reader.read_u8(); + + messagewriter writer(response_type::unknown); + writer.set_id(message_id); + + switch (request_type) + { + + default: + break; + } + + return std::move(writer); + } +private: + filesystem_i & fs_; + ws_client client; +}; + +provider::provider(filesystem_i & fs) +: d(new detail(fs)) +{ + +} + +provider::~provider() +{ + delete d; +} + +provider::provider(provider && other) +{ + this->d = other.d; + other.d = nullptr; +} + +provider& provider::operator=(provider && other) +{ + if (this != &other) + { + delete this->d; + this->d = other.d; + other.d = nullptr; + } + + return *this; +} + +void provider::connect(std::string const & url) +{ + d->connect(url); +} + +void provider::service() +{ + d->service(); +} + + +} \ No newline at end of file diff --git a/src/webfuse/provider.hpp b/src/webfuse/provider.hpp new file mode 100644 index 0000000..806fa83 --- /dev/null +++ b/src/webfuse/provider.hpp @@ -0,0 +1,28 @@ +#ifndef WEBFUSE_PROVIDER_I_HPP +#define WEBFUSE_PROVIDER_I_HPP + +#include "webfuse/filesystem/filesystem_i.hpp" +#include + +namespace webfuse +{ + +class provider +{ + provider(provider const &) = delete; + provider& operator=(provider const &) = delete; +public: + provider(filesystem_i & fs); + ~provider(); + provider(provider && other); + provider& operator=(provider && other); + void connect(std::string const & url); + void service(); +private: + class detail; + detail * d; +}; + +} + +#endif diff --git a/src/webfuse/request_type.hpp b/src/webfuse/request_type.hpp new file mode 100644 index 0000000..4d0687d --- /dev/null +++ b/src/webfuse/request_type.hpp @@ -0,0 +1,38 @@ +#ifndef WEBFUSE_REQUEST_TYPE +#define WEBFUSE_REQUEST_TYPE + +#include + +namespace webfuse +{ + +enum class request_type: uint8_t +{ + unknown = 0x00, + access = 0x01, + getattr = 0x02, + readlink = 0x03, + symlink = 0x04, + link = 0x05, + rename = 0x06, + chmod = 0x07, + chown = 0x08, + truncate = 0x09, + fsync = 0x0a, + open = 0x0b, + mknod = 0x0c, + create = 0x0d, + release = 0x0e, + unlink = 0x0f, + read = 0x10, + write = 0x11, + mkdir = 0x12, + readdir = 0x13, + rmdir = 0x14, + statfs = 0x15, + utimens = 0x16 +}; + +} + +#endif \ No newline at end of file diff --git a/src/webfuse/response_type.hpp b/src/webfuse/response_type.hpp new file mode 100644 index 0000000..064451e --- /dev/null +++ b/src/webfuse/response_type.hpp @@ -0,0 +1,38 @@ +#ifndef WEBFUSE_RESPONSE_TYPE +#define WEBFUSE_RESPONSE_TYPE + +#include + +namespace webfuse +{ + +enum class response_type: uint8_t +{ + unknown = 0x80, + access = 0x81, + getattr = 0x82, + readlink = 0x83, + symlink = 0x84, + link = 0x85, + rename = 0x86, + chmod = 0x87, + chown = 0x88, + truncate = 0x89, + fsync = 0x8a, + open = 0x8b, + mknod = 0x8c, + create = 0x8d, + release = 0x8e, + unlink = 0x8f, + read = 0x90, + write = 0x91, + mkdir = 0x92, + readdir = 0x93, + rmdir = 0x94, + statfs = 0x95, + utimens = 0x96 +}; + +} + +#endif \ No newline at end of file diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp new file mode 100644 index 0000000..29a5585 --- /dev/null +++ b/src/webfuse/ws/client.cpp @@ -0,0 +1,150 @@ +#include "webfuse/ws/client.hpp" +#include +#include +#include + +namespace +{ + +extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, void* user, void * in, size_t length) +{ + int result = 0; + + lws_protocols const * protocol = lws_get_protocol(wsi); + + if (nullptr != protocol) + { + switch(reason) + { + case LWS_CALLBACK_CLIENT_ESTABLISHED: + std::cout << "established" << std::endl; + break; + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + std::cout << "connect error" << std::endl; + break; + case LWS_CALLBACK_CLIENT_CLOSED: + std::cout << "closed" << std::endl; + break; + case LWS_CALLBACK_CLIENT_RECEIVE: + std::cout << "receive" << std::endl; + break; + case LWS_CALLBACK_SERVER_WRITEABLE: + // fall-through + case LWS_CALLBACK_CLIENT_WRITEABLE: + std::cout << "writable" << std::endl; + break; + default: + break; + } + } + + + return result; +} + +} + +namespace webfuse +{ + +class ws_client::detail +{ + detail(detail const &) = delete; + detail& operator=(detail const &) = delete; + detail(detail &&) = delete; + detail& operator=(detail &&) = delete; +public: + detail(ws_client_handler handler) + : handler_(handler) + { + memset(reinterpret_cast(protocols), 0, sizeof(lws_protocols) * 2); + protocols[0].callback = &webfuse_client_callback; + protocols[0].name = "webfuse2-client"; + protocols[0].per_session_data_size = 0; + protocols[0].user = nullptr; + + memset(reinterpret_cast(&info), 0, sizeof(lws_context_creation_info)); + info.port = CONTEXT_PORT_NO_LISTEN; + info.protocols = protocols; + info.uid = -1; + info.gid = -1; + + context = lws_create_context(&info); + } + + ~detail() + { + lws_context_destroy(context); + } + + void connect(std::string const & url) + { + lws_client_connect_info info; + memset(reinterpret_cast(&info), 0, sizeof(lws_client_connect_info)); + info.context = context; + info.port = 8081; + info.address = "localhost"; + info.host = "localhost"; + info.path = "/"; + info.origin = "localhost"; + info.ssl_connection = 0; + info.protocol = "webfuse2"; + info.local_protocol_name = "webfuse2-client"; + info.pwsi = &wsi; + + lws_client_connect_via_info(&info); + } + + void service() + { + lws_service(context, 0); + } + +private: + ws_client_handler handler_; + lws_context_creation_info info; + lws_protocols protocols[2]; + lws_context * context; + lws * wsi; +}; + +ws_client::ws_client(ws_client_handler handler) +: d(new detail(handler)) +{ + +} + +ws_client::~ws_client() +{ + delete d; +} + +ws_client::ws_client(ws_client && other) +{ + this->d = other.d; + other.d = nullptr; +} + +ws_client& ws_client::operator=(ws_client && other) +{ + if (this != &other) + { + delete this->d; + this->d = other.d; + other.d = nullptr; + } + + return *this; +} + +void ws_client::connect(std::string url) +{ + d->connect(url); +} + +void ws_client::service() +{ + d->service(); +} + +} \ No newline at end of file diff --git a/src/webfuse/ws/client.hpp b/src/webfuse/ws/client.hpp new file mode 100644 index 0000000..7441c79 --- /dev/null +++ b/src/webfuse/ws/client.hpp @@ -0,0 +1,33 @@ +#ifndef WEBFUSE_WSCLIENT_HPP +#define WEBFUSE_WSCLIENT_HPP + +#include "webfuse/ws/messagewriter.hpp" +#include "webfuse/ws/messagereader.hpp" + +#include + +namespace webfuse +{ + +using ws_client_handler = std::function; + +class ws_client +{ + ws_client(ws_client const &) = delete; + ws_client& operator=(ws_client const &) = delete; +public: + ws_client(ws_client_handler handler); + ~ws_client(); + ws_client(ws_client && other); + ws_client& operator=(ws_client && other); + + void connect(std::string url); + void service(); +private: + class detail; + detail * d; +}; + +} + +#endif diff --git a/src/webfuse/ws/messagewriter.cpp b/src/webfuse/ws/messagewriter.cpp index 3a8d4f6..0c81a2c 100644 --- a/src/webfuse/ws/messagewriter.cpp +++ b/src/webfuse/ws/messagewriter.cpp @@ -11,12 +11,20 @@ namespace webfuse constexpr uint8_t const rename_noreplace = 0x01; constexpr uint8_t const rename_exchange = 0x02; -messagewriter::messagewriter(message_type msg_type) +messagewriter::messagewriter(request_type req_type) : id(0) , data(LWS_PRE) { write_u32(0); - write_u8(static_cast(msg_type)); + write_u8(static_cast(req_type)); +} + +messagewriter::messagewriter(response_type res_type) +: id(0) +, data(LWS_PRE) +{ + write_u32(0); + write_u8(static_cast(res_type)); } messagewriter::messagewriter(messagewriter && other) diff --git a/src/webfuse/ws/messagewriter.hpp b/src/webfuse/ws/messagewriter.hpp index 22b58d6..cab7cec 100644 --- a/src/webfuse/ws/messagewriter.hpp +++ b/src/webfuse/ws/messagewriter.hpp @@ -1,7 +1,8 @@ #ifndef WEBFUSE_MESSAGEWRITER_HPP #define WEBFUSE_MESSAGEWRITER_HPP -#include "webfuse/message_type.hpp" +#include "webfuse/request_type.hpp" +#include "webfuse/response_type.hpp" #include #include @@ -15,7 +16,8 @@ class messagewriter messagewriter(messagewriter const &) = delete; messagewriter& operator=(messagewriter const &) = delete; public: - explicit messagewriter(message_type msg_type); + explicit messagewriter(request_type req_type); + explicit messagewriter(response_type res_type); ~messagewriter() = default; messagewriter(messagewriter && other); messagewriter& operator=(messagewriter && other); diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 4ae00b4..51cecb1 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -106,7 +106,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_SERVER_WRITEABLE: { - webfuse::messagewriter writer(webfuse::message_type::access_req); + webfuse::messagewriter writer(webfuse::request_type::unknown); bool has_msg = false; bool has_more = false; From a86061356adc24a95fb26291992fb000daef100d Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 30 Dec 2022 23:06:47 +0100 Subject: [PATCH 31/91] implemented some provider operations --- CMakeLists.txt | 4 ++ src/provider_main.cpp | 50 ++++++++++++- src/webfuse/provider.cpp | 68 ++++++++++++++++-- src/webfuse/provider.hpp | 2 + src/webfuse/request_type.cpp | 37 ++++++++++ src/webfuse/request_type.hpp | 2 + src/webfuse/response_type.cpp | 38 ++++++++++ src/webfuse/response_type.hpp | 3 + src/webfuse/ws/client.cpp | 96 ++++++++++++++++++++++--- src/webfuse/ws/client.hpp | 2 + src/webfuse/ws/messagereader.cpp | 9 +++ src/webfuse/ws/messagereader.hpp | 1 + src/webfuse/ws/messagewriter.cpp | 15 ++++ src/webfuse/ws/messagewriter.hpp | 3 + test-src/webfuse/test_request_type.cpp | 37 ++++++++++ test-src/webfuse/test_response_type.cpp | 38 ++++++++++ 16 files changed, 386 insertions(+), 19 deletions(-) create mode 100644 src/webfuse/request_type.cpp create mode 100644 src/webfuse/response_type.cpp create mode 100644 test-src/webfuse/test_request_type.cpp create mode 100644 test-src/webfuse/test_response_type.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index edbd14a..198c11a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,8 @@ add_library(webfuse_static STATIC src/webfuse/webfuse.cpp src/webfuse/provider.cpp src/webfuse/fuse.cpp + src/webfuse/request_type.cpp + src/webfuse/response_type.cpp src/webfuse/filesystem.cpp src/webfuse/filesystem/status.cpp src/webfuse/filesystem/accessmode.cpp @@ -51,6 +53,8 @@ if(NOT(WITHOUT_TEST)) test-src/webfuse/test/thread.cpp test-src/webfuse/test/tempdir.cpp test-src/webfuse/test_app.cpp + test-src/webfuse/test_request_type.cpp + test-src/webfuse/test_response_type.cpp test-src/webfuse/filesystem/test_status.cpp test-src/webfuse/filesystem/test_accessmode.cpp test-src/webfuse/filesystem/test_openflags.cpp diff --git a/src/provider_main.cpp b/src/provider_main.cpp index ec2c9a3..139a82e 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -1,4 +1,9 @@ #include "webfuse/provider.hpp" + +#include +#include +#include + #include #include @@ -29,12 +34,20 @@ public: int access(std::string const & path, int mode) override { - return -ENOENT; + auto const full_path = get_full_path(path); + std::cout << "access: " << full_path << std::endl; + + auto const result = ::access(full_path.c_str(), mode); + return (result == 0) ? 0 : -errno; } int getattr(std::string const & path, struct stat * attr) override { - return -ENOENT; + auto const full_path = get_full_path(path); + std::cout << "getattr: " << full_path << std::endl; + + auto const result = lstat(full_path.c_str(), attr); + return (result == 0) ? 0 : -errno; } int readlink(std::string const & path, std::string & out) override @@ -124,7 +137,27 @@ public: int readdir(std::string const & path, std::vector & entries, uint64_t handle) override { - return -ENOENT; + auto const full_path = get_full_path(path); + std::cout << "readdir: " << full_path << std::endl; + + int result = 0; + DIR * directory = opendir(full_path.c_str()); + if (NULL != directory) + { + dirent * entry = ::readdir(directory); + while (entry != nullptr) + { + entries.push_back(std::string(entry->d_name)); + entry = ::readdir(directory); + } + closedir(directory); + } + else + { + result = -errno; + } + + return result; } int rmdir(std::string const & path) override @@ -139,6 +172,11 @@ public: private: + std::string get_full_path(std::string const & path) + { + return base_path_ + path; + } + std::string base_path_; }; @@ -153,6 +191,12 @@ int main(int argc, char* argv[]) filesystem fs("."); webfuse::provider provider(fs); + provider.set_connection_listener([](bool connected) { + if (!connected) + { + shutdown_requested = true; + } + }); provider.connect("ws://localhost:8080/"); while (!shutdown_requested) { diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index adf3fdf..f4ffb4a 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -1,6 +1,11 @@ #include "webfuse/provider.hpp" #include "webfuse/ws/client.hpp" +#include +#include + +#include + namespace webfuse { @@ -29,24 +34,73 @@ public: client.service(); } + void set_connection_listener(std::function listener) + { + client.set_connection_listener(listener); + } + messagewriter on_message(messagereader & reader) { - auto message_id = reader.read_u32(); - auto request_type = reader.read_u8(); + auto const message_id = reader.read_u32(); + auto const req_type = get_request_type(reader.read_u8()); + auto const resp_type = get_response_type(req_type); - messagewriter writer(response_type::unknown); + messagewriter writer(resp_type); writer.set_id(message_id); - switch (request_type) + switch (req_type) { - + case request_type::access: + fs_access(reader, writer); + break; + case request_type::getattr: + fs_getattr(reader, writer); + break; + case request_type::readdir: + fs_readdir(reader, writer); default: + std::cout << "unknown request: " << ((int) req_type) << std::endl; break; } return std::move(writer); } private: + void fs_access(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const mode = reader.read_access_mode(); + + auto const result = fs_.access(path, mode); + writer.write_i32(result); + } + + void fs_getattr(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + + struct stat buffer; + auto const result = fs_.getattr(path, &buffer); + writer.write_i32(result); + if (0 == result) + { + writer.write_attr(&buffer); + } + } + + void fs_readdir(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + std::vector entries; + + auto const result = fs_.readdir(path, entries, static_cast(-1)); + writer.write_i32(result); + if (0 == result) + { + writer.write_strings(entries); + } + } + filesystem_i & fs_; ws_client client; }; @@ -90,5 +144,9 @@ void provider::service() d->service(); } +void provider::set_connection_listener(std::function listener) +{ + d->set_connection_listener(listener); +} } \ No newline at end of file diff --git a/src/webfuse/provider.hpp b/src/webfuse/provider.hpp index 806fa83..7709504 100644 --- a/src/webfuse/provider.hpp +++ b/src/webfuse/provider.hpp @@ -2,6 +2,7 @@ #define WEBFUSE_PROVIDER_I_HPP #include "webfuse/filesystem/filesystem_i.hpp" +#include #include namespace webfuse @@ -16,6 +17,7 @@ public: ~provider(); provider(provider && other); provider& operator=(provider && other); + void set_connection_listener(std::function listener); void connect(std::string const & url); void service(); private: diff --git a/src/webfuse/request_type.cpp b/src/webfuse/request_type.cpp new file mode 100644 index 0000000..0e7b8c0 --- /dev/null +++ b/src/webfuse/request_type.cpp @@ -0,0 +1,37 @@ +#include "webfuse/request_type.hpp" + +namespace webfuse +{ + +request_type get_request_type(uint8_t value) +{ + switch (value) + { + case static_cast(request_type::access): return request_type::access; + case static_cast(request_type::getattr): return request_type::getattr; + case static_cast(request_type::readlink): return request_type::readlink; + case static_cast(request_type::symlink): return request_type::symlink; + case static_cast(request_type::link): return request_type::link; + case static_cast(request_type::rename): return request_type::rename; + case static_cast(request_type::chmod): return request_type::chmod; + case static_cast(request_type::chown): return request_type::chown; + case static_cast(request_type::truncate): return request_type::truncate; + case static_cast(request_type::fsync): return request_type::fsync; + case static_cast(request_type::open): return request_type::open; + case static_cast(request_type::mknod): return request_type::mknod; + case static_cast(request_type::create): return request_type::create; + case static_cast(request_type::release): return request_type::release; + case static_cast(request_type::unlink): return request_type::unlink; + case static_cast(request_type::read): return request_type::read; + case static_cast(request_type::write): return request_type::write; + case static_cast(request_type::mkdir): return request_type::mkdir; + case static_cast(request_type::readdir): return request_type::readdir; + case static_cast(request_type::rmdir): return request_type::rmdir; + case static_cast(request_type::statfs): return request_type::statfs; + case static_cast(request_type::utimens): return request_type::utimens; + default: + return request_type::unknown; + } +} + +} \ No newline at end of file diff --git a/src/webfuse/request_type.hpp b/src/webfuse/request_type.hpp index 4d0687d..b605f0b 100644 --- a/src/webfuse/request_type.hpp +++ b/src/webfuse/request_type.hpp @@ -33,6 +33,8 @@ enum class request_type: uint8_t utimens = 0x16 }; +request_type get_request_type(uint8_t value); + } #endif \ No newline at end of file diff --git a/src/webfuse/response_type.cpp b/src/webfuse/response_type.cpp new file mode 100644 index 0000000..cb647e7 --- /dev/null +++ b/src/webfuse/response_type.cpp @@ -0,0 +1,38 @@ +#include "webfuse/response_type.hpp" + +namespace webfuse +{ + +response_type get_response_type(request_type value) +{ + switch (value) + { + case request_type::access: return response_type::access; + case request_type::getattr: return response_type::getattr; + case request_type::readlink: return response_type::readlink; + case request_type::symlink: return response_type::symlink; + case request_type::link: return response_type::link; + case request_type::rename: return response_type::rename; + case request_type::chmod: return response_type::chmod; + case request_type::chown: return response_type::chown; + case request_type::truncate: return response_type::truncate; + case request_type::fsync: return response_type::fsync; + case request_type::open: return response_type::open; + case request_type::mknod: return response_type::mknod; + case request_type::create: return response_type::create; + case request_type::release: return response_type::release; + case request_type::unlink: return response_type::unlink; + case request_type::read: return response_type::read; + case request_type::write: return response_type::write; + case request_type::mkdir: return response_type::mkdir; + case request_type::readdir: return response_type::readdir; + case request_type::rmdir: return response_type::rmdir; + case request_type::statfs: return response_type::statfs; + case request_type::utimens: return response_type::utimens; + default: + return response_type::unknown; + } +} + + +} \ No newline at end of file diff --git a/src/webfuse/response_type.hpp b/src/webfuse/response_type.hpp index 064451e..46cfebf 100644 --- a/src/webfuse/response_type.hpp +++ b/src/webfuse/response_type.hpp @@ -1,6 +1,7 @@ #ifndef WEBFUSE_RESPONSE_TYPE #define WEBFUSE_RESPONSE_TYPE +#include "request_type.hpp" #include namespace webfuse @@ -33,6 +34,8 @@ enum class response_type: uint8_t utimens = 0x96 }; +response_type get_response_type(request_type value); + } #endif \ No newline at end of file diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp index 29a5585..2266e1e 100644 --- a/src/webfuse/ws/client.cpp +++ b/src/webfuse/ws/client.cpp @@ -3,42 +3,94 @@ #include #include +#include +#include + namespace { +struct user_data +{ + webfuse::ws_client_handler handler; + std::function connection_listener; + struct lws * connection; + std::string current_message; + std::queue requests; +}; + extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, void* user, void * in, size_t length) { int result = 0; lws_protocols const * protocol = lws_get_protocol(wsi); + user_data * context = (nullptr != protocol) ? reinterpret_cast(protocol->user) : nullptr; - if (nullptr != protocol) + if (nullptr != context) { switch(reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: std::cout << "established" << std::endl; + context->connection_listener(true); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - std::cout << "connect error" << std::endl; - break; + // fall-through case LWS_CALLBACK_CLIENT_CLOSED: std::cout << "closed" << std::endl; + context->connection = nullptr; + context->requests = std::move(std::queue()); + context->current_message.clear(); + context->connection_listener(false); break; case LWS_CALLBACK_CLIENT_RECEIVE: - std::cout << "receive" << std::endl; + { + std::cout << "receive" << std::endl; + auto * fragment = reinterpret_cast(in); + context->current_message.append(fragment, length); + if (lws_is_final_fragment(wsi)) + { + try + { + webfuse::messagereader reader(context->current_message); + auto writer = context->handler(reader); + + context->requests.emplace(std::move(writer)); + lws_callback_on_writable(wsi); + } + catch(...) + { + // ToDo: log + std::cerr << "error: failed to create response" << std::endl; + } + } + } break; case LWS_CALLBACK_SERVER_WRITEABLE: // fall-through case LWS_CALLBACK_CLIENT_WRITEABLE: - std::cout << "writable" << std::endl; + { + std::cout << "writable" << std::endl; + if (!context->requests.empty()) + { + auto writer = std::move(context->requests.front()); + context->requests.pop(); + + size_t size; + auto * data = writer.get_data(size); + lws_write(wsi, data, size, LWS_WRITE_BINARY); + } + + if (!context->requests.empty()) + { + lws_callback_on_writable(wsi); + } + } break; default: break; } } - return result; } @@ -55,13 +107,12 @@ class ws_client::detail detail& operator=(detail &&) = delete; public: detail(ws_client_handler handler) - : handler_(handler) { memset(reinterpret_cast(protocols), 0, sizeof(lws_protocols) * 2); protocols[0].callback = &webfuse_client_callback; protocols[0].name = "webfuse2-client"; protocols[0].per_session_data_size = 0; - protocols[0].user = nullptr; + protocols[0].user = reinterpret_cast(&data); memset(reinterpret_cast(&info), 0, sizeof(lws_context_creation_info)); info.port = CONTEXT_PORT_NO_LISTEN; @@ -69,6 +120,10 @@ public: info.uid = -1; info.gid = -1; + data.handler = handler; + data.connection_listener = [](bool){ }; + data.connection = nullptr; + context = lws_create_context(&info); } @@ -90,7 +145,7 @@ public: info.ssl_connection = 0; info.protocol = "webfuse2"; info.local_protocol_name = "webfuse2-client"; - info.pwsi = &wsi; + info.pwsi = &data.connection; lws_client_connect_via_info(&info); } @@ -100,12 +155,21 @@ public: lws_service(context, 0); } + void interrupt() + { + lws_cancel_service(context); + } + + void set_connection_listener(std::function listener) + { + data.connection_listener = listener; + } + private: - ws_client_handler handler_; lws_context_creation_info info; lws_protocols protocols[2]; lws_context * context; - lws * wsi; + user_data data; }; ws_client::ws_client(ws_client_handler handler) @@ -147,4 +211,14 @@ void ws_client::service() d->service(); } +void ws_client::interrupt() +{ + d->interrupt(); +} + +void ws_client::set_connection_listener(std::function listener) +{ + d->set_connection_listener(listener); +} + } \ No newline at end of file diff --git a/src/webfuse/ws/client.hpp b/src/webfuse/ws/client.hpp index 7441c79..17fb475 100644 --- a/src/webfuse/ws/client.hpp +++ b/src/webfuse/ws/client.hpp @@ -21,8 +21,10 @@ public: ws_client(ws_client && other); ws_client& operator=(ws_client && other); + void set_connection_listener(std::function listener); void connect(std::string url); void service(); + void interrupt(); private: class detail; detail * d; diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp index f04c52a..620fec9 100644 --- a/src/webfuse/ws/messagereader.cpp +++ b/src/webfuse/ws/messagereader.cpp @@ -1,6 +1,7 @@ #include "webfuse/ws/messagereader.hpp" #include "webfuse/filesystem/status.hpp" #include "webfuse/filesystem/filemode.hpp" +#include "webfuse/filesystem/accessmode.hpp" #include @@ -37,6 +38,14 @@ int messagereader::read_result() return value.to_fusestatus(); } +int messagereader::read_access_mode() +{ + auto const value = read_u8(); + access_mode mode(static_cast(value)); + + return mode.to_int(); +} + void messagereader::read_attr(struct stat * attr) { attr->st_ino = static_cast(read_u64()); diff --git a/src/webfuse/ws/messagereader.hpp b/src/webfuse/ws/messagereader.hpp index 9157d8c..f1b6105 100644 --- a/src/webfuse/ws/messagereader.hpp +++ b/src/webfuse/ws/messagereader.hpp @@ -26,6 +26,7 @@ public: int read_result(); void read_attr(struct stat * attr); void read_statistics(struct statvfs * statistics); + int read_access_mode(); mode_t read_mode(); uint8_t read_u8(); diff --git a/src/webfuse/ws/messagewriter.cpp b/src/webfuse/ws/messagewriter.cpp index 0c81a2c..3fc9586 100644 --- a/src/webfuse/ws/messagewriter.cpp +++ b/src/webfuse/ws/messagewriter.cpp @@ -132,6 +132,21 @@ void messagewriter::write_strings(std::vector const & list) } } +void messagewriter::write_attr(struct stat const * attr) +{ + write_u64(static_cast(attr->st_ino)); + write_u64(static_cast(attr->st_nlink)); + write_mode(filemode::from_mode(attr->st_mode)); + write_u32(static_cast(attr->st_uid)); + write_u32(static_cast(attr->st_gid)); + write_u64(static_cast(attr->st_rdev)); + write_u64(static_cast(attr->st_size)); + write_u64(static_cast(attr->st_blocks)); + write_time(attr->st_atim); + write_time(attr->st_mtim); + write_time(attr->st_ctim); +} + void messagewriter::write_access_mode(int value) { access_mode mode = access_mode::from_int(value); diff --git a/src/webfuse/ws/messagewriter.hpp b/src/webfuse/ws/messagewriter.hpp index cab7cec..5b017d1 100644 --- a/src/webfuse/ws/messagewriter.hpp +++ b/src/webfuse/ws/messagewriter.hpp @@ -4,6 +4,8 @@ #include "webfuse/request_type.hpp" #include "webfuse/response_type.hpp" +#include + #include #include #include @@ -35,6 +37,7 @@ public: void write_data(char const * buffer, size_t size); void write_strings(std::vector const & list); + void write_attr(struct stat const * attr); void write_access_mode(int value); void write_rename_flags(unsigned int value); void write_mode(mode_t value); diff --git a/test-src/webfuse/test_request_type.cpp b/test-src/webfuse/test_request_type.cpp new file mode 100644 index 0000000..23134ad --- /dev/null +++ b/test-src/webfuse/test_request_type.cpp @@ -0,0 +1,37 @@ +#include "webfuse/request_type.hpp" +#include + +using webfuse::request_type; + +class request_type_test: public testing::TestWithParam { }; + +TEST_P(request_type_test, conversion) +{ + auto const expected = GetParam(); + auto const actual = webfuse::get_request_type(static_cast(expected)); + + ASSERT_EQ(expected, actual); +} + +INSTANTIATE_TEST_CASE_P(request_type_values, request_type_test, + testing::Values( + request_type::access, request_type::getattr, request_type::readlink, + request_type::symlink, request_type::link, request_type::link, + request_type::rename, request_type::chmod, request_type::chown, + request_type::truncate, request_type::fsync, request_type::open, + request_type::mknod, request_type::create, request_type::release, + request_type::unlink, request_type::read, request_type::write, + request_type::mkdir, request_type::readdir, request_type::rmdir, + request_type::statfs, request_type::utimens, request_type::unknown) +); + +TEST(request_type, unknown_values) +{ + auto const expected = request_type::unknown; + + ASSERT_EQ(expected, webfuse::get_request_type(0x20)); + ASSERT_EQ(expected, webfuse::get_request_type(0x30)); + ASSERT_EQ(expected, webfuse::get_request_type(0x80)); + ASSERT_EQ(expected, webfuse::get_request_type(0x42)); + ASSERT_EQ(expected, webfuse::get_request_type(0xff)); +} \ No newline at end of file diff --git a/test-src/webfuse/test_response_type.cpp b/test-src/webfuse/test_response_type.cpp new file mode 100644 index 0000000..6e20b21 --- /dev/null +++ b/test-src/webfuse/test_response_type.cpp @@ -0,0 +1,38 @@ +#include "webfuse/response_type.hpp" +#include + +using webfuse::request_type; + +class response_type_test: public testing::TestWithParam { }; + +TEST_P(response_type_test, conversion) +{ + auto const value = GetParam(); + auto const actual = webfuse::get_response_type(value); + auto const expected = static_cast(static_cast(value) | 0x80); + + ASSERT_EQ(expected, actual); +} + +INSTANTIATE_TEST_CASE_P(response_type_values, response_type_test, + testing::Values( + request_type::access, request_type::getattr, request_type::readlink, + request_type::symlink, request_type::link, request_type::link, + request_type::rename, request_type::chmod, request_type::chown, + request_type::truncate, request_type::fsync, request_type::open, + request_type::mknod, request_type::create, request_type::release, + request_type::unlink, request_type::read, request_type::write, + request_type::mkdir, request_type::readdir, request_type::rmdir, + request_type::statfs, request_type::utimens, request_type::unknown) +); + +TEST(respones_type, unknown_values) +{ + auto const expected = webfuse::response_type::unknown; + + ASSERT_EQ(expected, webfuse::get_response_type(static_cast(0x20))); + ASSERT_EQ(expected, webfuse::get_response_type(static_cast(0x30))); + ASSERT_EQ(expected, webfuse::get_response_type(static_cast(80))); + ASSERT_EQ(expected, webfuse::get_response_type(static_cast(0x42))); + ASSERT_EQ(expected, webfuse::get_response_type(static_cast(0xff))); +} \ No newline at end of file From 2e3d7a66b52fd0ed0d810da93787a1b86d58fa07 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 31 Dec 2022 20:41:45 +0100 Subject: [PATCH 32/91] added basic access test --- CMakeLists.txt | 5 +- src/webfuse/provider.cpp | 10 +++ src/webfuse/provider.hpp | 1 + src/webfuse/ws/client.cpp | 1 + test-src/webfuse/test/daemon.cpp | 37 +++++++++ test-src/webfuse/test/daemon.hpp | 25 ++++++ test-src/webfuse/test/filesystem_mock.hpp | 47 ++++++++++++ test-src/webfuse/test/fixture.cpp | 63 +++++++++++++++ test-src/webfuse/test/fixture.hpp | 46 +++++++++++ test-src/webfuse/test/process.cpp | 93 +++++++++++++++++++++++ test-src/webfuse/test/process.hpp | 29 +++++++ test-src/webfuse/test/tempdir.cpp | 2 +- test-src/webfuse/test/tempdir.hpp | 2 +- test-src/webfuse/test/thread.cpp | 39 ---------- test-src/webfuse/test/thread.hpp | 22 ------ test-src/webfuse/test_access.cpp | 45 +++++++++++ test-src/webfuse/test_app.cpp | 31 -------- 17 files changed, 403 insertions(+), 95 deletions(-) create mode 100644 test-src/webfuse/test/daemon.cpp create mode 100644 test-src/webfuse/test/daemon.hpp create mode 100644 test-src/webfuse/test/filesystem_mock.hpp create mode 100644 test-src/webfuse/test/fixture.cpp create mode 100644 test-src/webfuse/test/fixture.hpp create mode 100644 test-src/webfuse/test/process.cpp create mode 100644 test-src/webfuse/test/process.hpp delete mode 100644 test-src/webfuse/test/thread.cpp delete mode 100644 test-src/webfuse/test/thread.hpp create mode 100644 test-src/webfuse/test_access.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 198c11a..ce1b860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,11 +50,14 @@ if(NOT(WITHOUT_TEST)) pkg_check_modules(GMOCK REQUIRED gmock) add_executable(alltests - test-src/webfuse/test/thread.cpp test-src/webfuse/test/tempdir.cpp + test-src/webfuse/test/fixture.cpp + test-src/webfuse/test/process.cpp + test-src/webfuse/test/daemon.cpp test-src/webfuse/test_app.cpp test-src/webfuse/test_request_type.cpp test-src/webfuse/test_response_type.cpp + test-src/webfuse/test_access.cpp test-src/webfuse/filesystem/test_status.cpp test-src/webfuse/filesystem/test_accessmode.cpp test-src/webfuse/filesystem/test_openflags.cpp diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index f4ffb4a..1c544e7 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -34,6 +34,11 @@ public: client.service(); } + void interrupt() + { + client.interrupt(); + } + void set_connection_listener(std::function listener) { client.set_connection_listener(listener); @@ -144,6 +149,11 @@ void provider::service() d->service(); } +void provider::interrupt() +{ + d->interrupt(); +} + void provider::set_connection_listener(std::function listener) { d->set_connection_listener(listener); diff --git a/src/webfuse/provider.hpp b/src/webfuse/provider.hpp index 7709504..7c10a8a 100644 --- a/src/webfuse/provider.hpp +++ b/src/webfuse/provider.hpp @@ -20,6 +20,7 @@ public: void set_connection_listener(std::function listener); void connect(std::string const & url); void service(); + void interrupt(); private: class detail; detail * d; diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp index 2266e1e..760bcbf 100644 --- a/src/webfuse/ws/client.cpp +++ b/src/webfuse/ws/client.cpp @@ -34,6 +34,7 @@ extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, v context->connection_listener(true); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + std::cout << "connection error" << std::endl; // fall-through case LWS_CALLBACK_CLIENT_CLOSED: std::cout << "closed" << std::endl; diff --git a/test-src/webfuse/test/daemon.cpp b/test-src/webfuse/test/daemon.cpp new file mode 100644 index 0000000..31d17b2 --- /dev/null +++ b/test-src/webfuse/test/daemon.cpp @@ -0,0 +1,37 @@ +#include "webfuse/test/daemon.hpp" + +#include +#include +#include + +#include + +namespace +{ + +std::string get_executable_path() +{ + char directory[PATH_MAX] = { '\0' }; + readlink("/proc/self/exe", directory, PATH_MAX); + dirname(directory); + + return std::string(directory) + "/webfuse"; +} + +} + +namespace webfuse +{ + +daemon::daemon(std::string const & mountpoint) +: p({get_executable_path(), "-f", mountpoint}) +{ + +} + +daemon::~daemon() +{ + p.kill(SIGINT); +} + +} \ No newline at end of file diff --git a/test-src/webfuse/test/daemon.hpp b/test-src/webfuse/test/daemon.hpp new file mode 100644 index 0000000..b5827ae --- /dev/null +++ b/test-src/webfuse/test/daemon.hpp @@ -0,0 +1,25 @@ +#ifndef WEBFUSE_DAEMOM_HPP +#define WEBFUSE_DAEMOM_HPP + +#include "webfuse/test/process.hpp" +#include + +namespace webfuse +{ + +class daemon +{ + daemon(daemon const &) = delete; + daemon& operator=(daemon const &) = delete; + daemon(daemon &&) = delete; + daemon& operator=(daemon &&) = delete; +public: + explicit daemon(std::string const & mountpoint); + ~daemon(); +private: + process p; +}; + +} + +#endif diff --git a/test-src/webfuse/test/filesystem_mock.hpp b/test-src/webfuse/test/filesystem_mock.hpp new file mode 100644 index 0000000..c7d807c --- /dev/null +++ b/test-src/webfuse/test/filesystem_mock.hpp @@ -0,0 +1,47 @@ +#ifndef WEBFUSE_FILESYSTEM_MOCK_HPP +#define WEBFUSE_FILESYSTEM_MOCK_HPP + +#include "webfuse/filesystem/filesystem_i.hpp" +#include + +namespace webfuse +{ + +class filesystem_mock: public filesystem_i +{ +public: + ~filesystem_mock() override = default; + + MOCK_METHOD(int, access, (std::string const & path, int mode)); + MOCK_METHOD(int, getattr, (std::string const & path, struct stat * attr)); + + MOCK_METHOD(int, readlink, (std::string const & path, std::string & out)); + MOCK_METHOD(int, symlink, (std::string const & target, std::string const & linkpath)); + MOCK_METHOD(int, link, (std::string const & old_path, std::string const & new_path)); + + MOCK_METHOD(int, rename, (std::string const & old_path, std::string const & new_path, int flags)); + MOCK_METHOD(int, chmod, (std::string const & path, mode_t mode)); + MOCK_METHOD(int, chown, (std::string const & path, uid_t uid, gid_t gid)); + MOCK_METHOD(int, truncate, (std::string const & path, uint64_t size, uint64_t handle)); + MOCK_METHOD(int, fsync, (std::string const & path, bool is_datasync, uint64_t handle)); + MOCK_METHOD(int, utimens, (std::string const &path, struct timespec tv[2], uint64_t handle)); + + MOCK_METHOD(int, open, (std::string const & path, int flags, uint64_t & handle)); + MOCK_METHOD(int, mknod, (std::string const & path, mode_t mode, dev_t rdev)); + MOCK_METHOD(int, create, (std::string const & path, mode_t mode, uint64_t & handle)); + MOCK_METHOD(int, release, (std::string const & path, uint64_t handle)); + MOCK_METHOD(int, unlink, (std::string const & path)); + + MOCK_METHOD(int, read, (std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle)); + MOCK_METHOD(int, write, (std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle)); + + MOCK_METHOD(int, mkdir, (std::string const & path, mode_t mode)); + MOCK_METHOD(int, readdir, (std::string const & path, std::vector & entries, uint64_t handle)); + MOCK_METHOD(int, rmdir, (std::string const & path)); + + MOCK_METHOD(int, statfs, (std::string const & path, struct statvfs * statistics)); +}; + +} + +#endif diff --git a/test-src/webfuse/test/fixture.cpp b/test-src/webfuse/test/fixture.cpp new file mode 100644 index 0000000..58ad574 --- /dev/null +++ b/test-src/webfuse/test/fixture.cpp @@ -0,0 +1,63 @@ +#include "webfuse/test/fixture.hpp" +#include +#include +#include +#include + +namespace webfuse +{ + +fixture::fixture(filesystem_i & fs) +: shutdown_requested(false) +, provider_running(false) +, fs_provider(fs) +, app(working_dir.name()) +{ + fs_provider.set_connection_listener([this](bool is_connected) { + if (is_connected) + { + this->provider_running = true; + } + + if ((!is_connected) && (!this->provider_running)) + { + this->reconnect(); + } + }); + provider_thread = std::thread(std::bind(&fixture::provider_run, this)); + while (!provider_running) + { + std::this_thread::yield(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); +} + +fixture::~fixture() +{ + shutdown_requested = true; + fs_provider.interrupt(); + provider_thread.join(); +} + +void fixture::provider_run() +{ + fs_provider.connect("ws://localhost:8081/"); + while (!shutdown_requested) + { + fs_provider.service(); + } +} + +void fixture::reconnect() +{ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + fs_provider.connect("ws://localhost:8081/"); +} + +std::string const & fixture::get_path() const +{ + return working_dir.name(); +} + + +} \ No newline at end of file diff --git a/test-src/webfuse/test/fixture.hpp b/test-src/webfuse/test/fixture.hpp new file mode 100644 index 0000000..7d3e3e5 --- /dev/null +++ b/test-src/webfuse/test/fixture.hpp @@ -0,0 +1,46 @@ +#ifndef WEBFUSE_FIXTURE_HPP +#define WEBFUSE_FIXTURE_HPP + +#include "webfuse/filesystem/filesystem_i.hpp" +#include "webfuse/webfuse.hpp" +#include "webfuse/provider.hpp" + +#include "webfuse/test/tempdir.hpp" +#include "webfuse/test/daemon.hpp" + +#include +#include +#include +#include + +namespace webfuse +{ + +class fixture +{ + fixture(fixture const &) = delete; + fixture& operator=(fixture const &) = delete; + fixture(fixture const &&) = delete; + fixture& operator=(fixture &&) = delete; +public: + explicit fixture(filesystem_i & fs); + ~fixture(); + + std::string const & get_path() const; + + void reconnect(); +private: + void provider_run(); + std::atomic shutdown_requested; + std::atomic provider_running; + provider fs_provider; + + tempdir working_dir; + daemon app; + std::thread provider_thread; +}; + + +} + +#endif diff --git a/test-src/webfuse/test/process.cpp b/test-src/webfuse/test/process.cpp new file mode 100644 index 0000000..bd29c5c --- /dev/null +++ b/test-src/webfuse/test/process.cpp @@ -0,0 +1,93 @@ +#include "webfuse/test/process.hpp" + +#include +#include + +#include +#include +#include + +#include + +namespace webfuse +{ + +process::process(std::vector const & commandline) +{ + if (commandline.empty()) + { + throw std::runtime_error("missing command"); + } + + pid = fork(); + if (pid == 0) + { + size_t const count = commandline.size() + 1; + char ** args = reinterpret_cast(malloc(sizeof(char*) * count)); + args[count - 1] = nullptr; + for(size_t i = 0; i < commandline.size(); i++) + { + args[i] = strdup(commandline[i].c_str()); + } + + closefrom(0); + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + dup2(STDOUT_FILENO, STDERR_FILENO); + + execv(args[0], args); + + // this should not be reached + for(size_t i = 0; i < count; i++) + { + free(args[i]); + } + free(args); + + exit(EXIT_FAILURE); + } + else if (pid > 0) + { + // parent: do nothing + } + else + { + throw std::runtime_error("failed to fork"); + } +} + +process::~process() +{ + if (pid > 0) + { + wait(); + } +} + +void process::kill(int signal_number) +{ + if (pid > 0) + { + ::kill(pid, signal_number); + } +} + +int process::wait() +{ + int exit_code = -1; + + if (pid > 0) + { + int status = 0; + int rc = waitpid(pid, &status, 0); + if (rc == 0) + { + exit_code = WEXITSTATUS(status); + pid = 0; + } + } + + return exit_code; +} + +} \ No newline at end of file diff --git a/test-src/webfuse/test/process.hpp b/test-src/webfuse/test/process.hpp new file mode 100644 index 0000000..3154524 --- /dev/null +++ b/test-src/webfuse/test/process.hpp @@ -0,0 +1,29 @@ +#ifndef WEBFUSE_PROCESS_HPP +#define WEBFUSE_PROCESS_HPP + +#include + +#include +#include + +namespace webfuse +{ + +class process +{ + process(process const &) = delete; + process operator=(process const &) = delete; + process(process &&) = delete; + process operator=(process &&) = delete; +public: + process(std::vector const & commandline); + ~process(); + void kill(int signal_number); + int wait(); +private: + pid_t pid; +}; + +} + +#endif diff --git a/test-src/webfuse/test/tempdir.cpp b/test-src/webfuse/test/tempdir.cpp index 9474ba5..760b7dd 100644 --- a/test-src/webfuse/test/tempdir.cpp +++ b/test-src/webfuse/test/tempdir.cpp @@ -15,7 +15,7 @@ tempdir::~tempdir() unlink(path.c_str()); } -std::string const tempdir::name() const +std::string const & tempdir::name() const { return path; } diff --git a/test-src/webfuse/test/tempdir.hpp b/test-src/webfuse/test/tempdir.hpp index 216341b..0e73894 100644 --- a/test-src/webfuse/test/tempdir.hpp +++ b/test-src/webfuse/test/tempdir.hpp @@ -11,7 +11,7 @@ class tempdir public: tempdir(); ~tempdir(); - std::string const name() const; + std::string const & name() const; private: std::string path; diff --git a/test-src/webfuse/test/thread.cpp b/test-src/webfuse/test/thread.cpp deleted file mode 100644 index 2ebef00..0000000 --- a/test-src/webfuse/test/thread.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "webfuse/test/thread.hpp" -#include - -namespace -{ - -extern "C" void * webfuse_thread_main(void * args) -{ - auto * run = reinterpret_cast *>(args); - (*run)(); - return nullptr; -} - -} - - -namespace webfuse -{ - -thread::thread(std::function run) -{ - pthread_create(&real_thread, nullptr, - &webfuse_thread_main, - reinterpret_cast(&run)); - -} - -thread::~thread() -{ - pthread_join(real_thread, nullptr); -} - -void thread::kill(int signal_id) -{ - pthread_kill(real_thread, signal_id); -} - - -} \ No newline at end of file diff --git a/test-src/webfuse/test/thread.hpp b/test-src/webfuse/test/thread.hpp deleted file mode 100644 index 8dfbd67..0000000 --- a/test-src/webfuse/test/thread.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef WEBFUSE_THREAD_HPP -#define WEBFUSE_THREAD_HPP - -#include -#include - -namespace webfuse -{ - -class thread -{ -public: - explicit thread(std::function run); - ~thread(); - void kill(int signal_id); -private: - pthread_t real_thread; -}; - -} - -#endif diff --git a/test-src/webfuse/test_access.cpp b/test-src/webfuse/test_access.cpp new file mode 100644 index 0000000..c1ed4fe --- /dev/null +++ b/test-src/webfuse/test_access.cpp @@ -0,0 +1,45 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" +#include "webfuse/test/daemon.hpp" + +#include +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +namespace +{ +int fs_getattr (std::string const & path, struct stat * attr) +{ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/foo") + { + attr->st_nlink = 0; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } +} +} + +TEST(access, ok) +{ + webfuse::filesystem_mock fs; + webfuse::fixture fixture(fs); + std::cout << "setup" << std::endl; + + EXPECT_CALL(fs, access("/foo",_)).WillOnce(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr)); + auto const path = fixture.get_path() + "/foo"; + + int const rc = ::access(path.c_str(), F_OK); + ASSERT_EQ(0, rc); +} diff --git a/test-src/webfuse/test_app.cpp b/test-src/webfuse/test_app.cpp index 0f546c6..77d4f56 100644 --- a/test-src/webfuse/test_app.cpp +++ b/test-src/webfuse/test_app.cpp @@ -1,38 +1,7 @@ #include "webfuse/webfuse.hpp" -#include "webfuse/test/thread.hpp" -#include "webfuse/test/tempdir.hpp" #include -#include -#include -#include -#include -#include - -extern "C" void * run(void * args) -{ - webfuse::app * app = reinterpret_cast(args); - - return nullptr; -} TEST(app, init) { webfuse::app app; } - -TEST(app, run) -{ - webfuse::tempdir dir; - webfuse::thread thread([&dir](){ - webfuse::app app; - char arg0[] = "webfuse"; - char arg1[] = "-f"; - char* arg2 = strdup(dir.name().c_str()); - char* argv[] = { arg0, arg1, arg2, nullptr}; - int rc = app.run(3, argv); - free(arg2); - }); - - std::this_thread::sleep_for(std::chrono::seconds(1)); - thread.kill(SIGINT); -} \ No newline at end of file From 246dddab2c70f148c2d00f4e87b13231ec6bc8f2 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 31 Dec 2022 21:33:25 +0100 Subject: [PATCH 33/91] ignore integrative tests for memcheck --- CMakeLists.txt | 2 +- test-src/webfuse/test_access.cpp | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce1b860..4a79b7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ if(NOT(WITHOUT_TEST)) find_program(VALGRIND valgrind REQUIRED) if(VALGRIND) - add_custom_target(memcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./alltests) + add_custom_target(memcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./alltests --gtest_filter=-*_NO_MEMCHECK) endif() endif() \ No newline at end of file diff --git a/test-src/webfuse/test_access.cpp b/test-src/webfuse/test_access.cpp index c1ed4fe..68dd7f4 100644 --- a/test-src/webfuse/test_access.cpp +++ b/test-src/webfuse/test_access.cpp @@ -17,7 +17,13 @@ int fs_getattr (std::string const & path, struct stat * attr) { memset(reinterpret_cast(attr),0, sizeof(struct stat)); - if (path == "/foo") + if (path == "/") + { + attr->st_nlink = 0; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else if (path == "/foo") { attr->st_nlink = 0; attr->st_mode = S_IFREG | 0755; @@ -30,14 +36,14 @@ int fs_getattr (std::string const & path, struct stat * attr) } } -TEST(access, ok) +TEST(access, ok_NO_MEMCHECK) { webfuse::filesystem_mock fs; - webfuse::fixture fixture(fs); - std::cout << "setup" << std::endl; - + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); EXPECT_CALL(fs, access("/foo",_)).WillOnce(Return(0)); EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr)); + + webfuse::fixture fixture(fs); auto const path = fixture.get_path() + "/foo"; int const rc = ::access(path.c_str(), F_OK); From dcb590bd501dbd2980b691cd97c4a0c375cae3ed Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 12:32:21 +0100 Subject: [PATCH 34/91] fixed sending multiple pending messages --- src/webfuse/ws/server.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 51cecb1..aa9ad67 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -130,6 +130,10 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, int const rc = lws_write(data->connection, raw_data, size, LWS_WRITE_BINARY); } + if (has_more) + { + lws_callback_on_writable(data->connection); + } } break; default: From 0e0bb74872a27ae7e2d3b51b3782785edacfa59a Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 13:01:35 +0100 Subject: [PATCH 35/91] sepated unit and integration tests --- CMakeLists.txt | 47 ++++++++++++------- src/webfuse/ws/client.cpp | 6 ++- .../{webfuse => integration}/test_access.cpp | 0 .../{ => integration}/webfuse/test/daemon.cpp | 0 .../{ => integration}/webfuse/test/daemon.hpp | 0 .../webfuse/test/filesystem_mock.hpp | 0 .../webfuse/test/fixture.cpp | 0 .../webfuse/test/fixture.hpp | 0 .../webfuse/test/process.cpp | 0 .../webfuse/test/process.hpp | 0 .../webfuse/test/tempdir.cpp | 0 .../webfuse/test/tempdir.hpp | 0 .../webfuse/filesystem/test_accessmode.cpp | 0 .../webfuse/filesystem/test_filemode.cpp | 0 .../webfuse/filesystem/test_openflags.cpp | 0 .../webfuse/filesystem/test_status.cpp | 0 test-src/{ => unit}/webfuse/test_app.cpp | 0 .../{ => unit}/webfuse/test_request_type.cpp | 0 .../{ => unit}/webfuse/test_response_type.cpp | 0 19 files changed, 34 insertions(+), 19 deletions(-) rename test-src/{webfuse => integration}/test_access.cpp (100%) rename test-src/{ => integration}/webfuse/test/daemon.cpp (100%) rename test-src/{ => integration}/webfuse/test/daemon.hpp (100%) rename test-src/{ => integration}/webfuse/test/filesystem_mock.hpp (100%) rename test-src/{ => integration}/webfuse/test/fixture.cpp (100%) rename test-src/{ => integration}/webfuse/test/fixture.hpp (100%) rename test-src/{ => integration}/webfuse/test/process.cpp (100%) rename test-src/{ => integration}/webfuse/test/process.hpp (100%) rename test-src/{ => integration}/webfuse/test/tempdir.cpp (100%) rename test-src/{ => integration}/webfuse/test/tempdir.hpp (100%) rename test-src/{ => unit}/webfuse/filesystem/test_accessmode.cpp (100%) rename test-src/{ => unit}/webfuse/filesystem/test_filemode.cpp (100%) rename test-src/{ => unit}/webfuse/filesystem/test_openflags.cpp (100%) rename test-src/{ => unit}/webfuse/filesystem/test_status.cpp (100%) rename test-src/{ => unit}/webfuse/test_app.cpp (100%) rename test-src/{ => unit}/webfuse/test_request_type.cpp (100%) rename test-src/{ => unit}/webfuse/test_response_type.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a79b7e..359d3f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,34 +49,45 @@ if(NOT(WITHOUT_TEST)) pkg_check_modules(GTEST REQUIRED gtest_main) pkg_check_modules(GMOCK REQUIRED gmock) - add_executable(alltests - test-src/webfuse/test/tempdir.cpp - test-src/webfuse/test/fixture.cpp - test-src/webfuse/test/process.cpp - test-src/webfuse/test/daemon.cpp - test-src/webfuse/test_app.cpp - test-src/webfuse/test_request_type.cpp - test-src/webfuse/test_response_type.cpp - test-src/webfuse/test_access.cpp - test-src/webfuse/filesystem/test_status.cpp - test-src/webfuse/filesystem/test_accessmode.cpp - test-src/webfuse/filesystem/test_openflags.cpp - test-src/webfuse/filesystem/test_filemode.cpp + add_executable(unit_tests + test-src/unit/webfuse/test_app.cpp + test-src/unit/webfuse/test_request_type.cpp + test-src/unit/webfuse/test_response_type.cpp + test-src/unit/webfuse/filesystem/test_status.cpp + test-src/unit/webfuse/filesystem/test_accessmode.cpp + test-src/unit/webfuse/filesystem/test_openflags.cpp + test-src/unit/webfuse/filesystem/test_filemode.cpp ) - target_include_directories(alltests PRIVATE test-src ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) - target_compile_options(alltests PRIVATE + target_include_directories(unit_tests PRIVATE test-src/unit ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) + target_compile_options(unit_tests PRIVATE ${GTEST_CFLAGS} ${GTEST_CFLAGS_OTHER} ${GMOCK_CFLAGS} ${GMOCK_CFLAGS_OTHER} ) - target_link_libraries(alltests PRIVATE webfuse_static ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) + target_link_libraries(unit_tests PRIVATE webfuse_static ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) + + add_executable(integration_tests + test-src/integration/webfuse/test/tempdir.cpp + test-src/integration/webfuse/test/fixture.cpp + test-src/integration/webfuse/test/process.cpp + test-src/integration/webfuse/test/daemon.cpp + test-src/integration/test_access.cpp + ) + + target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) + target_compile_options(integration_tests PRIVATE + ${GTEST_CFLAGS} ${GTEST_CFLAGS_OTHER} + ${GMOCK_CFLAGS} ${GMOCK_CFLAGS_OTHER} + ) + target_link_libraries(integration_tests PRIVATE webfuse_static ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) enable_testing() - add_test(NAME alltests COMMAND alltests) + add_test(NAME unit_tests COMMAND unit_tests) + add_test(NAME integration_tests COMMAND integration_tests) find_program(VALGRIND valgrind REQUIRED) if(VALGRIND) - add_custom_target(memcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./alltests --gtest_filter=-*_NO_MEMCHECK) + add_custom_target(memcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./unit_tests) endif() endif() \ No newline at end of file diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp index 760bcbf..48b963a 100644 --- a/src/webfuse/ws/client.cpp +++ b/src/webfuse/ws/client.cpp @@ -35,7 +35,11 @@ extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, v break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: std::cout << "connection error" << std::endl; - // fall-through + context->connection = nullptr; + context->requests = std::move(std::queue()); + context->current_message.clear(); + context->connection_listener(false); + break; case LWS_CALLBACK_CLIENT_CLOSED: std::cout << "closed" << std::endl; context->connection = nullptr; diff --git a/test-src/webfuse/test_access.cpp b/test-src/integration/test_access.cpp similarity index 100% rename from test-src/webfuse/test_access.cpp rename to test-src/integration/test_access.cpp diff --git a/test-src/webfuse/test/daemon.cpp b/test-src/integration/webfuse/test/daemon.cpp similarity index 100% rename from test-src/webfuse/test/daemon.cpp rename to test-src/integration/webfuse/test/daemon.cpp diff --git a/test-src/webfuse/test/daemon.hpp b/test-src/integration/webfuse/test/daemon.hpp similarity index 100% rename from test-src/webfuse/test/daemon.hpp rename to test-src/integration/webfuse/test/daemon.hpp diff --git a/test-src/webfuse/test/filesystem_mock.hpp b/test-src/integration/webfuse/test/filesystem_mock.hpp similarity index 100% rename from test-src/webfuse/test/filesystem_mock.hpp rename to test-src/integration/webfuse/test/filesystem_mock.hpp diff --git a/test-src/webfuse/test/fixture.cpp b/test-src/integration/webfuse/test/fixture.cpp similarity index 100% rename from test-src/webfuse/test/fixture.cpp rename to test-src/integration/webfuse/test/fixture.cpp diff --git a/test-src/webfuse/test/fixture.hpp b/test-src/integration/webfuse/test/fixture.hpp similarity index 100% rename from test-src/webfuse/test/fixture.hpp rename to test-src/integration/webfuse/test/fixture.hpp diff --git a/test-src/webfuse/test/process.cpp b/test-src/integration/webfuse/test/process.cpp similarity index 100% rename from test-src/webfuse/test/process.cpp rename to test-src/integration/webfuse/test/process.cpp diff --git a/test-src/webfuse/test/process.hpp b/test-src/integration/webfuse/test/process.hpp similarity index 100% rename from test-src/webfuse/test/process.hpp rename to test-src/integration/webfuse/test/process.hpp diff --git a/test-src/webfuse/test/tempdir.cpp b/test-src/integration/webfuse/test/tempdir.cpp similarity index 100% rename from test-src/webfuse/test/tempdir.cpp rename to test-src/integration/webfuse/test/tempdir.cpp diff --git a/test-src/webfuse/test/tempdir.hpp b/test-src/integration/webfuse/test/tempdir.hpp similarity index 100% rename from test-src/webfuse/test/tempdir.hpp rename to test-src/integration/webfuse/test/tempdir.hpp diff --git a/test-src/webfuse/filesystem/test_accessmode.cpp b/test-src/unit/webfuse/filesystem/test_accessmode.cpp similarity index 100% rename from test-src/webfuse/filesystem/test_accessmode.cpp rename to test-src/unit/webfuse/filesystem/test_accessmode.cpp diff --git a/test-src/webfuse/filesystem/test_filemode.cpp b/test-src/unit/webfuse/filesystem/test_filemode.cpp similarity index 100% rename from test-src/webfuse/filesystem/test_filemode.cpp rename to test-src/unit/webfuse/filesystem/test_filemode.cpp diff --git a/test-src/webfuse/filesystem/test_openflags.cpp b/test-src/unit/webfuse/filesystem/test_openflags.cpp similarity index 100% rename from test-src/webfuse/filesystem/test_openflags.cpp rename to test-src/unit/webfuse/filesystem/test_openflags.cpp diff --git a/test-src/webfuse/filesystem/test_status.cpp b/test-src/unit/webfuse/filesystem/test_status.cpp similarity index 100% rename from test-src/webfuse/filesystem/test_status.cpp rename to test-src/unit/webfuse/filesystem/test_status.cpp diff --git a/test-src/webfuse/test_app.cpp b/test-src/unit/webfuse/test_app.cpp similarity index 100% rename from test-src/webfuse/test_app.cpp rename to test-src/unit/webfuse/test_app.cpp diff --git a/test-src/webfuse/test_request_type.cpp b/test-src/unit/webfuse/test_request_type.cpp similarity index 100% rename from test-src/webfuse/test_request_type.cpp rename to test-src/unit/webfuse/test_request_type.cpp diff --git a/test-src/webfuse/test_response_type.cpp b/test-src/unit/webfuse/test_response_type.cpp similarity index 100% rename from test-src/webfuse/test_response_type.cpp rename to test-src/unit/webfuse/test_response_type.cpp From 2234f69d2de9e2feedf987bfbef827dddec6e399 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 14:48:19 +0100 Subject: [PATCH 36/91] add readdir test --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 1 + test-src/integration/test_access.cpp | 28 ++++++-- test-src/integration/test_readdir.cpp | 97 +++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 test-src/integration/test_readdir.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 359d3f5..f522418 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/webfuse/test/process.cpp test-src/integration/webfuse/test/daemon.cpp test-src/integration/test_access.cpp + test-src/integration/test_readdir.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 1c544e7..cc1eef6 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -63,6 +63,7 @@ public: break; case request_type::readdir: fs_readdir(reader, writer); + break; default: std::cout << "unknown request: " << ((int) req_type) << std::endl; break; diff --git a/test-src/integration/test_access.cpp b/test-src/integration/test_access.cpp index 68dd7f4..6486c01 100644 --- a/test-src/integration/test_access.cpp +++ b/test-src/integration/test_access.cpp @@ -36,16 +36,36 @@ int fs_getattr (std::string const & path, struct stat * attr) } } -TEST(access, ok_NO_MEMCHECK) +TEST(readdir, existing_file) { webfuse::filesystem_mock fs; EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); - EXPECT_CALL(fs, access("/foo",_)).WillOnce(Return(0)); + EXPECT_CALL(fs, access("/foo", F_OK)).WillOnce(Return(0)); + EXPECT_CALL(fs, access("/foo", R_OK)).WillOnce(Return(0)); + EXPECT_CALL(fs, access("/foo", W_OK)).WillOnce(Return(0)); + EXPECT_CALL(fs, access("/foo", X_OK)).WillOnce(Return(-EACCES)); EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr)); webfuse::fixture fixture(fs); auto const path = fixture.get_path() + "/foo"; - int const rc = ::access(path.c_str(), F_OK); - ASSERT_EQ(0, rc); + ASSERT_EQ(0, ::access(path.c_str(), F_OK)); + ASSERT_EQ(0, ::access(path.c_str(), R_OK)); + ASSERT_EQ(0, ::access(path.c_str(), W_OK)); + ASSERT_EQ(-1, ::access(path.c_str(), X_OK)); + ASSERT_EQ(EACCES, errno); +} + +TEST(access, non_existing_file) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, access("/foo", F_OK)).WillOnce(Return(-ENOENT)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/foo"; + + ASSERT_EQ(-1, ::access(path.c_str(), F_OK)); + ASSERT_EQ(ENOENT, errno); } diff --git a/test-src/integration/test_readdir.cpp b/test-src/integration/test_readdir.cpp new file mode 100644 index 0000000..638192d --- /dev/null +++ b/test-src/integration/test_readdir.cpp @@ -0,0 +1,97 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" +#include "webfuse/test/daemon.hpp" + +#include +#include +#include + +#include +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(readdir, existing_dir) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](auto const & path, auto * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if ((path == "/") or (path == "/some_dir")) + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, readdir("/some_dir",_,_)).WillOnce(Invoke([](auto const & path, auto & entries, auto handle) { + entries.push_back("foo"); + return 0; + })); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_dir"; + + std::unordered_map expected_entries = { + {".", false}, + {"..", false}, + {"foo", false}, + }; + + DIR * dir = opendir(path.c_str()); + ASSERT_NE(nullptr, dir); + dirent * entry = readdir(dir); + int count = 0; + while (nullptr != entry) + { + count++; + + auto it = expected_entries.find(entry->d_name); + ASSERT_NE(expected_entries.end(), it); + ASSERT_FALSE(it->second); + it->second = true; + + entry = readdir(dir); + } + closedir(dir); + + ASSERT_EQ(3, count); +} + +TEST(readdir, non_existing_dir) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](auto const & path, auto * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, readdir("/some_dir",_,_)).Times(0); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_dir"; + + DIR * dir = opendir(path.c_str()); + ASSERT_EQ(nullptr, dir); + ASSERT_EQ(ENOENT, errno); +} + From 48a43c9260b68ff4890270590b0bcaab7c9b5a20 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 14:54:28 +0100 Subject: [PATCH 37/91] removed debug output --- src/webfuse/ws/client.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp index 48b963a..d47fdb4 100644 --- a/src/webfuse/ws/client.cpp +++ b/src/webfuse/ws/client.cpp @@ -30,18 +30,15 @@ extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, v switch(reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: - std::cout << "established" << std::endl; context->connection_listener(true); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - std::cout << "connection error" << std::endl; context->connection = nullptr; context->requests = std::move(std::queue()); context->current_message.clear(); context->connection_listener(false); break; case LWS_CALLBACK_CLIENT_CLOSED: - std::cout << "closed" << std::endl; context->connection = nullptr; context->requests = std::move(std::queue()); context->current_message.clear(); @@ -49,7 +46,6 @@ extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, v break; case LWS_CALLBACK_CLIENT_RECEIVE: { - std::cout << "receive" << std::endl; auto * fragment = reinterpret_cast(in); context->current_message.append(fragment, length); if (lws_is_final_fragment(wsi)) @@ -74,7 +70,6 @@ extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, v // fall-through case LWS_CALLBACK_CLIENT_WRITEABLE: { - std::cout << "writable" << std::endl; if (!context->requests.empty()) { auto writer = std::move(context->requests.front()); From 8511dae266ab4f546451bc38a8502dd0386440dd Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 16:15:44 +0100 Subject: [PATCH 38/91] fixed readlink (libfuse expects readlink to return 0 on success) --- src/webfuse/fuse.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp index 5fd8cac..a1e101e 100644 --- a/src/webfuse/fuse.cpp +++ b/src/webfuse/fuse.cpp @@ -56,7 +56,6 @@ static int fs_readlink(char const * path, char * buffer, size_t buffer_size) if (0 == result) { snprintf(buffer, buffer_size, "%s", out.c_str()); - result = strlen(buffer); } return result; From d75cc0736bd78a99797a5636a8f944730681eb4d Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 16:22:44 +0100 Subject: [PATCH 39/91] add test for readlink --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 16 ++++++ test-src/integration/test_readlink.cpp | 71 ++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 test-src/integration/test_readlink.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f522418..dbf302a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/webfuse/test/daemon.cpp test-src/integration/test_access.cpp test-src/integration/test_readdir.cpp + test-src/integration/test_readlink.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index cc1eef6..d22f8e0 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -61,6 +61,9 @@ public: case request_type::getattr: fs_getattr(reader, writer); break; + case request_type::readlink: + fs_readlink(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -107,6 +110,19 @@ private: } } + void fs_readlink(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + std::string out; + + auto const result = fs_.readlink(path, out); + writer.write_i32(result); + if (0 == result) + { + writer.write_str(out); + } + } + filesystem_i & fs_; ws_client client; }; diff --git a/test-src/integration/test_readlink.cpp b/test-src/integration/test_readlink.cpp new file mode 100644 index 0000000..da7e370 --- /dev/null +++ b/test-src/integration/test_readlink.cpp @@ -0,0 +1,71 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +namespace +{ + +int fs_getattr(std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else if (path == "/some_link") + { + attr->st_nlink = 1; + attr->st_mode = S_IFLNK | 0755; + return 0; + } + else + { + return -ENOENT; + } + } + +} + +TEST(readlink, existing_link) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr)); + EXPECT_CALL(fs, readlink("/some_link",_)).WillOnce(Invoke([](auto const & path, auto & out) { + out = "link-target"; + return 0; + })); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_link"; + + char buffer[100]; + auto const length = ::readlink(path.c_str(), buffer, 99); + ASSERT_LT(0, length); + buffer[length] = '\0'; + ASSERT_STREQ("link-target", buffer); +} + +TEST(readlink, non_existing_link) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr)); + EXPECT_CALL(fs, readlink("/some_link",_)).WillOnce(Invoke([](auto const & path, auto & out) { + return -ENOENT; + })); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_link"; + + char buffer[100]; + ASSERT_GT(0, ::readlink(path.c_str(), buffer, 100)); + ASSERT_EQ(ENOENT, errno); +} From 761b6edb05c77c45d189efbd2a463396fd9a57ae Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 17:05:42 +0100 Subject: [PATCH 40/91] add test for symlink --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 28 +++++++--- test-src/integration/test_readlink.cpp | 3 ++ test-src/integration/test_symlink.cpp | 74 ++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 test-src/integration/test_symlink.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dbf302a..859d8e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_access.cpp test-src/integration/test_readdir.cpp test-src/integration/test_readlink.cpp + test-src/integration/test_symlink.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index d22f8e0..721f6c7 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -64,6 +64,9 @@ public: case request_type::readlink: fs_readlink(reader, writer); break; + case request_type::symlink: + fs_symlink(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -97,29 +100,38 @@ private: } } - void fs_readdir(messagereader & reader, messagewriter & writer) + void fs_readlink(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); - std::vector entries; + std::string out; - auto const result = fs_.readdir(path, entries, static_cast(-1)); + auto const result = fs_.readlink(path, out); writer.write_i32(result); if (0 == result) { - writer.write_strings(entries); + writer.write_str(out); } } - void fs_readlink(messagereader & reader, messagewriter & writer) + void fs_symlink(messagereader & reader, messagewriter & writer) + { + auto const from = reader.read_str(); + auto const to = reader.read_str(); + + auto const result = fs_.symlink(from, to); + writer.write_i32(result); + } + + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); - std::string out; + std::vector entries; - auto const result = fs_.readlink(path, out); + auto const result = fs_.readdir(path, entries, static_cast(-1)); writer.write_i32(result); if (0 == result) { - writer.write_str(out); + writer.write_strings(entries); } } diff --git a/test-src/integration/test_readlink.cpp b/test-src/integration/test_readlink.cpp index da7e370..ce0bf6b 100644 --- a/test-src/integration/test_readlink.cpp +++ b/test-src/integration/test_readlink.cpp @@ -2,6 +2,9 @@ #include "webfuse/test/fixture.hpp" #include "webfuse/test/filesystem_mock.hpp" +#include + + using testing::_; using testing::Return; using testing::Invoke; diff --git a/test-src/integration/test_symlink.cpp b/test-src/integration/test_symlink.cpp new file mode 100644 index 0000000..3ed881e --- /dev/null +++ b/test-src/integration/test_symlink.cpp @@ -0,0 +1,74 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(symlink, sucessfully_create_symlink) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else if ((path == "/some_link") && (link_created)) + { + attr->st_nlink = 1; + attr->st_mode = S_IFLNK | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, symlink("link-target", "/some_link")).WillOnce(Invoke([&link_created](auto const &, auto const &){ + link_created = true; + return 0; + })); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_link"; + + ASSERT_EQ(0, ::symlink("link-target", path.c_str())); +} + +TEST(symlink, failed_to_create_symlink) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, symlink("link-target", "/some_link")).WillOnce(Return(-EDQUOT)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_link"; + + ASSERT_NE(0, ::symlink("link-target", path.c_str())); + ASSERT_EQ(EDQUOT, errno); +} From 815a03545fed6a73f353559be21f55e5446128fd Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 17:45:39 +0100 Subject: [PATCH 41/91] add test for link --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 12 ++++ test-src/integration/test_link.cpp | 88 ++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 test-src/integration/test_link.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 859d8e9..ebdfe8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_readdir.cpp test-src/integration/test_readlink.cpp test-src/integration/test_symlink.cpp + test-src/integration/test_link.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 721f6c7..1b1b53c 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -67,6 +67,9 @@ public: case request_type::symlink: fs_symlink(reader, writer); break; + case request_type::link: + fs_link(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -122,6 +125,15 @@ private: writer.write_i32(result); } + void fs_link(messagereader & reader, messagewriter & writer) + { + auto const from = reader.read_str(); + auto const to = reader.read_str(); + + auto const result = fs_.link(from, to); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_link.cpp b/test-src/integration/test_link.cpp new file mode 100644 index 0000000..5e1f490 --- /dev/null +++ b/test-src/integration/test_link.cpp @@ -0,0 +1,88 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(link, sucessfully_create_link) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else if (path == "/link-target") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else if ((path == "/some_link") && (link_created)) + { + attr->st_nlink = 2; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, link("/link-target", "/some_link")).WillOnce(Invoke([&link_created](auto const &, auto const &){ + link_created = true; + return 0; + })); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_link"; + auto const from = fixture.get_path() + "/link-target"; + + ASSERT_EQ(0, ::link(from.c_str(), path.c_str())); +} + +TEST(link, failed_to_create_link) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else if (path == "/link-target") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, link("/link-target", "/some_link")).WillOnce(Return(-EDQUOT)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_link"; + auto const from = fixture.get_path() + "/link-target"; + + ASSERT_NE(0, ::link(from.c_str(), path.c_str())); + ASSERT_EQ(EDQUOT, errno); +} From eb5419ecf46ae49409b9892f3f175ec6e124da54 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 18:49:59 +0100 Subject: [PATCH 42/91] readdir: removed parameter "handle" --- src/provider_main.cpp | 2 +- src/webfuse/filesystem.cpp | 4 ++-- src/webfuse/filesystem.hpp | 2 +- src/webfuse/filesystem/empty_filesystem.cpp | 2 +- src/webfuse/filesystem/empty_filesystem.hpp | 2 +- src/webfuse/filesystem/filesystem_i.hpp | 2 +- src/webfuse/fuse.cpp | 2 +- src/webfuse/provider.cpp | 2 +- test-src/integration/test_readdir.cpp | 4 ++-- test-src/integration/webfuse/test/filesystem_mock.hpp | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/provider_main.cpp b/src/provider_main.cpp index 139a82e..d3b779f 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -135,7 +135,7 @@ public: return -ENOENT; } - int readdir(std::string const & path, std::vector & entries, uint64_t handle) override + int readdir(std::string const & path, std::vector & entries) override { auto const full_path = get_full_path(path); std::cout << "readdir: " << full_path << std::endl; diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index feadeb7..517b73f 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -364,7 +364,7 @@ int filesystem::mkdir(std::string const & path, mode_t mode) } } -int filesystem::readdir(std::string const & path, std::vector & entries, uint64_t handle) +int filesystem::readdir(std::string const & path, std::vector & entries) { try { @@ -380,7 +380,7 @@ int filesystem::readdir(std::string const & path, std::vector & ent } catch(...) { - return fallback.readdir(path, entries, handle); + return fallback.readdir(path, entries); } } diff --git a/src/webfuse/filesystem.hpp b/src/webfuse/filesystem.hpp index ca1e29e..b20aca8 100644 --- a/src/webfuse/filesystem.hpp +++ b/src/webfuse/filesystem.hpp @@ -42,7 +42,7 @@ public: int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override; int mkdir(std::string const & path, mode_t mode) override; - int readdir(std::string const & path, std::vector & entries, uint64_t handle) override; + int readdir(std::string const & path, std::vector & entries) override; int rmdir(std::string const & path) override; int statfs(std::string const & path, struct statvfs * statistics) override; diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index b0d33b3..71b4fb3 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -115,7 +115,7 @@ int empty_filesystem::mkdir(std::string const & path, mode_t mode) return -EPERM; } -int empty_filesystem::readdir(std::string const & path, std::vector & entries, uint64_t handle) +int empty_filesystem::readdir(std::string const & path, std::vector & entries) { if (path == "/") { diff --git a/src/webfuse/filesystem/empty_filesystem.hpp b/src/webfuse/filesystem/empty_filesystem.hpp index c271063..97ff3ef 100644 --- a/src/webfuse/filesystem/empty_filesystem.hpp +++ b/src/webfuse/filesystem/empty_filesystem.hpp @@ -35,7 +35,7 @@ public: int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override; int mkdir(std::string const & path, mode_t mode) override; - int readdir(std::string const & path, std::vector & entries, uint64_t handle) override; + int readdir(std::string const & path, std::vector & entries) override; int rmdir(std::string const & path) override; int statfs(std::string const & path, struct statvfs * statistivs) override; diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp index 2d42e52..2e3f3c7 100644 --- a/src/webfuse/filesystem/filesystem_i.hpp +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -42,7 +42,7 @@ public: virtual int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) = 0; virtual int mkdir(std::string const & path, mode_t mode) = 0; - virtual int readdir(std::string const & path, std::vector & entries, uint64_t handle) = 0; + virtual int readdir(std::string const & path, std::vector & entries) = 0; virtual int rmdir(std::string const & path) = 0; virtual int statfs(std::string const & path, struct statvfs * statistics) = 0; diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp index a1e101e..6d856a0 100644 --- a/src/webfuse/fuse.cpp +++ b/src/webfuse/fuse.cpp @@ -184,7 +184,7 @@ static int fs_readdir(char const * path, void * buffer, auto * const fs = fs_get_filesystem(); auto handle = fs_get_handle(info); std::vector names; - auto const result = fs->readdir(path, names, handle); + auto const result = fs->readdir(path, names); if (0 == result) { filler(buffer, ".", nullptr, 0, static_cast(0)); diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 1b1b53c..b457f25 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -139,7 +139,7 @@ private: auto const path = reader.read_str(); std::vector entries; - auto const result = fs_.readdir(path, entries, static_cast(-1)); + auto const result = fs_.readdir(path, entries); writer.write_i32(result); if (0 == result) { diff --git a/test-src/integration/test_readdir.cpp b/test-src/integration/test_readdir.cpp index 638192d..7ea787f 100644 --- a/test-src/integration/test_readdir.cpp +++ b/test-src/integration/test_readdir.cpp @@ -33,7 +33,7 @@ TEST(readdir, existing_dir) return -ENOENT; } })); - EXPECT_CALL(fs, readdir("/some_dir",_,_)).WillOnce(Invoke([](auto const & path, auto & entries, auto handle) { + EXPECT_CALL(fs, readdir("/some_dir",_)).WillOnce(Invoke([](auto const & path, auto & entries) { entries.push_back("foo"); return 0; })); @@ -85,7 +85,7 @@ TEST(readdir, non_existing_dir) return -ENOENT; } })); - EXPECT_CALL(fs, readdir("/some_dir",_,_)).Times(0); + EXPECT_CALL(fs, readdir("/some_dir",_)).Times(0); webfuse::fixture fixture(fs); auto const path = fixture.get_path() + "/some_dir"; diff --git a/test-src/integration/webfuse/test/filesystem_mock.hpp b/test-src/integration/webfuse/test/filesystem_mock.hpp index c7d807c..56a6f55 100644 --- a/test-src/integration/webfuse/test/filesystem_mock.hpp +++ b/test-src/integration/webfuse/test/filesystem_mock.hpp @@ -36,7 +36,7 @@ public: MOCK_METHOD(int, write, (std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle)); MOCK_METHOD(int, mkdir, (std::string const & path, mode_t mode)); - MOCK_METHOD(int, readdir, (std::string const & path, std::vector & entries, uint64_t handle)); + MOCK_METHOD(int, readdir, (std::string const & path, std::vector & entries)); MOCK_METHOD(int, rmdir, (std::string const & path)); MOCK_METHOD(int, statfs, (std::string const & path, struct statvfs * statistics)); From 3b9521016dc139aa538604bf30f9a4980c7dab4b Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 19:15:38 +0100 Subject: [PATCH 43/91] add test for renmae --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 13 +++++ test-src/integration/test_rename.cpp | 81 ++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 test-src/integration/test_rename.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ebdfe8a..3a7d331 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_readlink.cpp test-src/integration/test_symlink.cpp test-src/integration/test_link.cpp + test-src/integration/test_rename.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index b457f25..920b18b 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -70,6 +70,9 @@ public: case request_type::link: fs_link(reader, writer); break; + case request_type::rename: + fs_rename(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -134,6 +137,16 @@ private: writer.write_i32(result); } + void fs_rename(messagereader & reader, messagewriter & writer) + { + auto const from = reader.read_str(); + auto const to = reader.read_str(); + auto const flags = reader.read_u8(); + + auto const result = fs_.rename(from, to, flags); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_rename.cpp b/test-src/integration/test_rename.cpp new file mode 100644 index 0000000..d944c99 --- /dev/null +++ b/test-src/integration/test_rename.cpp @@ -0,0 +1,81 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(rename, success) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, rename("/some_file", "/another_file", _)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const old_name = fixture.get_path() + "/some_file"; + auto const new_name = fixture.get_path() + "/another_file"; + + ASSERT_EQ(0, ::rename(old_name.c_str(), new_name.c_str())); +} + +TEST(rename, fail) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, rename("/some_file", "/another_file", _)).WillOnce(Return(-EDQUOT)); + + webfuse::fixture fixture(fs); + auto const old_name = fixture.get_path() + "/some_file"; + auto const new_name = fixture.get_path() + "/another_file"; + + ASSERT_NE(0, ::rename(old_name.c_str(), new_name.c_str())); + ASSERT_EQ(EDQUOT, errno); +} From 2b040b409ebffb1cd68d7622aebcade8f7777947 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 19:44:10 +0100 Subject: [PATCH 44/91] add test for chmod --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 12 +++++ test-src/integration/test_chmod.cpp | 79 +++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 test-src/integration/test_chmod.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a7d331..1084a72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_symlink.cpp test-src/integration/test_link.cpp test-src/integration/test_rename.cpp + test-src/integration/test_chmod.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 920b18b..f513452 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -73,6 +73,9 @@ public: case request_type::rename: fs_rename(reader, writer); break; + case request_type::chmod: + fs_chmod(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -147,6 +150,15 @@ private: writer.write_i32(result); } + void fs_chmod(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const mode = reader.read_mode(); + + auto const result = fs_.chmod(path, mode); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_chmod.cpp b/test-src/integration/test_chmod.cpp new file mode 100644 index 0000000..bff1fe9 --- /dev/null +++ b/test-src/integration/test_chmod.cpp @@ -0,0 +1,79 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(chmod, success) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, chmod("/some_file", S_IFREG | 0644)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + ASSERT_EQ(0, ::chmod(path.c_str(), 0644)); +} + +TEST(chmod, fail) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, chmod("/some_file", S_IFREG | 0644)).WillOnce(Return(-EACCES)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + ASSERT_NE(0, ::chmod(path.c_str(), 0644)); + ASSERT_EQ(EACCES, errno); +} From fc82e398064bd0d8caa682eda9fa9637efc03857 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 1 Jan 2023 19:50:29 +0100 Subject: [PATCH 45/91] add test for chown --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 13 +++++ test-src/integration/test_chown.cpp | 79 +++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 test-src/integration/test_chown.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1084a72..b8cff36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_link.cpp test-src/integration/test_rename.cpp test-src/integration/test_chmod.cpp + test-src/integration/test_chown.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index f513452..1407695 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -76,6 +76,9 @@ public: case request_type::chmod: fs_chmod(reader, writer); break; + case request_type::chown: + fs_chown(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -159,6 +162,16 @@ private: writer.write_i32(result); } + void fs_chown(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const uid = static_cast(reader.read_u32()); + auto const gid = static_cast(reader.read_u32()); + + auto const result = fs_.chown(path, uid, gid); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_chown.cpp b/test-src/integration/test_chown.cpp new file mode 100644 index 0000000..74407d7 --- /dev/null +++ b/test-src/integration/test_chown.cpp @@ -0,0 +1,79 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(chown, success) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, chown("/some_file", 42, 42)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + ASSERT_EQ(0, ::chown(path.c_str(), 42, 42)); +} + +TEST(chown, fail) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, chown("/some_file", 42, 42)).WillOnce(Return(-EACCES)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + ASSERT_NE(0, ::chown(path.c_str(), 42, 42)); + ASSERT_EQ(EACCES, errno); +} From ff25327eb5271aa89fd20a30ecd60a3bf7f10e4c Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Mon, 2 Jan 2023 18:27:44 +0100 Subject: [PATCH 46/91] add test for truncate --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 13 ++++++++ test-src/integration/test_truncate.cpp | 44 ++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 test-src/integration/test_truncate.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b8cff36..d7a5868 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_rename.cpp test-src/integration/test_chmod.cpp test-src/integration/test_chown.cpp + test-src/integration/test_truncate.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 1407695..5a22771 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -79,6 +79,9 @@ public: case request_type::chown: fs_chown(reader, writer); break; + case request_type::truncate: + fs_truncate(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -172,6 +175,16 @@ private: writer.write_i32(result); } + void fs_truncate(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const size = reader.read_u64(); + auto const handle = reader.read_u64(); + + auto const result = fs_.truncate(path, size, handle); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_truncate.cpp b/test-src/integration/test_truncate.cpp new file mode 100644 index 0000000..759f963 --- /dev/null +++ b/test-src/integration/test_truncate.cpp @@ -0,0 +1,44 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(truncate, success) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, truncate("/some_file", 42, _)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + ASSERT_EQ(0, ::truncate(path.c_str(), 42)); +} From 61b97f19aa05158b2f10a4cb140d21509ffd2bc4 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Mon, 2 Jan 2023 19:47:29 +0100 Subject: [PATCH 47/91] add test for fsync --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 42 ++++++++++++++++++++++ src/webfuse/ws/messagereader.cpp | 5 +++ src/webfuse/ws/messagereader.hpp | 1 + test-src/integration/test_fsync.cpp | 56 +++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 test-src/integration/test_fsync.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d7a5868..612b79e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_chmod.cpp test-src/integration/test_chown.cpp test-src/integration/test_truncate.cpp + test-src/integration/test_fsync.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 5a22771..512baac 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -82,6 +82,15 @@ public: case request_type::truncate: fs_truncate(reader, writer); break; + case request_type::fsync: + fs_fsync(reader, writer); + break; + case request_type::create: + fs_create(reader, writer); + break; + case request_type::release: + fs_release(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -185,6 +194,39 @@ private: writer.write_i32(result); } + void fs_fsync(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const is_datasync = reader.read_bool(); + auto const handle = reader.read_u64(); + + auto const result = fs_.fsync(path, is_datasync, handle); + writer.write_i32(result); + } + + void fs_create(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const mode = reader.read_mode(); + uint64_t handle = static_cast(-1); + + auto const result = fs_.create(path, mode, handle); + writer.write_i32(result); + if (result == 0) + { + writer.write_u64(handle); + } + } + + void fs_release(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const handle = reader.read_u64(); + + auto const result = fs_.release(path, handle); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp index 620fec9..6b28a67 100644 --- a/src/webfuse/ws/messagereader.cpp +++ b/src/webfuse/ws/messagereader.cpp @@ -83,6 +83,11 @@ mode_t messagereader::read_mode() return mode.to_mode(); } +bool messagereader::read_bool() +{ + return (1 == read_u8()); +} + uint8_t messagereader::read_u8() { diff --git a/src/webfuse/ws/messagereader.hpp b/src/webfuse/ws/messagereader.hpp index f1b6105..f7c490f 100644 --- a/src/webfuse/ws/messagereader.hpp +++ b/src/webfuse/ws/messagereader.hpp @@ -29,6 +29,7 @@ public: int read_access_mode(); mode_t read_mode(); + bool read_bool(); uint8_t read_u8(); uint32_t read_u32(); uint64_t read_u64(); diff --git a/test-src/integration/test_fsync.cpp b/test-src/integration/test_fsync.cpp new file mode 100644 index 0000000..9117f52 --- /dev/null +++ b/test-src/integration/test_fsync.cpp @@ -0,0 +1,56 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; +using testing::AtMost; + +TEST(fsync, success) +{ + bool file_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&file_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if ((path == "/some_file") && (file_created)) + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0644; + attr->st_size = 0; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, create("/some_file", _, _)).WillOnce(Invoke([&file_created](auto const &, auto const, auto &){ + file_created = true; + return 0; + })); + EXPECT_CALL(fs, fsync("/some_file", _, _)).WillOnce(Return(0)); + EXPECT_CALL(fs, release("/some_file", _)).Times(AtMost(1)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + int fd = creat(path.c_str(), 0644); + ASSERT_LT(0, fd); + ASSERT_EQ(0, ::fsync(fd)); + close(fd); +} From a5673b281c8c4e4da4cdb853c41e84e7f3410740 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Mon, 2 Jan 2023 20:18:19 +0100 Subject: [PATCH 48/91] add unit test for utimens ; fixed signature of utimens --- CMakeLists.txt | 1 + src/provider_main.cpp | 2 +- src/webfuse/filesystem.cpp | 2 +- src/webfuse/filesystem.hpp | 2 +- src/webfuse/filesystem/empty_filesystem.cpp | 2 +- src/webfuse/filesystem/empty_filesystem.hpp | 2 +- src/webfuse/filesystem/filesystem_i.hpp | 2 +- src/webfuse/fuse.cpp | 8 ++++ src/webfuse/provider.cpp | 15 ++++++ src/webfuse/ws/messagereader.cpp | 6 +++ src/webfuse/ws/messagereader.hpp | 2 +- test-src/integration/test_utimens.cpp | 46 +++++++++++++++++++ .../webfuse/test/filesystem_mock.hpp | 2 +- 13 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 test-src/integration/test_utimens.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 612b79e..be9fe3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_chown.cpp test-src/integration/test_truncate.cpp test-src/integration/test_fsync.cpp + test-src/integration/test_utimens.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/provider_main.cpp b/src/provider_main.cpp index d3b779f..d8fa8eb 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -90,7 +90,7 @@ public: return -ENOENT; } - int utimens(std::string const &path, struct timespec tv[2], uint64_t handle) override + int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) override { return -ENOENT; } diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index 517b73f..41bc258 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -190,7 +190,7 @@ int filesystem::fsync(std::string const & path, bool is_datasync, uint64_t handl } } -int filesystem::utimens(std::string const &path, struct timespec tv[2], uint64_t handle) +int filesystem::utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) { try { diff --git a/src/webfuse/filesystem.hpp b/src/webfuse/filesystem.hpp index b20aca8..b2f7002 100644 --- a/src/webfuse/filesystem.hpp +++ b/src/webfuse/filesystem.hpp @@ -30,7 +30,7 @@ public: int chown(std::string const & path, uid_t uid, gid_t gid) override; int truncate(std::string const & path, uint64_t size, uint64_t handle) override; int fsync(std::string const & path, bool is_datasync, uint64_t handle) override; - int utimens(std::string const &path, struct timespec tv[2], uint64_t handle) override; + int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) override; int open(std::string const & path, int flags, uint64_t & handle) override; int mknod(std::string const & path, mode_t mode, dev_t rdev) override; diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index 71b4fb3..3b37e3a 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -71,7 +71,7 @@ int empty_filesystem::fsync(std::string const & path, bool is_datasync, uint64_t return 0; } -int empty_filesystem::utimens(std::string const &path, struct timespec tv[2], uint64_t handle) +int empty_filesystem::utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) { return -ENOSYS; } diff --git a/src/webfuse/filesystem/empty_filesystem.hpp b/src/webfuse/filesystem/empty_filesystem.hpp index 97ff3ef..2b7400b 100644 --- a/src/webfuse/filesystem/empty_filesystem.hpp +++ b/src/webfuse/filesystem/empty_filesystem.hpp @@ -23,7 +23,7 @@ public: int chown(std::string const & path, uid_t uid, gid_t gid) override; int truncate(std::string const & path, uint64_t size, uint64_t handle) override; int fsync(std::string const & path, bool is_datasync, uint64_t handle) override; - int utimens(std::string const &path, struct timespec tv[2], uint64_t handle) override; + int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) override; int open(std::string const & path, int flags, uint64_t & handle) override; int mknod(std::string const & path, mode_t mode, dev_t rdev) override; diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp index 2e3f3c7..fe94ed8 100644 --- a/src/webfuse/filesystem/filesystem_i.hpp +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -30,7 +30,7 @@ public: virtual int chown(std::string const & path, uid_t uid, gid_t gid) = 0; virtual int truncate(std::string const & path, uint64_t size, uint64_t handle) = 0; virtual int fsync(std::string const & path, bool is_datasync, uint64_t handle) = 0; - virtual int utimens(std::string const &path, struct timespec tv[2], uint64_t handle) = 0; + virtual int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) = 0; virtual int open(std::string const & path, int flags, uint64_t & handle) = 0; virtual int mknod(std::string const & path, mode_t mode, dev_t rdev) = 0; diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp index 6d856a0..8600d02 100644 --- a/src/webfuse/fuse.cpp +++ b/src/webfuse/fuse.cpp @@ -210,6 +210,13 @@ static int fs_statfs(char const * path, struct statvfs * buffer) return fs->statfs(path, buffer); } +static int fs_utimes(const char * path, const struct timespec tv[2], struct fuse_file_info * info) +{ + auto * const fs = fs_get_filesystem(); + auto handle = fs_get_handle(info); + return fs->utimens(path, tv, handle); +} + } namespace webfuse @@ -277,6 +284,7 @@ int fuse::run(int argc, char * argv[]) operations.readdir = fs_readdir; operations.rmdir = fs_rmdir; operations.statfs = fs_statfs; + operations.utimens = fs_utimes; return fuse_main(argc, argv, &operations, context); } diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 512baac..ae92eb7 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -85,6 +85,9 @@ public: case request_type::fsync: fs_fsync(reader, writer); break; + case request_type::utimens: + fs_utimens(reader, writer); + break; case request_type::create: fs_create(reader, writer); break; @@ -204,6 +207,18 @@ private: writer.write_i32(result); } + void fs_utimens(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + struct timespec times[2]; + reader.read_time(times[0]); + reader.read_time(times[1]); + auto const handle = reader.read_u64(); + + auto const result = fs_.utimens(path, times, handle); + writer.write_i32(result); + } + void fs_create(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp index 6b28a67..1c22a85 100644 --- a/src/webfuse/ws/messagereader.cpp +++ b/src/webfuse/ws/messagereader.cpp @@ -180,5 +180,11 @@ void messagereader::read_strings(std::vector &entries) } } +void messagereader::read_time(struct timespec &time) +{ + time.tv_sec = static_cast(read_u64()); + time.tv_nsec = static_cast(read_u32()); +} + } \ No newline at end of file diff --git a/src/webfuse/ws/messagereader.hpp b/src/webfuse/ws/messagereader.hpp index f7c490f..22ad119 100644 --- a/src/webfuse/ws/messagereader.hpp +++ b/src/webfuse/ws/messagereader.hpp @@ -40,7 +40,7 @@ public: std::string read_bytes(); void read_strings(std::vector &entries); - + void read_time(struct timespec &time); private: diff --git a/test-src/integration/test_utimens.cpp b/test-src/integration/test_utimens.cpp new file mode 100644 index 0000000..24d04c3 --- /dev/null +++ b/test-src/integration/test_utimens.cpp @@ -0,0 +1,46 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +#include +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(utimens, success) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0644; + return 0; + } + else + { + std::cout << "getattr: failed" << std::endl; + return -ENOENT; + } + })); + EXPECT_CALL(fs, utimens("/some_file", _, _)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + timeval times[2] = {42,42}; + ASSERT_EQ(0, ::utimes(path.c_str(), times)); +} diff --git a/test-src/integration/webfuse/test/filesystem_mock.hpp b/test-src/integration/webfuse/test/filesystem_mock.hpp index 56a6f55..844160f 100644 --- a/test-src/integration/webfuse/test/filesystem_mock.hpp +++ b/test-src/integration/webfuse/test/filesystem_mock.hpp @@ -24,7 +24,7 @@ public: MOCK_METHOD(int, chown, (std::string const & path, uid_t uid, gid_t gid)); MOCK_METHOD(int, truncate, (std::string const & path, uint64_t size, uint64_t handle)); MOCK_METHOD(int, fsync, (std::string const & path, bool is_datasync, uint64_t handle)); - MOCK_METHOD(int, utimens, (std::string const &path, struct timespec tv[2], uint64_t handle)); + MOCK_METHOD(int, utimens, (std::string const &path, struct timespec const tv[2], uint64_t handle)); MOCK_METHOD(int, open, (std::string const & path, int flags, uint64_t & handle)); MOCK_METHOD(int, mknod, (std::string const & path, mode_t mode, dev_t rdev)); From 516749876bf4668b1de2eb1a9643e8fe1c0bd40b Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Tue, 3 Jan 2023 19:55:27 +0100 Subject: [PATCH 49/91] add test for open --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 17 +++++++++++ src/webfuse/ws/messagereader.cpp | 9 ++++++ src/webfuse/ws/messagereader.hpp | 2 +- test-src/integration/test_open.cpp | 49 ++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 test-src/integration/test_open.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index be9fe3b..390ca87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_truncate.cpp test-src/integration/test_fsync.cpp test-src/integration/test_utimens.cpp + test-src/integration/test_open.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index ae92eb7..e4a7dcd 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -88,6 +88,9 @@ public: case request_type::utimens: fs_utimens(reader, writer); break; + case request_type::open: + fs_open(reader, writer); + break; case request_type::create: fs_create(reader, writer); break; @@ -219,6 +222,20 @@ private: writer.write_i32(result); } + void fs_open(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const flags = reader.read_openflags(); + uint64_t handle = static_cast(-1); + + auto const result = fs_.open(path, flags, handle); + writer.write_i32(result); + if (result == 0) + { + writer.write_u64(handle); + } + } + void fs_create(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp index 1c22a85..1f966e3 100644 --- a/src/webfuse/ws/messagereader.cpp +++ b/src/webfuse/ws/messagereader.cpp @@ -2,6 +2,7 @@ #include "webfuse/filesystem/status.hpp" #include "webfuse/filesystem/filemode.hpp" #include "webfuse/filesystem/accessmode.hpp" +#include "webfuse/filesystem/openflags.hpp" #include @@ -186,5 +187,13 @@ void messagereader::read_time(struct timespec &time) time.tv_nsec = static_cast(read_u32()); } +int messagereader::read_openflags() +{ + auto const value = read_i32(); + openflags flags(value); + + return flags.to_int(); +} + } \ No newline at end of file diff --git a/src/webfuse/ws/messagereader.hpp b/src/webfuse/ws/messagereader.hpp index 22ad119..de4720c 100644 --- a/src/webfuse/ws/messagereader.hpp +++ b/src/webfuse/ws/messagereader.hpp @@ -41,7 +41,7 @@ public: void read_strings(std::vector &entries); void read_time(struct timespec &time); - + int read_openflags(); private: std::string data; diff --git a/test-src/integration/test_open.cpp b/test-src/integration/test_open.cpp new file mode 100644 index 0000000..dcfb3e9 --- /dev/null +++ b/test-src/integration/test_open.cpp @@ -0,0 +1,49 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; +using testing::AtMost; + +TEST(open, success) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, open("/some_file", O_RDONLY, _)).WillOnce(Return(0)); + EXPECT_CALL(fs, release("/some_file", _)).Times(AtMost(1)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + int fd = ::open(path.c_str(), O_RDONLY); + ASSERT_LT(0, fd); + ASSERT_EQ(0, ::close(fd)); +} From 02d6474e77fc4b850033576ec961d7ee6c261201 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Tue, 3 Jan 2023 20:42:07 +0100 Subject: [PATCH 50/91] add test for mknod --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 13 ++++++++ test-src/integration/test_mknod.cpp | 52 +++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 test-src/integration/test_mknod.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 390ca87..e63b02c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_fsync.cpp test-src/integration/test_utimens.cpp test-src/integration/test_open.cpp + test-src/integration/test_mknod.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index e4a7dcd..124c038 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -91,6 +91,9 @@ public: case request_type::open: fs_open(reader, writer); break; + case request_type::mknod: + fs_mknod(reader, writer); + break; case request_type::create: fs_create(reader, writer); break; @@ -236,6 +239,16 @@ private: } } + void fs_mknod(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const mode = reader.read_mode(); + auto const dev = reader.read_u32(); + + auto const result = fs_.mknod(path, mode, dev); + writer.write_i32(result); + } + void fs_create(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_mknod.cpp b/test-src/integration/test_mknod.cpp new file mode 100644 index 0000000..2af4234 --- /dev/null +++ b/test-src/integration/test_mknod.cpp @@ -0,0 +1,52 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include +#include +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; +using testing::AtMost; + +TEST(mknod, success) +{ + bool file_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&file_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else if ((path == "/some_dev") && (file_created)) + { + attr->st_nlink = 1; + attr->st_mode = S_IFIFO | 0644; + attr->st_size = 0; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, mknod("/some_dev", _, _)).Times(AtMost(1)).WillOnce(Invoke([&file_created](auto const &, auto, auto ){ + file_created = true; + return 0; + })); + EXPECT_CALL(fs, release("/some_dev", _)).Times(AtMost(1)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_dev"; + + ASSERT_EQ(0, ::mkfifo(path.c_str(), 0644)); +} From c366bba3437ca99e39298f6c11da519b967e35dc Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Tue, 3 Jan 2023 20:56:07 +0100 Subject: [PATCH 51/91] add test for unlink --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 11 +++++++ test-src/integration/test_unlink.cpp | 44 ++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 test-src/integration/test_unlink.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e63b02c..2fec69b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_utimens.cpp test-src/integration/test_open.cpp test-src/integration/test_mknod.cpp + test-src/integration/test_unlink.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 124c038..3a3fa0e 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -100,6 +100,9 @@ public: case request_type::release: fs_release(reader, writer); break; + case request_type::unlink: + fs_unlink(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -272,6 +275,14 @@ private: writer.write_i32(result); } + void fs_unlink(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + + auto const result = fs_.unlink(path); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_unlink.cpp b/test-src/integration/test_unlink.cpp new file mode 100644 index 0000000..2d14ea5 --- /dev/null +++ b/test-src/integration/test_unlink.cpp @@ -0,0 +1,44 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; + +TEST(unlink, success) +{ + bool link_created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, unlink("/some_file")).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + ASSERT_EQ(0, ::unlink(path.c_str())); +} \ No newline at end of file From 380e46f0daeb4993a22a1b569773ed1f5873415b Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Tue, 3 Jan 2023 21:38:22 +0100 Subject: [PATCH 52/91] fix removal of temp dir on destruct --- test-src/integration/webfuse/test/tempdir.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-src/integration/webfuse/test/tempdir.cpp b/test-src/integration/webfuse/test/tempdir.cpp index 760b7dd..33701b6 100644 --- a/test-src/integration/webfuse/test/tempdir.cpp +++ b/test-src/integration/webfuse/test/tempdir.cpp @@ -12,7 +12,7 @@ tempdir::tempdir() tempdir::~tempdir() { - unlink(path.c_str()); + rmdir(path.c_str()); } std::string const & tempdir::name() const From 861671741f43b43e8e2a1ac46c6560cafcd6df3c Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Tue, 3 Jan 2023 21:38:33 +0100 Subject: [PATCH 53/91] add test for read --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 20 +++++++++ test-src/integration/test_read.cpp | 70 ++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 test-src/integration/test_read.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fec69b..ae5f8e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_open.cpp test-src/integration/test_mknod.cpp test-src/integration/test_unlink.cpp + test-src/integration/test_read.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 3a3fa0e..6abe535 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -103,6 +103,9 @@ public: case request_type::unlink: fs_unlink(reader, writer); break; + case request_type::read: + fs_read(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -283,6 +286,23 @@ private: writer.write_i32(result); } + void fs_read(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const size = reader.read_u32(); + auto const offset = reader.read_u64(); + auto const handle = reader.read_u64(); + + std::vector buffer(size); + + auto const result = fs_.read(path, buffer.data(), size, offset, handle); + writer.write_i32(result); + if (0 < result) + { + writer.write_data(buffer.data(), result); + } + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_read.cpp b/test-src/integration/test_read.cpp new file mode 100644 index 0000000..eee59da --- /dev/null +++ b/test-src/integration/test_read.cpp @@ -0,0 +1,70 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include +#include +#include + + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; +using testing::AtMost; + +TEST(read, success) +{ + std::string expected = "Hello, World!"; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&expected](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0644; + attr->st_size = expected.size(); + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, open("/some_file", _, _)).WillOnce(Return(0)); + EXPECT_CALL(fs, read("/some_file", _, _, _, _)).Times(AnyNumber()).WillRepeatedly(Invoke([&expected](auto const &, char * buffer, size_t buffer_size, uint64_t offset, auto){ + if (offset < expected.size()) + { + auto const count = std::min(buffer_size, expected.size() - offset); + memcpy(reinterpret_cast(buffer), reinterpret_cast(&expected[offset]), count); + return (int) count; + } + else + { + return 0; + } + })); + EXPECT_CALL(fs, release("/some_file", _)).Times(AtMost(1)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + std::string contents; + { + std::ifstream in(path); + std::stringstream buffer; + buffer << in.rdbuf(); + contents = buffer.str(); + } + + ASSERT_EQ(expected, contents); +} From 534ae9efa8339272beab6082a5b1c2a825e1b50e Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Wed, 4 Jan 2023 19:24:14 +0100 Subject: [PATCH 54/91] add test for write --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 14 ++++++++ test-src/integration/test_write.cpp | 53 +++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 test-src/integration/test_write.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ae5f8e7..bbdfa06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_mknod.cpp test-src/integration/test_unlink.cpp test-src/integration/test_read.cpp + test-src/integration/test_write.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 6abe535..01a56e2 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -106,6 +106,9 @@ public: case request_type::read: fs_read(reader, writer); break; + case request_type::write: + fs_write(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -303,6 +306,17 @@ private: } } + void fs_write(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const buffer = reader.read_bytes(); + auto const offset = reader.read_u64(); + auto const handle = reader.read_u64(); + + auto const result = fs_.write(path, buffer.c_str(), buffer.size(), offset, handle); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_write.cpp b/test-src/integration/test_write.cpp new file mode 100644 index 0000000..c53df32 --- /dev/null +++ b/test-src/integration/test_write.cpp @@ -0,0 +1,53 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include +#include +#include + + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; +using testing::AtMost; + +TEST(write, success) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if (path == "/some_file") + { + attr->st_nlink = 1; + attr->st_mode = S_IFREG | 0644; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, open("/some_file", _, _)).WillOnce(Return(0)); + EXPECT_CALL(fs, write("/some_file", _, _, _, _)).Times(AnyNumber()).WillRepeatedly(Invoke([](auto const &, char const * buffer, size_t buffer_size, uint64_t offset, auto){ + return buffer_size; + })); + EXPECT_CALL(fs, release("/some_file", _)).Times(AtMost(1)).WillOnce(Return(0)); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_file"; + + { + std::ofstream out(path); + out << "Test"; + } +} From 777466bc7c2ae8d4aa96c382f9950ac3ac4a189e Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Wed, 4 Jan 2023 19:33:01 +0100 Subject: [PATCH 55/91] add test for mkdir --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 12 +++++++ test-src/integration/test_mkdir.cpp | 51 +++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 test-src/integration/test_mkdir.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bbdfa06..daef840 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_unlink.cpp test-src/integration/test_read.cpp test-src/integration/test_write.cpp + test-src/integration/test_mkdir.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 01a56e2..66ad206 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -109,6 +109,9 @@ public: case request_type::write: fs_write(reader, writer); break; + case request_type::mkdir: + fs_mkdir(reader, writer); + break; case request_type::readdir: fs_readdir(reader, writer); break; @@ -317,6 +320,15 @@ private: writer.write_i32(result); } + void fs_mkdir(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + auto const mode = reader.read_mode(); + + auto const result = fs_.mkdir(path, mode); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_mkdir.cpp b/test-src/integration/test_mkdir.cpp new file mode 100644 index 0000000..7cce68a --- /dev/null +++ b/test-src/integration/test_mkdir.cpp @@ -0,0 +1,51 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include +#include +#include + + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; +using testing::AtMost; + +TEST(mkdir, success) +{ + bool created = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&created](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if ((path == "/some_dir") && (created)) + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0644; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, mkdir("/some_dir", _)).WillOnce(Invoke([&created](auto const &, auto) { + created = true; + return 0; + })); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_dir"; + + ASSERT_EQ(0, ::mkdir(path.c_str(), 0644)); +} From a515bb6de149cb06a3758b90e18f23107798c93c Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Wed, 4 Jan 2023 19:43:57 +0100 Subject: [PATCH 56/91] add test for rmdir --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 11 +++++++ test-src/integration/test_rmdir.cpp | 51 +++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 test-src/integration/test_rmdir.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index daef840..43a5ed8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_read.cpp test-src/integration/test_write.cpp test-src/integration/test_mkdir.cpp + test-src/integration/test_rmdir.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 66ad206..fbd3b7b 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -115,6 +115,9 @@ public: case request_type::readdir: fs_readdir(reader, writer); break; + case request_type::rmdir: + fs_rmdir(reader, writer); + break; default: std::cout << "unknown request: " << ((int) req_type) << std::endl; break; @@ -329,6 +332,14 @@ private: writer.write_i32(result); } + void fs_rmdir(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + + auto const result = fs_.rmdir(path); + writer.write_i32(result); + } + void fs_readdir(messagereader & reader, messagewriter & writer) { auto const path = reader.read_str(); diff --git a/test-src/integration/test_rmdir.cpp b/test-src/integration/test_rmdir.cpp new file mode 100644 index 0000000..1df29cc --- /dev/null +++ b/test-src/integration/test_rmdir.cpp @@ -0,0 +1,51 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include +#include +#include + + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; +using testing::AtMost; + +TEST(rmdir, success) +{ + bool removed = false; + + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&removed](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + if ((path == "/some_dir") && (!removed)) + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0644; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, rmdir("/some_dir")).WillOnce(Invoke([&removed](auto const &) { + removed = true; + return 0; + })); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/some_dir"; + + ASSERT_EQ(0, ::rmdir(path.c_str())); +} From ae668d47ba9bdfec74ad232842be8d5e6ef15436 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Wed, 4 Jan 2023 20:51:57 +0100 Subject: [PATCH 57/91] add test for statfs --- CMakeLists.txt | 1 + src/webfuse/provider.cpp | 18 +++++++++++ src/webfuse/ws/messagewriter.cpp | 11 +++++++ src/webfuse/ws/messagewriter.hpp | 2 ++ test-src/integration/test_statfs.cpp | 46 ++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 test-src/integration/test_statfs.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 43a5ed8..de6f94a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ if(NOT(WITHOUT_TEST)) test-src/integration/test_write.cpp test-src/integration/test_mkdir.cpp test-src/integration/test_rmdir.cpp + test-src/integration/test_statfs.cpp ) target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}) diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index fbd3b7b..05cad76 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace webfuse @@ -118,6 +119,9 @@ public: case request_type::rmdir: fs_rmdir(reader, writer); break; + case request_type::statfs: + fs_statfs(reader, writer); + break; default: std::cout << "unknown request: " << ((int) req_type) << std::endl; break; @@ -353,6 +357,20 @@ private: } } + void fs_statfs(messagereader & reader, messagewriter & writer) + { + auto const path = reader.read_str(); + struct statvfs statistics; + memset(reinterpret_cast(&statistics), 0, sizeof(statistics)); + + auto const result = fs_.statfs(path, &statistics); + writer.write_i32(result); + if (0 == result) + { + writer.write_statistics(&statistics); + } + } + filesystem_i & fs_; ws_client client; }; diff --git a/src/webfuse/ws/messagewriter.cpp b/src/webfuse/ws/messagewriter.cpp index 3fc9586..59f5d01 100644 --- a/src/webfuse/ws/messagewriter.cpp +++ b/src/webfuse/ws/messagewriter.cpp @@ -190,6 +190,17 @@ void messagewriter::write_time(timespec const & value) write_u32(static_cast(value.tv_nsec)); } +void messagewriter::write_statistics(struct statvfs const * statistics) +{ + write_u64(statistics->f_bsize); + write_u64(statistics->f_frsize); + write_u64(statistics->f_blocks); + write_u64(statistics->f_bfree); + write_u64(statistics->f_bavail); + write_u64(statistics->f_files); + write_u64(statistics->f_ffree); + write_u64(statistics->f_namemax); +} unsigned char * messagewriter::get_data(size_t &size) { diff --git a/src/webfuse/ws/messagewriter.hpp b/src/webfuse/ws/messagewriter.hpp index 5b017d1..b89a327 100644 --- a/src/webfuse/ws/messagewriter.hpp +++ b/src/webfuse/ws/messagewriter.hpp @@ -5,6 +5,7 @@ #include "webfuse/response_type.hpp" #include +#include #include #include @@ -45,6 +46,7 @@ public: void write_gid(gid_t value); void write_openflags(int value); void write_time(timespec const & value); + void write_statistics(struct statvfs const * statistics); unsigned char * get_data(size_t &size); diff --git a/test-src/integration/test_statfs.cpp b/test-src/integration/test_statfs.cpp new file mode 100644 index 0000000..c112c09 --- /dev/null +++ b/test-src/integration/test_statfs.cpp @@ -0,0 +1,46 @@ +#include "webfuse/webfuse.hpp" +#include "webfuse/test/fixture.hpp" +#include "webfuse/test/filesystem_mock.hpp" + +#include + +#include + +#include +#include + + +using testing::_; +using testing::Return; +using testing::Invoke; +using testing::AnyNumber; +using testing::AtMost; + +TEST(statfs, success) +{ + webfuse::filesystem_mock fs; + EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](std::string const & path, struct stat * attr){ + memset(reinterpret_cast(attr),0, sizeof(struct stat)); + + if (path == "/") + { + attr->st_nlink = 1; + attr->st_mode = S_IFDIR | 0755; + return 0; + } + else + { + return -ENOENT; + } + })); + EXPECT_CALL(fs, statfs("/", _)).Times(AnyNumber()).WillRepeatedly(Invoke([](auto const &, struct statvfs * statistics){ + return 0; + })); + + webfuse::fixture fixture(fs); + auto const path = fixture.get_path() + "/"; + + struct statvfs statistics; + ASSERT_EQ(0, ::statvfs(path.c_str(), &statistics)); +} From b0376d4a2db3f59a024b923a7e1773e40364672e Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Wed, 4 Jan 2023 22:05:39 +0100 Subject: [PATCH 58/91] provider: parse command line options --- CMakeLists.txt | 3 + src/provider_main.cpp | 146 +++++++++++++++++++++++++++++++++---- src/webfuse/version.cpp.in | 11 +++ src/webfuse/version.hpp | 12 +++ 4 files changed, 157 insertions(+), 15 deletions(-) create mode 100644 src/webfuse/version.cpp.in create mode 100644 src/webfuse/version.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index de6f94a..209bdda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,10 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(FUSE REQUIRED IMPORTED_TARGET fuse3) pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets) +configure_file(src/webfuse/version.cpp.in version.cpp) + add_library(webfuse_static STATIC + ${CMAKE_CURRENT_BINARY_DIR}/version.cpp src/webfuse/webfuse.cpp src/webfuse/provider.cpp src/webfuse/fuse.cpp diff --git a/src/provider_main.cpp b/src/provider_main.cpp index d8fa8eb..c869e12 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -1,17 +1,115 @@ #include "webfuse/provider.hpp" +#include "webfuse/version.hpp" #include #include #include +#include #include #include namespace { -static bool shutdown_requested = false; +enum class command +{ + run, + show_help, + show_version +}; + +class context +{ +public: + context(int argc, char* argv[]) + : base_path(".") + , url("") + , cmd(command::run) + , exit_code() + { + struct option const long_options[] = + { + {"path" , required_argument, nullptr, 'p'}, + {"url" , required_argument, nullptr, 'u'}, + {"version", no_argument , nullptr, 'v'}, + {"help" , no_argument , nullptr, 'h'}, + {nullptr , 0 , nullptr, 0 } + }; + + optind = 0; + opterr = 0; + bool finished = false; + while (!finished) + { + int option_index = 0; + const int c = getopt_long(argc, argv, "p:u:vh", long_options, &option_index); + switch (c) + { + case -1: + finished = true; + break; + case 'p': + base_path = optarg; + break; + case 'u': + url = optarg; + break; + case 'h': + cmd = command::show_help; + break; + case 'v': + cmd = command::show_version; + break; + default: + std::cerr << "error: unknown option" << std::endl; + cmd = command::show_help; + exit_code = EXIT_FAILURE; + finished = true; + break; + } + } + + if ((cmd == command::run) && (url.empty())) + { + std::cerr << "error: missing url" << std::endl; + cmd = command::show_help; + exit_code = EXIT_FAILURE; + } + } + + std::string base_path; + std::string url; + command cmd; + int exit_code; +}; + +void print_usage() +{ + std::cout << R"(webfuse2 provider, (c) 2022 by Falk Werner +expose a local directory via webfuse2 + +Usage: + webfuse-provider -u [-p ] + +Options: + --url, -u set url of webfuse2 service + --path, -p set path of directory to expose (default: .) + --version, -v print version and quit + --help, -h print this message and quit + +Examples: + webfuse-provider -u ws://localhost:8080/ + webfuse-provider -u ws://localhost:8080/ -p /some/directory +)"; +} +void print_version() +{ + std::cout << webfuse::get_version() << std::endl; +} + +static bool shutdown_requested = false; void on_signal(int _) { (void) _; @@ -186,21 +284,39 @@ private: int main(int argc, char* argv[]) { - signal(SIGINT, &on_signal); - signal(SIGTERM, &on_signal); + context ctx(argc, argv); - filesystem fs("."); - webfuse::provider provider(fs); - provider.set_connection_listener([](bool connected) { - if (!connected) - { - shutdown_requested = true; - } - }); - provider.connect("ws://localhost:8080/"); - while (!shutdown_requested) + switch (ctx.cmd) { - provider.service(); + case command::run: + { + signal(SIGINT, &on_signal); + signal(SIGTERM, &on_signal); + + filesystem fs(ctx.base_path); + webfuse::provider provider(fs); + provider.set_connection_listener([](bool connected) { + if (!connected) + { + shutdown_requested = true; + } + }); + provider.connect(ctx.url); + while (!shutdown_requested) + { + provider.service(); + } + } + break; + case command::show_version: + print_version(); + break; + case command::show_help: + // fall-through + default: + print_usage(); + break; } - return EXIT_SUCCESS; + + return ctx.exit_code; } \ No newline at end of file diff --git a/src/webfuse/version.cpp.in b/src/webfuse/version.cpp.in new file mode 100644 index 0000000..b9f7137 --- /dev/null +++ b/src/webfuse/version.cpp.in @@ -0,0 +1,11 @@ +#include "webfuse/version.hpp" + +namespace webfuse +{ + +char const * get_version() +{ + return "@webfuse_VERSION@"; +} + +} \ No newline at end of file diff --git a/src/webfuse/version.hpp b/src/webfuse/version.hpp new file mode 100644 index 0000000..ad540a8 --- /dev/null +++ b/src/webfuse/version.hpp @@ -0,0 +1,12 @@ +#ifndef WEBFUSE_VERSION_HPP +#define WEBFUSE_VERSION_HPP + +namespace webfuse +{ + +char const * get_version(); + + +} + +#endif From 9555729fe99da37e46af28e8740be4b20dff77c3 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 6 Jan 2023 16:50:32 +0100 Subject: [PATCH 59/91] introduce clang tidy --- .github/workflows/build.yml | 2 +- CMakeLists.txt | 10 ++ src/webfuse/filesystem/empty_filesystem.cpp | 20 +-- src/webfuse/filesystem/empty_filesystem.hpp | 2 +- src/webfuse/filesystem/filemode.cpp | 4 +- src/webfuse/filesystem/status.cpp | 160 ++++++++++---------- src/webfuse/fuse.cpp | 2 +- src/webfuse/webfuse.cpp | 2 +- src/webfuse/ws/client.cpp | 4 +- src/webfuse/ws/config.cpp | 9 +- src/webfuse/ws/messagereader.cpp | 28 ++-- src/webfuse/ws/messagewriter.cpp | 15 +- src/webfuse/ws/server.cpp | 87 ++++++----- 13 files changed, 181 insertions(+), 164 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5cccfa6..030327b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Install APT dependencies - run: sudo apt install libfuse3-dev libwebsockets-dev libgtest-dev libgmock-dev valgrind + run: sudo apt install libfuse3-dev libwebsockets-dev libgtest-dev libgmock-dev clang-tidy valgrind - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 209bdda..586cc15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.10) project(webfuse VERSION 2.0.0) +option(WITHOUT_TEST "Disables unit and integration tests" OFF) +option(WITHOUT_CLANG_TIDY "Disables clang tidy" OFF) + set (CMAKE_CXX_STANDARD 17) find_package(PkgConfig REQUIRED) @@ -33,6 +36,13 @@ add_library(webfuse_static STATIC target_include_directories(webfuse_static PUBLIC src) target_link_libraries(webfuse_static PUBLIC PkgConfig::FUSE PkgConfig::LWS) +if(NOT(WITHOUT_CLANG_TIDY)) +set_property( + TARGET webfuse_static + PROPERTY CXX_CLANG_TIDY clang-tidy -checks=-*,readability-*,-readability-identifier-length -warnings-as-errors=*) +endif() + + add_executable(webfuse src/main.cpp) diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index 3b37e3a..e3af5b8 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -10,10 +10,8 @@ int empty_filesystem::access(std::string const & path, int mode) { return 0; } - else - { - return -ENOENT; - } + + return -ENOENT; } int empty_filesystem::getattr(std::string const & path, struct stat * attr) @@ -22,13 +20,11 @@ int empty_filesystem::getattr(std::string const & path, struct stat * attr) { attr->st_ino = 1; attr->st_nlink = 1; - attr->st_mode = S_IFDIR | 0555; + attr->st_mode = S_IFDIR | 0555; // NOLINT(readability-magic-numbers) return 0; } - else - { - return -ENOENT; - } + + return -ENOENT; } int empty_filesystem::readlink(std::string const & path, std::string & out) @@ -121,10 +117,8 @@ int empty_filesystem::readdir(std::string const & path, std::vector { return 0; } - else - { - return -ENOENT; - } + + return -ENOENT; } int empty_filesystem::rmdir(std::string const & path) diff --git a/src/webfuse/filesystem/empty_filesystem.hpp b/src/webfuse/filesystem/empty_filesystem.hpp index 2b7400b..16b390a 100644 --- a/src/webfuse/filesystem/empty_filesystem.hpp +++ b/src/webfuse/filesystem/empty_filesystem.hpp @@ -38,7 +38,7 @@ public: int readdir(std::string const & path, std::vector & entries) override; int rmdir(std::string const & path) override; - int statfs(std::string const & path, struct statvfs * statistivs) override; + int statfs(std::string const & path, struct statvfs * statistics) override; }; } diff --git a/src/webfuse/filesystem/filemode.cpp b/src/webfuse/filesystem/filemode.cpp index 6d1213f..14d54e8 100644 --- a/src/webfuse/filesystem/filemode.cpp +++ b/src/webfuse/filesystem/filemode.cpp @@ -18,7 +18,7 @@ filemode::operator uint32_t() const filemode filemode::from_mode(mode_t value) { - uint32_t result = value & 07777; + uint32_t result = value & 07777; // NOLINT(readability-magic-numbers) if (S_ISREG(value) ) { result |= filemode::reg; } if (S_ISDIR(value) ) { result |= filemode::dir; } @@ -33,7 +33,7 @@ filemode filemode::from_mode(mode_t value) mode_t filemode::to_mode() const { - mode_t result = value_ & 07777; + mode_t result = value_ & 07777; // NOLINT(readability-magic-numbers) if (is_reg() ) { result |= S_IFREG; } if (is_dir() ) { result |= S_IFDIR; } diff --git a/src/webfuse/filesystem/status.cpp b/src/webfuse/filesystem/status.cpp index b3b3977..9864380 100644 --- a/src/webfuse/filesystem/status.cpp +++ b/src/webfuse/filesystem/status.cpp @@ -21,48 +21,46 @@ status status::from_fusestatus(int value) { return static_cast(value); } - else + + switch(value) { - switch(value) - { - case -E2BIG: return status::bad_e2big; - case -EACCES: return status::bad_eacces; - case -EAGAIN: return status::bad_eagain; - case -EBADF: return status::bad_ebadf; - case -EBUSY: return status::bad_ebusy; - case -EDESTADDRREQ: return status::bad_edestaddrreq; - case -EDQUOT: return status::bad_edquot; - case -EEXIST: return status::bad_eexist; - case -EFAULT: return status::bad_efault; - case -EFBIG: return status::bad_efbig; - case -EINTR: return status::bad_eintr; - case -EINVAL: return status::bad_einval; - case -EIO: return status::bad_eio; - case -EISDIR: return status::bad_eisdir; - case -ELOOP: return status::bad_eloop; - case -EMFILE: return status::bad_emfile; - case -EMLINK: return status::bad_emlink; - case -ENAMETOOLONG: return status::bad_enametoolong; - case -ENFILE: return status::bad_enfile; - case -ENODATA: return status::bad_enodata; - case -ENODEV: return status::bad_enodev; - case -ENOENT: return status::bad_enoent; - case -ENOMEM: return status::bad_enomem; - case -ENOSPC: return status::bad_enospc; - case -ENOSYS: return status::bad_enosys; - case -ENOTDIR: return status::bad_enotdir; - case -ENOTEMPTY: return status::bad_enotempty; - case -ENOTSUP: return status::bad_enotsup; - case -ENXIO: return status::bad_enxio; - case -EOVERFLOW: return status::bad_eoverflow; - case -EPERM: return status ::bad_eperm; - case -EPIPE: return status::bad_epipe; - case -ERANGE: return status::bad_erange; - case -EROFS: return status::bad_erofs; - case -ETXTBSY: return status::bad_etxtbsy; - case -EXDEV: return status::bad_exdev; - default: return static_cast(value); - } + case -E2BIG: return status::bad_e2big; + case -EACCES: return status::bad_eacces; + case -EAGAIN: return status::bad_eagain; + case -EBADF: return status::bad_ebadf; + case -EBUSY: return status::bad_ebusy; + case -EDESTADDRREQ: return status::bad_edestaddrreq; + case -EDQUOT: return status::bad_edquot; + case -EEXIST: return status::bad_eexist; + case -EFAULT: return status::bad_efault; + case -EFBIG: return status::bad_efbig; + case -EINTR: return status::bad_eintr; + case -EINVAL: return status::bad_einval; + case -EIO: return status::bad_eio; + case -EISDIR: return status::bad_eisdir; + case -ELOOP: return status::bad_eloop; + case -EMFILE: return status::bad_emfile; + case -EMLINK: return status::bad_emlink; + case -ENAMETOOLONG: return status::bad_enametoolong; + case -ENFILE: return status::bad_enfile; + case -ENODATA: return status::bad_enodata; + case -ENODEV: return status::bad_enodev; + case -ENOENT: return status::bad_enoent; + case -ENOMEM: return status::bad_enomem; + case -ENOSPC: return status::bad_enospc; + case -ENOSYS: return status::bad_enosys; + case -ENOTDIR: return status::bad_enotdir; + case -ENOTEMPTY: return status::bad_enotempty; + case -ENOTSUP: return status::bad_enotsup; + case -ENXIO: return status::bad_enxio; + case -EOVERFLOW: return status::bad_eoverflow; + case -EPERM: return status ::bad_eperm; + case -EPIPE: return status::bad_epipe; + case -ERANGE: return status::bad_erange; + case -EROFS: return status::bad_erofs; + case -ETXTBSY: return status::bad_etxtbsy; + case -EXDEV: return status::bad_exdev; + default: return static_cast(value); } } @@ -72,48 +70,46 @@ int status::to_fusestatus() const { return static_cast(value_); } - else + + switch(value_) { - switch(value_) - { - case status::bad_e2big: return -E2BIG; - case status::bad_eacces: return -EACCES; - case status::bad_eagain: return -EAGAIN; - case status::bad_ebadf: return -EBADF; - case status::bad_ebusy: return -EBUSY; - case status::bad_edestaddrreq: return -EDESTADDRREQ; - case status::bad_edquot: return -EDQUOT; - case status::bad_eexist: return -EEXIST; - case status::bad_efault: return -EFAULT; - case status::bad_efbig: return -EFBIG; - case status::bad_eintr: return -EINTR; - case status::bad_einval: return -EINVAL; - case status::bad_eio: return -EIO; - case status::bad_eisdir: return -EISDIR; - case status::bad_eloop: return -ELOOP; - case status::bad_emfile: return -EMFILE; - case status::bad_emlink: return -EMLINK; - case status::bad_enametoolong: return -ENAMETOOLONG; - case status::bad_enfile: return -ENFILE; - case status::bad_enodata: return -ENODATA; - case status::bad_enodev: return -ENODEV; - case status::bad_enoent: return -ENOENT; - case status::bad_enomem: return -ENOMEM; - case status::bad_enospc: return -ENOSPC; - case status::bad_enosys: return -ENOSYS; - case status::bad_enotdir: return -ENOTDIR; - case status::bad_enotempty: return -ENOTEMPTY; - case status::bad_enotsup: return -ENOTSUP; - case status::bad_enxio: return -ENXIO; - case status::bad_eoverflow: return -EOVERFLOW; - case status::bad_eperm: return -EPERM; - case status::bad_epipe: return -EPIPE; - case status::bad_erange: return -ERANGE; - case status::bad_erofs: return -EROFS; - case status::bad_etxtbsy: return -ETXTBSY; - case status::bad_exdev: return -EXDEV; - default: return static_cast(value_); - } + case status::bad_e2big: return -E2BIG; + case status::bad_eacces: return -EACCES; + case status::bad_eagain: return -EAGAIN; + case status::bad_ebadf: return -EBADF; + case status::bad_ebusy: return -EBUSY; + case status::bad_edestaddrreq: return -EDESTADDRREQ; + case status::bad_edquot: return -EDQUOT; + case status::bad_eexist: return -EEXIST; + case status::bad_efault: return -EFAULT; + case status::bad_efbig: return -EFBIG; + case status::bad_eintr: return -EINTR; + case status::bad_einval: return -EINVAL; + case status::bad_eio: return -EIO; + case status::bad_eisdir: return -EISDIR; + case status::bad_eloop: return -ELOOP; + case status::bad_emfile: return -EMFILE; + case status::bad_emlink: return -EMLINK; + case status::bad_enametoolong: return -ENAMETOOLONG; + case status::bad_enfile: return -ENFILE; + case status::bad_enodata: return -ENODATA; + case status::bad_enodev: return -ENODEV; + case status::bad_enoent: return -ENOENT; + case status::bad_enomem: return -ENOMEM; + case status::bad_enospc: return -ENOSPC; + case status::bad_enosys: return -ENOSYS; + case status::bad_enotdir: return -ENOTDIR; + case status::bad_enotempty: return -ENOTEMPTY; + case status::bad_enotsup: return -ENOTSUP; + case status::bad_enxio: return -ENXIO; + case status::bad_eoverflow: return -EOVERFLOW; + case status::bad_eperm: return -EPERM; + case status::bad_epipe: return -EPIPE; + case status::bad_erange: return -ERANGE; + case status::bad_erofs: return -EROFS; + case status::bad_etxtbsy: return -ETXTBSY; + case status::bad_exdev: return -EXDEV; + default: return static_cast(value_); } } diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp index 8600d02..4132025 100644 --- a/src/webfuse/fuse.cpp +++ b/src/webfuse/fuse.cpp @@ -106,7 +106,7 @@ static int fs_truncate(char const * path, off_t size, fuse_file_info * info) static int fs_fsync(char const * path, int isdatasync, fuse_file_info * info) { auto * const fs = fs_get_filesystem(); - bool const is_datasync = (is_datasync != 0); + bool const is_datasync = (isdatasync != 0); auto const handle = fs_get_handle(info); return fs->fsync(path, is_datasync, handle); diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index e06a6ee..3977692 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -6,7 +6,7 @@ namespace webfuse { -int app::run(int argc, char * argv[]) +int app::run(int argc, char * argv[]) // NOLINT(readability-convert-member-functions-to-static) { ws_config config; ws_server server(config); diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp index d47fdb4..e164362 100644 --- a/src/webfuse/ws/client.cpp +++ b/src/webfuse/ws/client.cpp @@ -48,7 +48,7 @@ extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, v { auto * fragment = reinterpret_cast(in); context->current_message.append(fragment, length); - if (lws_is_final_fragment(wsi)) + if (0 != lws_is_final_fragment(wsi)) { try { @@ -137,7 +137,7 @@ public: lws_client_connect_info info; memset(reinterpret_cast(&info), 0, sizeof(lws_client_connect_info)); info.context = context; - info.port = 8081; + info.port = 8081; //NOLINT(readability-magic-numbers) info.address = "localhost"; info.host = "localhost"; info.path = "/"; diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp index 4acbbe6..f7a68e1 100644 --- a/src/webfuse/ws/config.cpp +++ b/src/webfuse/ws/config.cpp @@ -1,10 +1,17 @@ #include "webfuse/ws/config.hpp" +namespace +{ + +constexpr int const default_port = 8081; + +} + namespace webfuse { ws_config::ws_config() -: port(8081) +: port(default_port) { } diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp index 1f966e3..4c261ad 100644 --- a/src/webfuse/ws/messagereader.cpp +++ b/src/webfuse/ws/messagereader.cpp @@ -98,14 +98,13 @@ uint8_t messagereader::read_u8() pos++; return value; } - else - { - throw std::runtime_error("out of bounds"); - } + + throw std::runtime_error("out of bounds"); } uint32_t messagereader::read_u32() { + // NOLINTBEGIN(readability-magic-numbers) if ((pos + 3) < data.size()) { uint32_t value = @@ -116,14 +115,14 @@ uint32_t messagereader::read_u32() pos += 4; return value; } - else - { - throw std::runtime_error("out of bounds"); - } + // NOLINTEND(readability-magic-numbers) + + throw std::runtime_error("out of bounds"); } uint64_t messagereader::read_u64() { + // NOLINTBEGIN(readability-magic-numbers) if ((pos + 7) < data.size()) { uint32_t value = @@ -138,10 +137,9 @@ uint64_t messagereader::read_u64() pos += 8; return value; } - else - { - throw std::runtime_error("out of bounds"); - } + // NOLINTEND(readability-magic-numbers) + + throw std::runtime_error("out of bounds"); } @@ -165,10 +163,8 @@ std::string messagereader::read_bytes() pos += size; return std::move(value); } - else - { - throw std::runtime_error("out of bounds"); - } + + throw std::runtime_error("out of bounds"); } void messagereader::read_strings(std::vector &entries) diff --git a/src/webfuse/ws/messagewriter.cpp b/src/webfuse/ws/messagewriter.cpp index 59f5d01..fe565ed 100644 --- a/src/webfuse/ws/messagewriter.cpp +++ b/src/webfuse/ws/messagewriter.cpp @@ -47,10 +47,13 @@ messagewriter& messagewriter::operator=(messagewriter && other) void messagewriter::set_id(uint32_t value) { id = value; + + // NOLINTBEGIN(readability-magic-numbers) data[LWS_PRE ] = (id >> 24) & 0xff; data[LWS_PRE + 1] = (id >> 16) & 0xff; data[LWS_PRE + 2] = (id >> 8) & 0xff; data[LWS_PRE + 3] = id & 0xff; + // NOLINTEND(readability-magic-numbers) } uint32_t messagewriter::get_id() const @@ -81,16 +84,21 @@ void messagewriter::write_i32(int32_t value) void messagewriter::write_u32(uint32_t value) { auto const offset = data.size(); + + // NOLINTBEGIN(readability-magic-numbers) data.resize(offset + 4); data[offset ] = (value >> 24) & 0xff; data[offset + 1] = (value >> 16) & 0xff; data[offset + 2] = (value >> 8) & 0xff; data[offset + 3] = value & 0xff; + // NOLINTEND(readability-magic-numbers) } void messagewriter::write_u64(uint64_t value) { - auto const offset = data.size(); + auto const offset = data.size(); + + // NOLINTBEGIN(readability-magic-numbers) data.resize(offset + 8); data[offset ] = (value >> 56) & 0xff; data[offset + 1] = (value >> 48) & 0xff; @@ -100,6 +108,7 @@ void messagewriter::write_u64(uint64_t value) data[offset + 5] = (value >> 16) & 0xff; data[offset + 6] = (value >> 8) & 0xff; data[offset + 7] = value & 0xff; + // NOLINTEND(readability-magic-numbers) } void messagewriter::write_str(std::string const &value) @@ -116,7 +125,7 @@ void messagewriter::write_data(char const * buffer, size_t size) { auto const offset = data.size(); data.resize(offset + effective_size); - void * to = reinterpret_cast(&data.data()[offset]); + void * to = reinterpret_cast(&data[offset]); void const * from = reinterpret_cast(buffer); memcpy(to, from, effective_size); } @@ -205,7 +214,7 @@ void messagewriter::write_statistics(struct statvfs const * statistics) unsigned char * messagewriter::get_data(size_t &size) { size = data.size() - LWS_PRE; - void * result = reinterpret_cast(&data.data()[LWS_PRE]); + void * result = reinterpret_cast(&data[LWS_PRE]); return reinterpret_cast(result); } diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index aa9ad67..8dde316 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -21,6 +21,8 @@ namespace { +constexpr int64_t const timeout_secs = 10; + struct user_data { struct lws * connection = nullptr; @@ -32,6 +34,45 @@ struct user_data std::unordered_map> pending_responses; }; + +void do_receive(void * in, int len, lws* wsi, user_data * data) +{ + auto * fragment = reinterpret_cast(in); + data->current_message.append(fragment, len); + if (0 != lws_is_final_fragment(wsi)) + { + try + { + webfuse::messagereader reader(data->current_message); + uint32_t id = reader.read_u32(); + uint8_t message_type = reader.read_u8(); + + std::lock_guard lock(data->mut); + auto it = data->pending_responses.find(id); + if (it != data->pending_responses.end()) + { + it->second.set_value(std::move(reader)); + data->pending_responses.erase(it); + } + else + { + // ToDo: log request not found + std::cout << "warning: request not found: id=" << id << std::endl; + for(auto const & entry: data->pending_responses) + { + std::cout << "\t" << entry.first << std::endl; + } + } + } + catch(...) + { + // ToDo: log invalid message + std::cout << "warning: invalid message" << std::endl; + } + } +} + + } extern "C" @@ -66,43 +107,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, } break; case LWS_CALLBACK_RECEIVE: - { - auto * fragment = reinterpret_cast(in); - data->current_message.append(fragment, len); - if (lws_is_final_fragment(wsi)) - { - try - { - webfuse::messagereader reader(data->current_message); - uint32_t id = reader.read_u32(); - uint8_t message_type = reader.read_u8(); - - std::lock_guard lock(data->mut); - auto it = data->pending_responses.find(id); - if (it != data->pending_responses.end()) - { - it->second.set_value(std::move(reader)); - data->pending_responses.erase(it); - } - else - { - // ToDo: log request not found - std::cout << "warning: request not found: id=" << id << std::endl; - for(auto const & entry: data->pending_responses) - { - std::cout << "\t" << entry.first << std::endl; - } - } - } - catch(...) - { - // ToDo: log invalid message - std::cout << "warning: invalid message" << std::endl; - } - - - } - } + do_receive(in, len, wsi, data); break; case LWS_CALLBACK_SERVER_WRITEABLE: { @@ -111,7 +116,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, bool has_more = false; { - std::lock_guard lock(data->mut); + std::lock_guard lock(data->mut); has_msg = !(data->requests.empty()); if (has_msg) { @@ -181,7 +186,7 @@ public: while (!shutdown_requested) { { - std::lock_guard lock(data.mut); + std::lock_guard lock(data.mut); if (!data.requests.empty()) { if (nullptr != data.connection) @@ -264,7 +269,7 @@ messagereader ws_server::perform(messagewriter writer) std::promise p; f = p.get_future(); - std::lock_guard lock(d->data.mut); + std::lock_guard lock(d->data.mut); uint32_t id = d->next_id(); writer.set_id(id); d->data.requests.emplace(std::move(writer)); @@ -272,7 +277,7 @@ messagereader ws_server::perform(messagewriter writer) } lws_cancel_service(d->context); - if(std::future_status::timeout == f.wait_for(std::chrono::seconds(10))) + if(std::future_status::timeout == f.wait_for(std::chrono::seconds(timeout_secs))) { throw std::runtime_error("timeout"); } From 4610b82d7811354b533fdcee09812d5f77af0aa4 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 6 Jan 2023 17:10:56 +0100 Subject: [PATCH 60/91] clang-tidy: enable default checks --- CMakeLists.txt | 2 +- src/webfuse/fuse.cpp | 1 - src/webfuse/ws/server.cpp | 7 ++++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 586cc15..fc41227 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ target_link_libraries(webfuse_static PUBLIC PkgConfig::FUSE PkgConfig::LWS) if(NOT(WITHOUT_CLANG_TIDY)) set_property( TARGET webfuse_static - PROPERTY CXX_CLANG_TIDY clang-tidy -checks=-*,readability-*,-readability-identifier-length -warnings-as-errors=*) + PROPERTY CXX_CLANG_TIDY clang-tidy -checks=readability-*,-readability-identifier-length -warnings-as-errors=*) endif() diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp index 4132025..02b9f47 100644 --- a/src/webfuse/fuse.cpp +++ b/src/webfuse/fuse.cpp @@ -182,7 +182,6 @@ static int fs_readdir(char const * path, void * buffer, fuse_readdir_flags flags) { auto * const fs = fs_get_filesystem(); - auto handle = fs_get_handle(info); std::vector names; auto const result = fs->readdir(path, names); if (0 == result) diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 8dde316..651a62c 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -45,7 +45,7 @@ void do_receive(void * in, int len, lws* wsi, user_data * data) { webfuse::messagereader reader(data->current_message); uint32_t id = reader.read_u32(); - uint8_t message_type = reader.read_u8(); + reader.read_u8(); // read message type: ToDo: use it std::lock_guard lock(data->mut); auto it = data->pending_responses.find(id); @@ -132,7 +132,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, { size_t size; unsigned char * raw_data = writer.get_data(size); - int const rc = lws_write(data->connection, raw_data, size, LWS_WRITE_BINARY); + lws_write(data->connection, raw_data, size, LWS_WRITE_BINARY); } if (has_more) @@ -178,7 +178,8 @@ public: context = lws_create_context(&info); - lws_vhost * const vhost = lws_create_vhost(context, &info); + lws_create_vhost(context, &info); + // lws_vhost * const vhost = lws_create_vhost(context, &info); // port = lws_get_vhost_port(vhost); From 023b595b1c8e71be2ee2864940a6d5d6b730bc24 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 6 Jan 2023 21:59:19 +0100 Subject: [PATCH 61/91] implemented filesystem provider --- src/provider_main.cpp | 203 +++++++++++++++++++++--- src/webfuse/filesystem/filesystem_i.hpp | 2 + 2 files changed, 181 insertions(+), 24 deletions(-) diff --git a/src/provider_main.cpp b/src/provider_main.cpp index c869e12..fa0d22f 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -2,6 +2,7 @@ #include "webfuse/version.hpp" #include +#include #include #include @@ -120,9 +121,22 @@ class filesystem: public webfuse::filesystem_i { public: explicit filesystem(std::string const & base_path) - : base_path_(base_path) { + char buffer[PATH_MAX]; + char * resolved_path = ::realpath(base_path.c_str(), buffer); + if (nullptr == resolved_path) + { + throw std::runtime_error("failed to resolve path"); + } + + struct stat info; + int const rc = stat(resolved_path, &info); + if (!S_ISDIR(info.st_mode)) + { + throw std::runtime_error("path is not a directory"); + } + base_path_ = resolved_path; } ~filesystem() override @@ -133,7 +147,6 @@ public: int access(std::string const & path, int mode) override { auto const full_path = get_full_path(path); - std::cout << "access: " << full_path << std::endl; auto const result = ::access(full_path.c_str(), mode); return (result == 0) ? 0 : -errno; @@ -142,7 +155,6 @@ public: int getattr(std::string const & path, struct stat * attr) override { auto const full_path = get_full_path(path); - std::cout << "getattr: " << full_path << std::endl; auto const result = lstat(full_path.c_str(), attr); return (result == 0) ? 0 : -errno; @@ -150,93 +162,219 @@ public: int readlink(std::string const & path, std::string & out) override { - return -ENOENT; + auto const full_path = get_full_path(path); + + char buffer[PATH_MAX]; + int const result = ::readlink(full_path.c_str(), buffer, PATH_MAX); + if ((0 <= result) && (result < PATH_MAX)) + { + buffer[result] = '\0'; + out = buffer; + } + + return (result >= 0) ? 0 : -errno; } - int symlink(std::string const & target, std::string const & linkpath) override + int symlink(std::string const & from, std::string const & to) override { - return -ENOENT; + auto const full_from = ('/' == from.at(0)) ? get_full_path(from) : from; + auto const full_to = get_full_path(to); + + int const result = ::symlink(full_from.c_str(), full_to.c_str()); + return (result == 0) ? 0 : -errno; } int link(std::string const & old_path, std::string const & new_path) override { - return -ENOENT; + auto const from = get_full_path(old_path); + auto const to = get_full_path(new_path); + + int const result = ::link(from.c_str(), to.c_str()); + return (result == 0) ? 0 : -errno; } int rename(std::string const & old_path, std::string const & new_path, int flags) override { - return -ENOENT; + auto const full_old = get_full_path(old_path); + auto const full_new = get_full_path(new_path); + + int const result = ::renameat2(-1, full_old.c_str(), -1, full_new.c_str(), flags); + return (result == 0) ? 0 : -errno; } int chmod(std::string const & path, mode_t mode) override { - return -ENOENT; + auto const full_path = get_full_path(path); + + int const result = ::chmod(full_path.c_str(), mode); + return (result == 0) ? 0 : -errno; } int chown(std::string const & path, uid_t uid, gid_t gid) override { - return -ENOENT; + auto const full_path = get_full_path(path); + + int const result = ::chown(full_path.c_str(), uid, gid); + return (result == 0) ? 0 : -errno; } int truncate(std::string const & path, uint64_t size, uint64_t handle) override { - return -ENOENT; + auto const full_path = get_full_path(path); + + int result = 0; + if (handle == webfuse::invalid_handle) + { + result = ::truncate(full_path.c_str(), size); + } + else + { + result = ::ftruncate(static_cast(handle), size); + } + + return (result == 0) ? 0 : -errno; } int fsync(std::string const & path, bool is_datasync, uint64_t handle) override { - return -ENOENT; + int result = 0; + if (handle != webfuse::invalid_handle) + { + if (!is_datasync) + { + result = ::fsync(static_cast(handle)); + } + else + { + result = ::fdatasync(static_cast(handle)); + } + } + // we do not sync files, which are not open + + return (result == 0) ? 0 : -errno; } int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) override { - return -ENOENT; + int result = 0; + if (handle == webfuse::invalid_handle) + { + auto const full_path = get_full_path(path); + result = ::utimensat(-1, full_path.c_str(), tv, 0); + } + else + { + result = ::futimens(static_cast(handle), tv); + } + + return (result == 0) ? 0 : -errno; } int open(std::string const & path, int flags, uint64_t & handle) override { - return -ENOENT; + auto const full_path = get_full_path(path); + int const fd = ::open(full_path.c_str(), flags); + if (0 <= fd) + { + handle = static_cast(fd); + } + + return (0 <= fd) ? 0 : -errno; } int mknod(std::string const & path, mode_t mode, dev_t rdev) override { - return -ENOENT; + auto const full_path = get_full_path(path); + int const result = ::mknod(full_path.c_str(), mode, rdev); + + return (result == 0) ? 0 : -errno; } int create(std::string const & path, mode_t mode, uint64_t & handle) override { - return -ENOENT; + auto const full_path = get_full_path(path); + int const fd = ::creat(full_path.c_str(), mode); + if (0 <= fd) + { + handle = static_cast(fd); + } + + return (0 <= fd) ? 0 : -errno; } int release(std::string const & path, uint64_t handle) override { - return -ENOENT; + int result = 0; + if (handle != webfuse::invalid_handle) + { + result = ::close(static_cast(handle)); + } + + return (result == 0) ? 0 : -errno; } int unlink(std::string const & path) override { - return -ENOENT; + auto const full_path = get_full_path(path); + int const result = ::unlink(full_path.c_str()); + + return (result == 0) ? 0 : -errno; } int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override { - return -ENOENT; + int result = -1; + if (handle != webfuse::invalid_handle) + { + auto const full_path = get_full_path(path); + int fd = ::open(full_path.c_str(), O_RDONLY); + if (0 <= fd) + { + result = ::pread(fd, buffer, buffer_size, offset); + ::close(fd); + } + } + else + { + result = ::pread(static_cast(handle), buffer, buffer_size, offset); + } + + return (result >= 0) ? result : -errno; } int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override { - return -ENOENT; + int result = -1; + if (handle == webfuse::invalid_handle) + { + auto const full_path = get_full_path(path); + int fd = ::open(full_path.c_str(), O_WRONLY); + if (0 <= fd) + { + result = ::pwrite(fd, buffer, buffer_size, offset); + ::close(fd); + } + } + else + { + result = ::pwrite(static_cast(handle), buffer, buffer_size, offset); + + } + + return (result >= 0) ? result : -errno; } int mkdir(std::string const & path, mode_t mode) override { - return -ENOENT; + auto const full_path = get_full_path(path); + int const result = ::mkdir(full_path.c_str(), mode); + + return (result == 0) ? 0 : -errno; } int readdir(std::string const & path, std::vector & entries) override { auto const full_path = get_full_path(path); - std::cout << "readdir: " << full_path << std::endl; int result = 0; DIR * directory = opendir(full_path.c_str()); @@ -260,12 +398,18 @@ public: int rmdir(std::string const & path) override { - return -ENOENT; + auto const full_path = get_full_path(path); + int const result = ::rmdir(full_path.c_str()); + + return (result == 0) ? 0 : -errno; } int statfs(std::string const & path, struct statvfs * statistics) override { - return -ENOENT; + auto const full_path = get_full_path(path); + int const result = ::statvfs(full_path.c_str(), statistics); + + return (result == 0) ? 0 : -errno; } @@ -289,6 +433,7 @@ int main(int argc, char* argv[]) switch (ctx.cmd) { case command::run: + try { signal(SIGINT, &on_signal); signal(SIGTERM, &on_signal); @@ -307,6 +452,16 @@ int main(int argc, char* argv[]) provider.service(); } } + catch (std::exception const & ex) + { + std::cerr << "error: " << ex.what() << std::endl; + ctx.exit_code = EXIT_FAILURE; + } + catch (...) + { + std::cerr << "error: unspecified error" << std::endl; + ctx.exit_code = EXIT_FAILURE; + } break; case command::show_version: print_version(); diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp index fe94ed8..cf2f0ce 100644 --- a/src/webfuse/filesystem/filesystem_i.hpp +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -13,6 +13,8 @@ namespace webfuse { +constexpr uint64_t const invalid_handle = static_cast(-1); + class filesystem_i { public: From 1502ac93979dd1b117bf2244122ba3e47254b60f Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 7 Jan 2023 00:33:00 +0100 Subject: [PATCH 62/91] fixed message id --- script/provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/provider.py b/script/provider.py index 3c22286..9a87edc 100755 --- a/script/provider.py +++ b/script/provider.py @@ -268,7 +268,7 @@ class FilesystemProvider: 0x13: FilesystemProvider.readdir, 0x14: FilesystemProvider.rmdir, 0x15: FilesystemProvider.statfs, - 0x15: FilesystemProvider.utimens, + 0x16: FilesystemProvider.utimens, } async def run(self): From 50e1ec8f5ceea2d15d0ba74d81491cc82872a5d4 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 7 Jan 2023 00:33:23 +0100 Subject: [PATCH 63/91] enhance protocol documentation --- doc/protocol.md | 514 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 505 insertions(+), 9 deletions(-) diff --git a/doc/protocol.md b/doc/protocol.md index cd15349..03d728b 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -1,4 +1,36 @@ -# Webufse 2 Protocol +# Webfuse 2 Protocol + +## Scope + +This document describes the webfuse 2 communication protocol. The protocol is used to transfer messages between a `webfuse2 service` and a `webfuse2 provider`. In contrast to `legacy webfuse`, which is based on `JSON RPC` the `webfuse2 protocol` is a binary protocol. + +## Definitions + +### Webfuse2 Service + +A `webfuse2 service` is both, +- a [websocket](https://en.wikipedia.org/wiki/WebSocket) service providing the `webfuse2` protocol +- a [fuse](https://github.com/libfuse/libfuse) filesystem attached to a local mountpoint + +The `webfuse2 service` awaits incoming connections from a `webfuse2 provider`. Once connected, it communicates all the filesystem requests originated by the `libfuse` to the connected `webfuse2 provider` using the `websocket`-based `webfuse2 protocol`. + +By doing so, `webfuse2` allows to inject a filesystem to a remote device. + +### Webfuse2 Provider + +A `webfuse2 provider` provides a filesystem to a remote device using the `websocket`-based `webfuse2 protocol`. Therefore, a `webfuse2 provider` implements a `websocket` client. + +## Message exchange + +Once connected, the `webfuse2 protocol` implements a strict request-response scheme, where +- all requests are send by the `webfuse2 service`, +- all requests require a response and +- all responses are send by the `webfuse2 provider` + +Note that this communication is reversed the typical client-server-communication scheme. In `webfuse2` the `webfuse2 serive` (server) sends all the requests and the `webfuse provider` (client) sends the responses. + +For message transfer, the [websocket](https://en.wikipedia.org/wiki/WebSocket) protocol is used. All messages are in binary form, plain text messages are never used by the `webfuse2 protocol`. + ## Endianness @@ -86,7 +118,70 @@ For instance, the uint32 value 1 will be transferred as | EXDEV | -18 | improper link | | EWOULDBLOCK | -11 | resource temporarily unavailable | - +#### access mode + +| Mode | Value | Description | +| ---- | ----- | ----------- | +| F_OK | 0 | Tests, whether the file exists | +| X_OK | 1 | Tests, whether the file is executable | +| W_OK | 2 | Tests, whether the file is writable | +| R_OK | 4 | Tests, whether the file is readable | + +#### mode + +_Note that the following numbers are in `octal` notation._ + +| Fields and Flags | Mask | Description | +| ---------------- | -------- | ----------- | +| Protection mask | 0o000777 | Cointains the file protection flags (rwx for owner, group and others) | +| Sticky mask | 0o007000 | Sticky bits | +| S_ISVTX | 0o001000 | Sticky bit | +| S_ISGID | 0o002000 | Set-Group-ID bit | +| S_ISUID | 0o004000 | Set-User-ID bit | +| Filetype mask | 0o170000 | Filetype mask | +| S_IFREG | 0o100000 | regular file | +| S_IFDIR | 0o040000 | directory | +| S_IFCHR | 0o020000 | character device | +| S_IFBLK | 0o060000 | block device | +| S_IFIFO | 0o010000 | named pipe | +| S_IFLNK | 0o120000 | link | +| S_IFSOCK | 0o140000 | socket | + +#### open_flags + +_Note that the following numbers are in `octal` notation._ + +| Flags | Value | Description | +| ---------------- | -------- | ----------- | +| O_ACCMODE | 0o03 | Access mode mask | +| O_RDONLY | 0o00 | open file read-only | +| O_WRONLY | 0o01 | open file write-only | +| O_RDWR | 0o02 | open file for reading and writing | +| O_APPEND | 0o000002000 | open file in append mode | +| O_ASYNC | 0o000020000 | enable signal-driven I/O | +| O_CLOEXEC | 0o002000000 | enable close-on-exec | +| O_CREAT | 0o000000100 | create file if path does not exists | +| O_DIRECT | 0o000040000 | try to minimize cache effects on I/O | +| O_DIRECTORY | 0o000200000 | open a directory | +| O_DSYNC | 0o000010000 | write with synchronized I/O data integrity | +| O_EXCL | 0o000000200 | ensure that file exists when specified in conjunction with O_CREATE | +| O_LARGEFILE | 0o000100000 | allow large files to be opened | +| O_NOATIME | 0o001000000 | do not update file last access time | +| O_NOCTTY | 0o000000400 | make device the process's controlling terminal | +| O_NOFOLLOW | 0o000400000 | fail to open, if basename is a symbolic link | +| O_NONBLOCK | 0o000004000 | open in nonblocking mode | +| O_NDELAY | 0o000004000 | open in nonblocking mode | +| O_PATH | 0o010000000 | see manual entry of `open(2)` for details | +| O_SYNC | 0o004010000 | write using synchronized I/O | +| O_TMPFILE | 0o020200000 | create an unnamed temporary file | +| O_TRUNC | 0o000001000 | truncate fole to length 0 | + +#### rename_flags + +| Flag | Value | Description | +| ---------------- | ----- | ----------- | +| RENAME_NOREPLACE | 1 | do not overwrite the new file | +| RENAME_EXCHANGE | 2 | atomically exchange the files | ### Complex Types @@ -137,22 +232,423 @@ For instance, the uint32 value 1 will be transferred as #### statistics - -## Message - - +## Messages + +| Field | Type | Descripton | +| ------- | ---- | ---------- | +| id | u32 | Unique ID of the message | +| type | u8 | Type of the message | +| payload | u8[] | Payload according to the message type | + +The `id` is just a number without any meaning for the `webfuse2 provider`. It is set by the `webfuse2 service` of a request and is copied by the `webfuse2 provider` to the response. A `webfuse2 service` implementation might choose to keep track on pending requests using the `id`. + +### Erroneous Responses + +Most responses contain a `result` encoding the status of the operation. While successful responses may contain additional data, erroneous responses must not be decoded by a `webfuse2 service` implementation beyond the `result` value. + +### Message Types + +_Note that the following numbers are in `hexadecimal` notation._ + +| Method | Request | Response | +| -------- | ------- | -------- | +| access | 0x01 | 0x81 | +| getattr | 0x02 | 0x82 | +| readlink | 0x03 | 0x83 | +| symlink | 0x04 | 0x84 | +| link | 0x05 | 0x85 | +| rename | 0x06 | 0x86 | +| chmod | 0x07 | 0x87 | +| chown | 0x08 | 0x88 | +| truncate | 0x09 | 0x89 | +| fsync | 0x0a | 0x8a | +| open | 0x0b | 0x8b | +| mknod | 0x0c | 0x8c | +| create | 0x0d | 0x8d | +| release | 0x0e | 0x8e | +| unlink | 0x0f | 0x8f | +| read | 0x10 | 0x90 | +| write | 0x11 | 0x91 | +| mkdir | 0x12 | 0x92 | +| readdir | 0x13 | 0x93 | +| rmdir | 0x14 | 0x94 | +| statfs | 0x15 | 0x95 | +| utimens | 0x16 | 0x96 | ## Methods ### access +#### Request + | Field | Data Type | Description | | ----- | ---------------- | ----------- | -| | uint32 | message id | -| type | uint8 | message type (0x00) | +| id | u32 | message id | +| type | u8 | message type (0x01) | | path | string | | | mode | access_mode (i8) | #### Response -| Field | \ No newline at end of file +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### getattr + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### readlink + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### symlink + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### link + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### rename + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### chmod + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### chown + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### truncate + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### fsync + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### open + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### mknod + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### create + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### release + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### unlink + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### read + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### write + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### mkdir + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### readdir + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### rmdir + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### statfs + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | + +### utimens + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x81) | +| result | result | operation status | From b9c0f769d8e18bce68ca2741b903e8ffeb5f05b9 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 7 Jan 2023 11:07:16 +0100 Subject: [PATCH 64/91] add some documentation --- doc/protocol.md | 272 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 204 insertions(+), 68 deletions(-) diff --git a/doc/protocol.md b/doc/protocol.md index 03d728b..757b4c6 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -277,16 +277,20 @@ _Note that the following numbers are in `hexadecimal` notation._ ## Methods +Since `webfuse2` aims to communicate the `libfuse API` over a `websocket` connection, `webfuse2` methods are tightly connected to [fuse operations](https://libfuse.github.io/doxygen/structfuse__operations.html) which itself have a tight connection to `posix filesystem operations`. Therefore, additional information about most `webfuse2` operations can be found in the [fuse operations documentation](https://libfuse.github.io/doxygen/structfuse__operations.html) and / or the [man pages](https://man7.org/index.html). + ### access +Checks the user's permissions for a file (see [man access(2)](https://man7.org/linux/man-pages/man2/access.2.html)). + #### Request | Field | Data Type | Description | | ----- | ---------------- | ----------- | | id | u32 | message id | | type | u8 | message type (0x01) | -| path | string | | -| mode | access_mode (i8) | +| path | string | path of file to check | +| mode | access_mode (i8) | access mode to check | #### Response @@ -298,258 +302,337 @@ _Note that the following numbers are in `hexadecimal` notation._ ### getattr +Retrieve file attributes (see [man getattr(2)](https://man7.org/linux/man-pages/man2/getxattr.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x02) | +| path | string | path | + #### Response -| Field | Data Type | Description | -| ------ | --------- | ----------- | -| id | u32 | message id | -| type | u8 | message type (0x81) | -| result | result | operation status | +| Field | Data Type | Description | +| --------- | ---------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x82) | +| result | result | operation status | +| attibutes | attributes | attributes of file | ### readlink +Read value of a symbolik link (see [man readlink(2)](https://man7.org/linux/man-pages/man2/readlink.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | -| id | u32 | message id | -| type | u8 | message type (0x81) | +| id | u32 | message id | +| type | u8 | message type (0x03) | +| path | string | path of link | + #### Response -| Field | Data Type | Description | -| ------ | --------- | ----------- | -| id | u32 | message id | -| type | u8 | message type (0x81) | -| result | result | operation status | +| Field | Data Type | Description | +| -------- | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x83) | +| result | result | operation status | +| resolved | string | resolved path | ### symlink +Make a new name of a file (see [man symlink(2)](https://man7.org/linux/man-pages/man2/symlink.2.html)). + #### Request -| Field | Data Type | Description | -| ------ | --------- | ----------- | -| id | u32 | message id | -| type | u8 | message type (0x81) | +| Field | Data Type | Description | +| -------- | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x04) | +| target | string | target of link | +| linkpath | string | name of the link | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x84) | | result | result | operation status | ### link +Make a new name for a file (see [man link(2)](https://man7.org/linux/man-pages/man2/link.2.html)). + #### Request -| Field | Data Type | Description | -| ------ | --------- | ----------- | -| id | u32 | message id | -| type | u8 | message type (0x81) | +| Field | Data Type | Description | +| -------- | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x05) | +| old_path | string | path of the existing file | +| new_path | string | new name of the file | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x85) | | result | result | operation status | ### rename +Change the name of a file (see [man rename(2)](https://man7.org/linux/man-pages/man2/rename.2.html)). + #### Request -| Field | Data Type | Description | -| ------ | --------- | ----------- | -| id | u32 | message id | -| type | u8 | message type (0x81) | +| Field | Data Type | Description | +| -------- | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x06) | +| old_path | string | old name of the file | +| new_path | string | new name of the file | +| flags | rename_flags (u8) | flags to control the rename operation | + +The following `flags` are defined: +- **0x00:** move the file from `old_path` to `new_path` +- **0x01 (RENAME_NOREPLACE):** do not override `new_path` + This results in an error, when `new_path` already exists. +- **0x02 (RENAME_EXCHANGE):** atomically exchange the files #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x86) | | result | result | operation status | ### chmod +Change permissions of a file (see [man chmod(2)](https://man7.org/linux/man-pages/man2/chmod.2.html)). + #### Request -| Field | Data Type | Description | -| ------ | --------- | ----------- | -| id | u32 | message id | -| type | u8 | message type (0x81) | +| Field | Data Type | Description | +| ------ | ---------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x07) | +| path | string | path of the file | +| mode | mode (u32) | new file permissions | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x87) | | result | result | operation status | ### chown +Change ownership of a file (see [man chown(2)](https://man7.org/linux/man-pages/man2/chown.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x08) | +| path | string | path of the file | +| uid | uid (u32) | user id of the new owner | +| gid | gid (u32) | group id of the new owning group | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x88) | | result | result | operation status | ### truncate +Truncate a file to a specified length (see [man truncate(2)](https://man7.org/linux/man-pages/man2/truncate64.2.html)). + #### Request -| Field | Data Type | Description | -| ------ | --------- | ----------- | -| id | u32 | message id | -| type | u8 | message type (0x81) | +| Field | Data Type | Description | +| ------ | ------------ | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x09) | +| path | string | path of the file | +| size | u64 | new file size | +| handle | handle (u64) | handle of the file | + +_Note that handle might be invalid (-1), even if the file is open._ #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x89) | | result | result | operation status | ### fsync +Sychronize a file's in-core state with storage device (see [man fsync(2)](https://man7.org/linux/man-pages/man2/fsync.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x0a) | +| path | string | path of the file | +| is_datasync | bool | if true, sync only user data | +| handle | handle (u64) | handle of the file | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x8a) | | result | result | operation status | ### open +Open and possibly create a file ([man open(2)](https://man7.org/linux/man-pages/man2/open.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x0b) | +| path | string | path of the file | +| flags | open_flags (i32) | flags | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x8b) | | result | result | operation status | +| handle | handle (u64) | handle of the file | ### mknod +Create a special or ordinary file (see [man mknod(2)](https://man7.org/linux/man-pages/man2/mknod.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x0c) | +| path | string | path of the file | +| mode | mode (u32) | mode of the file | +| dev | dev (64) | device type | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x8c) | | result | result | operation status | ### create +Create a new file or rewrite an existing one (see [man creat(3p)](https://man7.org/linux/man-pages/man3/creat.3p.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x0d) | +| path | string | path of the file | +| mode | mode (u32) | mode of the file | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x8d) | | result | result | operation status | +| handle | handle (u64) | handle of the file | ### release +Releases a file handle (see [man close(2)](https://man7.org/linux/man-pages/man2/close.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x0e) | +| path | string | path of the file | +| handle | handle (u64) | handle of the file | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x8e) | | result | result | operation status | ### unlink +Delete a name and possibly the file it refers to ([man unlink(2)](https://man7.org/linux/man-pages/man2/unlink.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x0f) | +| path | string | path of the file | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x8f) | | result | result | operation status | ### read +Read from a file description (see [man read(2)](https://man7.org/linux/man-pages/man2/read.2.html), [man pread(2)](https://man7.org/linux/man-pages/man2/pread.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x10) | +| path | string | path of the file | +| buffer_size | u32 | max. amount of bytes requested | +| offset | u64 | offset of the file | +| handle | handle (u64) | handle of the file | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x90) | | result | result | operation status | +| data | bytes | requested data | ### write @@ -558,14 +641,14 @@ _Note that the following numbers are in `hexadecimal` notation._ | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x17) | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x91) | | result | result | operation status | ### mkdir @@ -575,14 +658,14 @@ _Note that the following numbers are in `hexadecimal` notation._ | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x12) | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x92) | | result | result | operation status | ### readdir @@ -592,14 +675,14 @@ _Note that the following numbers are in `hexadecimal` notation._ | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x13) | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x93) | | result | result | operation status | ### rmdir @@ -609,14 +692,14 @@ _Note that the following numbers are in `hexadecimal` notation._ | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x14) | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x94) | | result | result | operation status | ### statfs @@ -626,29 +709,82 @@ _Note that the following numbers are in `hexadecimal` notation._ | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x15) | #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x95) | | result | result | operation status | ### utimens +Change the file timestamps with nanosecond precision ([man utimesat(2)](https://man7.org/linux/man-pages/man2/utimensat.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x16) | +| path | string | path of the file | +| atime | timestamp | new last access time | +| mtime | timestamp | new last modified time | +| handle | handle (u64) | handle of the file | + +_Note that handle might be invalid (-1), even if the file is open._ #### Response | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | -| type | u8 | message type (0x81) | +| type | u8 | message type (0x96) | | result | result | operation status | + +## Examples + +### Get file attributes + +```` +service -> provider: + 00 00 00 01 # message id = 1 + 02 # message type = getattr request + 01 '/' # path = "/" + +provider -> service: + 00 00 00 01 # message id = 1 + 82 # message type = getattr response + 00 00 00 00 # result = 0 (OK) + 00 00 00 00 00 00 00 01 # attributes.inode = 1 + 00 00 00 00 00 00 00 02 # attributes.nlink = 2 + 00 00 41 a4 # attributes.mode = 0o40644 (S_IDDIR | 0o0644) + 00 00 03 e8 # attributes.uid = 1000 + 00 00 03 e8 # attributes.gid = 1000 + 00 00 00 00 00 00 00 00 # attributes.size = 0 + 00 00 00 00 00 00 00 00 # attributes.blocks = 0 + 00 00 00 00 00 00 00 00 # attrbites.atime.sec = 0 + 00 00 00 00 00 # attributs.atime.nsec = 0 + 00 00 00 00 00 00 00 00 # attrbites.mtime.sec = 0 + 00 00 00 00 00 # attributs.mtime.nsec = 0 + 00 00 00 00 00 00 00 00 # attrbites.ctime.sec = 0 + 00 00 00 00 00 # attributs.ctime.nsec = 0 +```` + +### Get file attributes (Failure) + +_Note that attributs are skipped in case of an error._ + +```` +service -> provider: + 00 00 00 01 # message id = 1 + 02 # message type = getattr request + 04 "/foo" # path = "/foo" + +provider -> service: + 00 00 00 01 # message id = 1 + 82 # message type = getattr response + ff ff ff fe # result = -2 (ENOENT) +```` From 36f222577fca29e39030f6068d00440910bd8ab0 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 7 Jan 2023 14:09:13 +0100 Subject: [PATCH 65/91] added more documentation --- doc/protocol.md | 108 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 5 deletions(-) diff --git a/doc/protocol.md b/doc/protocol.md index 757b4c6..9993862 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -1,4 +1,4 @@ -# Webfuse 2 Protocol +# Webfuse2 Protocol ## Scope @@ -188,6 +188,7 @@ _Note that the following numbers are in `octal` notation._ | Data Type | Description | | ---------- | --------------------- | | string | UTF-8 string | +| strings | list of strings | | bytes | array of bytes | | timestamp | date and time | | attributes | file attributes | @@ -200,6 +201,13 @@ _Note that the following numbers are in `octal` notation._ | size | u32 | length of the string in bytes | | data | u8[] | string data (UTF-8) | +#### strings + +| Field | Data Type | Description | +| ----- | --------- | --------------------------------- | +| size | u32 | count of the elements in the list | +| data | string[] | strings | + #### bytes | Field | Data Type | Description | @@ -232,6 +240,17 @@ _Note that the following numbers are in `octal` notation._ #### statistics +| Field | Data Type | Description | +| ------ | ---------- | -------------------------- | +| bsize | u64 | Filesystem block size | +| frsize | u64 | Fragment size | +| blocks | u64 | Size of the filesystem if `frsize` units | +| bfree | u64 | Number of free blocks | +| bavail | u64 | Number of free blocks for unprivileged users | +| files | u64 | Number of inodes | +| ffree | u64 | Number of free inodes | +| namemax | u64 | Maximum filename length | + ## Messages | Field | Type | Descripton | @@ -246,6 +265,22 @@ The `id` is just a number without any meaning for the `webfuse2 provider`. It is Most responses contain a `result` encoding the status of the operation. While successful responses may contain additional data, erroneous responses must not be decoded by a `webfuse2 service` implementation beyond the `result` value. +### Unknown requests + +There are two reserved message types in `webfuse2`: +- **0x00:** Unknown request +- **0x80:** Unknown response + +A `webfuse2 service` may send a request of type `unknown request` for conformance testing reasons. + +Since each request requires a response, a `webfuse2 provider` must respond to any unknown requests with a message of `unknown response` type. This allows to add new request types in future. + +### Accept additional data in requests + +Both, a `webfuse2 provider` and a `webfuse service` must accept messages that contain more data than specified. This allows to add optional fields to existing requests and / or responses in future. + +_Note there are no optional fields in the current revision of the `webfuse2 protocol`. + ### Message Types _Note that the following numbers are in `hexadecimal` notation._ @@ -631,17 +666,24 @@ Read from a file description (see [man read(2)](https://man7.org/linux/man-pages | ------ | --------- | ----------- | | id | u32 | message id | | type | u8 | message type (0x90) | -| result | result | operation status | +| result | result | amount of byte read or error code | | data | bytes | requested data | +_Note that results returns the amount of bytes read on success._ + ### write +Write to a file (see [man write(2)](https://man7.org/linux/man-pages/man2/write.2.html), [man pread(2)](https://man7.org/linux/man-pages/man2/pread.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | | type | u8 | message type (0x17) | +| data | bytes | data to write | +| offset | u64 | offset to write to | +| handle | handle (u64) | handle of the file | #### Response @@ -649,16 +691,22 @@ Read from a file description (see [man read(2)](https://man7.org/linux/man-pages | ------ | --------- | ----------- | | id | u32 | message id | | type | u8 | message type (0x91) | -| result | result | operation status | +| result | result | amount of bytes written or error code | + +_Note that results returns the amount of bytes written on success._ ### mkdir +Create a directory (see [man mkdir(2)](https://man7.org/linux/man-pages/man2/mkdir.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | | type | u8 | message type (0x12) | +| path | string | path of the directory | +| mode | mode (u32) | directory permissions | #### Response @@ -670,12 +718,15 @@ Read from a file description (see [man read(2)](https://man7.org/linux/man-pages ### readdir +Reads the contents of a directory. + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | | type | u8 | message type (0x13) | +| path | string | path of the directory | #### Response @@ -684,15 +735,19 @@ Read from a file description (see [man read(2)](https://man7.org/linux/man-pages | id | u32 | message id | | type | u8 | message type (0x93) | | result | result | operation status | +| items | strings | names of the directory entries | ### rmdir +Delete a directory (see [man rmdir(2)](https://man7.org/linux/man-pages/man2/rmdir.2.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | | type | u8 | message type (0x14) | +| path | string | path of the directory | #### Response @@ -704,12 +759,15 @@ Read from a file description (see [man read(2)](https://man7.org/linux/man-pages ### statfs +Get filesystem statistics (see [man statvfs(3)](https://man7.org/linux/man-pages/man3/statvfs.3.html)). + #### Request | Field | Data Type | Description | | ------ | --------- | ----------- | | id | u32 | message id | | type | u8 | message type (0x15) | +| path | string | path of the file | #### Response @@ -718,6 +776,7 @@ Read from a file description (see [man read(2)](https://man7.org/linux/man-pages | id | u32 | message id | | type | u8 | message type (0x95) | | result | result | operation status | +| statistics | statistics | filesystem statistics | ### utimens @@ -752,7 +811,8 @@ _Note that handle might be invalid (-1), even if the file is open._ service -> provider: 00 00 00 01 # message id = 1 02 # message type = getattr request - 01 '/' # path = "/" + 00 00 00 01 # path.size = 1 + '/' # path = "/" provider -> service: 00 00 00 01 # message id = 1 @@ -781,10 +841,48 @@ _Note that attributs are skipped in case of an error._ service -> provider: 00 00 00 01 # message id = 1 02 # message type = getattr request - 04 "/foo" # path = "/foo" + 00 00 00 04 # path.size = 4 + "/foo" # path = "/foo" provider -> service: 00 00 00 01 # message id = 1 82 # message type = getattr response ff ff ff fe # result = -2 (ENOENT) ```` + +### List directory contents + +_Note that '.' and '..' should not be included in the response._ + +```` +service -> provider: + 00 00 00 02 # message id = 2 + 13 # message type = readdir request + 00 00 00 04 # path.size = 4 + '/dir' # path = "/dir" + +provider -> service: + 00 00 00 02 # message id = 2 + 93 # message type = readdir response + 00 00 00 00 # result = 0 (OK) + 00 00 00 03 # items.size = 3 + 00 00 00 03 # items[0].size = 3 + "foo" # items[0] = "foo" + 00 00 00 03 # items[0].size = 3 + "bar" # items[0] = "bar" + 00 00 00 03 # items[0].size = 3 + "baz" # items[0] = "baz" +```` + +### Unknown request + +```` +service -> provider: + 00 00 00 23 # message id = 0x23 + 42 # message type = ??? (not specified yet) + ... # some more data + +provider -> service: + 00 00 00 23 # message id = 0x23 + 80 # message type = unknown response +```` From e922139a31d25ce72bb22bfcfab1224b8ab32c8c Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 7 Jan 2023 18:55:06 +0100 Subject: [PATCH 66/91] add project documentation --- CMakeLists.txt | 12 ++++---- README.md | 74 +++++++++++++++++++++++++++++++++++++++++++----- doc/README.md | 4 +++ doc/build.md | 35 +++++++++++++++++++++++ doc/concept.png | Bin 0 -> 22387 bytes doc/concept.uml | 26 +++++++++++++++++ doc/protocol.md | 44 +++++++++++++++------------- 7 files changed, 162 insertions(+), 33 deletions(-) create mode 100644 doc/README.md create mode 100644 doc/build.md create mode 100644 doc/concept.png create mode 100644 doc/concept.uml diff --git a/CMakeLists.txt b/CMakeLists.txt index fc41227..966db70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.10) project(webfuse VERSION 2.0.0) +option(WITHOUT_PROVIDER "Disabled build of webfuse provider" OFF) option(WITHOUT_TEST "Disables unit and integration tests" OFF) option(WITHOUT_CLANG_TIDY "Disables clang tidy" OFF) @@ -43,17 +44,16 @@ set_property( endif() -add_executable(webfuse - src/main.cpp) - +add_executable(webfuse src/main.cpp) target_link_libraries(webfuse PRIVATE webfuse_static) +install(TARGETS webfuse DESTINATION bin) -if(NOT(WITHOUT_PROVIDER)) -add_executable(webfuse_provider - src/provider_main.cpp) +if(NOT(WITHOUT_PROVIDER)) +add_executable(webfuse_provider src/provider_main.cpp) target_link_libraries(webfuse_provider PRIVATE webfuse_static) +install(TARGETS webfuse_provider DESTINATION bin) endif() diff --git a/README.md b/README.md index 2d68498..6b7dde4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,72 @@ [![build](https://github.com/falk-werner/webfuse/actions/workflows/build.yml/badge.svg)](https://github.com/falk-werner/webfuse/actions/workflows/build.yml) -# webfuse2 +# webfuse -Reimplementation of webfuse. +webfuse combines libwebsockets and libfuse. It allows to attach a remote filesystem via websockets. -## Build +## Motivation -```` -cmake -B build -cmake --build build -```` \ No newline at end of file +Many embedded devices, such as smart home or [IoT](https://en.wikipedia.org/wiki/Internet_of_things) devices are very limited regarding to their (non-volatile) memory resources. Such devices are typically comprised of an embedded linux and a small web server, providing an interface for maintenance purposes. + +Some use cases, such as firmware update, require to transfer (larger) files to the device. The firmware file is often stored multiple times on the device: + +1. cached by the web server, e.g. [lighttpd](https://redmine.lighttpd.net/boards/2/topics/3451) +2. copied to locally, e.g. /tmp +3. uncompressed, also to /tmp + +Techniques like [SquashFS](https://en.wikipedia.org/wiki/SquashFS) help to avoid the third step, since the upgrade file can be mounted directly. [RAUC](https://rauc.io/) shows the use of SquashFS within an update facility. +However at least one (unecessary) copy of the upload file is needed on the device. + +To avoid Steps 1 and 2, it would be great to keep the update file entirely in web server, just like [NFS](https://en.wikipedia.org/wiki/Network_File_System) or [WebDAV](https://wiki.archlinux.org/index.php/WebDAV). Unfortunately, NFS is not based on any protocol, natively usable by a web application. WebDAV is based on HTTP, but it needs a server providing the update file. + +webfuse solves this problem by using the [WebSocket](https://en.wikipedia.org/wiki/WebSocket) protocol. The emdedded device runs a service, known as webfuse adapter, awaiting incoming connections, e.g. from a web browser. The browser acts as a file system provider, providing the update file to the device. + +## Concept + +![concept](doc/concept.png) + +With webfuse it is possible to implement remote filesystems based on websockets. +Therefore, webfuse defined two roles participating in a webfuse connection: + +- webfuse service +- webfuse provider + +### Webfuse Service + +A `webfuse service` is both, +- a [websocket](https://en.wikipedia.org/wiki/WebSocket) service providing the `webfuse` protocol +- a [fuse](https://github.com/libfuse/libfuse) filesystem attached to a local mountpoint + +The `webfuse service` awaits incoming connections from a `webfuse provider`. Once connected, it communicates all the filesystem requests originated by the `libfuse` to the connected `webfuse provider` using the `websocket`-based `webfuse protocol`. + +By doing so, `webfuse` allows to inject a filesystem to a remote device. + +### Webfuse Provider + +A `webfuse provider` provides a filesystem to a remote device using the `websocket`-based `webfuse protocol`. Therefore, a `webfuse provider` implements a `websocket` client. + + +## Similar Projects + +### Davfs2 + +[davfs2](http://savannah.nongnu.org/projects/davfs2) is a Linux file system driver that allows to mount a [WebDAV](https://wiki.archlinux.org/index.php/WebDAV) resource. WebDAV is an extension to HTTP/1.1 that allows remote collaborative authoring of Web resources. + +Unlike webfuse, davfs2 mounts a remote filesystem locally, that is provided by a WebDAV server. In contrast, webfuse starts a server awaiting client connections to attach the remote file system. + +## Further Documentation + +- [Build instructions](doc/build.md) +- [Webfuse Protocol](doc/protocol.md) + +## webfuse legacy + +`webfuse2` is a complete re-implementation of the original idea behind `webfuse`. In contrast to the original `webfuse` implementation, `webfuse2` provides also write access to the filesystem and allows to use all standard options of a fuse filesystem. + +But `webfuse2` marks also some breaking changes: + +- `webfuse2` uses a new, binary protocol which is not compatible to the JSON-based protocol of legacy `webfuse` +- `webfuse` does not provide an API nor a library to program against + _(if you are interested in an API or a library for webfuse2 feel free to create an issue)_ + +When you are interested in the original `webfuse` implementation take a look at it [branch](https://github.com/falk-werner/webfuse/tree/master). diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..f193966 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,4 @@ +# webfuse developer documentation + +- [Build instructions](build.md) +- [Webfuse2 protocol](protocol.md) diff --git a/doc/build.md b/doc/build.md new file mode 100644 index 0000000..2f40343 --- /dev/null +++ b/doc/build.md @@ -0,0 +1,35 @@ +# webfuse build instructions + +## Build + +```` +cmake -B build +cmake --build build +sudo cmake --install build +```` + +## Build options + +| Options | Default | Description | +| ------------------ | -------- | ----------- | +| WITHOUT_PROVIDER | OFF | Disables build of webfuse provider | +| WITHOUT_TEST | OFF | Disables build of unit and integration tests | +| WITHOUT_CLANG_TIDY | OFF | Disables running clang tidy on build | + +## Dependencies + +- [libfuse](https://github.com/libfuse/libfuse) +- [libwebsockets](https://libwebsockets.org/) + +### Optional dependencies + +- [googletest](https://github.com/google/googletest) +- [valgrind](https://valgrind.org/) +- [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) + +## Additional cmake targets + +| Target | Description | +| -------- | ----------- | +| test | runs unit and integration tests | +| memcheck | runs unit test with valgrind/memcheck | diff --git a/doc/concept.png b/doc/concept.png new file mode 100644 index 0000000000000000000000000000000000000000..bf5b87560850abe68dd38a5697d03ad898306143 GIT binary patch literal 22387 zcmc$GbzGHO)Fz=)Qc?=iB~sEIlG5F!gfvJ?ql6$JA|>73-5`QU2^_jXLb|(WA29B{ z-<|I_Gv7Be|LBi%_Sx@#cdYfSXFY2N$jgeOBI6^&z`&qNJP}cZfq^Z8fq_#)gae9kMyWcNKX9NB(3)8kW? z*9?Sh)6qM`Ck%JCvvPfGy5+>uA^1|W_g$8}-+kYVelsDd5u~%0q}okeFQL8$H#?j< zu%T>>#~p)I^nex)azfO*kG#wz%RP+0UL;Oov`9DOdkNEhd4NLHmrOtzJIorrV1c}M zU-lj<7h?(8oQ6#?BL4F}p-AC`z!Z#8dfuQ!(pJXNFLQ3l-8ZECa2(7BxnII*mjp~m z9toGqqawdFQ;-vDM8|tHq-7LrCGX0v@lE!1?CPGbv$}oQt3it%jrGnu%5Xg|JzO24 zDq4^2tEqIK?Dpo1Dr~LAV0LsIZ4`}gg#>$L_kH&c-jeYf3v)7kh>W7ne<`^+qP!yW zo*<`0=m5i$xEAXXeyLn0&sO@<>1-bijEbCu$YbS~y6g2Q+A1njm*~289^r^8Qf)@v zptYXE_De@xX8o3xt(3~OJ0LA*ptfl9LqI9pC|dQRMJqc*(OCisI;<+_XI-DBp3^<0VdQ+e*3f`V)?FkYH) zN4H>Lw$w;OU|?Rrkix-u!6RbAJY_7na9gn0m`bS}CXS1I5~7@m+YojaH1g7^Iy(>1 zllEY#H?pw(M7Z){%C-$J46k3+13?ZR{NCd6@lh@^Ttl{Mow1=$;4^GyBXtVfFEMC!k4m)JU-58A#x={#Hy7H#ToHk`V6tcI8uCj# zTNushbmbx_5n3;cI58BNaH?5)DJm*zWJD<-U;hdN76y0s3ku4}*j7h6U-v_0WJ{yf zmXM1B#b?i+Nk{~IEk^{S%9~&5QR~w}CH=-VO~Zq?BFv{0gF2Q_Tb`7ZR8>`#m)GSj z>jSz{Rjjep`zEJgMZBCR5M^Z0gUKe~5a)fx5~4;w-Rx!%pMi(iM4MkL}0k;gl;Q zIk~~LciteOt zk`~k&aqfG1uF%~f)1vsXumI6v5h$R6E=t6}ML3u2qFi(*f4+t+)$~ibofV2ZvmFt% z!v#80PT#(K`Epj3;q`N_0_{D}Y=j#T%=sO6Ke5oz&~!#I>J{sYnwgo&%E~@_MkJ9SuCew?77S}4D1 z*gQXPIKd1Llja-uaq6IwZ3v~xz+k}N^6;GAxc4Y7Iiz4!cUKAf>sZl{pNOL3H&_3o zWMmjYO<6p`%cWvd8jc}4oI+ZeP1$?EpKC?Z`ds*fym$nR(l4F;w?xm;I6 zu38r`mN2ERELlcTkD!xCHi09=H=1VD77YD!ojqmBfZ0Quf62T{Gr5&m9e<6 zke;4SIV=?6usRfqyp5m_3**sZtb`kev3tpv*do5#6bHI+J+#uqanbg@4fgqrO1IUd z(+f@gcuJ?O8+XX~6x~;qSO&jTvjn#jw_;YBSkX6Ad zJPqveh@`D?JRcem@G0^wHfDgLAO0!&n897}}`t;n+s9 zd1?L=Pe|dIw48(+4i1hUon&(4{4FP|=;9aPtWb#bdu|lT@=}SsJzg0+e-IhPQr*r? zP4K!jXQ`*H@WHezMgB60ixFDGj^jQXL5Toy?EXmLK*Al?7vr@w6fGxbNa~g(BBRK$ z7Fs_j`o4)ie*E~#4k79Axmh4~*Q5xLUw;40kvS}W_D(laj@a3^JnQl!d%gUs=P8Ay z4i;{sYcoPe;+F=G+vqT)9jYUSQt^@Rqobq4!om_0<#zM)@`6H8n>5t+jOVGfFD|}* zs|-#bHZ-45Qi<{`D9|Cwxh#j7q}iOf*si&l%a(H)4)skWd@8$lM20C8o?p~{Fty7P z_Td9FGqXtp1m6dj34tO2hMsYxCIYwu&6QG+B5;kFa}~zsmpC#VW{Sew7g= z+xx3^9O>2T(I&Hd6G>ncZyrtqV2+A zAX~HH!qD?Xra9~K4fz(nncGRrAzCV`3yxFlxx>41`|{;hf)- z=xI`$%7`M%UrySo7tblYeEfFIPuk6*ACk8Yd_;161^=!l-{sc?7K{`N(Mv<&7pmY_ z?ccUlV#DA!rA_bZfYsjLXE*E=-k5&-kdWV#Bi@~x+Or$Eha1u$bQ=-zg_R*CA78Rg zEjvD;g)VEiwn12Tif0Kar|=i=%zOdz0Gi2)5ck=dt#1QngzSc{G?P1-jQP%}*8azI zrrvfO{YPmSwf5D{i`_=u>kW#m4XnX3+<_PEelxdmf(tuMz+B`h-=}%r7^b!*V3+GK z{yy1$YJ0lh)Q62;AKl}qxVBqtTY{9`q&F$YEYYs^hUi@Gkj9?e&+Qtn&8yXryxTTE z);RW}JSXSmB;IAx}rliTq9d0teq|)XK+S%4V9QTkhf#4*(*)4GI-sO zzE2k;%LHs~MN-RJJeM2Jj~&mH3!##+N?K^9E#!4R-XR*2-8CD`xmWMvfR1J*6g%ba z6w_DmDIo_v$BKqdTfo+kj&QY=K={_}rSfOZZs{Sl-nMg9ys#Cf%YE7Qhj@4IwrS!r zCQOu@*R}=|K;}MBDrGHnMXwbV;o>o@sUD=b-c!SyrMUKlaxe@(cz0D#{N~Nc-c)hw6aCfuiL@syA3o@yHwfxoh&%DS zEM(%3s_0&Z3RmzB6p?y!ZFdDI5u664Lf9`4OpO$>i`C*0vB@IZ+GGa+^ z`?HmZ<u2+I=Ad-ThTOF4u!JyTu9><+YK9^Y~}Yw+5W$t?mb7`3IkW+n&={TOKQC zvko>gW=w{MkPi+QCW@~&UY<{`jP6Xi7NQaJ`>%IvvRmtpWcL>iypd-&jiYd~FIY;n zD7j^<@3b$aX>4XV;_p8c9lhIl>48u$nJWHukc%H2Z3@aVAqwg~gYw*v`^<%=9C^ZFpN=ejT+N*WYlh~$Y@1m z&VT**kzZLtCf;GQ9wNy@==3e-@~fjU{ozD^2#-&R#2iI+svS6PVQidOwYhqI+)Ak` z)cXqDS*|q%SF1+kSlTrzfBCRyVnq?YSZJckrs@1}S_UWPj_2ukn?kCeJRkSTL0(>^ z`kSGAVFGpwLEofWoSGk5-RuhWb#ArH*zV1lJa|T7eFaQ~39cIo_?MfW#Px2+w3L*@ z4+&Y(I)YPe;36UtAI54{SjcJ(@@4xz#AEaj`l=rmMwH!spxFKX);wCWMIWcc#*#Kf zVAK$`yJce~s;qn9wRf!B;iT$D(%77xsHa$8Dq`rfS$Dzx(~#Sz$e|mAOAoo+Pf{~v zyqB4H>nQpBd~VQeLT=ogOlmM3{4!BjV{24cqWH8$T z1_om90k^@&7x!vy3KmI_^?jchwY{I_y*PT{h%R)9t(+-tL_4X;pt<*YS69uvP++4w z(CSfyjHIi&dLWB&NkpdWAyxIBr~~;l}TwVUuF{#m9r9i%3O6Qr-nR<7{RF^2Z~=iWzdAIW}`*drP*5EiHHw!~@A%l?M}F zttR5rb|HhmjtWm>+E{ja`nTuAg#m9@q_cmXbH0e-W@~mhj__)T4+_L7aF=2`0QjJG z{J8VLiy*J4R~VDHh?t`CkVMQaW{i|^IMNivhbJs>y0?Hc2bKh}I|m?&86yBW-ax}~MXaeeIOty>S{SJ6G+Qhll`Q32DA5vKx93Ue}Pi^us1B?U!zRMh2GGdi*7 z*>=&hX7$tVcplgN710np25k~kG(v9s)}VVG4aLR9({8({t}8T0O02LyBJ%$-I8;)0 z^P#o*_OM~=JW_sskDdg+4yBd;d@T;uOm~6HGdrWO%o#bzjgM*-4^Ms~uP0QjK1UOF zJ>^!DC!3y^JoWg~9;f+zukZ>|cWPos&uIf8j-G zA79^iK37%e8tZBPC`GWN)dO+q)%mEXo^Q{78Mbzsv7J7dgZ@~kSOvlYrm9?2PK=q! zac#tAt`+ij^Xz!Hy1JUr^*}j|{pHq-Oe}juM8x^gTnOl9Wx7$oyr;X{YA^?{g!h&wxP>)m>AiF-xQ}t11Y9OI|#m%lvo(WhXDYnZEr|{ zMNuwrdEE2;`}Z=Kpm*=OySkLq#AX9SLae9ioL7hP#e?xh14WDqEypWDZ^W`&N>=tk zgU@>TyvYV3<*=wIaXn*2#Xj4o@DC8*dJEAoF#JfZA-Z9V1KnYtnuX{dJRtEkHZ+v) zqV-Cbzkz+@%j4wm@bE1K9rV|wH-zuOGR{0B?JE%w5J-f}uy)D*r1$_1`Som)d5Em1_q+IhaCr>Zp$>5lpx_(!NaA8@2T_+mKI1;Bp1<=p<3vR%lZ zH?jpr+Plv1%LhYJ5h5vln2g7|CHfL27-S)oOfdZ?4 z@7SDARMLQqN>PjE@P2HuCn6mNm{vA+inTmY_dFb|z${ zx)#q1W8F`RR7_M9>D+jz3i0Kcwm-4Q(RUrE$-z43-QM0_z?YPjmF<{wE6H>Bd}Q)Y z00?#_Bi8DqQ}4odwgqXZyXIRATdwQj1}Xu2ozUIYme_pwi5N2(O{0s*AU{&&1P0O zb2SvHS7KB3u7}J0nZUxxjGc@agcLgtIRl)*>R+=E#b>Ss2M<3e7lD??Z@<*r{05zv z&n5if-d=v?lp?ol9EjYR883HWrEF+KYK*8z3)G9r*w`X)1MG+MHk3R`M2H0N$mnld zSAD-#Gw{T2#nCu5n}(J)HGh4q+(1rakzD-U1Hw3`%*?Wv$^t6fMJ^s5{H~G_USqD5 z>7~Y`1bxU(;}w=?od|R{h01PvFtqsi_`H9Q@jSP|{j@-<@?CkZ@0X*c)DV-iqM}DN zM$fgfXA4R@aV<$7ie(`4p`xLwa=T)T^i~M^SA6R6RoA5@sTrV>OOQUTupEzM%kllP z)SC=nUUzwMY77jg^G;fgx1I4KZU$tt;q=`K0JgSMlPI(zBfZ_%J>12^t2FJWx})vr z$i6;aZmu>YOZ(WR#`Dr6eX-DEpVmx5PENq%3>YPW4GY$E*@tuGv~ueCvf<_ z1`{mSo%SXRFK3)CK~kIjP*XFl8;&zd3dOxpEzx{TP{8)E54lf3FrFeB2z({_ea=T5 z98u+9aJ7H}F|)LssM@X=x2pZ_M*!$Cajp2^)|K0fla&^uwx{cr2sdwviiwpZw!f%l z)UJBpnt6I?HCgj5nnnCx2l*$z1B9zd6yZsVOhtSuoCpK{#7J2{#38d-q6IGq{=RP` zx$=J}$-B-Px&e+9dL7v|TEN&gA5cUC}cQ=pmGr zlUvk!wFyHy2Iqv;g|}@?$_~tJNy!K6eVa40CX_#)OD?0)G(K?p{c}}+*oPxZR z#>R*1;~bovoDUzG?XL{x73bubo$M{gb32%gmMo-(@Vi``9qS#~lO-K^HcmK>Te2d4 z8zkg$q|>U%1Lq2m3J?fH{N4R$lP3AxHnS~Yb5xl2`#tRF8=MSKFk0w{0AA-d5>m1a z+I`MX_=9@kd(crV+}Kq(Q}rJlu5FEXA|oT~>+56Ct$XqVAM|W4{p5*%U?9}XIXfR8 zZO>85#O!q@$o9$~PJ3Ps>v_sd#W5L(0Wa!@POJ;KrG&oKo*J2WZZ>}Y3qyL~@fjPA zQc7x>!%t{b#NOT4(AJiot2^82WMX2{M(~8L+y>Q0MmSRL;X%3aO6$g_O|P-2WwR>9 z#Kefe5gqJ_UsN(gw19aEDW71-2=QFa8Aw^I`Z# zk||-wigI$MHo``Bw`27yus3Rvegq2o-@b#9U#cE|-uci73sBL6!2}%8^TxLhNquv1 zd9d28J)z`8E8;}`!z%96CqakWiC>FKA|fU&-POt1=mu8I;Oje7Vt(yWR{a+L^V7rr z55YcnF)^F)7r({g5fghJZ9P*|0rp~Ft<|PAzPc(ij9N~0O87prVOuzjl6q||mK}%e z%a@$-A6>#){yg9oeMd@as<4nyN=k}qfp)fqq@3L5WG%-eIdl_&!^WnnqC&4;M8eGc z;pA|0+VlKmzk{>8iUsd5JnpoMrKb!;NFD2?SKSK<2?57ZsiI0EqBUdn`k3s3PGn{k zJnSE50itxH>hsXrV;C&F?@cwyteozaQRcuk;;JtA;CgeXv(oeZTEi@ln z=U*-!MF4GVA0X_XQhcyv3lC03G=mMI0_zZwjum(|Xp2VrwskDE6BRtcu%?An1fuS0 zf3+skQJg*x@XbRQIH1RI`OX^*QG<2J!Y$}A)tSX0vsu;_h4cj_Hqg&;ynM|af+qd5 zdjUNC(RV$LK}VN;%^UQ9y_x<&%K8p%1?l<`=Su!%6ui6!qcs(Wln7JBR7XTM?0Jq3 z_A`L)hK^{%S}2Y3dqr|j#so%K-wVO9U~iVsv%X_RngzLEtga?WYC_H@iw;RCwuNH& z!ox-^_a)=VKfX4x{4R(15^-fw2KSCv_wJ5Ek_R}2VEShyZ^Sr|7so|`?LGp`5w#bQx!@N3l@c+qlp#R zS$|H;0D{pwpx+rgENOVe(B?SiuJr}9o_86oy={KnT5#auw`Cs1!jsy5EcL3cpZGos z8Muo-DsLNI=Bim)ZO93n?*XC&{M#SG zckr>#vc(^hl9IBcU++L+dp=B#L9@*NA+=nB#ZcZTy1<#BLm!suyN5_fNH#V$U*8du zlLrwCc=V-7STN$#1B@!M_&c(H&$up{D0j@|!FnToMF` z{+kta{-dYQpHr!jk&}lI@pZT)aM@*mImnYurCIIpMnLal4ja@-ctlST(Oq_+%-v5G zo^a7uh>L#=pj%4$GY^l3J}^2-VzemZ6sC7OnxTTG5Xs!(l`%Gp#qRh~D!x;0s2%v} zlsABnnQsfV*BS#022kHno5oWBN`(DxC!bb%Y2ve6WZBoc?C11-iw6VV7LU19u0B5) z=dhZ1IjrNP5U-gP|Iog(-OmVk5U+Jn2TiwPZk5-sU#m|pAoZ(8#Sg9{voa&?9WN%T zahS#V)x_do4eO-51|YEyVPV-j7pI#c1RUMH4flZ=d%@Ap4aVPN zJ^J4KMk8L;vm+&>DZZ!{hb+5e0gzXDrrVt6Lv-noNmCOyh1(m_6T!{(s>E(vOjCB)%CgggS`ZUpK|Uw?a_YAr#=I{JO3@&Mx~b(>)lkgGrSaIRAp5AS%<8gd z;v_VGpeW4I$uaH5GWk>y{YWlPg)P^a*7mwik~n5uQMuF+TpD{5uM00kJUC@2Wvq2=ku%ai?84OhW+xo9x)6FDlh zw$VG@22s?~sxCjGjmHLra2D8)K{&!#w*_|`{TUK0dQ*J`MhY8nm!|<3$5f2^fAMxHCV6}{ zP`DwOA$4B}JNUouTAyfht0|PR&ShE?UJ`vO9dNdPdIgKY)2lH$9o;HcoACY<{~7Z> zeDngn{gqQ5QjJy|YOW3;;Y;LeXMY-tVJcB&FyN`^y`hU`E4PB`lwKKxW!7v~U4UB) zdyN4EFjGn>q|Mnm)0^T4KH&29qOj*hu9_7STs!+iEK*IaBP=+g*RGbNBIdyTgHKc= z&-E@md(sx3*(M1qYPks1F}P*nsrm~-kCqpPJBovDW?!Y{#`z0dZxgeLUegF1JV=NC_0^1jFg*g)FLoxutIz6%t$uiY3c=duL~7SC_P;q$UGiINtP)G(mEWnxJL<45^x`s*jDms|bJr z$i+ZGMqc7E6BE0oBTY;3dS^D)3%g1xlHuvkrfF$Dznd=qAOj2x1Nq5N%tOqpE*H4Qu+PubuK!D(s}{z7k7gR^N(+@GS$v>&a`;HSs)mg z-+Am1N)(6R-TxPPBOReQU|C2SpItFKMNAj(hXE0I%(aX#qUMh+0R!{tir49p5csqq zbf5FZjHegoNcwFbAzG0J2{IvJVNwxmeL;WL=FV4`OQLgnk~5}s(d@e3GJJ>-^QD+# zj1(>s5yq$OW!v}`EBnj+)QpUU!0Z(j6{V%6fi>KuCXb}ZHE$L2|KWvE??u_)YF?D> zw3=V}{Q2`A^YinIi(y!ufS>bV28>tD^E^YLJoiLyFKZkapQW4EPpZ$#$^xAMlW7Y? zFQ%bwhhdmf9|qYBNJZ?xF8=hvbdGIgATTIMyTOf%D|9Hpep6dpyX#l0Z%EsdPSfkd zg?hk8fVlv0M}O!Dd{n>PN>DjrEA`^Vi^GjcJ=b+MvuCj%GpAfu>1!S*_>L?hxo0xU=AK{Si8;;9c3)>7?# zHpTe3Hg+cA^JO{D6d4dI+&wahzFZ5J!?~dcL2&=Vwnp#{# z(&oo8Y2ObJ;!)I{m-tc{!NXvhgC39q+Bekk7$CJON6i)mp5=fQ1ho4}++g5cN+5Dg zITSX3M%;K1)U-NTDwm>>*po}KOXfCyzB1TP@=0J)Sld|FysYT39a^p;~~$;f<0>w9nWwW~jzmqsluEddtF zq*CqQ0-LdTbo`{(``gB>J0AeM+PM^?fI&6~QbFl>-&YG8`2pz1lH+Z{Z&%&k-ljGW z2o84N{(`5$Q?Cz*O*#e!s`C14%4Tf+cW6P&$OuA&oZlldSR{!5GmwtjBg4kQ z$)4_x<#jIEA)0+pDx4u5{dsIMphxEvFxn<@0K4|P+k1FiY<+Lm0I%rx=8ck^>Y?4w z5rR#D&jc*pu`iyk3D5|Mh_ryQYLHL%@b7l9SehljwRK&Ub=w%r&B?)~Q^&Y>@7|p| zeT|-%VAGd6Mj^;t;nfAd4*`I&pw)~4^u^7_CZu04^c>peC)M2>0@Mnj?IIYERxk%0sLV=OxQmMJ{TpV7| zU-#p!c@bpajtnBt39g;#y)4~F9r1Wz1p(X5ekgCDHFN`YqR)v}#iZ$I{YRZ4?X^w2 z-q?cPAP?0iw4vbhvF{Ogy~^2Q4}A-=g4M5d%M4mMsuDhG+q>ht9IJPM_)aAHG=~1;$FS}p9c^t%FIC$RuiH>N zL+n!}#Fav*o=Qj{3D6t0aQHmw+&?>WL-uRXNbmTM&46t1XS3<}2X{``MWqh!-%GxG zMD^6*jDj)7ignp!1#%-VE9-PF`pyi2P}-v<0_=1};6fjeW5Yw=GI3tCKbVS8hA+ko zqzuo>!ZD{YQuh9*`K-SnnLmoWHiLGp>ff2Mf6?m!X&;f)RL9NV$*(`Z{R>drQQuKz z_?<@k?*w_EXLqFEXzah1lT(et02}~MoAu9qavOkBo2!(X(*&D6Qm7wzBN&>yye}VZ zR+-<$nEmt${t|xPSLXpgLO%Hs70FGq52&c9`yVk(yTjp~ll_lp++n4rOyP6|MM|WN zFvzk95HUxPG259V{v+ZNT23-Ydnw|-wlP_~Av|y?^{6t+3DFDVRXj#0ozf^^FJgY{ z_hf;jSBZNm2L1pqau}{daQ^cPlF@DgxXO#Z_fbl%b7@Kx0hqs@IUz zyX)5Uuoz@TSY#iJ9{v-6<=OYLA#@?xfaEU}WdUCYas~A!5VCT@xK%*huK>&eGC0`_ z6V-NQ)=)A;NGL0mT_KQpt#-3vyil(ZByd1b>FGcf0-H6f4BY9z(uJ() z-ax~WcL5TP5V!={or7`f=@KK9aS=N8qEC~#)UQFRu($XK%sOWOtr1wv_J@^S6fHQk zs%aS+K$E)>6M>cjJ?Cy?Mj!zu4s32I%D_HIvl9Y@Qr?xo7pqC+vP5cjJluF8y3b|z zqe!N(@xn!L9X+7oXf_bAOZ*o{b1RS4(0da+4(`PT6Q8{`!GvyL5(kpXIZHc}E3~^0faWaO%=v;{fZ}E7?Z3M0%uB3*yi{d&<-;r|AO$jZ&#{9-fbJxHj6HfUHR>`Lx-L z>3Yj(QngguE#A88jJ_SkS6F|}VK$Ie2#_jB&S;mL4FVc$gzA7uA1Exi-UUH7N<{e^ zUtfjOi}TZn#{d%Ckv}Dx1(1K#)t{AaeBMnYMXgY`j2RlofTXpFXuG8m^i}6(XW1>s zL~=XV)-22@@JbyQOKmye4#Vr;yu3U=1li_203!g+_?>&0n9JfXFR!laN-hz|?1)eU za1q!DYHClUrPEw{o_~vhq@7Q0dWhF3u-ET)^Q6&_4S)?sDHU1N3WA18t4U2hWm8j{ z{NXm4zar-64ma^5%flq9Wsp}02OVm}sl+QyASobn0syh=+c%^2ZsUc5jWc_|OmyUO zfGnlg*r|+4UsZt^v}v6(M@R{qfxe$C6@kdFprqgXA+ zl$i-s1EbkLq4_@<3KJSQBG+doCA&p}2BaNiK+S8WIJ*{Zl8zDEwVT0{;){vg7QFvs=QYN9NVQ@4tDldy zbEXmM>h6Y;%pjOmRvuUx%q`r!nb)FSGA8KnhlvS~@us>CoAjTAe|-a9(Vf3_tmyNH zMxQ-d|K#$wfk|P`0+Jo1Q}5y84qXx1d8@9lv`kTX>=-G}L|R~4o-r^2nH<=ulT|hb zAb1!I{-pWOF8ndc4?tdUdsg(0K0vSg^#>@PFElze1nL?L1t?bu!d=Dgf|FI9(1=45&wL?{GzDTXBj>sX|%d9{8E=Q z8=a#^BfUh_@G-l7H z4n*f6{cH&j6i5C*WOVhj|IR;}Zom#-;!)gmTK5Q)*?}Vx7b&@V{ikqFvr1!=4qF@+ zAMmy~e;pRAs@?cn$8oX~AIpDy?ElY?4Y+T)1l+;G0%8%M@_r%*qM8i3#HnnhOu)_X zpDd*=_9PllHh?e@_@YXnAqTb^op(!6BYMusf>Ebt4^SYAt{_grqt{G(*`iZhlfqws zAD^kbNLrP-(c1oxQ!~nd+`*af#a{Q*5C12)U`s9EU82Ni(iZ_>+cgGAXYlZx)<%i} zb_b?eE`~Lr{BUKEmV@IkGtuJ&q*%be%U_9HgUTEC`lLWmn<*R54cvu=MV>x})B4KW zlPWX&1jYC_GYK^W)Ijq>Ez!q!uXI!9zJ}1skzZ%1 zR*bvjmPd+HOC*v3uNG;YC!1Re4RWDw89SuPTup7@t4iu__zK8>4@`2dk5}pyJ#7lW zq8?GY<%8HoN~QsJc!ohz)Do}`9ZEHJx8)&8kKu$Bj_!XL06Hti#~-^)w)r zwsxM?vtHYv51atOMbN4(MJkO3j-eCtcda0x6XlgoR?h}qDA_tA-dXTP!Bam6ypm*X ztx(2m>?vhqWZBArN<8fvtomec-rnIUP`AofJ_y%w`X;LW{#_E)1gnjwrl;U4$}?pc6N3e z23G=ztX7F3tbS+MSn{hia%d$vPfWw3X2cwb#@kX50pdY?F>=2W!bD4ggS9c$ZQIL! zG7ta&TneV#=H07EpEt&ITEhnopq6Ne_1=R@)Lc@Lr|8!|B!s6&szh^VU-0`F?gy`k zKT%!boVUlm$0Z_7@D$;yji0`Jq))bt{t^ui(0ciQymEA4^WXmRe#i}Rm*lM+Hop)w zC;_f6OuJU-%y#3Hp!S_4dMyViKVn}60XuJO?VC;1Jh7iB6CC~lrttM6I_tFoHqt}G zi9Ib&NW!0K zO!%i6^Y8g>#=_&{fzD`pW@ZM!C6KzOJ!R0YDgxXwDD#p7hVR_VlW4jjrP@pyUA(xg@5lT9$22d}r9i>vv_^8&gX_HH{>3LdpMFs-*07XjrCR<|>u54@47M3%8{+e3y#TW|Bk z00c-I$`R|Yla=Z9(|{^1T`&m<00r3hy!f>Dp`i-DGweo%z_X$euuD7l6m&Z-zGdN- zGX)#r^l&qCfryKijO;ZV^eG^NkW;6|2|U{ZpNQs)Y&5gza?isFr6})O9;}emP`oYE zpJwY;H=>G)3JNZr|KC#HryyHy;yzGiGY3@q@(4zZit``|0^~HJQbk_^@rFk?v~9!| zY0@(@L24$}3R<2V8ynk+4{zq#0r&!AZYXh_e8w6*aPUV`#zR{3&qX8CFQUdzD3ALa z*Lm6AISq{=pr~yGiw7n2!DNNC<E^hPeKXzr9G4z4{g1m*#OD9R2J*!BS6jSzvhpmMD9GKiT$=oUS+0>5 z9!~nul|BC&!WCDlx=`&{6xyy3z?e2n#zoORy$)2bf$9GDmQ&&fv@LtRRY6m!G7!Ri|Vh_9yC}SXi*}baoR=3a&L=h5w1it2#U9E_RN*1PPqlhY)_^ z`xjABU(Xk|VPCOte8@-t3DUnaFov9V&cVZ3PEcwQ|3u5pI=V!MQC|^mj8#ch@c#-R zYU|?8)2(c#I(nDptnf$>|K4?JV|XOc(6qL+Xg5NDVD6^6!=jq_bl249l=_}hCTSKVrC;v~9{`~aI?CkiM4CB1n{Q;6)31)fj-8792* zF$0^d#f!z5q*t*nydQ_?BDEg9y#lY$=<$7&TyxwLQpM3;)Au7gvbSP8vU$3mf|42JKa`vodIWlJ@x(*)H^l8+ycznZPJL1L4b%AKGZI zGli-@yAZUKwoWGnizlm1Y)yx2E?YP2-k9(}Bf(d7ZrCNyuFp&aCF$_vUnd3nO=wIH zX@&nnq|g0dUK9k=3wARcDsD*77#P$%V20*V`~Hn>hq9=c?H^h|IW!E+d#@AZpBe|i z6as~AJ%F*HAB3i(pu{#Plkk^v1S))r7LYma>guYis{>O0FAg<6SwNNJ6&C}9OwIZ4 zL$3-vuG%BYMDB^+A&&^{m6VjkZoG#}M$fpryK74F!8|8O{K2%aCWC5Wa zPfU!AjNVS)MhUf$6n?L2njKAkO_oCkncV)N;fa14^BqmmFO0-6%yc4S(LR!?y}?e& z|JD=W)S*2oxQMu&Ao)YXvj0#O^8ZDK@P~l^uaNiPw?c&fO}zU5yrgE|>sKv*Scd%V z?qGg32G2|K#4s$|8;iBh%k6E6+bX09`SlMJEL0>b*XQhP;hHqG_6+BHC{D{TLb~JK z9nG2FE-Wm?jsC8$Pu#X7RqRqL+z>CWi))t|;`&*B_6a0rOK{jnE4n1QIy-3}Ja|Ay zM@K`Gos~87vsxS$BxwKBV)+|&x3B8W*XtLJsVFIp3=MnTQrQsc1&>BB;v7JU+plM+ z@^csf{TJ)B398Vc5x6+xSABdmBI1l?YMI;qpc=@P-bP0UWuOEIx9=8#;=lABkl_ZU zHvJ$;3XLgSeX{7S4XEY^TB zuQ}mg3g3i;{XrEDU{2@v3C#UNLZ(ZNLw3SX>{o{XR-5h-xYQn<|JUj~{=*47kd(hT zoxYSUYz3N(T>GR}zjCMy(keJxU@*cRpyRZ?J?t}@7{T7U6vmP2(1^V~AHd|A*cUlN zIPLzF!J{Ws{4dww)e98X{O8s5|EmRgpeK`aZI$w#YMs|9+bXMcf6543-x2ayO>k;% z#|wD!b8u7w6ZUohlv&l8_Ggrolz_5E8%T;Mh&+wgfesm{jZ@T?L2iVWp8i`$$J^jw zqgXdN|7o|%X?WBt50mr*e ztf#S&4Dpgqo*<*0XS6-ru2WlKK2e3q$DqONtJxjbj)r1x^Zm1x5n~QuU}o1w@9%Rp z?sl`{$2qP|)icCpnOT! zeV?Wx;KXU|Zn1;;EV{s2*EgLppnRa$!hIMUE^N}3>v2v7RtCs`DnOC%cx5At#fTR- z`c>t3ad9J;2X7!Bn2{g(#cOMn_9Wnblau}3tnDd)ad;24TZrBpxwqoVs*84?gca}JU6Uv!_HsYrTix`U&XFt~hTp!p13Bk`;^Nsl%*+%edB zFi%6th%1!=A3+6g9qVYV6x*m@$7w$!ze8!qKw6qOCoQA9=5Wd+J^o$o^KX|1wWX1+ z8_Qn_g+|kCK1@KWugiY}CI=Z!s_BIRoJwE$QLACbfequIDeCd$y-|3f`C@7vdhfqCOy0uT4%kbmJ zU<9q}ApM@@WiSWTyL(u2e)>`gxiMh2;;CN@MqorFU{M7{GtHUi^iK9;$(@Cc$2;0n ziv)++s|M8kx;$aSuuLhH@oPE;TPw8o`z$P1K#wc7wsL8GA0d;nk9;uOca!2)u}~VQ zt+6KZbi=MiNOnce=}#?*u%gGKcWH7i%q<@#c3KqW_}D>}&PSK`C7NZlqrcuYO#37y z%S1R3m!qVn^{U=mJHOtjyX|A@M!QvX_S#r^op^|>a`M;7S~c5R^9g42xU^12d72aI z*u19f!0yRvVe?G(moGdWvA*F1U!5?|xt&7+4<=_Dm(HsB)yo>ra`{@ki{;lZpaM0K z8t0q~uIGhaSTbwVYUZ!tWc1G4~t5yI3T%1hoR8a6a>bS@-@gY3If`^Rz@jJrP zahth~)Xgq|iz-@bV~L&wodGL3io;}>?ISkx#GiK|te&E`y2YiEK zRSS{t_7?h6T?|!|g=@Kgu`87mitnxf{0SCeN{aqhJiW9&?y_qe z(U{`48QSI8X#Cepy^cf9?RG5<)X6Q=`OmC~M5};i^~16xw;N|XC`8Op=Pu9U>Fr8h z^|Si6wBa{IJ~}F;?&`JDPY};DeReR;NK>1|s8@4zakhB6obV184g#qR3mA}176sCB zndJ@Zfh^K8^{ngr71Wc{9}gF-|CW%w z7KsS6E5BU7WO%6>_@|P82Lar)Mp>@{ z?i`ET439_I&vst^mz&9qXn*;zHsS}9s&<4{4en{XC<`TdwF~G20_TIrT;-Q8ui}=P zE_-eQB=q|h83FmSvgeS>#chdh#_-OP)zBEHJ8GBv0_MX7Mxfl*SdRVjCczDDHl##cWaN-95iA2e|Np;LK)*$mr9y z7 z7kpp${(^iLP=)>*3W8%AyPe=eib7d3(gN*jTc9qpA70D$oLyleO&Q5qiUv0zT-^%w zJrHk7dgzd=C1fM#_`ZGzd=Cgi{9o?#lC*SAUp@)cRR!QtIzDm>Tz{m-&*8lzR-8wt zrLnm1dWMdHn|TMz%u{KhVZH`vkSc~o9?`wip|)5jBY@gS%pgVK3y-BZGs{5h+SUqn8AKLnu-Ub~DqUU!zx3HD)hcF5G~H@yblvMBQ_L&06`rQ8!D2^j*O*PCdwIpIOr@ zYt-0hpVX_3pW7gSs|T^mhEge6{*!ywp0k%tLa$nbK^JkKDTDOuSCc7aNwku1-= zg6GpDxy#?)a&gUN%+WFz}n-zb5-Wb`x*W<0GBlGsX zvz?sQoS)wSoMvZXh&bPx@3+4Hhiv5g%kFWBy07e)`Ieu;dI%aY_?`hP(7b)QIzRJp z9CVglcjHoN%ENT_8N#;vJisS8IbDdvnb!RC_q9@aS6g#8 z`%hn|w_Z8;*!&#mFg&jpax-Pzzn?C-9<#RUb-+f&-`4||XZ`#&`R_|}-o^jq?LPQt zeg84L6MS$U@a#9Uo4Pp8C0n_3f8DdKRsZ^J(-nZRVk#5A)e<=C0^Iir49W2LeBcRI zPyVf4ap~db^2;}u7k_%b=EEA`JU2rF>lEPBxgBusJ1uJ~pY*1^Q}TXZo_?)H;Cb7O z^OMXh%8hwJ@o6UN-gWc+6VX5C?_}LIOVOBH{^$D_VD{hAVf*9Z@po3ztiZ6XnA)9w z8aVkqGv;@=^g7_?;CSH4S>LWL-1cCG@cDU`pD)&)2O7Nm?qB!M+*4wI+;amR_ZOFX zy;*<4QM+BAFKXLgJ+bZ+=(w?`3)^@l&vd5y&AD|WJ?5-?S>5j~n^s)8vDy#hx$uRe zN|qDlR>#&pnNaC_yNzdCe&E4_{O{-gIC{>X_w8#wP;QiblQwhn!hfOjc3xe|1`afxD_u^3Q|+>>qq~IPcr&5dmE9$KdJe=d#Wzp$P!5 C&XPR< literal 0 HcmV?d00001 diff --git a/doc/concept.uml b/doc/concept.uml new file mode 100644 index 0000000..e4dcb20 --- /dev/null +++ b/doc/concept.uml @@ -0,0 +1,26 @@ +@startuml +participant "webfuse provider" as provider +participant "webfuse service" as service +actor "user" as user + +group startup +service -> service : fuse_mount +service -> service : start websocket server +end +... + +group connect +provider -> service : connect +end +... + + +group directory listing +user -> service : ls +service -> provider : readdir request +provider --> service : readdir response +service --> user : [., ..] +end +... + +@enduml \ No newline at end of file diff --git a/doc/protocol.md b/doc/protocol.md index 9993862..b6cda83 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -2,34 +2,38 @@ ## Scope -This document describes the webfuse 2 communication protocol. The protocol is used to transfer messages between a `webfuse2 service` and a `webfuse2 provider`. In contrast to `legacy webfuse`, which is based on `JSON RPC` the `webfuse2 protocol` is a binary protocol. +This document describes the webfuse 2 communication protocol. The protocol is used to transfer messages between a `webfuse service` and a `webfuse provider`. In contrast to `legacy webfuse`, which is based on `JSON RPC` the `webfuse2 protocol` is a binary protocol. ## Definitions -### Webfuse2 Service +### Webfuse Service -A `webfuse2 service` is both, -- a [websocket](https://en.wikipedia.org/wiki/WebSocket) service providing the `webfuse2` protocol +A `webfuse service` is both, +- a [websocket](https://en.wikipedia.org/wiki/WebSocket) service providing the `webfuse` protocol - a [fuse](https://github.com/libfuse/libfuse) filesystem attached to a local mountpoint -The `webfuse2 service` awaits incoming connections from a `webfuse2 provider`. Once connected, it communicates all the filesystem requests originated by the `libfuse` to the connected `webfuse2 provider` using the `websocket`-based `webfuse2 protocol`. +The `webfuse service` awaits incoming connections from a `webfuse provider`. Once connected, it communicates all the filesystem requests originated by the `libfuse` to the connected `webfuse provider` using the `websocket`-based `webfuse protocol`. -By doing so, `webfuse2` allows to inject a filesystem to a remote device. +By doing so, `webfuse` allows to inject a filesystem to a remote device. -### Webfuse2 Provider +### Webfuse Provider -A `webfuse2 provider` provides a filesystem to a remote device using the `websocket`-based `webfuse2 protocol`. Therefore, a `webfuse2 provider` implements a `websocket` client. +A `webfuse provider` provides a filesystem to a remote device using the `websocket`-based `webfuse protocol`. Therefore, a `webfuse provider` implements a `websocket` client. + +## Websocket protocol name + +The webfuse2 protocol uses the following websocket protocol name: `webfuse2`. ## Message exchange Once connected, the `webfuse2 protocol` implements a strict request-response scheme, where -- all requests are send by the `webfuse2 service`, +- all requests are send by the `webfuse service`, - all requests require a response and -- all responses are send by the `webfuse2 provider` +- all responses are send by the `webfuse provider` -Note that this communication is reversed the typical client-server-communication scheme. In `webfuse2` the `webfuse2 serive` (server) sends all the requests and the `webfuse provider` (client) sends the responses. +Note that this communication is reversed the typical client-server-communication scheme. In `webfuse` the `webfuse service` (server) sends all the requests and the `webfuse provider` (client) sends the responses. -For message transfer, the [websocket](https://en.wikipedia.org/wiki/WebSocket) protocol is used. All messages are in binary form, plain text messages are never used by the `webfuse2 protocol`. +For message transfer, the [websocket](https://en.wikipedia.org/wiki/WebSocket) protocol is used. All messages are in binary form, plain text messages are never used by the `webfuse protocol`. ## Endianness @@ -259,27 +263,27 @@ _Note that the following numbers are in `octal` notation._ | type | u8 | Type of the message | | payload | u8[] | Payload according to the message type | -The `id` is just a number without any meaning for the `webfuse2 provider`. It is set by the `webfuse2 service` of a request and is copied by the `webfuse2 provider` to the response. A `webfuse2 service` implementation might choose to keep track on pending requests using the `id`. +The `id` is just a number without any meaning for the `webfuse provider`. It is set by the `webfuse service` of a request and is copied by the `webfuse provider` to the response. A `webfuse service` implementation might choose to keep track on pending requests using the `id`. ### Erroneous Responses -Most responses contain a `result` encoding the status of the operation. While successful responses may contain additional data, erroneous responses must not be decoded by a `webfuse2 service` implementation beyond the `result` value. +Most responses contain a `result` encoding the status of the operation. While successful responses may contain additional data, erroneous responses must not be decoded by a `webfuse service` implementation beyond the `result` value. ### Unknown requests -There are two reserved message types in `webfuse2`: +There are two reserved message types: - **0x00:** Unknown request - **0x80:** Unknown response -A `webfuse2 service` may send a request of type `unknown request` for conformance testing reasons. +A `webfuse service` may send a request of type `unknown request` for conformance testing reasons. -Since each request requires a response, a `webfuse2 provider` must respond to any unknown requests with a message of `unknown response` type. This allows to add new request types in future. +Since each request requires a response, a `webfuse provider` must respond to any unknown requests with a message of `unknown response` type. This allows to add new request types in future. ### Accept additional data in requests -Both, a `webfuse2 provider` and a `webfuse service` must accept messages that contain more data than specified. This allows to add optional fields to existing requests and / or responses in future. +Both, a `webfuse provider` and a `webfuse service` must accept messages that contain more data than specified. This allows to add optional fields to existing requests and / or responses in future. -_Note there are no optional fields in the current revision of the `webfuse2 protocol`. +_Note there are no optional fields in the current revision of the `webfuse2 protocol` yet._ ### Message Types @@ -312,7 +316,7 @@ _Note that the following numbers are in `hexadecimal` notation._ ## Methods -Since `webfuse2` aims to communicate the `libfuse API` over a `websocket` connection, `webfuse2` methods are tightly connected to [fuse operations](https://libfuse.github.io/doxygen/structfuse__operations.html) which itself have a tight connection to `posix filesystem operations`. Therefore, additional information about most `webfuse2` operations can be found in the [fuse operations documentation](https://libfuse.github.io/doxygen/structfuse__operations.html) and / or the [man pages](https://man7.org/index.html). +Since `webfuse` aims to communicate the `libfuse API` over a `websocket` connection, `webfuse` methods are tightly connected to [fuse operations](https://libfuse.github.io/doxygen/structfuse__operations.html) which itself have a tight connection to `posix filesystem operations`. Therefore, additional information about most `webfuse` operations can be found in the [fuse operations documentation](https://libfuse.github.io/doxygen/structfuse__operations.html) and / or the [man pages](https://man7.org/index.html). ### access From e9826fd0ef5a1083b1dde2beab43a013389db25c Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 7 Jan 2023 18:55:57 +0100 Subject: [PATCH 67/91] fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b7dde4..bf40528 100644 --- a/README.md +++ b/README.md @@ -69,4 +69,4 @@ But `webfuse2` marks also some breaking changes: - `webfuse` does not provide an API nor a library to program against _(if you are interested in an API or a library for webfuse2 feel free to create an issue)_ -When you are interested in the original `webfuse` implementation take a look at it [branch](https://github.com/falk-werner/webfuse/tree/master). +When you are interested in the original `webfuse` implementation take a look at this [branch](https://github.com/falk-werner/webfuse/tree/master). From 412c1f9a51e194d955d78a831116d6aec9ec3f45 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 14 Jan 2023 19:48:41 +0100 Subject: [PATCH 68/91] read webfuse specific command line arguments --- CMakeLists.txt | 2 + src/webfuse/fuse.cpp | 13 ++++ src/webfuse/fuse.hpp | 1 + src/webfuse/util/commandline_args.cpp | 56 +++++++++++++++++ src/webfuse/util/commandline_args.hpp | 29 +++++++++ src/webfuse/util/commandline_reader.cpp | 30 +++++++++ src/webfuse/util/commandline_reader.hpp | 22 +++++++ src/webfuse/webfuse.cpp | 36 +++++++++-- src/webfuse/ws/config.cpp | 84 ++++++++++++++++++++++++- src/webfuse/ws/config.hpp | 20 +++++- 10 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 src/webfuse/util/commandline_args.cpp create mode 100644 src/webfuse/util/commandline_args.hpp create mode 100644 src/webfuse/util/commandline_reader.cpp create mode 100644 src/webfuse/util/commandline_reader.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 966db70..c86e64f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(webfuse_static STATIC src/webfuse/fuse.cpp src/webfuse/request_type.cpp src/webfuse/response_type.cpp + src/webfuse/util/commandline_args.cpp + src/webfuse/util/commandline_reader.cpp src/webfuse/filesystem.cpp src/webfuse/filesystem/status.cpp src/webfuse/filesystem/accessmode.cpp diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp index 02b9f47..f2432b0 100644 --- a/src/webfuse/fuse.cpp +++ b/src/webfuse/fuse.cpp @@ -288,4 +288,17 @@ int fuse::run(int argc, char * argv[]) return fuse_main(argc, argv, &operations, context); } +void fuse::print_usage() +{ + struct fuse_operations operations; + memset(reinterpret_cast(&operations), 0, sizeof(operations)); + + int const argc = 2; + char progname[] = "webfuse"; + char show_help[] = "--help"; + char * argv[] = { progname, show_help, nullptr}; + fuse_main(argc, argv, &operations, nullptr); +} + + } \ No newline at end of file diff --git a/src/webfuse/fuse.hpp b/src/webfuse/fuse.hpp index aa5bbbb..b0c1fc5 100644 --- a/src/webfuse/fuse.hpp +++ b/src/webfuse/fuse.hpp @@ -16,6 +16,7 @@ public: fuse (fuse && other); fuse& operator=(fuse && other); int run(int argc, char * argv[]); + static void print_usage(); private: class detail; detail * d; diff --git a/src/webfuse/util/commandline_args.cpp b/src/webfuse/util/commandline_args.cpp new file mode 100644 index 0000000..b97266d --- /dev/null +++ b/src/webfuse/util/commandline_args.cpp @@ -0,0 +1,56 @@ +#include "webfuse/util/commandline_args.hpp" +#include +#include + +namespace webfuse +{ + +commandline_args::commandline_args(char const * prog_name, int capacity) +: capacity_(capacity) +, argc(0) +{ + if (capacity < 1) + { + throw std::runtime_error("too few commandline args"); + } + + argv = new char*[capacity_ + 1]; + argv[0] = nullptr; + push(prog_name); +} + +commandline_args::~commandline_args() +{ + for(int i = 0; i < argc; i++) + { + free(argv[i]); + } + delete[] argv; +} + +void commandline_args::push(char const * arg) +{ + if (argc < capacity_) + { + argv[argc] = strdup(arg); + argv[argc + 1] = nullptr; + argc++; + } + else + { + throw std::runtime_error("capacity exceeded"); + } +} + +int commandline_args::get_argc() const +{ + return argc; +} + +char ** commandline_args::get_argv() +{ + return argv; +} + + +} \ No newline at end of file diff --git a/src/webfuse/util/commandline_args.hpp b/src/webfuse/util/commandline_args.hpp new file mode 100644 index 0000000..9c9b631 --- /dev/null +++ b/src/webfuse/util/commandline_args.hpp @@ -0,0 +1,29 @@ +#ifndef WEBFUSE_COMMANDLINE_ARGS_HPP +#define WEBFUSE_COMMANDLINE_ARGS_HPP + +namespace webfuse +{ + +class commandline_args +{ + commandline_args (commandline_args const &) = delete; + commandline_args& operator=(commandline_args const &) = delete; + commandline_args (commandline_args &&) = delete; + commandline_args& operator=(commandline_args &&) = delete; +public: + commandline_args(char const * prog_name, int capacity); + ~commandline_args(); + + void push(char const * arg); + int get_argc() const; + char ** get_argv(); + +private: + int capacity_; + int argc; + char ** argv; +}; + +} + +#endif diff --git a/src/webfuse/util/commandline_reader.cpp b/src/webfuse/util/commandline_reader.cpp new file mode 100644 index 0000000..e4b07a9 --- /dev/null +++ b/src/webfuse/util/commandline_reader.cpp @@ -0,0 +1,30 @@ +#include "webfuse/util/commandline_reader.hpp" +namespace webfuse +{ + +commandline_reader::commandline_reader(int argc, char * argv[]) +: current_(0) +, argc_(argc) +, argv_(argv) +{ + +} + +bool commandline_reader::next() +{ + if (current_ < argc_) + { + current_++; + } + + bool const has_next = (current_ < argc_); + return has_next; +} + +char const * commandline_reader::current() const +{ + return argv_[current_]; +} + + +} \ No newline at end of file diff --git a/src/webfuse/util/commandline_reader.hpp b/src/webfuse/util/commandline_reader.hpp new file mode 100644 index 0000000..252f869 --- /dev/null +++ b/src/webfuse/util/commandline_reader.hpp @@ -0,0 +1,22 @@ +#ifndef WEBFUSE_COMMANDLINE_READER_HPP +#define WEBFUSE_COMMANDLINE_READER_HPP + +namespace webfuse +{ + +class commandline_reader +{ +public: + commandline_reader(int argc, char * argv[]); + ~commandline_reader() = default; + bool next(); + char const * current() const; +private: + int current_; + int argc_; + char ** argv_; +}; + +} + +#endif diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index 3977692..a172d1e 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -3,17 +3,43 @@ #include "webfuse/filesystem.hpp" #include "webfuse/ws/server.hpp" +#include + namespace webfuse { int app::run(int argc, char * argv[]) // NOLINT(readability-convert-member-functions-to-static) { - ws_config config; - ws_server server(config); - filesystem filesystem(server); - fuse fuse_fs(filesystem); + ws_config config(argc, argv); + + switch (config.cmd) + { + case command::run: + { + ws_server server(config); + filesystem filesystem(server); + fuse fuse_fs(filesystem); + + config.exit_code = fuse_fs.run(config.args.get_argc(), config.args.get_argv()); + } + break; + case command::show_help: + // fall-through + default: + { + fuse::print_usage(); + std::cout << R"( +WEBFUSE options: + --wf-port PORT port number of websocket server (default: 8081) + --wf-vhost VHOST name of the virtual host (default: localhost) + --wf-cert PATH path of the server's public certificate (optional) + --wf-key PATH path of the server's private key (optional) +)"; + } + break; + } - return fuse_fs.run(argc, argv); + return config.exit_code; } } \ No newline at end of file diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp index f7a68e1..61bf44a 100644 --- a/src/webfuse/ws/config.cpp +++ b/src/webfuse/ws/config.cpp @@ -1,19 +1,99 @@ #include "webfuse/ws/config.hpp" +#include "webfuse/util/commandline_reader.hpp" + +#include namespace { constexpr int const default_port = 8081; +constexpr char const default_vhost_name[] = "localhost"; + +void verify(webfuse::ws_config & config) +{ + if (config.cmd == webfuse::command::run) + { + if ((config.use_tls) && ((config.key_path.empty()) || (config.cert_path.empty()))) + { + std::cerr << "error: use of TLS requires both, key ander certificate path" << std::endl; + config.cmd = webfuse::command::show_help; + config.exit_code = EXIT_FAILURE; + } + } +} + +bool get_arg(webfuse::ws_config & config, webfuse::commandline_reader& reader, std::string & value, std::string const error_message) +{ + const bool has_next = reader.next(); + if (has_next) + { + value = reader.current(); + } + else + { + std::cerr << "error: " << error_message << std::endl; + config.cmd = webfuse::command::show_help; + config.exit_code = EXIT_FAILURE; + } + + return has_next; +} } namespace webfuse { -ws_config::ws_config() -: port(default_port) +ws_config::ws_config(int argc, char * argv[]) +: exit_code(EXIT_SUCCESS) +, args(argv[0], argc) +, cmd(command::run) +, port(default_port) +, vhost_name(default_vhost_name) +, use_tls(false) { + commandline_reader reader(argc, argv); + + while ((exit_code == EXIT_SUCCESS) && (reader.next())) + { + std::string const arg = reader.current(); + if ((arg == "-h") || (arg == "--help")) + { + cmd = command::show_help; + } + else if (arg == "--wf-port") + { + std::string value; + if (get_arg(*this, reader, value, "missing PORT")) + { + port = static_cast(std::stoi(value)); + } + } + else if (arg == "--wf-vhost") + { + get_arg(*this, reader, vhost_name, "missing VHOST"); + } + else if (arg == "--wf-key") + { + if (get_arg(*this, reader, key_path, "missing KEY_PATH")) + { + use_tls = true; + } + } + else if (arg == "--wf-cert") + { + if (get_arg(*this, reader, cert_path, "missing CERT_PATH")) + { + use_tls = true; + } + } + else + { + args.push(arg.c_str()); + } + } + verify(*this); } diff --git a/src/webfuse/ws/config.hpp b/src/webfuse/ws/config.hpp index 97c20e7..6f09ffb 100644 --- a/src/webfuse/ws/config.hpp +++ b/src/webfuse/ws/config.hpp @@ -1,17 +1,35 @@ #ifndef WEBFUSE_WS_CONFIG_HPP #define WEBFUSE_WS_CONFIG_HPP +#include + #include +#include namespace webfuse { +enum class command +{ + run, + show_help +}; + class ws_config { public: - ws_config(); + ws_config(int argc, char * argv[]); + + int exit_code; + commandline_args args; + command cmd; uint16_t port; + std::string vhost_name; + + bool use_tls; + std::string cert_path; + std::string key_path; }; } From f12f46115492284993d907c687f5df9b08176510 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 14 Jan 2023 23:58:22 +0100 Subject: [PATCH 69/91] use tls configuration in server and provider --- .gitignore | 3 +- CMakeLists.txt | 1 + script/create_cert.sh | 7 ++ src/provider_main.cpp | 12 +++- src/webfuse/provider.cpp | 8 +-- src/webfuse/provider.hpp | 2 +- src/webfuse/ws/client.cpp | 34 ++++++--- src/webfuse/ws/client.hpp | 2 +- src/webfuse/ws/server.cpp | 14 +++- src/webfuse/ws/url.cpp | 70 +++++++++++++++++++ src/webfuse/ws/url.hpp | 24 +++++++ test-src/integration/webfuse/test/fixture.cpp | 2 +- 12 files changed, 156 insertions(+), 23 deletions(-) create mode 100755 script/create_cert.sh create mode 100644 src/webfuse/ws/url.cpp create mode 100644 src/webfuse/ws/url.hpp diff --git a/.gitignore b/.gitignore index 8745738..d431297 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /build/ -/.vscode/ \ No newline at end of file +/.vscode/ +*.pem \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c86e64f..28c9db1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(webfuse_static STATIC src/webfuse/ws/client.cpp src/webfuse/ws/messagewriter.cpp src/webfuse/ws/messagereader.cpp + src/webfuse/ws/url.cpp ) target_include_directories(webfuse_static PUBLIC src) diff --git a/script/create_cert.sh b/script/create_cert.sh new file mode 100755 index 0000000..26115bb --- /dev/null +++ b/script/create_cert.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +openssl req -x509 -newkey rsa:4096 \ + -keyout server-key.pem \ + -out server-cert.pem \ + -days 365 -nodes -batch \ + -subj /CN=localhost diff --git a/src/provider_main.cpp b/src/provider_main.cpp index fa0d22f..c992ae2 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -33,6 +33,7 @@ public: { {"path" , required_argument, nullptr, 'p'}, {"url" , required_argument, nullptr, 'u'}, + {"ca-path", required_argument, nullptr, 'a'}, {"version", no_argument , nullptr, 'v'}, {"help" , no_argument , nullptr, 'h'}, {nullptr , 0 , nullptr, 0 } @@ -44,7 +45,7 @@ public: while (!finished) { int option_index = 0; - const int c = getopt_long(argc, argv, "p:u:vh", long_options, &option_index); + const int c = getopt_long(argc, argv, "p:u:a:vh", long_options, &option_index); switch (c) { case -1: @@ -56,6 +57,9 @@ public: case 'u': url = optarg; break; + case 'a': + ca_path = optarg; + break; case 'h': cmd = command::show_help; break; @@ -81,6 +85,7 @@ public: std::string base_path; std::string url; + std::string ca_path; command cmd; int exit_code; }; @@ -91,11 +96,12 @@ void print_usage() expose a local directory via webfuse2 Usage: - webfuse-provider -u [-p ] + webfuse-provider -u [-p ] [-a ] Options: --url, -u set url of webfuse2 service --path, -p set path of directory to expose (default: .) + --ca-path, -a set path of ca file (default: not set) --version, -v print version and quit --help, -h print this message and quit @@ -439,7 +445,7 @@ int main(int argc, char* argv[]) signal(SIGTERM, &on_signal); filesystem fs(ctx.base_path); - webfuse::provider provider(fs); + webfuse::provider provider(fs, ctx.ca_path); provider.set_connection_listener([](bool connected) { if (!connected) { diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 05cad76..9ca6fee 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -13,9 +13,9 @@ namespace webfuse class provider::detail { public: - detail(filesystem_i & fs) + detail(filesystem_i & fs, std::string const & ca_path) : fs_(fs) - , client([this](auto& reader) { return this->on_message(reader); }) + , client(ca_path, [this](auto& reader) { return this->on_message(reader); }) { } @@ -375,8 +375,8 @@ private: ws_client client; }; -provider::provider(filesystem_i & fs) -: d(new detail(fs)) +provider::provider(filesystem_i & fs, std::string const & ca_path) +: d(new detail(fs, ca_path)) { } diff --git a/src/webfuse/provider.hpp b/src/webfuse/provider.hpp index 7c10a8a..4748d87 100644 --- a/src/webfuse/provider.hpp +++ b/src/webfuse/provider.hpp @@ -13,7 +13,7 @@ class provider provider(provider const &) = delete; provider& operator=(provider const &) = delete; public: - provider(filesystem_i & fs); + provider(filesystem_i & fs, std::string const & ca_path); ~provider(); provider(provider && other); provider& operator=(provider && other); diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp index e164362..87dae9d 100644 --- a/src/webfuse/ws/client.cpp +++ b/src/webfuse/ws/client.cpp @@ -1,4 +1,6 @@ #include "webfuse/ws/client.hpp" +#include "webfuse/ws/url.hpp" + #include #include #include @@ -106,8 +108,10 @@ class ws_client::detail detail(detail &&) = delete; detail& operator=(detail &&) = delete; public: - detail(ws_client_handler handler) + detail(std::string const & ca_path, ws_client_handler handler) { + lws_set_log_level(0, nullptr); + memset(reinterpret_cast(protocols), 0, sizeof(lws_protocols) * 2); protocols[0].callback = &webfuse_client_callback; protocols[0].name = "webfuse2-client"; @@ -119,12 +123,22 @@ public: info.protocols = protocols; info.uid = -1; info.gid = -1; + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; data.handler = handler; data.connection_listener = [](bool){ }; data.connection = nullptr; context = lws_create_context(&info); + + struct lws_vhost * vhost = lws_create_vhost(context, &info); + info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + if (!ca_path.empty()) + { + info.client_ssl_ca_filepath = ca_path.c_str(); + } + + lws_init_vhost_client_ssl(&info, vhost); } ~detail() @@ -134,15 +148,17 @@ public: void connect(std::string const & url) { + ws_url parsed_url(url); + lws_client_connect_info info; memset(reinterpret_cast(&info), 0, sizeof(lws_client_connect_info)); info.context = context; - info.port = 8081; //NOLINT(readability-magic-numbers) - info.address = "localhost"; - info.host = "localhost"; - info.path = "/"; - info.origin = "localhost"; - info.ssl_connection = 0; + info.port = parsed_url.port; + info.address = parsed_url.hostname.c_str(); + info.host = info.address; + info.path = parsed_url.path.c_str(); + info.origin = info.address; + info.ssl_connection = (parsed_url.use_tls) ? LCCSCF_USE_SSL : 0; info.protocol = "webfuse2"; info.local_protocol_name = "webfuse2-client"; info.pwsi = &data.connection; @@ -172,8 +188,8 @@ private: user_data data; }; -ws_client::ws_client(ws_client_handler handler) -: d(new detail(handler)) +ws_client::ws_client(std::string const & ca_path, ws_client_handler handler) +: d(new detail(ca_path, handler)) { } diff --git a/src/webfuse/ws/client.hpp b/src/webfuse/ws/client.hpp index 17fb475..0fa3161 100644 --- a/src/webfuse/ws/client.hpp +++ b/src/webfuse/ws/client.hpp @@ -16,7 +16,7 @@ class ws_client ws_client(ws_client const &) = delete; ws_client& operator=(ws_client const &) = delete; public: - ws_client(ws_client_handler handler); + ws_client(std::string const & ca_path, ws_client_handler handler); ~ws_client(); ws_client(ws_client && other); ws_client& operator=(ws_client && other); diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 651a62c..199e7ab 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -164,6 +164,8 @@ public: detail(ws_config const & config) : shutdown_requested(false) { + lws_set_log_level(0, nullptr); + memset(reinterpret_cast(protocols), 0, sizeof(protocols)); protocols[0].name = "webfuse2"; protocols[0].callback = &ws_server_callback; @@ -173,15 +175,21 @@ public: memset(reinterpret_cast(&info), 0, sizeof(info)); info.port = config.port; info.protocols = protocols; - info.vhost_name = "localhost"; + info.vhost_name = config.vhost_name.c_str(); info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE | LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + if (config.use_tls) + { + info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.ssl_cert_filepath = config.cert_path.c_str(); + info.ssl_private_key_filepath = config.key_path.c_str(); + } + context = lws_create_context(&info); lws_create_vhost(context, &info); // lws_vhost * const vhost = lws_create_vhost(context, &info); - // port = lws_get_vhost_port(vhost); - + // int port = lws_get_vhost_port(vhost); thread = std::thread([this]() { while (!shutdown_requested) diff --git a/src/webfuse/ws/url.cpp b/src/webfuse/ws/url.cpp new file mode 100644 index 0000000..f024265 --- /dev/null +++ b/src/webfuse/ws/url.cpp @@ -0,0 +1,70 @@ +#include "webfuse/ws/url.hpp" + +#include +#include + +namespace +{ + +bool starts_with(std::string const & value, std::string const & prefix) +{ + return (0 == value.find(prefix)); +} + +std::string parse_protocol(std::string const &url, bool &use_tls) +{ + if (starts_with(url, "ws://")) + { + use_tls = false; + return url.substr(strlen("ws://")); + } + + if (starts_with(url, "wss://")) + { + use_tls = true; + return url.substr(strlen("wss://")); + } + + throw std::runtime_error("unknown protocol"); +} + + +} + +namespace webfuse +{ + +constexpr uint16_t const ws_port = 80; +constexpr uint16_t const wss_port = 443; + +ws_url::ws_url(std::string const & url) +{ + auto remainder = parse_protocol(url, use_tls); + + auto const path_start = remainder.find('/'); + if (path_start != std::string::npos) + { + path = remainder.substr(path_start); + remainder = remainder.substr(0, path_start); + } + else + { + path = "/"; + } + + auto const port_start = remainder.find(':'); + if (port_start != std::string::npos) + { + auto const port_str = remainder.substr(port_start + 1); + port = static_cast(std::stoi(port_str)); + hostname = remainder.substr(0, port_start); + } + else + { + port = (use_tls) ? wss_port : ws_port; + hostname = remainder; + } +} + + +} \ No newline at end of file diff --git a/src/webfuse/ws/url.hpp b/src/webfuse/ws/url.hpp new file mode 100644 index 0000000..d7b07c7 --- /dev/null +++ b/src/webfuse/ws/url.hpp @@ -0,0 +1,24 @@ +#ifndef WEBFUSE_URL_HPP +#define WEBFUSE_URL_HPP + +#include +#include + +namespace webfuse +{ + +class ws_url +{ +public: + ws_url(std::string const & url); + ~ws_url() = default; + + bool use_tls; + std::string hostname; + uint16_t port; + std::string path; +}; + +} + +#endif diff --git a/test-src/integration/webfuse/test/fixture.cpp b/test-src/integration/webfuse/test/fixture.cpp index 58ad574..1e286fc 100644 --- a/test-src/integration/webfuse/test/fixture.cpp +++ b/test-src/integration/webfuse/test/fixture.cpp @@ -10,7 +10,7 @@ namespace webfuse fixture::fixture(filesystem_i & fs) : shutdown_requested(false) , provider_running(false) -, fs_provider(fs) +, fs_provider(fs, "") , app(working_dir.name()) { fs_provider.set_connection_listener([this](bool is_connected) { From 8c290b8c02091aebf239190f3d28f8b8d42efd9e Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 15 Jan 2023 14:55:31 +0100 Subject: [PATCH 70/91] webfuse-server: add header-based authentication --- CMakeLists.txt | 1 + script/authenticator.sh | 13 +++ script/provider.py | 2 +- src/webfuse/util/authenticator.cpp | 55 +++++++++++++ src/webfuse/util/authenticator.hpp | 23 ++++++ src/webfuse/webfuse.cpp | 10 ++- src/webfuse/ws/config.cpp | 14 ++++ src/webfuse/ws/config.hpp | 3 + src/webfuse/ws/server.cpp | 81 +++++++++++++++++++ test-src/integration/webfuse/test/process.cpp | 2 +- 10 files changed, 198 insertions(+), 6 deletions(-) create mode 100755 script/authenticator.sh create mode 100644 src/webfuse/util/authenticator.cpp create mode 100644 src/webfuse/util/authenticator.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 28c9db1..5b88404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(webfuse_static STATIC src/webfuse/response_type.cpp src/webfuse/util/commandline_args.cpp src/webfuse/util/commandline_reader.cpp + src/webfuse/util/authenticator.cpp src/webfuse/filesystem.cpp src/webfuse/filesystem/status.cpp src/webfuse/filesystem/accessmode.cpp diff --git a/script/authenticator.sh b/script/authenticator.sh new file mode 100755 index 0000000..5a4bf93 --- /dev/null +++ b/script/authenticator.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +AUTH_TOKEN="$1" + +if [[ "$AUTH_TOKEN" == "user:bob;token=foo" ]] +then + echo "$(date): webfuse: auth granted: $AUTH_TOKEN" >> /tmp/webfuse_auth.log +else + echo "$(date): webfuse: auth denied: $AUTH_TOKEN" >> /tmp/webfuse_auth.log + exit 1 +fi + + diff --git a/script/provider.py b/script/provider.py index 9a87edc..c5cfaba 100755 --- a/script/provider.py +++ b/script/provider.py @@ -272,7 +272,7 @@ class FilesystemProvider: } async def run(self): - async with websockets.connect(self.url) as connection: + async with websockets.connect(self.url, extra_headers=[("X-Auth-Token", "user:bob;token=foo")]) as connection: while True: request = await connection.recv() reader = MessageReader(request) diff --git a/src/webfuse/util/authenticator.cpp b/src/webfuse/util/authenticator.cpp new file mode 100644 index 0000000..013ec7f --- /dev/null +++ b/src/webfuse/util/authenticator.cpp @@ -0,0 +1,55 @@ +#include "webfuse/util/authenticator.hpp" + +#include +#include +#include + +namespace webfuse +{ + +authenticator::authenticator(std::string const & app) +: app_(app) +{ + +} + +bool authenticator::authenticate(std::string const & token) +{ + bool result = false; + + pid_t const pid = fork(); + + if (pid == 0) + { + // child + + // prepare file descriptors + closefrom(0); + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + dup2(STDOUT_FILENO, STDERR_FILENO); + + execl(app_.c_str(), app_.c_str(), token.c_str(), nullptr); + exit(EXIT_FAILURE); + } + else if (pid > 0) + { + // parent + int exit_status = EXIT_FAILURE; + + int status = 0; + int const rc = waitpid(pid, &status, 0); + if (rc == pid) + { + exit_status = WEXITSTATUS(status); + } + + result = (exit_status == EXIT_SUCCESS); + } + + return result; +} + + + +} \ No newline at end of file diff --git a/src/webfuse/util/authenticator.hpp b/src/webfuse/util/authenticator.hpp new file mode 100644 index 0000000..348deb9 --- /dev/null +++ b/src/webfuse/util/authenticator.hpp @@ -0,0 +1,23 @@ +#ifndef WEBFUSE_AUTHENTICATOR_HPP +#define WEBFUSE_AUTHENTICATOR_HPP + +#include + +namespace webfuse +{ + +class authenticator +{ +public: + explicit authenticator(std::string const & app); + ~authenticator() = default; + + bool authenticate(std::string const & token); + +private: + std::string app_; +}; + +} + +#endif diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index a172d1e..cdedd04 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -30,10 +30,12 @@ int app::run(int argc, char * argv[]) // NOLINT(readability-convert-member-funct fuse::print_usage(); std::cout << R"( WEBFUSE options: - --wf-port PORT port number of websocket server (default: 8081) - --wf-vhost VHOST name of the virtual host (default: localhost) - --wf-cert PATH path of the server's public certificate (optional) - --wf-key PATH path of the server's private key (optional) + --wf-port PORT port number of websocket server (default: 8081) + --wf-vhost VHOST name of the virtual host (default: localhost) + --wf-cert PATH path of the server's public certificate (optional) + --wf-key PATH path of the server's private key (optional) + --wf-authenticator PATH path of authenticatior app (optional) + --wf-auth-header NAME name of the authentication header (optional) )"; } break; diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp index 61bf44a..c3e3925 100644 --- a/src/webfuse/ws/config.cpp +++ b/src/webfuse/ws/config.cpp @@ -1,6 +1,8 @@ #include "webfuse/ws/config.hpp" #include "webfuse/util/commandline_reader.hpp" +#include +#include #include namespace @@ -87,6 +89,18 @@ ws_config::ws_config(int argc, char * argv[]) use_tls = true; } } + else if (arg == "--wf-authenticator") + { + get_arg(*this, reader, authenticator, "missing AUTHENTICATOR"); + } + else if (arg == "--wf-auth-header") + { + if (get_arg(*this, reader, auth_header, "missing AUTH_HEADER")) + { + std::transform(auth_header.begin(), auth_header.end(), auth_header.begin(), + [](auto c) {return std::tolower(c); }); + } + } else { args.push(arg.c_str()); diff --git a/src/webfuse/ws/config.hpp b/src/webfuse/ws/config.hpp index 6f09ffb..f884a5c 100644 --- a/src/webfuse/ws/config.hpp +++ b/src/webfuse/ws/config.hpp @@ -30,6 +30,9 @@ public: bool use_tls; std::string cert_path; std::string key_path; + + std::string authenticator; + std::string auth_header; }; } diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 199e7ab..ee6ab53 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -1,4 +1,5 @@ #include "webfuse/ws/server.hpp" +#include "webfuse/util/authenticator.hpp" #include @@ -32,6 +33,9 @@ struct user_data uint32_t id = 0; std::queue requests; std::unordered_map> pending_responses; + + std::string authenticator; + std::string auth_header; }; @@ -72,6 +76,75 @@ void do_receive(void * in, int len, lws* wsi, user_data * data) } } +std::string get_auth_token_of_known_header(lws * wsi, lws_token_indexes header) +{ + std::string token; + int const length = lws_hdr_total_length(wsi, header); + if (length > 0) + { + std::vector data(length + 1); + int const actual_length = lws_hdr_copy(wsi, data.data(), length + 1, header); + if (actual_length > 0) + { + token = data.data(); + } + } + + return token; +} + +std::string get_auth_token_from_custom_header(lws * wsi, std::string const & auth_header) +{ + std::string token; + int const length = lws_hdr_custom_length(wsi, auth_header.c_str(), auth_header.size()); + if (length > 0) + { + std::vector data(length + 1); + int const actual_length = lws_hdr_custom_copy(wsi, data.data(), length + 1, + auth_header.c_str(), auth_header.size()); + if (actual_length > 0) + { + token = data.data(); + } + } + + return token; +} + +std::string get_auth_token(lws * wsi, std::string const & auth_header) +{ + if (auth_header == "authorization") + { + return get_auth_token_of_known_header(wsi, WSI_TOKEN_HTTP_AUTHORIZATION); + } + + if (auth_header == "x-auth-token") + { + return get_auth_token_of_known_header(wsi, WSI_TOKEN_X_AUTH_TOKEN); + } + + return get_auth_token_from_custom_header(wsi, auth_header); +} + +int do_authenticate(lws * wsi, std::string const & authenticator_app, std::string const & auth_header) +{ + int result = 0; + if ((!authenticator_app.empty()) && (!auth_header.empty())) + { + std::string token = get_auth_token(wsi, auth_header); + if (!token.empty()) + { + webfuse::authenticator authenticator(authenticator_app); + result = authenticator.authenticate(token) ? 0 : -1; + } + else + { + result = -1; + } + } + + return result; +} } @@ -90,6 +163,11 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, int result = 0; switch(reason) { + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + { + result = do_authenticate(wsi, data->authenticator, data->auth_header); + } + break; case LWS_CALLBACK_ESTABLISHED: if (nullptr == data->connection) { @@ -164,6 +242,9 @@ public: detail(ws_config const & config) : shutdown_requested(false) { + data.authenticator = config.authenticator; + data.auth_header = config.auth_header; + lws_set_log_level(0, nullptr); memset(reinterpret_cast(protocols), 0, sizeof(protocols)); diff --git a/test-src/integration/webfuse/test/process.cpp b/test-src/integration/webfuse/test/process.cpp index bd29c5c..b40b61e 100644 --- a/test-src/integration/webfuse/test/process.cpp +++ b/test-src/integration/webfuse/test/process.cpp @@ -80,7 +80,7 @@ int process::wait() { int status = 0; int rc = waitpid(pid, &status, 0); - if (rc == 0) + if (rc == pid) { exit_code = WEXITSTATUS(status); pid = 0; From 75905d64804ac3d01b7120cf7050bad2e1094b11 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 15 Jan 2023 21:00:10 +0100 Subject: [PATCH 71/91] enhance documentation --- README.md | 3 ++ doc/README.md | 3 ++ doc/authentication.md | 30 +++++++++++++++++ doc/webfuse.md | 74 +++++++++++++++++++++++++++++++++++++++++ doc/webfuse_provider.md | 30 +++++++++++++++++ src/provider_main.cpp | 2 +- 6 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 doc/authentication.md create mode 100644 doc/webfuse.md create mode 100644 doc/webfuse_provider.md diff --git a/README.md b/README.md index bf40528..c4d14e3 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,10 @@ Unlike webfuse, davfs2 mounts a remote filesystem locally, that is provided by a ## Further Documentation - [Build instructions](doc/build.md) +- [Webfuse command line options](webfuse.md) +- [Webfuse provider command line options](webfuse_provider.md) - [Webfuse Protocol](doc/protocol.md) +- [Authentication](authentication.md) ## webfuse legacy diff --git a/doc/README.md b/doc/README.md index f193966..4bd4579 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,4 +1,7 @@ # webfuse developer documentation - [Build instructions](build.md) +- [Webfuse command line options](webfuse.md) +- [Webfuse provider command line options](webfuse_provider.md) - [Webfuse2 protocol](protocol.md) +- [Authentication](authentication.md) diff --git a/doc/authentication.md b/doc/authentication.md new file mode 100644 index 0000000..2155e79 --- /dev/null +++ b/doc/authentication.md @@ -0,0 +1,30 @@ +# Authentication + +Webfuse supports token-based authentication using HTTP headers. To activate authentication, two command line option needs to be given: + +- `--wf-authenticator PATH` + allows to specify an executable used for authentication +- `--wf-auth-header HEADER` + allows to specify the HTTP header used for authentication + +## Authenticator + +An authenticator is an executable or script used for token-based +authentication. During HTTP handshake, webfuse will scan for the +configured HTTP header and invoke the authenticator. + + authenticator TOKEN + +The provided `token` contains the contents of the HTTP header. + +## Header restrictions + +Note that not any HTTP header can be specified using `--wf-auth-header` +option. The following headers are supported: + +- `X-Auth-Token` +- `Authorization` + +In addition to that, any non-standard header can be specified. + +Due to implementation limitation, most standard headers can not be used by now. Please let us know, when you intend to use a header that is not supported yet. Please create an issue in that case. diff --git a/doc/webfuse.md b/doc/webfuse.md new file mode 100644 index 0000000..1f2e23e --- /dev/null +++ b/doc/webfuse.md @@ -0,0 +1,74 @@ +# webfuse command line options + +In order to inject a remote filesystem, webfuse mounts a local +filesystem via fuse and exposes it's API via websockets. + +## Usage + + webfuse [options] <mountpoint> + +## Webfuse specific options + +| Option | Argument | Default | Description | +| ------------ | -------- | --------- | ----------- | +| --wf-port | port | 8081 | Specify the port of the websocket server | +| --wf-vhost | vhost | localhost | Specify the name of the websocket server's virtual host | +| --wf-cert | path | - | Optional. Specify the file path of the server's public certificate | +| --wf-key | path | - | Optional. Specify the file path of the server's private key | +| --wf- authenticator | path | - | Optional. Specify the file path of the authenticator executable | +| --wf-auth-header | name | - | Optional. Specify the name of the HTTP header used for authentication | + +## Fuse options + +| Option | Descripion | +| --------------------- | ---------- | +| -h, --help | print help | +| -V --version | print version | +| -d -o debug | enable debug output (implies -f) | +| -f | foreground operation | +| -s | disable multi-threaded operation | +| -o clone_fd | use separate fuse device fd for each thread | +| | (may improve performance) | +| -o max_idle_threads | the maximum number of idle worker threads | +| | allowed (default: 10) | +| -o kernel_cache | cache files in kernel | +| -o [no]auto_cache | enable caching based on modification times (off) | +| -o umask=M | set file permissions (octal) | +| -o uid=N | set file owner | +| -o gid=N | set file group | +| -o entry_timeout=T | cache timeout for names (1.0s) | +| -o negative_timeout=T | cache timeout for deleted names (0.0s) | +| -o attr_timeout=T | cache timeout for attributes (1.0s) | +| -o ac_attr_timeout=T | auto cache timeout for attributes (attr_timeout) | +| -o noforget | never forget cached inodes | +| -o remember=T | remember cached inodes for T seconds (0s) | +| -o modules=M1[:M2...] | names of modules to push onto filesystem stack | +| -o allow_other | allow access by all users | +| -o allow_root | allow access by root | +| -o auto_unmount | auto unmount on process termination | + +### Options for subdir module + +| Option | Descripion | +| --------------------- | ---------- | +| -o subdir=DIR | prepend this directory to all paths (mandatory) | +| -o [no]rellinks | transform absolute symlinks to relative | + +### Options for iconv module + +| Option | Descripion | +| --------------------- | ---------- | +| -o from_code=CHARSET | original encoding of file names (default: UTF-8) | +| -o to_code=CHARSET | new encoding of the file names (default: UTF-8) | + +## Examples + +- run webfuse in foreground on default port: + `webfuse -f /path/to/mointpoint` +- run webfuse in forground on port 8080: + `webfuse -f --wf-port 8080 /path/to/mountpoint` +- run webfuse using TLS: + `webfuse -f --wf-cert /path/to/cert --wf-key /path/to/key /path/to/mountpoint` +- run webfuse using authentication via `X-Auth-Token` header: + `webfuse -f --wf-authenticator /path/to/authenticator \` + ` --wf-auth-header X-Auth-Token /path/to/mountpoint` \ No newline at end of file diff --git a/doc/webfuse_provider.md b/doc/webfuse_provider.md new file mode 100644 index 0000000..3733a18 --- /dev/null +++ b/doc/webfuse_provider.md @@ -0,0 +1,30 @@ +# webfuse_provider command line options + +Inject a remote filesystem via webfuse. + +## Usage + + webfuse_provider -u [-p ] [-a ] + +## Options + +| Short Option | Long Option | Argument | Description | +| ------------ | ----------- | -------- | ----------- | +| -h | --help | - | print usage and exit | +| -v | --version | - | print version an exit | +| -p | --path | path | path of local filesystem to inject (default: .) | +| -u | --url | url | url of webfuse server | +| -a | --ca-path | path | path of ca file | + +## Examples + +- inject current directory: + `webfuse_provider -u ws://localhost/` +- inject a given directory: + `webfuse_provider -u ws://localhost/ -p /path/to/directory` +- inject current directory to port 8080: + `webfuse_provider -u ws://localhost:8080/` +- inject current directory via TLS: + `webfuse_provider -u wss://localhost/` +- inject current diectory via TLS using a specific ca: + `webfuse_provider -u wss://localhost/ -a /path/to/server-cert.pem` diff --git a/src/provider_main.cpp b/src/provider_main.cpp index c992ae2..6213ac7 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -96,7 +96,7 @@ void print_usage() expose a local directory via webfuse2 Usage: - webfuse-provider -u [-p ] [-a ] + webfuse_provider -u [-p ] [-a ] Options: --url, -u set url of webfuse2 service From bf1ddbcee617e86d40325cbcf6c55e2c28471206 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 15 Jan 2023 21:02:18 +0100 Subject: [PATCH 72/91] fix links --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c4d14e3..baeb335 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,10 @@ Unlike webfuse, davfs2 mounts a remote filesystem locally, that is provided by a ## Further Documentation - [Build instructions](doc/build.md) -- [Webfuse command line options](webfuse.md) -- [Webfuse provider command line options](webfuse_provider.md) +- [Webfuse command line options](doc/webfuse.md) +- [Webfuse provider command line options](doc/webfuse_provider.md) - [Webfuse Protocol](doc/protocol.md) -- [Authentication](authentication.md) +- [Authentication](doc/authentication.md) ## webfuse legacy From d7c84ad085b9ff8711a52d6fdeef7dd5623a7e53 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 22 Jan 2023 13:56:16 +0100 Subject: [PATCH 73/91] moved server's user_data into a server_handler class --- CMakeLists.txt | 1 + src/webfuse/ws/server.cpp | 226 ++-------------------------- src/webfuse/ws/server_handler.cpp | 235 ++++++++++++++++++++++++++++++ src/webfuse/ws/server_handler.hpp | 57 ++++++++ 4 files changed, 306 insertions(+), 213 deletions(-) create mode 100644 src/webfuse/ws/server_handler.cpp create mode 100644 src/webfuse/ws/server_handler.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b88404..d35e8c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ add_library(webfuse_static STATIC src/webfuse/filesystem/empty_filesystem.cpp src/webfuse/ws/config.cpp src/webfuse/ws/server.cpp + src/webfuse/ws/server_handler.cpp src/webfuse/ws/client.cpp src/webfuse/ws/messagewriter.cpp src/webfuse/ws/messagereader.cpp diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index ee6ab53..727d8df 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -1,5 +1,5 @@ #include "webfuse/ws/server.hpp" -#include "webfuse/util/authenticator.hpp" +#include "webfuse/ws/server_handler.hpp" #include @@ -24,128 +24,6 @@ namespace constexpr int64_t const timeout_secs = 10; -struct user_data -{ - struct lws * connection = nullptr; - std::string current_message; - - std::mutex mut; - uint32_t id = 0; - std::queue requests; - std::unordered_map> pending_responses; - - std::string authenticator; - std::string auth_header; -}; - - -void do_receive(void * in, int len, lws* wsi, user_data * data) -{ - auto * fragment = reinterpret_cast(in); - data->current_message.append(fragment, len); - if (0 != lws_is_final_fragment(wsi)) - { - try - { - webfuse::messagereader reader(data->current_message); - uint32_t id = reader.read_u32(); - reader.read_u8(); // read message type: ToDo: use it - - std::lock_guard lock(data->mut); - auto it = data->pending_responses.find(id); - if (it != data->pending_responses.end()) - { - it->second.set_value(std::move(reader)); - data->pending_responses.erase(it); - } - else - { - // ToDo: log request not found - std::cout << "warning: request not found: id=" << id << std::endl; - for(auto const & entry: data->pending_responses) - { - std::cout << "\t" << entry.first << std::endl; - } - } - } - catch(...) - { - // ToDo: log invalid message - std::cout << "warning: invalid message" << std::endl; - } - } -} - -std::string get_auth_token_of_known_header(lws * wsi, lws_token_indexes header) -{ - std::string token; - int const length = lws_hdr_total_length(wsi, header); - if (length > 0) - { - std::vector data(length + 1); - int const actual_length = lws_hdr_copy(wsi, data.data(), length + 1, header); - if (actual_length > 0) - { - token = data.data(); - } - } - - return token; -} - -std::string get_auth_token_from_custom_header(lws * wsi, std::string const & auth_header) -{ - std::string token; - int const length = lws_hdr_custom_length(wsi, auth_header.c_str(), auth_header.size()); - if (length > 0) - { - std::vector data(length + 1); - int const actual_length = lws_hdr_custom_copy(wsi, data.data(), length + 1, - auth_header.c_str(), auth_header.size()); - if (actual_length > 0) - { - token = data.data(); - } - } - - return token; -} - -std::string get_auth_token(lws * wsi, std::string const & auth_header) -{ - if (auth_header == "authorization") - { - return get_auth_token_of_known_header(wsi, WSI_TOKEN_HTTP_AUTHORIZATION); - } - - if (auth_header == "x-auth-token") - { - return get_auth_token_of_known_header(wsi, WSI_TOKEN_X_AUTH_TOKEN); - } - - return get_auth_token_from_custom_header(wsi, auth_header); -} - -int do_authenticate(lws * wsi, std::string const & authenticator_app, std::string const & auth_header) -{ - int result = 0; - if ((!authenticator_app.empty()) && (!auth_header.empty())) - { - std::string token = get_auth_token(wsi, auth_header); - if (!token.empty()) - { - webfuse::authenticator authenticator(authenticator_app); - result = authenticator.authenticate(token) ? 0 : -1; - } - else - { - result = -1; - } - } - - return result; -} - } extern "C" @@ -158,66 +36,25 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, if (nullptr == protocol) { return 0; } if (&ws_server_callback != protocol->callback) { return 0; } - auto * data = reinterpret_cast(protocol->user); + auto * handler = reinterpret_cast(protocol->user); int result = 0; switch(reason) { case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: - { - result = do_authenticate(wsi, data->authenticator, data->auth_header); - } + result = handler->filter_connection(wsi); break; case LWS_CALLBACK_ESTABLISHED: - if (nullptr == data->connection) - { - data->connection = wsi; - } - else - { - result = -1; - } + result = handler->on_established(wsi); break; case LWS_CALLBACK_CLOSED: - if (wsi == data->connection) - { - data->connection = nullptr; - } + handler->on_closed(wsi); break; case LWS_CALLBACK_RECEIVE: - do_receive(in, len, wsi, data); + handler->on_receive(wsi, in, len); break; case LWS_CALLBACK_SERVER_WRITEABLE: - { - webfuse::messagewriter writer(webfuse::request_type::unknown); - bool has_msg = false; - bool has_more = false; - - { - std::lock_guard lock(data->mut); - has_msg = !(data->requests.empty()); - if (has_msg) - { - has_msg = true; - writer = std::move(data->requests.front()); - data->requests.pop(); - - has_more = !(data->requests.empty()); - } - } - - if (has_msg) - { - size_t size; - unsigned char * raw_data = writer.get_data(size); - lws_write(data->connection, raw_data, size, LWS_WRITE_BINARY); - } - - if (has_more) - { - lws_callback_on_writable(data->connection); - } - } + handler->on_writable(); break; default: break; @@ -241,10 +78,8 @@ class ws_server::detail public: detail(ws_config const & config) : shutdown_requested(false) + , data(config.authenticator, config.auth_header) { - data.authenticator = config.authenticator; - data.auth_header = config.auth_header; - lws_set_log_level(0, nullptr); memset(reinterpret_cast(protocols), 0, sizeof(protocols)); @@ -275,22 +110,7 @@ public: thread = std::thread([this]() { while (!shutdown_requested) { - { - std::lock_guard lock(data.mut); - if (!data.requests.empty()) - { - if (nullptr != data.connection) - { - lws_callback_on_writable(data.connection); - } - else - { - data.requests = std::move(std::queue()); - data.pending_responses.clear(); - } - } - } - + data.poll(); lws_service(context, 0); } @@ -305,22 +125,12 @@ public: lws_context_destroy(context); } - uint32_t next_id() - { - data.id++; - if (0 == data.id) - { - data.id = 1; - } - return data.id; - } - std::thread thread; std::atomic shutdown_requested; lws_protocols protocols[2]; lws_context_creation_info info; lws_context * context; - user_data data; + server_handler data; }; ws_server::ws_server(ws_config const & config) @@ -354,25 +164,15 @@ ws_server& ws_server::operator=(ws_server && other) messagereader ws_server::perform(messagewriter writer) { - std::future f; - { - std::promise p; - f = p.get_future(); - - std::lock_guard lock(d->data.mut); - uint32_t id = d->next_id(); - writer.set_id(id); - d->data.requests.emplace(std::move(writer)); - d->data.pending_responses.emplace(id, std::move(p)); - } + auto result = d->data.perform(std::move(writer)); lws_cancel_service(d->context); - if(std::future_status::timeout == f.wait_for(std::chrono::seconds(timeout_secs))) + if(std::future_status::timeout == result.wait_for(std::chrono::seconds(timeout_secs))) { throw std::runtime_error("timeout"); } - return std::move(f.get()); + return std::move(result.get()); } diff --git a/src/webfuse/ws/server_handler.cpp b/src/webfuse/ws/server_handler.cpp new file mode 100644 index 0000000..c56216c --- /dev/null +++ b/src/webfuse/ws/server_handler.cpp @@ -0,0 +1,235 @@ +#include "webfuse/ws/server_handler.hpp" +#include "webfuse/util/authenticator.hpp" + +#include + +namespace +{ +std::string get_auth_token_of_known_header(lws * wsi, lws_token_indexes header) +{ + std::string token; + int const length = lws_hdr_total_length(wsi, header); + if (length > 0) + { + std::vector data(length + 1); + int const actual_length = lws_hdr_copy(wsi, data.data(), length + 1, header); + if (actual_length > 0) + { + token = data.data(); + } + } + + return token; +} + +std::string get_auth_token_from_custom_header(lws * wsi, std::string const & auth_header) +{ + std::string token; + int const length = lws_hdr_custom_length(wsi, auth_header.c_str(), auth_header.size()); + if (length > 0) + { + std::vector data(length + 1); + int const actual_length = lws_hdr_custom_copy(wsi, data.data(), length + 1, + auth_header.c_str(), auth_header.size()); + if (actual_length > 0) + { + token = data.data(); + } + } + + return token; +} + +} + +namespace webfuse +{ + +server_handler::server_handler(std::string const & auth_app, std::string const & auth_hdr) +: connection(nullptr) +, id(0) +, authenticator(auth_app) +, auth_header(auth_hdr) +{ + +} + + +int server_handler::filter_connection(lws * wsi) +{ + return authenticate_via_header(wsi); +} + +int server_handler::on_established(lws * wsi) +{ + if (nullptr == connection) + { + connection = wsi; + return 0; + } + + return -1; +} + + +void server_handler::on_receive(lws * wsi, void* in, int len) +{ + auto * fragment = reinterpret_cast(in); + current_message.append(fragment, len); + if (0 != lws_is_final_fragment(wsi)) + { + try + { + webfuse::messagereader reader(current_message); + uint32_t id = reader.read_u32(); + reader.read_u8(); // read message type: ToDo: use it + + std::lock_guard lock(mut); + auto it = pending_responses.find(id); + if (it != pending_responses.end()) + { + it->second.set_value(std::move(reader)); + pending_responses.erase(it); + } + else + { + // ToDo: log request not found + std::cout << "warning: request not found: id=" << id << std::endl; + for(auto const & entry: pending_responses) + { + std::cout << "\t" << entry.first << std::endl; + } + } + } + catch(...) + { + // ToDo: log invalid message + std::cout << "warning: invalid message" << std::endl; + } + } +} + +void server_handler::on_writable() +{ + webfuse::messagewriter writer(webfuse::request_type::unknown); + bool has_msg = false; + bool has_more = false; + + { + std::lock_guard lock(mut); + has_msg = !(requests.empty()); + if (has_msg) + { + has_msg = true; + writer = std::move(requests.front()); + requests.pop(); + + has_more = !(requests.empty()); + } + } + + if (has_msg) + { + size_t size; + unsigned char * raw_data = writer.get_data(size); + lws_write(connection, raw_data, size, LWS_WRITE_BINARY); + } + + if (has_more) + { + lws_callback_on_writable(connection); + } +} + +void server_handler::on_closed(lws * wsi) +{ + if (wsi == connection) + { + connection = nullptr; + } +} + +void server_handler::poll() +{ + std::lock_guard lock(mut); + if (!requests.empty()) + { + if (nullptr != connection) + { + lws_callback_on_writable(connection); + } + else + { + requests = std::move(std::queue()); + pending_responses.clear(); + } + } +} + + +std::future server_handler::perform(messagewriter writer) +{ + std::future result; + { + std::promise p; + result = p.get_future(); + + std::lock_guard lock(mut); + uint32_t id = next_id(); + writer.set_id(id); + requests.emplace(std::move(writer)); + pending_responses.emplace(id, std::move(p)); + } + + return result; + +} + + +int server_handler::authenticate_via_header(lws * wsi) +{ + int result = 0; + if ((!authenticator.empty()) && (!auth_header.empty())) + { + std::string token = get_auth_token(wsi); + if (!token.empty()) + { + webfuse::authenticator auth(authenticator); + result = auth.authenticate(token) ? 0 : -1; + } + else + { + result = -1; + } + } + + return result; +} + +std::string server_handler::get_auth_token(lws * wsi) const +{ + if (auth_header == "authorization") + { + return get_auth_token_of_known_header(wsi, WSI_TOKEN_HTTP_AUTHORIZATION); + } + + if (auth_header == "x-auth-token") + { + return get_auth_token_of_known_header(wsi, WSI_TOKEN_X_AUTH_TOKEN); + } + + return get_auth_token_from_custom_header(wsi, auth_header); +} + +uint32_t server_handler::next_id() +{ + id++; + if (0 == id) + { + id = 1; + } + return id; +} + + +} \ No newline at end of file diff --git a/src/webfuse/ws/server_handler.hpp b/src/webfuse/ws/server_handler.hpp new file mode 100644 index 0000000..bfc21d0 --- /dev/null +++ b/src/webfuse/ws/server_handler.hpp @@ -0,0 +1,57 @@ +#ifndef WEBFUSE_SERVER_HANDLER_HPP +#define WEBFUSE_SERVER_HANDLER_HPP + +#include "webfuse/ws/messagereader.hpp" +#include "webfuse/ws/messagewriter.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace webfuse +{ + +class server_handler +{ +public: + server_handler(std::string const & auth_app, std::string const & auth_hdr); + + int filter_connection(lws * wsi); + + int on_established(lws* wsi); + void on_receive(lws * wsi, void* in, int len); + void on_writable(); + void on_closed(lws * wsi); + + std::future perform(messagewriter writer); + void poll(); + +private: + int authenticate_via_header(lws * wsi); + std::string get_auth_token(lws * wsi) const; + uint32_t next_id(); + + struct lws * connection; + uint32_t id; + + std::string authenticator; + std::string auth_header; + + std::string current_message; + + std::mutex mut; + std::queue requests; + std::unordered_map> pending_responses; + + + +}; + + +} + +#endif From 9423d75021161e6994b9c1802b331327137dd63e Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 22 Jan 2023 14:38:10 +0100 Subject: [PATCH 74/91] delay authentication if header is not provided --- src/webfuse/ws/server_handler.cpp | 59 ++++++++++++++++++++++--------- src/webfuse/ws/server_handler.hpp | 2 ++ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/webfuse/ws/server_handler.cpp b/src/webfuse/ws/server_handler.cpp index c56216c..a6ae89c 100644 --- a/src/webfuse/ws/server_handler.cpp +++ b/src/webfuse/ws/server_handler.cpp @@ -1,8 +1,11 @@ #include "webfuse/ws/server_handler.hpp" #include "webfuse/util/authenticator.hpp" +#include +#include #include + namespace { std::string get_auth_token_of_known_header(lws * wsi, lws_token_indexes header) @@ -48,6 +51,7 @@ namespace webfuse server_handler::server_handler(std::string const & auth_app, std::string const & auth_hdr) : connection(nullptr) , id(0) +, is_authenticated(false) , authenticator(auth_app) , auth_header(auth_hdr) { @@ -146,6 +150,7 @@ void server_handler::on_closed(lws * wsi) if (wsi == connection) { connection = nullptr; + is_authenticated = false; } } @@ -169,17 +174,28 @@ void server_handler::poll() std::future server_handler::perform(messagewriter writer) { - std::future result; + std::promise p; + std::future result = p.get_future(); + if (is_authenticated) { - std::promise p; - result = p.get_future(); - std::lock_guard lock(mut); uint32_t id = next_id(); writer.set_id(id); requests.emplace(std::move(writer)); pending_responses.emplace(id, std::move(p)); } + else + { + try + { + throw std::runtime_error("unauthenticated"); + } + catch(std::exception const &ex) + { + p.set_exception(std::current_exception()); + } + + } return result; @@ -188,21 +204,32 @@ std::future server_handler::perform(messagewriter writer) int server_handler::authenticate_via_header(lws * wsi) { - int result = 0; - if ((!authenticator.empty()) && (!auth_header.empty())) + // authentication is disabled + if (authenticator.empty()) { - std::string token = get_auth_token(wsi); - if (!token.empty()) - { - webfuse::authenticator auth(authenticator); - result = auth.authenticate(token) ? 0 : -1; - } - else - { - result = -1; - } + is_authenticated = true; + return 0; + } + + // authentication is enabled, but not via HTTP header + if (auth_header.empty()) + { + is_authenticated = false; + return 0; + } + + // delay authentication if HTTP header is not provided + std::string token = get_auth_token(wsi); + if (token.empty()) + { + is_authenticated = false; + return 0; } + // close connection, when authentication fails + webfuse::authenticator auth(authenticator); + int const result = auth.authenticate(token) ? 0 : -1; + is_authenticated = (result == 0); return result; } diff --git a/src/webfuse/ws/server_handler.hpp b/src/webfuse/ws/server_handler.hpp index bfc21d0..d8a3249 100644 --- a/src/webfuse/ws/server_handler.hpp +++ b/src/webfuse/ws/server_handler.hpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace webfuse { @@ -38,6 +39,7 @@ private: struct lws * connection; uint32_t id; + std::atomic is_authenticated; std::string authenticator; std::string auth_header; From 5db3b28b5a493bfeb8e8ce22bef4e4f7400569aa Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 22 Jan 2023 20:53:50 +0100 Subject: [PATCH 75/91] add in-protocol authentication mechanism --- doc/authentication.md | 13 +++- doc/protocol.md | 24 +++++++ script/provider.py | 11 ++- src/provider_main.cpp | 5 ++ src/webfuse/filesystem.cpp | 7 ++ src/webfuse/filesystem.hpp | 1 + src/webfuse/filesystem/empty_filesystem.cpp | 6 ++ src/webfuse/filesystem/empty_filesystem.hpp | 2 + src/webfuse/filesystem/filesystem_i.hpp | 2 + src/webfuse/provider.cpp | 10 +++ src/webfuse/request_type.cpp | 1 + src/webfuse/request_type.hpp | 3 +- src/webfuse/response_type.cpp | 1 + src/webfuse/response_type.hpp | 3 +- src/webfuse/ws/server.cpp | 2 +- src/webfuse/ws/server_handler.cpp | 68 ++++++++++++++++--- src/webfuse/ws/server_handler.hpp | 4 +- .../webfuse/test/filesystem_mock.hpp | 3 + 18 files changed, 147 insertions(+), 19 deletions(-) diff --git a/doc/authentication.md b/doc/authentication.md index 2155e79..6292dc8 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -1,12 +1,21 @@ # Authentication -Webfuse supports token-based authentication using HTTP headers. To activate authentication, two command line option needs to be given: +Webfuse supports two authentications mechanisms: + +- token-based authentication using HTTP headers +- in-protocol authentication + +To activate authentication, two command line option needs to be given: - `--wf-authenticator PATH` allows to specify an executable used for authentication -- `--wf-auth-header HEADER` +- `--wf-auth-header HEADER` _(optional)_ allows to specify the HTTP header used for authentication +When `--wf-auth-header` is not specifiend or the header is not contained +in the HTTP request, the in-protocol solutions is used: Before any other +operation, the credentials are queried via `getcreds`request. + ## Authenticator An authenticator is an executable or script used for token-based diff --git a/doc/protocol.md b/doc/protocol.md index b6cda83..ac4322a 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -313,6 +313,7 @@ _Note that the following numbers are in `hexadecimal` notation._ | rmdir | 0x14 | 0x94 | | statfs | 0x15 | 0x95 | | utimens | 0x16 | 0x96 | +| getcreds | 0x17 | 0x97 | ## Methods @@ -807,6 +808,29 @@ _Note that handle might be invalid (-1), even if the file is open._ | type | u8 | message type (0x96) | | result | result | operation status | +### getcreds + +Query credentials. When authentication is active and the in-protocol +authentication mechanism is used, this is the first request a +webfuse service sends to a provider. + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x17) | + +_Note that handle might be invalid (-1), even if the file is open._ + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x97) | +| creds | str | credentials | + ## Examples ### Get file attributes diff --git a/script/provider.py b/script/provider.py index c5cfaba..4cc5a52 100755 --- a/script/provider.py +++ b/script/provider.py @@ -5,6 +5,7 @@ import os import stat import websockets import errno +import getpass INVALID_FD = 0xffffffffffffffff @@ -269,10 +270,12 @@ class FilesystemProvider: 0x14: FilesystemProvider.rmdir, 0x15: FilesystemProvider.statfs, 0x16: FilesystemProvider.utimens, + 0x17: FilesystemProvider.getcreds, } async def run(self): - async with websockets.connect(self.url, extra_headers=[("X-Auth-Token", "user:bob;token=foo")]) as connection: + extra_headers = [("X-Auth-Token", "user:bob;token=foo")] + async with websockets.connect(self.url, extra_headers=extra_headers) as connection: while True: request = await connection.recv() reader = MessageReader(request) @@ -565,7 +568,11 @@ class FilesystemProvider: writer.write_u64(buffer.f_files) writer.write_u64(buffer.f_ffree) writer.write_u64(buffer.f_namemax) - + + def getcreds(self, reader, writer): + credentials = getpass.getpass(prompt="credentials: ") + writer.write_str(credentials) + if __name__ == '__main__': provider = FilesystemProvider('.', 'ws://localhost:8081') diff --git a/src/provider_main.cpp b/src/provider_main.cpp index 6213ac7..c1bcddf 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -418,6 +418,11 @@ public: return (result == 0) ? 0 : -errno; } + std::string get_credentials() override + { + return getpass("credentials: "); + } + private: std::string get_full_path(std::string const & path) diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index 41bc258..6904f29 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -419,4 +419,11 @@ int filesystem::statfs(std::string const & path, struct statvfs * statistics) } } +// get credentials is handled internally +std::string filesystem::get_credentials() +{ + throw std::runtime_error("not implemented"); +} + + } \ No newline at end of file diff --git a/src/webfuse/filesystem.hpp b/src/webfuse/filesystem.hpp index b2f7002..e57c7a2 100644 --- a/src/webfuse/filesystem.hpp +++ b/src/webfuse/filesystem.hpp @@ -47,6 +47,7 @@ public: int statfs(std::string const & path, struct statvfs * statistics) override; + std::string get_credentials() override; private: ws_server &proxy; diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index e3af5b8..f89f416 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -131,5 +131,11 @@ int empty_filesystem::statfs(std::string const & path, struct statvfs * statisti return -ENOSYS; } +std::string empty_filesystem::get_credentials() +{ + return ""; +} + + } \ No newline at end of file diff --git a/src/webfuse/filesystem/empty_filesystem.hpp b/src/webfuse/filesystem/empty_filesystem.hpp index 16b390a..d4cca3a 100644 --- a/src/webfuse/filesystem/empty_filesystem.hpp +++ b/src/webfuse/filesystem/empty_filesystem.hpp @@ -39,6 +39,8 @@ public: int rmdir(std::string const & path) override; int statfs(std::string const & path, struct statvfs * statistics) override; + + std::string get_credentials() override; }; } diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp index cf2f0ce..67abb64 100644 --- a/src/webfuse/filesystem/filesystem_i.hpp +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -48,6 +48,8 @@ public: virtual int rmdir(std::string const & path) = 0; virtual int statfs(std::string const & path, struct statvfs * statistics) = 0; + + virtual std::string get_credentials() = 0; }; } diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 9ca6fee..26fc1be 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -122,6 +122,9 @@ public: case request_type::statfs: fs_statfs(reader, writer); break; + case request_type::getcreds: + fs_get_credentials(reader, writer); + break; default: std::cout << "unknown request: " << ((int) req_type) << std::endl; break; @@ -371,6 +374,13 @@ private: } } + void fs_get_credentials(messagereader & reader, messagewriter & writer) + { + std::string credentials = fs_.get_credentials(); + writer.write_str(credentials); + } + + filesystem_i & fs_; ws_client client; }; diff --git a/src/webfuse/request_type.cpp b/src/webfuse/request_type.cpp index 0e7b8c0..f8ebe9a 100644 --- a/src/webfuse/request_type.cpp +++ b/src/webfuse/request_type.cpp @@ -29,6 +29,7 @@ request_type get_request_type(uint8_t value) case static_cast(request_type::rmdir): return request_type::rmdir; case static_cast(request_type::statfs): return request_type::statfs; case static_cast(request_type::utimens): return request_type::utimens; + case static_cast(request_type::getcreds): return request_type::getcreds; default: return request_type::unknown; } diff --git a/src/webfuse/request_type.hpp b/src/webfuse/request_type.hpp index b605f0b..6a61119 100644 --- a/src/webfuse/request_type.hpp +++ b/src/webfuse/request_type.hpp @@ -30,7 +30,8 @@ enum class request_type: uint8_t readdir = 0x13, rmdir = 0x14, statfs = 0x15, - utimens = 0x16 + utimens = 0x16, + getcreds =0x17 }; request_type get_request_type(uint8_t value); diff --git a/src/webfuse/response_type.cpp b/src/webfuse/response_type.cpp index cb647e7..a7bb02e 100644 --- a/src/webfuse/response_type.cpp +++ b/src/webfuse/response_type.cpp @@ -29,6 +29,7 @@ response_type get_response_type(request_type value) case request_type::rmdir: return response_type::rmdir; case request_type::statfs: return response_type::statfs; case request_type::utimens: return response_type::utimens; + case request_type::getcreds: return response_type::getcreds; default: return response_type::unknown; } diff --git a/src/webfuse/response_type.hpp b/src/webfuse/response_type.hpp index 46cfebf..6d86715 100644 --- a/src/webfuse/response_type.hpp +++ b/src/webfuse/response_type.hpp @@ -31,7 +31,8 @@ enum class response_type: uint8_t readdir = 0x93, rmdir = 0x94, statfs = 0x95, - utimens = 0x96 + utimens = 0x96, + getcreds = 0x97 }; response_type get_response_type(request_type value); diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 727d8df..51bd70c 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -54,7 +54,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, handler->on_receive(wsi, in, len); break; case LWS_CALLBACK_SERVER_WRITEABLE: - handler->on_writable(); + result = handler->on_writable(); break; default: break; diff --git a/src/webfuse/ws/server_handler.cpp b/src/webfuse/ws/server_handler.cpp index a6ae89c..2475047 100644 --- a/src/webfuse/ws/server_handler.cpp +++ b/src/webfuse/ws/server_handler.cpp @@ -51,6 +51,7 @@ namespace webfuse server_handler::server_handler(std::string const & auth_app, std::string const & auth_hdr) : connection(nullptr) , id(0) +, shutdown_requested(false) , is_authenticated(false) , authenticator(auth_app) , auth_header(auth_hdr) @@ -69,9 +70,26 @@ int server_handler::on_established(lws * wsi) if (nullptr == connection) { connection = wsi; + id = 0; + + if ((!is_authenticated) && (!authenticator.empty())) + { + { + messagewriter writer(request_type::getcreds); + std::lock_guard lock(mut); + + uint32_t id = next_id(); + writer.set_id(id); + requests.emplace(std::move(writer)); + } + + lws_callback_on_writable(wsi); + } + return 0; } + // already connected: refuse return -1; } @@ -86,22 +104,29 @@ void server_handler::on_receive(lws * wsi, void* in, int len) { webfuse::messagereader reader(current_message); uint32_t id = reader.read_u32(); - reader.read_u8(); // read message type: ToDo: use it + auto const message_type = reader.read_u8(); // read message type: ToDo: use it - std::lock_guard lock(mut); - auto it = pending_responses.find(id); - if (it != pending_responses.end()) + if (static_cast(message_type) == response_type::getcreds) { - it->second.set_value(std::move(reader)); - pending_responses.erase(it); + finish_authentication(wsi, std::move(reader)); } else { - // ToDo: log request not found - std::cout << "warning: request not found: id=" << id << std::endl; - for(auto const & entry: pending_responses) + std::lock_guard lock(mut); + auto it = pending_responses.find(id); + if (it != pending_responses.end()) + { + it->second.set_value(std::move(reader)); + pending_responses.erase(it); + } + else { - std::cout << "\t" << entry.first << std::endl; + // ToDo: log request not found + std::cout << "warning: request not found: id=" << id << std::endl; + for(auto const & entry: pending_responses) + { + std::cout << "\t" << entry.first << std::endl; + } } } } @@ -113,8 +138,10 @@ void server_handler::on_receive(lws * wsi, void* in, int len) } } -void server_handler::on_writable() +int server_handler::on_writable() { + if (shutdown_requested) { return -1; } + webfuse::messagewriter writer(webfuse::request_type::unknown); bool has_msg = false; bool has_more = false; @@ -143,6 +170,8 @@ void server_handler::on_writable() { lws_callback_on_writable(connection); } + + return 0; } void server_handler::on_closed(lws * wsi) @@ -258,5 +287,22 @@ uint32_t server_handler::next_id() return id; } +void server_handler::finish_authentication(lws * wsi, messagereader reader) +{ + auto const credentials = reader.read_str(); + + webfuse::authenticator auth(authenticator); + auto const result = auth.authenticate(credentials); + if (result) + { + is_authenticated = true; + } + else + { + shutdown_requested = true; + lws_callback_on_writable(wsi); + } +} + } \ No newline at end of file diff --git a/src/webfuse/ws/server_handler.hpp b/src/webfuse/ws/server_handler.hpp index d8a3249..025a6ed 100644 --- a/src/webfuse/ws/server_handler.hpp +++ b/src/webfuse/ws/server_handler.hpp @@ -25,7 +25,7 @@ public: int on_established(lws* wsi); void on_receive(lws * wsi, void* in, int len); - void on_writable(); + int on_writable(); void on_closed(lws * wsi); std::future perform(messagewriter writer); @@ -35,9 +35,11 @@ private: int authenticate_via_header(lws * wsi); std::string get_auth_token(lws * wsi) const; uint32_t next_id(); + void finish_authentication(lws * wsi, messagereader reader); struct lws * connection; uint32_t id; + bool shutdown_requested; std::atomic is_authenticated; std::string authenticator; diff --git a/test-src/integration/webfuse/test/filesystem_mock.hpp b/test-src/integration/webfuse/test/filesystem_mock.hpp index 844160f..6e583ab 100644 --- a/test-src/integration/webfuse/test/filesystem_mock.hpp +++ b/test-src/integration/webfuse/test/filesystem_mock.hpp @@ -40,6 +40,9 @@ public: MOCK_METHOD(int, rmdir, (std::string const & path)); MOCK_METHOD(int, statfs, (std::string const & path, struct statvfs * statistics)); + + MOCK_METHOD(std::string, get_credentials, ()); + }; } From 7d4f3a1d86f11713562fdea502aa434ad613c571 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Wed, 25 Jan 2023 21:22:51 +0100 Subject: [PATCH 76/91] add newline at end of file --- .gitignore | 2 +- CMakeLists.txt | 2 +- doc/concept.uml | 2 +- doc/webfuse.md | 2 +- script/requirements.txt | 2 +- src/main.cpp | 2 +- src/provider_main.cpp | 2 +- src/webfuse/filesystem.cpp | 2 +- src/webfuse/filesystem/accessmode.cpp | 2 +- src/webfuse/filesystem/empty_filesystem.cpp | 3 +-- src/webfuse/filesystem/filemode.cpp | 2 +- src/webfuse/filesystem/filesystem_statistics.cpp | 2 +- src/webfuse/filesystem/openflags.cpp | 2 +- src/webfuse/filesystem/status.cpp | 2 +- src/webfuse/filesystem/status.hpp | 2 +- src/webfuse/fuse.cpp | 2 +- src/webfuse/provider.cpp | 2 +- src/webfuse/request_type.cpp | 2 +- src/webfuse/request_type.hpp | 2 +- src/webfuse/response_type.cpp | 2 +- src/webfuse/response_type.hpp | 2 +- src/webfuse/util/authenticator.cpp | 3 +-- src/webfuse/util/commandline_args.cpp | 2 +- src/webfuse/util/commandline_reader.cpp | 2 +- src/webfuse/version.cpp.in | 2 +- src/webfuse/webfuse.cpp | 2 +- src/webfuse/webfuse.hpp | 2 +- src/webfuse/ws/client.cpp | 2 +- src/webfuse/ws/config.cpp | 2 +- src/webfuse/ws/messagereader.cpp | 2 +- src/webfuse/ws/messagewriter.cpp | 2 +- src/webfuse/ws/server.cpp | 2 +- src/webfuse/ws/server_handler.cpp | 2 +- src/webfuse/ws/url.cpp | 2 +- test-src/integration/test_unlink.cpp | 2 +- test-src/integration/webfuse/test/daemon.cpp | 2 +- test-src/integration/webfuse/test/fixture.cpp | 2 +- test-src/integration/webfuse/test/process.cpp | 2 +- test-src/integration/webfuse/test/tempdir.cpp | 2 +- test-src/unit/webfuse/filesystem/test_filemode.cpp | 2 +- test-src/unit/webfuse/filesystem/test_openflags.cpp | 2 +- test-src/unit/webfuse/test_request_type.cpp | 2 +- test-src/unit/webfuse/test_response_type.cpp | 2 +- 43 files changed, 43 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index d431297..c07155d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /build/ /.vscode/ -*.pem \ No newline at end of file +*.pem diff --git a/CMakeLists.txt b/CMakeLists.txt index d35e8c9..75525b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,4 +126,4 @@ if(NOT(WITHOUT_TEST)) add_custom_target(memcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./unit_tests) endif() -endif() \ No newline at end of file +endif() diff --git a/doc/concept.uml b/doc/concept.uml index e4dcb20..eb2737a 100644 --- a/doc/concept.uml +++ b/doc/concept.uml @@ -23,4 +23,4 @@ service --> user : [., ..] end ... -@enduml \ No newline at end of file +@enduml diff --git a/doc/webfuse.md b/doc/webfuse.md index 1f2e23e..a23589c 100644 --- a/doc/webfuse.md +++ b/doc/webfuse.md @@ -71,4 +71,4 @@ filesystem via fuse and exposes it's API via websockets. `webfuse -f --wf-cert /path/to/cert --wf-key /path/to/key /path/to/mountpoint` - run webfuse using authentication via `X-Auth-Token` header: `webfuse -f --wf-authenticator /path/to/authenticator \` - ` --wf-auth-header X-Auth-Token /path/to/mountpoint` \ No newline at end of file + ` --wf-auth-header X-Auth-Token /path/to/mountpoint` diff --git a/script/requirements.txt b/script/requirements.txt index 707d0d3..2da5042 100644 --- a/script/requirements.txt +++ b/script/requirements.txt @@ -1 +1 @@ -websockets==10.4 \ No newline at end of file +websockets==10.4 diff --git a/src/main.cpp b/src/main.cpp index f582bd2..4467a6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,4 +4,4 @@ int main(int argc, char* argv[]) { webfuse::app app; return app.run(argc, argv); -} \ No newline at end of file +} diff --git a/src/provider_main.cpp b/src/provider_main.cpp index c1bcddf..e9f63e3 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -485,4 +485,4 @@ int main(int argc, char* argv[]) } return ctx.exit_code; -} \ No newline at end of file +} diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index 6904f29..d326a70 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -426,4 +426,4 @@ std::string filesystem::get_credentials() } -} \ No newline at end of file +} diff --git a/src/webfuse/filesystem/accessmode.cpp b/src/webfuse/filesystem/accessmode.cpp index 6753a5c..50abab4 100644 --- a/src/webfuse/filesystem/accessmode.cpp +++ b/src/webfuse/filesystem/accessmode.cpp @@ -38,4 +38,4 @@ int access_mode::to_int() const } -} \ No newline at end of file +} diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index f89f416..b5ce3f5 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -137,5 +137,4 @@ std::string empty_filesystem::get_credentials() } - -} \ No newline at end of file +} diff --git a/src/webfuse/filesystem/filemode.cpp b/src/webfuse/filesystem/filemode.cpp index 14d54e8..0b962eb 100644 --- a/src/webfuse/filesystem/filemode.cpp +++ b/src/webfuse/filesystem/filemode.cpp @@ -47,4 +47,4 @@ mode_t filemode::to_mode() const } -} \ No newline at end of file +} diff --git a/src/webfuse/filesystem/filesystem_statistics.cpp b/src/webfuse/filesystem/filesystem_statistics.cpp index 5422b80..f1bd09b 100644 --- a/src/webfuse/filesystem/filesystem_statistics.cpp +++ b/src/webfuse/filesystem/filesystem_statistics.cpp @@ -40,4 +40,4 @@ void filesystem_statistics::copy_to(struct statvfs & other) const other.f_namemax = f_namemax; } -} \ No newline at end of file +} diff --git a/src/webfuse/filesystem/openflags.cpp b/src/webfuse/filesystem/openflags.cpp index 3547eaa..4700e8d 100644 --- a/src/webfuse/filesystem/openflags.cpp +++ b/src/webfuse/filesystem/openflags.cpp @@ -76,4 +76,4 @@ int openflags::to_int() const } -} \ No newline at end of file +} diff --git a/src/webfuse/filesystem/status.cpp b/src/webfuse/filesystem/status.cpp index 9864380..4ba6e50 100644 --- a/src/webfuse/filesystem/status.cpp +++ b/src/webfuse/filesystem/status.cpp @@ -119,4 +119,4 @@ bool status::is_good() const } -} \ No newline at end of file +} diff --git a/src/webfuse/filesystem/status.hpp b/src/webfuse/filesystem/status.hpp index 5b3f6af..e946221 100644 --- a/src/webfuse/filesystem/status.hpp +++ b/src/webfuse/filesystem/status.hpp @@ -61,4 +61,4 @@ private: } -#endif \ No newline at end of file +#endif diff --git a/src/webfuse/fuse.cpp b/src/webfuse/fuse.cpp index f2432b0..89c49ea 100644 --- a/src/webfuse/fuse.cpp +++ b/src/webfuse/fuse.cpp @@ -301,4 +301,4 @@ void fuse::print_usage() } -} \ No newline at end of file +} diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 26fc1be..257aadc 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -434,4 +434,4 @@ void provider::set_connection_listener(std::function listener) d->set_connection_listener(listener); } -} \ No newline at end of file +} diff --git a/src/webfuse/request_type.cpp b/src/webfuse/request_type.cpp index f8ebe9a..6c35893 100644 --- a/src/webfuse/request_type.cpp +++ b/src/webfuse/request_type.cpp @@ -35,4 +35,4 @@ request_type get_request_type(uint8_t value) } } -} \ No newline at end of file +} diff --git a/src/webfuse/request_type.hpp b/src/webfuse/request_type.hpp index 6a61119..0016b65 100644 --- a/src/webfuse/request_type.hpp +++ b/src/webfuse/request_type.hpp @@ -38,4 +38,4 @@ request_type get_request_type(uint8_t value); } -#endif \ No newline at end of file +#endif diff --git a/src/webfuse/response_type.cpp b/src/webfuse/response_type.cpp index a7bb02e..9142ea4 100644 --- a/src/webfuse/response_type.cpp +++ b/src/webfuse/response_type.cpp @@ -36,4 +36,4 @@ response_type get_response_type(request_type value) } -} \ No newline at end of file +} diff --git a/src/webfuse/response_type.hpp b/src/webfuse/response_type.hpp index 6d86715..d413714 100644 --- a/src/webfuse/response_type.hpp +++ b/src/webfuse/response_type.hpp @@ -39,4 +39,4 @@ response_type get_response_type(request_type value); } -#endif \ No newline at end of file +#endif diff --git a/src/webfuse/util/authenticator.cpp b/src/webfuse/util/authenticator.cpp index 013ec7f..dd6fb38 100644 --- a/src/webfuse/util/authenticator.cpp +++ b/src/webfuse/util/authenticator.cpp @@ -51,5 +51,4 @@ bool authenticator::authenticate(std::string const & token) } - -} \ No newline at end of file +} diff --git a/src/webfuse/util/commandline_args.cpp b/src/webfuse/util/commandline_args.cpp index b97266d..a31fab9 100644 --- a/src/webfuse/util/commandline_args.cpp +++ b/src/webfuse/util/commandline_args.cpp @@ -53,4 +53,4 @@ char ** commandline_args::get_argv() } -} \ No newline at end of file +} diff --git a/src/webfuse/util/commandline_reader.cpp b/src/webfuse/util/commandline_reader.cpp index e4b07a9..db5430f 100644 --- a/src/webfuse/util/commandline_reader.cpp +++ b/src/webfuse/util/commandline_reader.cpp @@ -27,4 +27,4 @@ char const * commandline_reader::current() const } -} \ No newline at end of file +} diff --git a/src/webfuse/version.cpp.in b/src/webfuse/version.cpp.in index b9f7137..f77e3cf 100644 --- a/src/webfuse/version.cpp.in +++ b/src/webfuse/version.cpp.in @@ -8,4 +8,4 @@ char const * get_version() return "@webfuse_VERSION@"; } -} \ No newline at end of file +} diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index cdedd04..742fea6 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -44,4 +44,4 @@ WEBFUSE options: return config.exit_code; } -} \ No newline at end of file +} diff --git a/src/webfuse/webfuse.hpp b/src/webfuse/webfuse.hpp index 6e76c1e..8738cc6 100644 --- a/src/webfuse/webfuse.hpp +++ b/src/webfuse/webfuse.hpp @@ -12,4 +12,4 @@ public: } -#endif \ No newline at end of file +#endif diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp index 87dae9d..1f488fb 100644 --- a/src/webfuse/ws/client.cpp +++ b/src/webfuse/ws/client.cpp @@ -237,4 +237,4 @@ void ws_client::set_connection_listener(std::function listener) d->set_connection_listener(listener); } -} \ No newline at end of file +} diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp index c3e3925..df2b737 100644 --- a/src/webfuse/ws/config.cpp +++ b/src/webfuse/ws/config.cpp @@ -111,4 +111,4 @@ ws_config::ws_config(int argc, char * argv[]) } -} \ No newline at end of file +} diff --git a/src/webfuse/ws/messagereader.cpp b/src/webfuse/ws/messagereader.cpp index 4c261ad..c78aa6d 100644 --- a/src/webfuse/ws/messagereader.cpp +++ b/src/webfuse/ws/messagereader.cpp @@ -192,4 +192,4 @@ int messagereader::read_openflags() } -} \ No newline at end of file +} diff --git a/src/webfuse/ws/messagewriter.cpp b/src/webfuse/ws/messagewriter.cpp index fe565ed..37328ce 100644 --- a/src/webfuse/ws/messagewriter.cpp +++ b/src/webfuse/ws/messagewriter.cpp @@ -220,4 +220,4 @@ unsigned char * messagewriter::get_data(size_t &size) } -} \ No newline at end of file +} diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 51bd70c..50ba599 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -176,4 +176,4 @@ messagereader ws_server::perform(messagewriter writer) } -} \ No newline at end of file +} diff --git a/src/webfuse/ws/server_handler.cpp b/src/webfuse/ws/server_handler.cpp index 2475047..72e1cd6 100644 --- a/src/webfuse/ws/server_handler.cpp +++ b/src/webfuse/ws/server_handler.cpp @@ -305,4 +305,4 @@ void server_handler::finish_authentication(lws * wsi, messagereader reader) } -} \ No newline at end of file +} diff --git a/src/webfuse/ws/url.cpp b/src/webfuse/ws/url.cpp index f024265..6209564 100644 --- a/src/webfuse/ws/url.cpp +++ b/src/webfuse/ws/url.cpp @@ -67,4 +67,4 @@ ws_url::ws_url(std::string const & url) } -} \ No newline at end of file +} diff --git a/test-src/integration/test_unlink.cpp b/test-src/integration/test_unlink.cpp index 2d14ea5..8b4f488 100644 --- a/test-src/integration/test_unlink.cpp +++ b/test-src/integration/test_unlink.cpp @@ -41,4 +41,4 @@ TEST(unlink, success) auto const path = fixture.get_path() + "/some_file"; ASSERT_EQ(0, ::unlink(path.c_str())); -} \ No newline at end of file +} diff --git a/test-src/integration/webfuse/test/daemon.cpp b/test-src/integration/webfuse/test/daemon.cpp index 31d17b2..b4e0c09 100644 --- a/test-src/integration/webfuse/test/daemon.cpp +++ b/test-src/integration/webfuse/test/daemon.cpp @@ -34,4 +34,4 @@ daemon::~daemon() p.kill(SIGINT); } -} \ No newline at end of file +} diff --git a/test-src/integration/webfuse/test/fixture.cpp b/test-src/integration/webfuse/test/fixture.cpp index 1e286fc..7b377a6 100644 --- a/test-src/integration/webfuse/test/fixture.cpp +++ b/test-src/integration/webfuse/test/fixture.cpp @@ -60,4 +60,4 @@ std::string const & fixture::get_path() const } -} \ No newline at end of file +} diff --git a/test-src/integration/webfuse/test/process.cpp b/test-src/integration/webfuse/test/process.cpp index b40b61e..2866565 100644 --- a/test-src/integration/webfuse/test/process.cpp +++ b/test-src/integration/webfuse/test/process.cpp @@ -90,4 +90,4 @@ int process::wait() return exit_code; } -} \ No newline at end of file +} diff --git a/test-src/integration/webfuse/test/tempdir.cpp b/test-src/integration/webfuse/test/tempdir.cpp index 33701b6..5be4f0f 100644 --- a/test-src/integration/webfuse/test/tempdir.cpp +++ b/test-src/integration/webfuse/test/tempdir.cpp @@ -20,4 +20,4 @@ std::string const & tempdir::name() const return path; } -} \ No newline at end of file +} diff --git a/test-src/unit/webfuse/filesystem/test_filemode.cpp b/test-src/unit/webfuse/filesystem/test_filemode.cpp index 7c72af8..9330d4c 100644 --- a/test-src/unit/webfuse/filesystem/test_filemode.cpp +++ b/test-src/unit/webfuse/filesystem/test_filemode.cpp @@ -22,4 +22,4 @@ INSTANTIATE_TEST_CASE_P(filemode_value, filemode_test, S_IFREG | 0644, S_IFDIR | 0755 ) -); \ No newline at end of file +); diff --git a/test-src/unit/webfuse/filesystem/test_openflags.cpp b/test-src/unit/webfuse/filesystem/test_openflags.cpp index f36d9e1..db9b74c 100644 --- a/test-src/unit/webfuse/filesystem/test_openflags.cpp +++ b/test-src/unit/webfuse/filesystem/test_openflags.cpp @@ -21,4 +21,4 @@ INSTANTIATE_TEST_CASE_P(openflags_values, openflags_test, O_NDELAY, O_SYNC, O_WRONLY | O_CREAT | O_TRUNC ) -); \ No newline at end of file +); diff --git a/test-src/unit/webfuse/test_request_type.cpp b/test-src/unit/webfuse/test_request_type.cpp index 23134ad..4f49639 100644 --- a/test-src/unit/webfuse/test_request_type.cpp +++ b/test-src/unit/webfuse/test_request_type.cpp @@ -34,4 +34,4 @@ TEST(request_type, unknown_values) ASSERT_EQ(expected, webfuse::get_request_type(0x80)); ASSERT_EQ(expected, webfuse::get_request_type(0x42)); ASSERT_EQ(expected, webfuse::get_request_type(0xff)); -} \ No newline at end of file +} diff --git a/test-src/unit/webfuse/test_response_type.cpp b/test-src/unit/webfuse/test_response_type.cpp index 6e20b21..13e1744 100644 --- a/test-src/unit/webfuse/test_response_type.cpp +++ b/test-src/unit/webfuse/test_response_type.cpp @@ -35,4 +35,4 @@ TEST(respones_type, unknown_values) ASSERT_EQ(expected, webfuse::get_response_type(static_cast(80))); ASSERT_EQ(expected, webfuse::get_response_type(static_cast(0x42))); ASSERT_EQ(expected, webfuse::get_response_type(static_cast(0xff))); -} \ No newline at end of file +} From 62147484959909f7113793debcdf6627ac621271 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Fri, 27 Jan 2023 17:55:58 +0100 Subject: [PATCH 77/91] refine directory structure --- {script => example/authenticator/simple}/authenticator.sh | 0 {script => example/provider/python}/requirements.txt | 0 script/provider.py => example/provider/python/webfuse_provider.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {script => example/authenticator/simple}/authenticator.sh (100%) rename {script => example/provider/python}/requirements.txt (100%) rename script/provider.py => example/provider/python/webfuse_provider.py (100%) diff --git a/script/authenticator.sh b/example/authenticator/simple/authenticator.sh similarity index 100% rename from script/authenticator.sh rename to example/authenticator/simple/authenticator.sh diff --git a/script/requirements.txt b/example/provider/python/requirements.txt similarity index 100% rename from script/requirements.txt rename to example/provider/python/requirements.txt diff --git a/script/provider.py b/example/provider/python/webfuse_provider.py similarity index 100% rename from script/provider.py rename to example/provider/python/webfuse_provider.py From 7a131d60243d87122160b5ece034ee95966ce101 Mon Sep 17 00:00:00 2001 From: Falk Werner <47070255+falk-werner@users.noreply.github.com> Date: Wed, 1 Feb 2023 18:38:40 +0100 Subject: [PATCH 78/91] Apply suggestions from code review --- doc/authentication.md | 2 +- src/webfuse/request_type.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/authentication.md b/doc/authentication.md index 6292dc8..3e4cd19 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -5,7 +5,7 @@ Webfuse supports two authentications mechanisms: - token-based authentication using HTTP headers - in-protocol authentication -To activate authentication, two command line option needs to be given: +To activate authentication, two command line option can be specified: - `--wf-authenticator PATH` allows to specify an executable used for authentication diff --git a/src/webfuse/request_type.hpp b/src/webfuse/request_type.hpp index 0016b65..a66d763 100644 --- a/src/webfuse/request_type.hpp +++ b/src/webfuse/request_type.hpp @@ -31,7 +31,7 @@ enum class request_type: uint8_t rmdir = 0x14, statfs = 0x15, utimens = 0x16, - getcreds =0x17 + getcreds = 0x17 }; request_type get_request_type(uint8_t value); From 7559bebb057e911cf18225dd1073070c132ed97b Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Thu, 2 Feb 2023 21:05:49 +0100 Subject: [PATCH 79/91] add example of PAM authenticator --- example/authenticator/pam/.gitignore | 1 + example/authenticator/pam/CMakeLists.txt | 26 +++ example/authenticator/pam/README.md | 31 +++ example/authenticator/pam/etc/pam.d/webfuse | 7 + example/authenticator/pam/src/main.cpp | 188 ++++++++++++++++++ .../authenticator/pam/src/token_decode.cpp | 47 +++++ .../authenticator/pam/src/token_encode.cpp | 32 +++ 7 files changed, 332 insertions(+) create mode 100644 example/authenticator/pam/.gitignore create mode 100644 example/authenticator/pam/CMakeLists.txt create mode 100644 example/authenticator/pam/README.md create mode 100644 example/authenticator/pam/etc/pam.d/webfuse create mode 100644 example/authenticator/pam/src/main.cpp create mode 100644 example/authenticator/pam/src/token_decode.cpp create mode 100644 example/authenticator/pam/src/token_encode.cpp diff --git a/example/authenticator/pam/.gitignore b/example/authenticator/pam/.gitignore new file mode 100644 index 0000000..f3d6549 --- /dev/null +++ b/example/authenticator/pam/.gitignore @@ -0,0 +1 @@ +/build/ \ No newline at end of file diff --git a/example/authenticator/pam/CMakeLists.txt b/example/authenticator/pam/CMakeLists.txt new file mode 100644 index 0000000..6f46a4b --- /dev/null +++ b/example/authenticator/pam/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) +project(webfuse VERSION 2.0.0) + +option(WITHOUT_CLANG_TIDY "Disables clang tidy" OFF) + +set (CMAKE_CXX_STANDARD 17) + +find_library(PAM pam REQUIRED) +find_library(B64 b64 REQUIRED) + +add_executable(webfuse_pam_authenticator src/main.cpp) +target_link_libraries(webfuse_pam_authenticator pam b64) +install(TARGETS webfuse_pam_authenticator DESTINATION bin) +install(FILES etc/pam.d/webfuse DESTINATION /etc/pam.d) + +add_executable(webfuse_pam_token_encode src/token_encode.cpp) +target_link_libraries(webfuse_pam_token_encode b64) + +add_executable(webfuse_pam_token_decode src/token_decode.cpp) +target_link_libraries(webfuse_pam_token_decode b64) + +if(NOT(WITHOUT_CLANG_TIDY)) +set_property( + TARGET webfuse_pam_authenticator webfuse_pam_token_encode webfuse_pam_token_decode + PROPERTY CXX_CLANG_TIDY clang-tidy -checks=readability-*,-readability-identifier-length -warnings-as-errors=*) +endif() diff --git a/example/authenticator/pam/README.md b/example/authenticator/pam/README.md new file mode 100644 index 0000000..b89da3e --- /dev/null +++ b/example/authenticator/pam/README.md @@ -0,0 +1,31 @@ +# webfuse PAM authenticator + +This directory contains an example of a webfuse authenticator using PAM. + +The authenticator uses `username` and `password` for authentication. +Since webfuse only provides a token, username and password are encoded as follows: + + TOKEN := base64 ( USERNAME ":" PASSWORD ) + +Example: + + USERNAME := "user" + PASSWORD := "secret" + TOKEN := base64 ( "user:secret" ) = "XNlcjpzZWNyZXQ=" + +The utilities `webfuse_pam_token_encode` and `webfuse_pam_token_decode` can be used +to encode and decode tokens. + +## Build + + cmake -b build + cmake build + +## Dependencies + +- libpam +- libb64 + +## Notes + +- in order to make the authenticator work, read access to /etc/shadow is needed diff --git a/example/authenticator/pam/etc/pam.d/webfuse b/example/authenticator/pam/etc/pam.d/webfuse new file mode 100644 index 0000000..593775d --- /dev/null +++ b/example/authenticator/pam/etc/pam.d/webfuse @@ -0,0 +1,7 @@ +# Use unix authentication for webfuse authentication. +# +# NOTE: +# - in order to use unix authentication, read access to /etc/shadow is required + +auth required pam_unix.so +account required pam_unix.so diff --git a/example/authenticator/pam/src/main.cpp b/example/authenticator/pam/src/main.cpp new file mode 100644 index 0000000..aa9cb60 --- /dev/null +++ b/example/authenticator/pam/src/main.cpp @@ -0,0 +1,188 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace +{ + +bool decode(std::string const & token, std::string & username, std::string & password) +{ + std::istringstream encoded(token); + std::stringstream decoded; + + base64::decoder decoder; + decoder.decode(encoded, decoded); + auto const plain = decoded.str(); + auto const pos = plain.find(':'); + bool const success = (pos != std::string::npos); + if (success) + { + username = plain.substr(0, pos); + password = plain.substr(pos + 1); + } + + return success; +} + +struct credentials +{ + std::string username; + std::string password; +}; + +extern "C" int conversation( + int count, + struct pam_message const ** messages, + struct pam_response ** ret_responses, + void * user_data) +{ + if (count <= 0) { return PAM_CONV_ERR; } + + int result = PAM_SUCCESS; + auto * responses = reinterpret_cast(malloc(count * sizeof(struct pam_response))); + auto * creds = reinterpret_cast(user_data); + + for(int i = 0; (PAM_SUCCESS == result) && (i < count); i++) + { + auto * response = &responses[i]; + auto const * message = messages[i]; + + response->resp_retcode = 0; + response->resp = nullptr; + + switch(message->msg_style) + { + case PAM_PROMPT_ECHO_ON: + response->resp = strdup(creds->username.c_str()); + break; + case PAM_PROMPT_ECHO_OFF: + response->resp = strdup(creds->password.c_str()); + break; + case PAM_TEXT_INFO: + break; + case PAM_ERROR_MSG: + break; + default: + free(responses); + result = PAM_CONV_ERR; + break; + } + } + + if (PAM_SUCCESS == result) + { + *ret_responses = responses; + } + + return result; +} + +bool authenticate(std::string const & username, std::string const & password) +{ + credentials creds; + creds.username = username; + creds.password = password; + + struct pam_conv conv; + conv.conv = &conversation; + conv.appdata_ptr = reinterpret_cast(&creds); + + pam_handle_t * handle = nullptr; + bool cleanup_handle = false; + bool result = true; + { + auto const rc = pam_start("webfuse", nullptr, &conv, &handle); + result = (PAM_SUCCESS == rc); + cleanup_handle = result; + if (!result) + { + syslog(LOG_AUTH, "failed to start PAM conversation"); + } + } + + if (result) + { + pam_set_item(handle, PAM_USER, reinterpret_cast(username.c_str())); + auto const rc = pam_authenticate(handle, PAM_DISALLOW_NULL_AUTHTOK); + result = (PAM_SUCCESS == rc); + } + + if (result) + { + auto const rc = pam_acct_mgmt(handle, PAM_DISALLOW_NULL_AUTHTOK); + result = (PAM_SUCCESS == rc); + } + + if (cleanup_handle) + { + pam_end(handle, 0); + } + + return result; +} + +} + +int main(int argc, char* argv[]) +{ + int exit_code = EXIT_FAILURE; + bool print_usage = true; + + if (argc == 2) + { + std::string const token = argv[1]; + if (("-h" != token) && ("--help" != token)) + { + print_usage = false; + + openlog("webfuse_pam_auth", 0, LOG_AUTH); + + std::string username; + std::string password; + auto const decode_valid = decode(token, username, password); + if (decode_valid) + { + auto const is_authenticated = authenticate(username, password); + if (is_authenticated) + { + syslog(LOG_AUTH, "authenticate user \"%s\"", username.c_str()); + exit_code = EXIT_SUCCESS; + } + else + { + syslog(LOG_AUTH, "failed to authenticate user \"%s\"", username.c_str()); + } + } + else + { + syslog(LOG_AUTH, "failed to decode authentication token"); + } + + closelog(); + } + } + + + if (print_usage) + { + std::cout << R"(webfuse_pam_authenticator, (c) 2023 Falk Werner +webfuse PAM authenticator + +Usage: + webfuse_pam_authenticator + +Arguments: + token used for authentication + token := base64( ":" ) +)"; + } + + return exit_code; +} \ No newline at end of file diff --git a/example/authenticator/pam/src/token_decode.cpp b/example/authenticator/pam/src/token_decode.cpp new file mode 100644 index 0000000..8fa38df --- /dev/null +++ b/example/authenticator/pam/src/token_decode.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include + +int main(int argc, char* argv[]) +{ + int exit_code = EXIT_FAILURE; + + if (argc == 2) + { + std::istringstream token(argv[1]); + + base64::decoder decoder; + std::stringstream plain; + decoder.decode(token, plain); + + auto const plain_str = plain.str(); + auto const pos = plain_str.find(':'); + if (pos != std::string::npos) + { + auto const username = plain_str.substr(0, pos); + auto const password = plain_str.substr(pos + 1); + + std::cout << "username: " << username << std::endl; + std::cout << "password: " << password << std::endl; + exit_code = EXIT_SUCCESS; + } + + if (exit_code != EXIT_SUCCESS) + { + std::cerr << "error: failed to decode" << std::endl; + } + } + else + { + std::cout << R"(webfuse_pam_token_encode, (c) 2023 Falk Werner + Encode token for webfuse PAM authenticator + + Usage: + webfuse_pam_token_encode +)"; + } + + return exit_code; +} \ No newline at end of file diff --git a/example/authenticator/pam/src/token_encode.cpp b/example/authenticator/pam/src/token_encode.cpp new file mode 100644 index 0000000..3b930fd --- /dev/null +++ b/example/authenticator/pam/src/token_encode.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include +#include + +int main(int argc, char* argv[]) +{ + if (argc == 3) + { + std::string const username = argv[1]; + std::string const password = argv[2]; + + base64::encoder encoder; + std::istringstream plain(username + ':' + password); + std::stringstream token; + encoder.encode(plain, token); + + std::cout << token.str(); + } + else + { + std::cout << R"(webfuse_pam_token_encode, (c) 2023 Falk Werner + Encode token for webfuse PAM authenticator + + Usage: + webfuse_pam_token_encode +)"; + } + + return EXIT_SUCCESS; +} \ No newline at end of file From a83fcd72602d47f9f28d2fe2a1cc74e736dc8c09 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 08:58:15 +0100 Subject: [PATCH 80/91] make pylint happy --- example/provider/python/pylintrc | 17 ++++ example/provider/python/webfuse_provider.py | 93 +++++++++++---------- 2 files changed, 66 insertions(+), 44 deletions(-) create mode 100644 example/provider/python/pylintrc diff --git a/example/provider/python/pylintrc b/example/provider/python/pylintrc new file mode 100644 index 0000000..61caec8 --- /dev/null +++ b/example/provider/python/pylintrc @@ -0,0 +1,17 @@ +[BASIC] + +good-names=i,j,k,ex,Run,_,f,fd + +[MESSAGES CONTROL] + + +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + missing-function-docstring + diff --git a/example/provider/python/webfuse_provider.py b/example/provider/python/webfuse_provider.py index 4cc5a52..73298ee 100755 --- a/example/provider/python/webfuse_provider.py +++ b/example/provider/python/webfuse_provider.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 +"""Example webfuse provider written in python.""" + import asyncio import os import stat -import websockets import errno import getpass +import websockets INVALID_FD = 0xffffffffffffffff @@ -90,6 +92,8 @@ O_TMPFILE = 0o20200000 O_TRUNC = 0o00001000 class MessageReader: + """Reads webfuse messages from buffer.""" + def __init__(self, buffer): self.buffer = buffer self.offset = 0 @@ -101,21 +105,22 @@ class MessageReader: def read_bool(self): return self.read_u8() == 1 - + def read_u32(self): - value = (self.buffer[self.offset] << 24) + (self.buffer[self.offset + 1] << 16) + (self.buffer[self.offset + 2] << 8) + self.buffer[self.offset + 3] + value = (self.buffer[self.offset] << 24) + (self.buffer[self.offset + 1] << 16) + \ + (self.buffer[self.offset + 2] << 8) + self.buffer[self.offset + 3] self.offset += 4 return value def read_u64(self): value = ( - (self.buffer[self.offset ] << 56) + - (self.buffer[self.offset + 1] << 48) + - (self.buffer[self.offset + 2] << 40) + + (self.buffer[self.offset ] << 56) + + (self.buffer[self.offset + 1] << 48) + + (self.buffer[self.offset + 2] << 40) + (self.buffer[self.offset + 3] << 32) + - (self.buffer[self.offset + 4] << 24) + - (self.buffer[self.offset + 5] << 16) + - (self.buffer[self.offset + 6] << 8) + + (self.buffer[self.offset + 4] << 24) + + (self.buffer[self.offset + 5] << 16) + + (self.buffer[self.offset + 6] << 8) + self.buffer[self.offset + 7]) self.offset += 8 return value @@ -144,7 +149,7 @@ class MessageReader: def read_rename_flags(self): return self.read_u8() - + def read_mode(self): value = self.read_u32() mode = value & 0o7777 @@ -152,7 +157,7 @@ class MessageReader: mode |= stat.S_IFDIR if MODE_DIR == (value & MODE_DIR ) else 0 mode |= stat.S_IFCHR if MODE_CHR == (value & MODE_CHR ) else 0 mode |= stat.S_IFBLK if MODE_BLK == (value & MODE_BLK ) else 0 - mode |= stat.S_IFFIFO if MODE_FIFO == (value & MODE_FIFO) else 0 + mode |= stat.S_IFIFO if MODE_FIFO == (value & MODE_FIFO) else 0 mode |= stat.S_IFLNK if MODE_LNK == (value & MODE_LNK ) else 0 mode |= stat.S_IFSOCK if MODE_SOCK == (value & MODE_SOCK) else 0 return mode @@ -187,14 +192,16 @@ class MessageReader: class MessageWriter: + """"Writes webfuse messages into buffer.""" + def __init__(self, message_id, message_type): self.buffer = [] self.write_u32(message_id) self.write_u8(message_type) - + def write_u8(self, value): self.buffer.append(value) - + def write_u32(self, value): self.buffer.extend([ (value >> 24) & 0xff, @@ -217,22 +224,21 @@ class MessageWriter: def write_i32(self, value): self.write_u32(value & 0xffffffff) - + def write_result(self, value): if 0 > value: - if value in ERRNO: - value = ERRNO[value] + value = ERRNO.get(value, value) self.write_i32(value) def write_str(self, value): data = value.encode('utf-8') self.write_bytes(data) - + def write_bytes(self, value): size = len(value) self.write_u32(size) self.buffer.extend(value) - + def write_strings(self, values): count = len(values) self.write_u32(count) @@ -241,9 +247,11 @@ class MessageWriter: def get_bytes(self): return bytearray(self.buffer) - +# pylint: disable=too-many-public-methods class FilesystemProvider: + """Webfuse filesystem provider.""" + def __init__(self, path, url): self.root = os.path.abspath(path) self.url = url @@ -253,28 +261,29 @@ class FilesystemProvider: 0x03: FilesystemProvider.readlink, 0x04: FilesystemProvider.symlink, 0x05: FilesystemProvider.link, - 0x06: FilesystemProvider.rename, - 0x07: FilesystemProvider.chmod, - 0x08: FilesystemProvider.chown, + 0x06: FilesystemProvider.rename, + 0x07: FilesystemProvider.chmod, + 0x08: FilesystemProvider.chown, 0x09: FilesystemProvider.truncate, - 0x0a: FilesystemProvider.fsync, - 0x0b: FilesystemProvider.open, - 0x0c: FilesystemProvider.mknod, - 0x0d: FilesystemProvider.create, + 0x0a: FilesystemProvider.fsync, + 0x0b: FilesystemProvider.open, + 0x0c: FilesystemProvider.mknod, + 0x0d: FilesystemProvider.create, 0x0e: FilesystemProvider.release, - 0x0f: FilesystemProvider.unlink, - 0x10: FilesystemProvider.read, - 0x11: FilesystemProvider.write, - 0x12: FilesystemProvider.mkdir, + 0x0f: FilesystemProvider.unlink, + 0x10: FilesystemProvider.read, + 0x11: FilesystemProvider.write, + 0x12: FilesystemProvider.mkdir, 0x13: FilesystemProvider.readdir, 0x14: FilesystemProvider.rmdir, 0x15: FilesystemProvider.statfs, 0x16: FilesystemProvider.utimens, 0x17: FilesystemProvider.getcreds, } - + async def run(self): extra_headers = [("X-Auth-Token", "user:bob;token=foo")] + # pylint: disable-next=no-member async with websockets.connect(self.url, extra_headers=extra_headers) as connection: while True: request = await connection.recv() @@ -286,7 +295,7 @@ class FilesystemProvider: method = self.commands[message_type] method(self, reader, writer) else: - print("unknown message type: %d" % message_type) + print(f"unknown message type: {message_type}") response = writer.get_bytes() await connection.send(response) @@ -295,7 +304,7 @@ class FilesystemProvider: mode = reader.read_access_mode() result = -errno.EACCES try: - if os.access(path, mode) == True: + if os.access(path, mode) is True: result = 0 except OSError as ex: result = -ex.errno @@ -407,7 +416,7 @@ class FilesystemProvider: writer.write_result(result) def fsync(self, reader, writer): - path = reader.read_path(self.root) + _ = reader.read_path(self.root) _ = reader.read_bool() fd = reader.read_u64() result = 0 @@ -423,7 +432,6 @@ class FilesystemProvider: atime_ns = reader.read_u32() mtime = reader.read_u64() mtime_ns = reader.read_u32() - fd = reader.read_u64() result = 0 try: os.utime(path, (atime, mtime), ns = (atime_ns, mtime_ns)) @@ -431,7 +439,6 @@ class FilesystemProvider: result = -ex.errno writer.write_result(result) - def open(self, reader, writer): path = reader.read_path(self.root) flags = reader.read_openflags() @@ -442,7 +449,6 @@ class FilesystemProvider: return writer.write_result(0) writer.write_u64(fd) - def mknod(self, reader, writer): path = reader.read_path(self.root) @@ -452,7 +458,7 @@ class FilesystemProvider: try: os.mknod(path, mode, rdev) except OSError as ex: - result = -ex.errno + result = -ex.errno writer.write_result(result) def create(self, reader, writer): @@ -466,10 +472,9 @@ class FilesystemProvider: return writer.write_result(0) writer.write_u64(fd) - pass def release(self, reader, writer): - path = reader.read_path(self.root) + _ = reader.read_path(self.root) fd = reader.read_u64() result = 0 try: @@ -534,8 +539,8 @@ class FilesystemProvider: path = reader.read_path(self.root) names = [] try: - with os.scandir(path) as it: - for entry in it: + with os.scandir(path) as entries: + for entry in entries: names.append(entry.name) except OSError as ex: writer.write_result(-ex.errno) @@ -553,7 +558,7 @@ class FilesystemProvider: writer.write_result(result) def statfs(self, reader, writer): - path = self.get_path(reader) + path = reader.read_path(self.root) try: buffer = os.statvfs(path) except OSError as ex: @@ -569,7 +574,7 @@ class FilesystemProvider: writer.write_u64(buffer.f_ffree) writer.write_u64(buffer.f_namemax) - def getcreds(self, reader, writer): + def getcreds(self, _, writer): credentials = getpass.getpass(prompt="credentials: ") writer.write_str(credentials) From b55ae450182eef426737a626ea9d9b127c1b9c60 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 09:36:37 +0100 Subject: [PATCH 81/91] allow to specify path, url and authentication token via command line --- example/authenticator/simple/authenticator.sh | 2 +- example/provider/python/webfuse_provider.py | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/example/authenticator/simple/authenticator.sh b/example/authenticator/simple/authenticator.sh index 5a4bf93..23ee163 100755 --- a/example/authenticator/simple/authenticator.sh +++ b/example/authenticator/simple/authenticator.sh @@ -2,7 +2,7 @@ AUTH_TOKEN="$1" -if [[ "$AUTH_TOKEN" == "user:bob;token=foo" ]] +if [[ "$AUTH_TOKEN" == "simple_token" ]] then echo "$(date): webfuse: auth granted: $AUTH_TOKEN" >> /tmp/webfuse_auth.log else diff --git a/example/provider/python/webfuse_provider.py b/example/provider/python/webfuse_provider.py index 73298ee..041126c 100755 --- a/example/provider/python/webfuse_provider.py +++ b/example/provider/python/webfuse_provider.py @@ -7,6 +7,7 @@ import os import stat import errno import getpass +import argparse import websockets INVALID_FD = 0xffffffffffffffff @@ -252,9 +253,10 @@ class MessageWriter: class FilesystemProvider: """Webfuse filesystem provider.""" - def __init__(self, path, url): + def __init__(self, path, url, token): self.root = os.path.abspath(path) self.url = url + self.token = token self.commands = { 0x01: FilesystemProvider.access, 0x02: FilesystemProvider.getattr, @@ -282,7 +284,7 @@ class FilesystemProvider: } async def run(self): - extra_headers = [("X-Auth-Token", "user:bob;token=foo")] + extra_headers = [("X-Auth-Token", self.token)] if self.token != "" else [] # pylint: disable-next=no-member async with websockets.connect(self.url, extra_headers=extra_headers) as connection: while True: @@ -575,10 +577,24 @@ class FilesystemProvider: writer.write_u64(buffer.f_namemax) def getcreds(self, _, writer): - credentials = getpass.getpass(prompt="credentials: ") + credentials = self.token if self.token == "" else getpass.getpass(prompt="credentials: ") writer.write_str(credentials) +def main(): + parser = argparse.ArgumentParser(prog='webfuse_provider') + parser.add_argument('-p', '--path', type=str, required=False, default='.', + help='path to provide (default: \".\")') + parser.add_argument('-u', '--url', type=str, required=True, + help='URL of webfuse server, e.g. \"ws://localhost:8081\"') + parser.add_argument('-t', '--token', type=str, required=False, default='', + help='authentication token (default: contents of environment variable WEBFUSE_TOKEN)') + args = parser.parse_args() + token = args.token if args.token != "" else os.environ.get('WEBFUSE_TOKEN') + provider = FilesystemProvider(args.path, args.url, token) + try: + asyncio.run(provider.run()) + except KeyboardInterrupt as _: + pass if __name__ == '__main__': - provider = FilesystemProvider('.', 'ws://localhost:8081') - asyncio.run(provider.run()) + main() From 97f4acb98e7805ee428f73bbf3cdababc3a864e4 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 09:59:32 +0100 Subject: [PATCH 82/91] allow to specify authentication token via command line or environment variable --- doc/webfuse_provider.md | 9 +++++++++ src/provider_main.cpp | 31 +++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/doc/webfuse_provider.md b/doc/webfuse_provider.md index 3733a18..7ce47dd 100644 --- a/doc/webfuse_provider.md +++ b/doc/webfuse_provider.md @@ -15,6 +15,13 @@ Inject a remote filesystem via webfuse. | -p | --path | path | path of local filesystem to inject (default: .) | | -u | --url | url | url of webfuse server | | -a | --ca-path | path | path of ca file | +| -t | --token | token | authentication token (overrides WEBFUSE_TOKEN) | + +## Environment variables + +| Variable | Description | +| -------- | ----------- | +| WEBFUSE_TOKEN | Default value of webfuse token | ## Examples @@ -28,3 +35,5 @@ Inject a remote filesystem via webfuse. `webfuse_provider -u wss://localhost/` - inject current diectory via TLS using a specific ca: `webfuse_provider -u wss://localhost/ -a /path/to/server-cert.pem` +- inject current directory, authenticate via token: + `webfuse_provider -u wss://localhost/ -t my_token` diff --git a/src/provider_main.cpp b/src/provider_main.cpp index e9f63e3..94199ae 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include namespace @@ -29,11 +31,18 @@ public: , cmd(command::run) , exit_code() { + char const * const webfuse_token = getenv("WEBFUSE_TOKEN"); + if (nullptr != webfuse_token) + { + token = webfuse_token; + } + struct option const long_options[] = { {"path" , required_argument, nullptr, 'p'}, {"url" , required_argument, nullptr, 'u'}, {"ca-path", required_argument, nullptr, 'a'}, + {"token" , required_argument, nullptr, 't'}, {"version", no_argument , nullptr, 'v'}, {"help" , no_argument , nullptr, 'h'}, {nullptr , 0 , nullptr, 0 } @@ -45,7 +54,7 @@ public: while (!finished) { int option_index = 0; - const int c = getopt_long(argc, argv, "p:u:a:vh", long_options, &option_index); + const int c = getopt_long(argc, argv, "p:u:a:t:vh", long_options, &option_index); switch (c) { case -1: @@ -60,6 +69,9 @@ public: case 'a': ca_path = optarg; break; + case 't': + token = optarg; + break; case 'h': cmd = command::show_help; break; @@ -86,6 +98,7 @@ public: std::string base_path; std::string url; std::string ca_path; + std::string token; command cmd; int exit_code; }; @@ -102,9 +115,17 @@ Options: --url, -u set url of webfuse2 service --path, -p set path of directory to expose (default: .) --ca-path, -a set path of ca file (default: not set) + --token, -t set authentication token (default: see below) --version, -v print version and quit --help, -h print this message and quit +Authentication Token: + When requested by webfuse server, the authentication token + is determined as follows: + - if present, use contents of environment variable WEBFUSE_TOKEN + - else if specified, use the contents fo the -t option + - else query user for authentication token + Examples: webfuse-provider -u ws://localhost:8080/ webfuse-provider -u ws://localhost:8080/ -p /some/directory @@ -126,7 +147,8 @@ void on_signal(int _) class filesystem: public webfuse::filesystem_i { public: - explicit filesystem(std::string const & base_path) + filesystem(std::string const & base_path, std::string const & token) + : token_(token) { char buffer[PATH_MAX]; char * resolved_path = ::realpath(base_path.c_str(), buffer); @@ -420,7 +442,7 @@ public: std::string get_credentials() override { - return getpass("credentials: "); + return (!token_.empty()) ? token_ : getpass("credentials: "); } @@ -431,6 +453,7 @@ private: } std::string base_path_; + std::string token_; }; } @@ -449,7 +472,7 @@ int main(int argc, char* argv[]) signal(SIGINT, &on_signal); signal(SIGTERM, &on_signal); - filesystem fs(ctx.base_path); + filesystem fs(ctx.base_path, ctx.token); webfuse::provider provider(fs, ctx.ca_path); provider.set_connection_listener([](bool connected) { if (!connected) From 8e8c5c2b610549a954c1a3209a3b413030d1657e Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 10:06:43 +0100 Subject: [PATCH 83/91] fix: reset shutdown flag when connection is closed --- src/webfuse/ws/server_handler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webfuse/ws/server_handler.cpp b/src/webfuse/ws/server_handler.cpp index 72e1cd6..6fb1208 100644 --- a/src/webfuse/ws/server_handler.cpp +++ b/src/webfuse/ws/server_handler.cpp @@ -180,6 +180,7 @@ void server_handler::on_closed(lws * wsi) { connection = nullptr; is_authenticated = false; + shutdown_requested = false; } } From 8a68ecd0b74d3b9de9d5f8ae7c366da2f91d9c99 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 10:07:06 +0100 Subject: [PATCH 84/91] fixed logic error --- example/provider/python/webfuse_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/provider/python/webfuse_provider.py b/example/provider/python/webfuse_provider.py index 041126c..6a075c2 100755 --- a/example/provider/python/webfuse_provider.py +++ b/example/provider/python/webfuse_provider.py @@ -577,7 +577,7 @@ class FilesystemProvider: writer.write_u64(buffer.f_namemax) def getcreds(self, _, writer): - credentials = self.token if self.token == "" else getpass.getpass(prompt="credentials: ") + credentials = self.token if self.token != "" else getpass.getpass(prompt="credentials: ") writer.write_str(credentials) def main(): From b3bc3144bf16f78fe9e5bb1e4f6f0f5f46172d21 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 14:54:23 +0100 Subject: [PATCH 85/91] make timeout configurable --- doc/webfuse.md | 1 + src/webfuse/webfuse.cpp | 1 + src/webfuse/ws/config.cpp | 11 +++++++++++ src/webfuse/ws/config.hpp | 2 ++ src/webfuse/ws/server.cpp | 11 +++-------- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/doc/webfuse.md b/doc/webfuse.md index a23589c..cc5f72e 100644 --- a/doc/webfuse.md +++ b/doc/webfuse.md @@ -17,6 +17,7 @@ filesystem via fuse and exposes it's API via websockets. | --wf-key | path | - | Optional. Specify the file path of the server's private key | | --wf- authenticator | path | - | Optional. Specify the file path of the authenticator executable | | --wf-auth-header | name | - | Optional. Specify the name of the HTTP header used for authentication | +| --wf-timeout | timeout | 10 | Optional. Specify the communication timeout. | ## Fuse options diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index 742fea6..37916e0 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -36,6 +36,7 @@ WEBFUSE options: --wf-key PATH path of the server's private key (optional) --wf-authenticator PATH path of authenticatior app (optional) --wf-auth-header NAME name of the authentication header (optional) + --wf-timeout TIMEOUT communication timeout in seconds (default: 10) )"; } break; diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp index df2b737..e8f0120 100644 --- a/src/webfuse/ws/config.cpp +++ b/src/webfuse/ws/config.cpp @@ -10,6 +10,7 @@ namespace constexpr int const default_port = 8081; constexpr char const default_vhost_name[] = "localhost"; +constexpr uint64_t const default_timeout_secs = 10; void verify(webfuse::ws_config & config) { @@ -46,6 +47,7 @@ bool get_arg(webfuse::ws_config & config, webfuse::commandline_reader& reader, s namespace webfuse { +// NOLINTNEXTLINE ws_config::ws_config(int argc, char * argv[]) : exit_code(EXIT_SUCCESS) , args(argv[0], argc) @@ -53,6 +55,7 @@ ws_config::ws_config(int argc, char * argv[]) , port(default_port) , vhost_name(default_vhost_name) , use_tls(false) +, timeout_secs(default_timeout_secs) { commandline_reader reader(argc, argv); @@ -101,6 +104,14 @@ ws_config::ws_config(int argc, char * argv[]) [](auto c) {return std::tolower(c); }); } } + else if (arg == "--wf-timeout") + { + std::string timeout_str; + if (get_arg(*this, reader, timeout_str, "missing TIMEOUT")) + { + timeout_secs = static_cast(std::stoi(timeout_str)); + } + } else { args.push(arg.c_str()); diff --git a/src/webfuse/ws/config.hpp b/src/webfuse/ws/config.hpp index f884a5c..53b2616 100644 --- a/src/webfuse/ws/config.hpp +++ b/src/webfuse/ws/config.hpp @@ -33,6 +33,8 @@ public: std::string authenticator; std::string auth_header; + + uint64_t timeout_secs; }; } diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 50ba599..731ae77 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -19,13 +19,6 @@ #include -namespace -{ - -constexpr int64_t const timeout_secs = 10; - -} - extern "C" { @@ -79,6 +72,7 @@ public: detail(ws_config const & config) : shutdown_requested(false) , data(config.authenticator, config.auth_header) + , timeout_secs(config.timeout_secs) { lws_set_log_level(0, nullptr); @@ -131,6 +125,7 @@ public: lws_context_creation_info info; lws_context * context; server_handler data; + uint64_t timeout_secs; }; ws_server::ws_server(ws_config const & config) @@ -167,7 +162,7 @@ messagereader ws_server::perform(messagewriter writer) auto result = d->data.perform(std::move(writer)); lws_cancel_service(d->context); - if(std::future_status::timeout == result.wait_for(std::chrono::seconds(timeout_secs))) + if(std::future_status::timeout == result.wait_for(std::chrono::seconds(d->timeout_secs))) { throw std::runtime_error("timeout"); } From 1cbdfac3cc56a5126b694dd994657d091022d3a0 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 15:00:15 +0100 Subject: [PATCH 86/91] add option to print webfuse version --- doc/webfuse.md | 1 + src/webfuse/webfuse.cpp | 5 +++++ src/webfuse/ws/config.cpp | 4 ++++ src/webfuse/ws/config.hpp | 1 + 4 files changed, 11 insertions(+) diff --git a/doc/webfuse.md b/doc/webfuse.md index cc5f72e..adc749c 100644 --- a/doc/webfuse.md +++ b/doc/webfuse.md @@ -18,6 +18,7 @@ filesystem via fuse and exposes it's API via websockets. | --wf- authenticator | path | - | Optional. Specify the file path of the authenticator executable | | --wf-auth-header | name | - | Optional. Specify the name of the HTTP header used for authentication | | --wf-timeout | timeout | 10 | Optional. Specify the communication timeout. | +| --wf-version | - | - | Print version and exit. | ## Fuse options diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index 37916e0..2a9df2a 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -2,6 +2,7 @@ #include "webfuse/fuse.hpp" #include "webfuse/filesystem.hpp" #include "webfuse/ws/server.hpp" +#include "webfuse/version.hpp" #include @@ -23,6 +24,9 @@ int app::run(int argc, char * argv[]) // NOLINT(readability-convert-member-funct config.exit_code = fuse_fs.run(config.args.get_argc(), config.args.get_argv()); } break; + case command::print_version: + std::cout << webfuse::get_version() << std::endl; + break; case command::show_help: // fall-through default: @@ -37,6 +41,7 @@ WEBFUSE options: --wf-authenticator PATH path of authenticatior app (optional) --wf-auth-header NAME name of the authentication header (optional) --wf-timeout TIMEOUT communication timeout in seconds (default: 10) + --wf-version print version and exit )"; } break; diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp index e8f0120..f280ed8 100644 --- a/src/webfuse/ws/config.cpp +++ b/src/webfuse/ws/config.cpp @@ -112,6 +112,10 @@ ws_config::ws_config(int argc, char * argv[]) timeout_secs = static_cast(std::stoi(timeout_str)); } } + else if (arg == "--wf-version") + { + cmd = command::print_version; + } else { args.push(arg.c_str()); diff --git a/src/webfuse/ws/config.hpp b/src/webfuse/ws/config.hpp index 53b2616..a1183bb 100644 --- a/src/webfuse/ws/config.hpp +++ b/src/webfuse/ws/config.hpp @@ -12,6 +12,7 @@ namespace webfuse enum class command { run, + print_version, show_help }; From 6f1841e610be580b8094b02766ed43c4ede92a7b Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 15:48:17 +0100 Subject: [PATCH 87/91] use stdin to provide authentication token to authenticator rather than command line option --- doc/authentication.md | 7 +-- example/authenticator/pam/src/main.cpp | 55 ++++++++----------- example/authenticator/simple/authenticator.sh | 2 +- example/provider/python/webfuse_provider.py | 2 +- src/webfuse/util/authenticator.cpp | 21 +++++-- 5 files changed, 44 insertions(+), 43 deletions(-) diff --git a/doc/authentication.md b/doc/authentication.md index 3e4cd19..a0c6036 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -19,12 +19,7 @@ operation, the credentials are queried via `getcreds`request. ## Authenticator An authenticator is an executable or script used for token-based -authentication. During HTTP handshake, webfuse will scan for the -configured HTTP header and invoke the authenticator. - - authenticator TOKEN - -The provided `token` contains the contents of the HTTP header. +authentication. Credentials are passed to the authenticator via `stdin`. ## Header restrictions diff --git a/example/authenticator/pam/src/main.cpp b/example/authenticator/pam/src/main.cpp index aa9cb60..3a6c2aa 100644 --- a/example/authenticator/pam/src/main.cpp +++ b/example/authenticator/pam/src/main.cpp @@ -133,54 +133,47 @@ bool authenticate(std::string const & username, std::string const & password) int main(int argc, char* argv[]) { int exit_code = EXIT_FAILURE; - bool print_usage = true; - if (argc == 2) + if (argc == 1) { - std::string const token = argv[1]; - if (("-h" != token) && ("--help" != token)) - { - print_usage = false; + std::string token; + std::getline(std::cin, token); - openlog("webfuse_pam_auth", 0, LOG_AUTH); + openlog("webfuse_pam_auth", 0, LOG_AUTH); - std::string username; - std::string password; - auto const decode_valid = decode(token, username, password); - if (decode_valid) + std::string username; + std::string password; + auto const decode_valid = decode(token, username, password); + if (decode_valid) + { + auto const is_authenticated = authenticate(username, password); + if (is_authenticated) { - auto const is_authenticated = authenticate(username, password); - if (is_authenticated) - { - syslog(LOG_AUTH, "authenticate user \"%s\"", username.c_str()); - exit_code = EXIT_SUCCESS; - } - else - { - syslog(LOG_AUTH, "failed to authenticate user \"%s\"", username.c_str()); - } + syslog(LOG_AUTH, "authenticate user \"%s\"", username.c_str()); + exit_code = EXIT_SUCCESS; } else { - syslog(LOG_AUTH, "failed to decode authentication token"); + syslog(LOG_AUTH, "failed to authenticate user \"%s\"", username.c_str()); } - - closelog(); } - } - - if (print_usage) + closelog(); + } + else { std::cout << R"(webfuse_pam_authenticator, (c) 2023 Falk Werner webfuse PAM authenticator Usage: - webfuse_pam_authenticator + webfuse_pam_authenticator [-h] + +Options: + --help, -h print this message and exit -Arguments: - token used for authentication - token := base64( ":" ) +Credentials: + Credentials are passed as based64-encoded token via stdin: + token := base64( ":" ) )"; } diff --git a/example/authenticator/simple/authenticator.sh b/example/authenticator/simple/authenticator.sh index 23ee163..5330483 100755 --- a/example/authenticator/simple/authenticator.sh +++ b/example/authenticator/simple/authenticator.sh @@ -1,6 +1,6 @@ #!/usr/bin/bash -AUTH_TOKEN="$1" +read AUTH_TOKEN if [[ "$AUTH_TOKEN" == "simple_token" ]] then diff --git a/example/provider/python/webfuse_provider.py b/example/provider/python/webfuse_provider.py index 6a075c2..d508a29 100755 --- a/example/provider/python/webfuse_provider.py +++ b/example/provider/python/webfuse_provider.py @@ -577,7 +577,7 @@ class FilesystemProvider: writer.write_u64(buffer.f_namemax) def getcreds(self, _, writer): - credentials = self.token if self.token != "" else getpass.getpass(prompt="credentials: ") + credentials = self.token if self.token != None and self.token != "" else getpass.getpass(prompt="credentials: ") writer.write_str(credentials) def main(): diff --git a/src/webfuse/util/authenticator.cpp b/src/webfuse/util/authenticator.cpp index dd6fb38..579aaab 100644 --- a/src/webfuse/util/authenticator.cpp +++ b/src/webfuse/util/authenticator.cpp @@ -17,23 +17,35 @@ bool authenticator::authenticate(std::string const & token) { bool result = false; + int fds[2]; + int const rc = pipe(fds); + if (0 != rc) + { + return false; + } + pid_t const pid = fork(); if (pid == 0) { // child + close(STDIN_FILENO); + dup2(fds[0], STDIN_FILENO); + // prepare file descriptors - closefrom(0); - open("/dev/null", O_RDONLY); + closefrom(1); open("/dev/null", O_WRONLY); dup2(STDOUT_FILENO, STDERR_FILENO); - execl(app_.c_str(), app_.c_str(), token.c_str(), nullptr); + execl(app_.c_str(), app_.c_str(), nullptr); exit(EXIT_FAILURE); } else if (pid > 0) { + write(fds[1], reinterpret_cast(token.c_str()), token.size()); + close(fds[1]); + // parent int exit_status = EXIT_FAILURE; @@ -44,7 +56,8 @@ bool authenticator::authenticate(std::string const & token) exit_status = WEXITSTATUS(status); } - result = (exit_status == EXIT_SUCCESS); + close(fds[0]); + result = (exit_status == EXIT_SUCCESS); } return result; From a24dfd0fb534a00f6511c761329280e53f83e5a4 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 15:54:15 +0100 Subject: [PATCH 88/91] added missing newline --- example/authenticator/pam/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/authenticator/pam/.gitignore b/example/authenticator/pam/.gitignore index f3d6549..84c048a 100644 --- a/example/authenticator/pam/.gitignore +++ b/example/authenticator/pam/.gitignore @@ -1 +1 @@ -/build/ \ No newline at end of file +/build/ From 7783b294ccd57cc9be6a4a02a85a854a23f4cedb Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Feb 2023 19:59:52 +0100 Subject: [PATCH 89/91] add HTTP server support with given document root. --- doc/webfuse.md | 5 ++++- src/webfuse/webfuse.cpp | 1 + src/webfuse/ws/config.cpp | 4 ++++ src/webfuse/ws/config.hpp | 1 + src/webfuse/ws/server.cpp | 27 +++++++++++++++++++++------ 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/doc/webfuse.md b/doc/webfuse.md index adc749c..34cca0f 100644 --- a/doc/webfuse.md +++ b/doc/webfuse.md @@ -15,11 +15,14 @@ filesystem via fuse and exposes it's API via websockets. | --wf-vhost | vhost | localhost | Specify the name of the websocket server's virtual host | | --wf-cert | path | - | Optional. Specify the file path of the server's public certificate | | --wf-key | path | - | Optional. Specify the file path of the server's private key | -| --wf- authenticator | path | - | Optional. Specify the file path of the authenticator executable | +| --wf-authenticator | path | - | Optional. Specify the file path of the authenticator executable | | --wf-auth-header | name | - | Optional. Specify the name of the HTTP header used for authentication | +| --wf-docroot | path | - | Optional. Enabled HTTP server with given document root. | | --wf-timeout | timeout | 10 | Optional. Specify the communication timeout. | | --wf-version | - | - | Print version and exit. | +**Note:** All paths must be absolute _(this might be relaxed if future versions)_. + ## Fuse options | Option | Descripion | diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index 2a9df2a..58c1799 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -40,6 +40,7 @@ WEBFUSE options: --wf-key PATH path of the server's private key (optional) --wf-authenticator PATH path of authenticatior app (optional) --wf-auth-header NAME name of the authentication header (optional) + --wf-docroot PATH enables HTTP server with given document root (optional) --wf-timeout TIMEOUT communication timeout in seconds (default: 10) --wf-version print version and exit )"; diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp index f280ed8..2a35f4d 100644 --- a/src/webfuse/ws/config.cpp +++ b/src/webfuse/ws/config.cpp @@ -112,6 +112,10 @@ ws_config::ws_config(int argc, char * argv[]) timeout_secs = static_cast(std::stoi(timeout_str)); } } + else if (arg == "--wf-docroot") + { + get_arg(*this, reader, docroot, "missing DOCROOT"); + } else if (arg == "--wf-version") { cmd = command::print_version; diff --git a/src/webfuse/ws/config.hpp b/src/webfuse/ws/config.hpp index a1183bb..068fb4a 100644 --- a/src/webfuse/ws/config.hpp +++ b/src/webfuse/ws/config.hpp @@ -27,6 +27,7 @@ public: uint16_t port; std::string vhost_name; + std::string docroot; bool use_tls; std::string cert_path; diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 731ae77..e41eba8 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -71,20 +71,33 @@ class ws_server::detail public: detail(ws_config const & config) : shutdown_requested(false) + , docroot(config.docroot) , data(config.authenticator, config.auth_header) , timeout_secs(config.timeout_secs) { lws_set_log_level(0, nullptr); + memset(reinterpret_cast(&http_mount), 0, sizeof(http_mount)); + http_mount.mount_next = nullptr; + http_mount.mountpoint = "/"; + http_mount.origin = docroot.c_str(); + http_mount.def = "index.html"; + http_mount.origin_protocol = LWSMPRO_FILE; + http_mount.mountpoint_len = 1; + memset(reinterpret_cast(protocols), 0, sizeof(protocols)); - protocols[0].name = "webfuse2"; - protocols[0].callback = &ws_server_callback; - protocols[0].per_session_data_size = 0; - protocols[0].user = reinterpret_cast(&data); + protocols[0].name = "http"; + protocols[0].callback = lws_callback_http_dummy; + + protocols[1].name = "webfuse2"; + protocols[1].callback = &ws_server_callback; + protocols[1].per_session_data_size = 0; + protocols[1].user = reinterpret_cast(&data); memset(reinterpret_cast(&info), 0, sizeof(info)); info.port = config.port; - info.protocols = protocols; + info.mounts = &http_mount; + info.protocols = (docroot.empty()) ? &protocols[1] : protocols; info.vhost_name = config.vhost_name.c_str(); info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE | LWS_SERVER_OPTION_EXPLICIT_VHOSTS; @@ -121,9 +134,11 @@ public: std::thread thread; std::atomic shutdown_requested; - lws_protocols protocols[2]; + lws_protocols protocols[3]; + lws_http_mount http_mount; lws_context_creation_info info; lws_context * context; + std::string docroot; server_handler data; uint64_t timeout_secs; }; From 17d6275d96fa3489df63dba534a81594fa9c8b39 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 5 Feb 2023 00:11:02 +0100 Subject: [PATCH 90/91] add basic infrastructure of javascript example --- example/provider/javascript/README.md | 6 + example/provider/javascript/index.html | 17 + example/provider/javascript/js/startup.js | 26 ++ .../javascript/js/static_filesystem.js | 41 +++ .../javascript/js/webfuse/accessmode.js | 9 + .../javascript/js/webfuse/basefilesystem.js | 90 ++++++ .../provider/javascript/js/webfuse/errno.js | 40 +++ .../javascript/js/webfuse/messagereader.js | 59 ++++ .../javascript/js/webfuse/messagewriter.js | 76 +++++ .../provider/javascript/js/webfuse/webfuse.js | 305 ++++++++++++++++++ 10 files changed, 669 insertions(+) create mode 100644 example/provider/javascript/README.md create mode 100644 example/provider/javascript/index.html create mode 100644 example/provider/javascript/js/startup.js create mode 100644 example/provider/javascript/js/static_filesystem.js create mode 100644 example/provider/javascript/js/webfuse/accessmode.js create mode 100644 example/provider/javascript/js/webfuse/basefilesystem.js create mode 100644 example/provider/javascript/js/webfuse/errno.js create mode 100644 example/provider/javascript/js/webfuse/messagereader.js create mode 100644 example/provider/javascript/js/webfuse/messagewriter.js create mode 100644 example/provider/javascript/js/webfuse/webfuse.js diff --git a/example/provider/javascript/README.md b/example/provider/javascript/README.md new file mode 100644 index 0000000..629ac37 --- /dev/null +++ b/example/provider/javascript/README.md @@ -0,0 +1,6 @@ +# Webfuse JavaScript example + + mkdir -p /tmp/test + webfuse -f /tmp/test --wf-docroot . + +Visit [http://localhost:8081/](http://localhost:8081/). diff --git a/example/provider/javascript/index.html b/example/provider/javascript/index.html new file mode 100644 index 0000000..8570de9 --- /dev/null +++ b/example/provider/javascript/index.html @@ -0,0 +1,17 @@ + + + + Webfuse Example + + + +

Webfuse

+

+ + +

+

+ +

+ + \ No newline at end of file diff --git a/example/provider/javascript/js/startup.js b/example/provider/javascript/js/startup.js new file mode 100644 index 0000000..8189e5b --- /dev/null +++ b/example/provider/javascript/js/startup.js @@ -0,0 +1,26 @@ + +import { Webfuse } from "./webfuse/webfuse.js"; +import { StaticFileSystem } from "./static_filesystem.js"; + +let webfuse = null; +const filesystem = new StaticFileSystem(new Map([ + ["/foo", "foo"], + ["/bar", "foo"] +])); + +function onConnectButtonClicked() { + if (webfuse) { webfuse.close(); } + + const urlTextfield = document.querySelector('#url'); + const url = urlTextfield.value; + console.log(url); + + webfuse = new Webfuse(url, filesystem); +} + +function startup() { + const connectButton = document.querySelector('#connect'); + connectButton.addEventListener('click', onConnectButtonClicked); +} + +document.addEventListener('DOMContentLoaded', startup(),false); diff --git a/example/provider/javascript/js/static_filesystem.js b/example/provider/javascript/js/static_filesystem.js new file mode 100644 index 0000000..02a81a5 --- /dev/null +++ b/example/provider/javascript/js/static_filesystem.js @@ -0,0 +1,41 @@ + +import { BaseFileSystem, ERRNO, Mode } from "./webfuse/webfuse.js" + +class StaticFileSystem extends BaseFileSystem { + + constructor(files) { + super(); + this.files = files; + } + + getattr(path) { + console.log("getattr", path); + + if (path == "/") { + return { + nlink: 2, + mode: Mode.DIR | 0o555 + }; + } + else if (this.files.has(path)) { + const contents = this.files.get(path); + return { + nlink: 1, + mode: Mode.REG | 0o444, + size: contents.length + } + } + + return ERRNO.ENOENT; + } + + readdir(path) { + if (path == "/") { + return ["foo", "bar"] + } + + return ERRNO.ENOENT; + } +} + +export { StaticFileSystem } diff --git a/example/provider/javascript/js/webfuse/accessmode.js b/example/provider/javascript/js/webfuse/accessmode.js new file mode 100644 index 0000000..dca6300 --- /dev/null +++ b/example/provider/javascript/js/webfuse/accessmode.js @@ -0,0 +1,9 @@ + +const AccessMode = { + F_OK: 0, + R_OK: 4, + W_OK: 2, + X_OK: 1 +}; + +export { AccessMode } diff --git a/example/provider/javascript/js/webfuse/basefilesystem.js b/example/provider/javascript/js/webfuse/basefilesystem.js new file mode 100644 index 0000000..1f987df --- /dev/null +++ b/example/provider/javascript/js/webfuse/basefilesystem.js @@ -0,0 +1,90 @@ +import { ERRNO } from "./errno.js" + +class BaseFileSystem { + + access(path, mode) { + return ERRNO.ENOENT; + } + + getattr(path) { + return ERRNO.ENOENT; + } + + readlink(path) { + return ERRNO.ENOENT; + } + + symlink(target, linkpath) { + return ERRNO.ENOENT; + } + + link(oldpath, newpath) { + return ERRNO.ENOENT; + } + + rename(oldpath, newpath, flags) { + return ERRNO.ENOENT; + } + + chmod(path, mode) { + return ERRNO.ENOENT; + } + + chown(path, uid, gid) { + return ERRNO.ENOENT; + } + + truncate(path, size, fd) { + return ERRNO.ENOENT; + } + + open(path, flags) { + return [ERRNO.ENOENT, 0]; + } + + mknod(path, mode, rdev) { + return ERRNO.ENOENT; + } + + create(path, mode) { + return [ERNNO.ENOEND, 0]; + } + + release(path, fd) { + return ERRNO.ENOENT; + } + + unlink(path) { + return ERRNO.ENOENT; + } + + read(path, size, offset, fd) { + return ERRNO.ENOENT; + } + + write(path, data, offset, fd) { + return ERRNO.ENOENT; + } + + mkdir(path, mode) { + return ERRNO.ENOENT; + } + + readdir(path) { + return ERRNO.ENOENT; + } + + rmdir(path) { + return ERRNO.ENOENT; + } + + statfs(path) { + return ERRNO.ENOENT; + } + + getcreds() { + return ""; + } +} + +export { BaseFileSystem } diff --git a/example/provider/javascript/js/webfuse/errno.js b/example/provider/javascript/js/webfuse/errno.js new file mode 100644 index 0000000..1c6047b --- /dev/null +++ b/example/provider/javascript/js/webfuse/errno.js @@ -0,0 +1,40 @@ +const ERRNO = { + E2BIG : -7, + EACCES : -13, + EAGAIN : -11, + EBADF : -9, + EBUSY : -16, + EDESTADDRREQ : -89, + EDQUOT : -122, + EEXIST : -17, + EFAULT : -14, + EFBIG : -27, + EINTR : -4, + EINVAL : -22, + EIO : -5, + EISDIR : -21, + ELOOP : -40, + EMFILE : -24, + EMLINK : -31, + ENAMETOOLONG : -36, + ENFILE : -23, + ENODATA : -61, + ENODEV : -19, + ENOENT : -2, + ENOMEM : -12, + ENOSPC : -28, + ENOSYS : -38, + ENOTDIR : -20, + ENOTEMPTY : -39, + ENOTSUP : -95, + ENXIO : -6, + EOVERFLOW : -75, + EPERM : -1, + EPIPE : -32, + ERANGE : -34, + EROFS : -30, + ETXTBSY : -26, + EXDEV : -18 +}; + +export { ERRNO } \ No newline at end of file diff --git a/example/provider/javascript/js/webfuse/messagereader.js b/example/provider/javascript/js/webfuse/messagereader.js new file mode 100644 index 0000000..f58958a --- /dev/null +++ b/example/provider/javascript/js/webfuse/messagereader.js @@ -0,0 +1,59 @@ +class MessageReader { + + constructor(data) { + // console.log(new Uint8Array(data)); + this.raw = data; + this.data = new DataView(data); + this.pos = 0; + this.decoder = new TextDecoder('utf-8'); + } + + read_u8() { + const result = this.data.getUint8(this.pos); + this.pos++; + return result; + } + + read_bool() { + return this.read_u8() == 1; + } + + read_u32() { + const result = this.data.getUint32(this.pos); + this.pos += 4; + return result; + } + + read_u64() { + const result = this.data.getBigUint64(this.pos); + this.pos += 8; + return Number(result); + } + + read_str() { + const length = this.read_u32(); + if (length > 0) { + const view = new Uint8Array(this.raw, this.pos, length); + this.pos += length; + return this.decoder.decode(view); + } + else { + return ""; + } + } + + read_bytes() { + const length = this.read_u32(); + if (length > 0) { + const view = new Uint8Array(this.raw, this.pos, length); + this.pos += length; + return view; + } + else { + return []; + } + } + +} + +export { MessageReader } \ No newline at end of file diff --git a/example/provider/javascript/js/webfuse/messagewriter.js b/example/provider/javascript/js/webfuse/messagewriter.js new file mode 100644 index 0000000..ec1f504 --- /dev/null +++ b/example/provider/javascript/js/webfuse/messagewriter.js @@ -0,0 +1,76 @@ +class MessageWriter { + + constructor(message_id, message_type) { + this.data = [ ] + this.write_u32(message_id) + this.write_u8(message_type) + this.encoder = new TextEncoder("utf-8"); + } + + write_u8(value) { + this.data.push(value) + } + + write_u32(value) { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setUint32(0, value); + + const data = new Uint8Array(buffer); + this.data.push(...data); + } + + write_i32(value) { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setInt32(0, value); + + const data = new Uint8Array(buffer); + this.data.push(...data); + } + + write_u64(value) { + const buffer = new ArrayBuffer(8); + const view = new DataView(buffer); + view.setBigUint64(0, BigInt(value)); + + const data = new Uint8Array(buffer); + this.data.push(...data); + } + + // value in milliseconds + write_time(value) { + const seconds = Math.floor(value / 1000); + const millis = value % 1000; + const nanos = millis * 1000 * 1000; + + this.write_u64(seconds); + this.write_u32(nanos); + } + + write_str(value) { + const data = this.encoder.encode(value); + this.write_u32(data.length); + this.data.push(...data); + } + + write_strings(list) { + this.write_u32(list.length); + for(const item of list) { + this.write_str(item); + } + } + + write_bytes(value) { + this.write_u32(value.length); + this.data.push(...value); + } + + get_data() { + // console.log(this.data) + return new Uint8Array(this.data); + } + +} + +export { MessageWriter } \ No newline at end of file diff --git a/example/provider/javascript/js/webfuse/webfuse.js b/example/provider/javascript/js/webfuse/webfuse.js new file mode 100644 index 0000000..1c53751 --- /dev/null +++ b/example/provider/javascript/js/webfuse/webfuse.js @@ -0,0 +1,305 @@ +import { MessageWriter } from "./messagewriter.js"; +import { MessageReader } from "./messagereader.js"; +import { ERRNO } from "./errno.js"; +import { AccessMode } from "./accessmode.js"; +import { BaseFileSystem } from "./basefilesystem.js"; + + +const Mode = { + REG : 0o100000, + DIR : 0o040000, + CHR : 0o020000, + BLK : 0o060000, + FIFO : 0o010000, + LNK : 0o120000, + SOCK : 0o140000 +}; + + + +function fs_access(reader, writer, filesystem) { + const path = reader.read_str(); + const mode = reader.read_u8(); + result = filesystem.access(path, mode); + writer.write_i32(result); +} + +function fs_getattr(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.getattr(path); + if (typeof(result) !== "number") { + writer.write_i32(0); + writer.write_u64(result.ino | 0); + writer.write_u64(result.nlink | 0); + writer.write_u32(result.mode | 0); + writer.write_i32(result.uid | 0); + writer.write_i32(result.gid | 0); + writer.write_u64(result.dev | 0); + writer.write_u64(result.size | 0); + writer.write_u64(result.blocks | 0); + writer.write_time(result.atime | 0); + writer.write_time(result.mtime | 0); + writer.write_time(result.ctime | 0); + } + else { + writer.write_i32(result); + } +} + +function fs_readlink(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.readlink(path); + if (typeof(result) != "number") { + writer.write_i32(0); + writer.write_str(result); + } + else { + writer.write_i32(result); + } +} + +function fs_symlink(reader, writer, filesystem) { + const target = reader.read_str(); + const linkpath = reader.read_str(); + const result = filesystem.symlink(target, linkpath); + writer.write_i32(result); +} + + +function fs_link(reader, writer, filesystem) { + const oldpath = reader.read_str(); + const newpath = reader.read_str(); + const result = filesystem.link(oldpath, newpath); + writer.write_i32(result); +} + +function fs_rename(reader, writer, filesystem) { + const oldpath = reader.read_str(); + const newpath = reader.read_str(); + const flags = reader.read_u8(); + const result = filesystem.rename(oldpath, newpath, flags); + writer.write_i32(result); +} + +function fs_chmod(reader, writer, filesystem) { + const path = reader.read_str(); + const mode = reader.read_u32(); + const result = filesystem.chmod(path, mode); + writer.write_i32(result); +} + +function fs_chown(reader, writer, filesystem) { + const path = reader.read_str(); + const uid = reader.read_u32(); + const gid = reader.read_u32(); + const result = filesystem.chown(path, uid, gid); + writer.write_i32(result); +} + +function fs_truncate(reader, writer, filesystem) { + const path = reader.read_str(); + const size = reader.read_u64(); + const fd = reader.read_u64(); + const result = filesystem.truncate(path, size, fd); + writer.write_i32(result); +} + +function fs_fsync(reader, writer, filesystem) { + const path = reader.read_str(); + const isDataSync = reader.read_bool(); + const fd = reader.read_fd(); + const result = filesystem.fsync(path, isDataSync, fd); + writer.write_i32(result); +} + +function fs_open(reader, writer, filesystem) { + const path = reader.read_str(); + const flags = reader.read_u32(); + const [result, fd] = filesystem.open(path, flags); + writer.write_i32(result); + writer.write_u64(fd); +} + +function fs_mknod(reader, writer, filesystem) { + const path = reader.read_str(); + const mode = reader.read_u32(); + const rdev = reader.read_u64(); + const result = filesystem.mknod(path, mode, rdev); + writer.write_i32(result); +} + +function fs_create(reader, writer, filesystem) { + const path = reader.read_str(); + const mode = reader.read_u32(); + const [result, fd] = filesystem.create(path, mode); + writer.write_i32(result); + writer.write_u64(fd); +} + +function fs_release(reader, writer, filesystem) { + const path = reader.read_str(); + const fd = reader.read_u64(); + const result = filesystem.release(path, fd); + writer.write_i32(result); +} + +function fs_unlink(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.unlink(path); + writer.write_i32(result); +} + +function fs_read(reader, writer, filesystem) { + const path = reader.read_str(); + const size = reader.read_u32(); + const offset = reader.read_u64(); + const fd = reader.read_u64(); + const result = filesystem.read(path, size, offset, fd); + if (typeof(result) != "number") { + writer.write_i32(0); + writer.write_bytes(result); + } + else { + writer.write_i32(result); + } +} + +function fs_write(reader, wriuter, filesystem) { + const path = reader.read_str(); + const data = reader.read_bytes(); + const offset = reader.read_u64(); + const fd = reader.read_u64(); + const result = filesystem.write(path, data, offset, fd); + writer.write_i32(result); +} + +function fs_mkdir(reader, writer, filesystem) { + const path = reader.read_str() + const mode = reader.read_u32(); + const result = filesystem.mkdir(path, mode); + writer.write_i32(result); +} + +function fs_readdir(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.readdir(path); + if (typeof(result) != "number") { + writer.write_i32(0); + writer.write_strings(result); + } + else { + writer.write_i32(result); + } +} + +function fs_rmdir(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.rmdir(path); + writer.write_i32(result); +} + +function fs_statfs(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.statfs(path); + if (typeof(result) != "number") { + writer.write_i32t(0) + writer.write_u64(result.bsize | 0); + writer.write_u64(result.frsize | 0); + writer.write_u64(result.blocks | 0); + writer.write_u64(result.bfree | 0); + writer.write_u64(result.bavail | 0); + writer.write_u64(result.files | 0); + writer.write_u64(result.ffree | 0); + writer.write_u64(result.namemax | 0); + } + else { + writer.write_i32(result); + } +} + +function fs_utimens(reader, writer, filesystem) { + const path = reader.read_str(); + const atime = reader.read_time(); + const mtime = reader.read_time(); + const result = filesystem.utimens(path, atime, mtime); + writer.write_i32(result); +} + +function fs_getcreds(reader, writer, filesystem) { + const credentials = filesystem.getcreds(); + writer.write_str(credentials); +} + +const commands = new Map([ + [0x01, fs_access], + [0x02, fs_getattr], + [0x03, fs_readlink], + [0x04, fs_symlink], + [0x05, fs_link], + [0x06, fs_rename], + [0x07, fs_chmod], + [0x08, fs_chown], + [0x09, fs_truncate], + [0x0a, fs_fsync], + [0x0b, fs_open], + [0x0c, fs_mknod], + [0x0d, fs_create], + [0x0e, fs_release], + [0x0f, fs_unlink], + [0x10, fs_read], + [0x11, fs_write], + [0x12, fs_mkdir], + [0x13, fs_readdir], + [0x14, fs_rmdir], + [0x15, fs_statfs], + [0x16, fs_utimens], + [0x17, fs_getcreds] +]); + +class Webfuse { + + constructor(url, filesystem) { + console.log('webfuse: ctor') + + this.ws = new WebSocket(url, ["webfuse2"]); + this.ws.binaryType = 'arraybuffer'; + this.ws.addEventListener('close', (event) => this.on_closed(event)); + this.ws.addEventListener('error', (event) => this.on_error(event)); + this.ws.addEventListener('message', (event) => this.on_message(event)); + + this.filesystem = filesystem; + } + + close() { + this.ws.close(); + } + + on_message(event) { + const reader = new MessageReader(event.data); + const message_id = reader.read_u32(); + const message_type = reader.read_u8(); + + const writer = new MessageWriter(message_id, 0x80 + message_type); + if (commands.has(message_type)) { + const command = commands.get(message_type); + command(reader, writer, this.filesystem); + } + else { + console.error(`unknow message type: ${message_type}`); + } + + this.ws.send(writer.get_data()); + } + + on_error(event) { + console.log('error', event); + this.ws.close(); + } + + on_closed(event) { + console.log('closed', event); + } + +} + +export { Webfuse, BaseFileSystem, ERRNO, Mode, AccessMode } From acbcbc1d97f0a808a645bb491447d842e029d4d8 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 5 Feb 2023 11:37:19 +0100 Subject: [PATCH 91/91] add javascript example --- example/provider/javascript/index.html | 22 +++- example/provider/javascript/js/filesystem.js | 104 ++++++++++++++++++ example/provider/javascript/js/startup.js | 33 +++++- .../javascript/js/static_filesystem.js | 41 ------- .../javascript/js/webfuse/basefilesystem.js | 36 ++++-- .../javascript/js/webfuse/messagereader.js | 1 - .../javascript/js/webfuse/messagewriter.js | 1 - .../javascript/js/webfuse/openflags.js | 28 +++++ .../provider/javascript/js/webfuse/webfuse.js | 20 ++-- example/provider/javascript/style.css | 28 +++++ 10 files changed, 244 insertions(+), 70 deletions(-) create mode 100644 example/provider/javascript/js/filesystem.js delete mode 100644 example/provider/javascript/js/static_filesystem.js create mode 100644 example/provider/javascript/js/webfuse/openflags.js create mode 100644 example/provider/javascript/style.css diff --git a/example/provider/javascript/index.html b/example/provider/javascript/index.html index 8570de9..588f949 100644 --- a/example/provider/javascript/index.html +++ b/example/provider/javascript/index.html @@ -3,15 +3,35 @@ Webfuse Example + -

Webfuse

+

Webfuse Example

+ +

+ This example provides a single file "README.md", which + contents can be altered below. +

+ +

Connection

+

+ + +

+ disconnected +

+ +

README.md

+

+

\ No newline at end of file diff --git a/example/provider/javascript/js/filesystem.js b/example/provider/javascript/js/filesystem.js new file mode 100644 index 0000000..d571d5c --- /dev/null +++ b/example/provider/javascript/js/filesystem.js @@ -0,0 +1,104 @@ + +import { BaseFileSystem, ERRNO, Mode, AccessMode, OpenFlags } from "./webfuse/webfuse.js" + +class FileSystem extends BaseFileSystem { + + constructor(tokenProvider, stateListener, files) { + super(); + + this.tokenProvider = tokenProvider; + this.stateListener = stateListener + this.files = new Map(); + for(const file of files) { + this.files.set("/" + file.name, file); + } + } + + access(path, mode) { + // we do not allow write or execute + if ((mode & AccessMode.W_OK) || (mode & AccessMode.X_OK)) { + return ERRNO.EACCES; + } + + if ((path = "/") || (this.files.has(path))) { + return 0; + } + + return ERRNO.ENOENT; + } + + getattr(path) { + if (path == "/") { + return { + nlink: 2, + mode: Mode.DIR | 0o555 + }; + } + else if (this.files.has(path)) { + const file = this.files.get(path); + const contents = file.contents(); + return { + nlink: 1, + mode: Mode.REG | 0o444, + size: contents.length + }; + } + + return ERRNO.ENOENT; + } + + readdir(path) { + if (path == "/") { + const list = []; + for(const file of this.files.values()) { + list.push(file.name); + } + return list; + } + + return ERRNO.ENOENT; + } + + open(path, flags) { + if (this.files.has(path)) { + const accessMode = flags & OpenFlags.ACCESS_MODE; + if (accessMode == OpenFlags.RDONLY) { + return [0, 0]; + } + else { + return [ERRNO.EPERM, 0]; + } + } + + return [ERRNO.ENOENT, 0]; + } + + read(path, size, offset, fd) { + if (this.files.has(path)) { + const file = this.files.get(path); + const contents = file.contents(); + if (offset < contents.length) { + const available = contents.length - offset; + const length = (size < available) ? size : available; + const data = contents.slice(offset, offset + length); + return data; + } + + return []; + } + else { + return ERRNO.EBADF; + } + } + + getcreds() { + const token = this.tokenProvider(); + return token; + } + + connectionstatechanged(state) { + this.stateListener(state); + } +} + +export { FileSystem } diff --git a/example/provider/javascript/js/startup.js b/example/provider/javascript/js/startup.js index 8189e5b..576224e 100644 --- a/example/provider/javascript/js/startup.js +++ b/example/provider/javascript/js/startup.js @@ -1,19 +1,40 @@ import { Webfuse } from "./webfuse/webfuse.js"; -import { StaticFileSystem } from "./static_filesystem.js"; +import { FileSystem } from "./filesystem.js"; + + +function encode(value) { + const encoder = new TextEncoder('utf-8'); + return encoder.encode(value); +} + +function get_contents() { + const contentTextArea = document.querySelector("#contents"); + const contents = contentTextArea.value; + return encode(contents); +} + +function get_token() { + const tokenTextfield = document.querySelector('#token'); + const token = tokenTextfield.value; + return token; +} + +function update_state(state) { + const stateTextField = document.querySelector("#state"); + stateTextField.textContent = (state == "connected") ? "connected" : "disconnected"; +} let webfuse = null; -const filesystem = new StaticFileSystem(new Map([ - ["/foo", "foo"], - ["/bar", "foo"] -])); +const filesystem = new FileSystem(get_token, update_state, [ + {name: "README.md", contents: get_contents } +]); function onConnectButtonClicked() { if (webfuse) { webfuse.close(); } const urlTextfield = document.querySelector('#url'); const url = urlTextfield.value; - console.log(url); webfuse = new Webfuse(url, filesystem); } diff --git a/example/provider/javascript/js/static_filesystem.js b/example/provider/javascript/js/static_filesystem.js deleted file mode 100644 index 02a81a5..0000000 --- a/example/provider/javascript/js/static_filesystem.js +++ /dev/null @@ -1,41 +0,0 @@ - -import { BaseFileSystem, ERRNO, Mode } from "./webfuse/webfuse.js" - -class StaticFileSystem extends BaseFileSystem { - - constructor(files) { - super(); - this.files = files; - } - - getattr(path) { - console.log("getattr", path); - - if (path == "/") { - return { - nlink: 2, - mode: Mode.DIR | 0o555 - }; - } - else if (this.files.has(path)) { - const contents = this.files.get(path); - return { - nlink: 1, - mode: Mode.REG | 0o444, - size: contents.length - } - } - - return ERRNO.ENOENT; - } - - readdir(path) { - if (path == "/") { - return ["foo", "bar"] - } - - return ERRNO.ENOENT; - } -} - -export { StaticFileSystem } diff --git a/example/provider/javascript/js/webfuse/basefilesystem.js b/example/provider/javascript/js/webfuse/basefilesystem.js index 1f987df..1a68d87 100644 --- a/example/provider/javascript/js/webfuse/basefilesystem.js +++ b/example/provider/javascript/js/webfuse/basefilesystem.js @@ -27,15 +27,19 @@ class BaseFileSystem { } chmod(path, mode) { - return ERRNO.ENOENT; + return ERRNO.EPERM; } chown(path, uid, gid) { - return ERRNO.ENOENT; + return ERRNO.EPERM; } truncate(path, size, fd) { - return ERRNO.ENOENT; + return ERRNO.EPERM; + } + + fsync(path, isDataSync, fd) { + return 0; } open(path, flags) { @@ -43,31 +47,31 @@ class BaseFileSystem { } mknod(path, mode, rdev) { - return ERRNO.ENOENT; + return ERRNO.EPERM; } create(path, mode) { - return [ERNNO.ENOEND, 0]; + return [ERRNO.EPERM, 0]; } release(path, fd) { - return ERRNO.ENOENT; + return 0; } unlink(path) { - return ERRNO.ENOENT; + return ERRNO.EPERM; } read(path, size, offset, fd) { - return ERRNO.ENOENT; + return ERRNO.EBADF; } write(path, data, offset, fd) { - return ERRNO.ENOENT; + return ERRNO.EBADF; } mkdir(path, mode) { - return ERRNO.ENOENT; + return ERRNO.EPERM; } readdir(path) { @@ -75,16 +79,24 @@ class BaseFileSystem { } rmdir(path) { - return ERRNO.ENOENT; + return ERRNO.EPERM; } statfs(path) { - return ERRNO.ENOENT; + return ERRNO.ENOSYS; + } + + utimens(path, atime, mtime) { + return ERRNO.ENOSYS; } getcreds() { return ""; } + + connectionstatechanged(state) { + // pass + } } export { BaseFileSystem } diff --git a/example/provider/javascript/js/webfuse/messagereader.js b/example/provider/javascript/js/webfuse/messagereader.js index f58958a..737564a 100644 --- a/example/provider/javascript/js/webfuse/messagereader.js +++ b/example/provider/javascript/js/webfuse/messagereader.js @@ -1,7 +1,6 @@ class MessageReader { constructor(data) { - // console.log(new Uint8Array(data)); this.raw = data; this.data = new DataView(data); this.pos = 0; diff --git a/example/provider/javascript/js/webfuse/messagewriter.js b/example/provider/javascript/js/webfuse/messagewriter.js index ec1f504..3c5a9f9 100644 --- a/example/provider/javascript/js/webfuse/messagewriter.js +++ b/example/provider/javascript/js/webfuse/messagewriter.js @@ -67,7 +67,6 @@ class MessageWriter { } get_data() { - // console.log(this.data) return new Uint8Array(this.data); } diff --git a/example/provider/javascript/js/webfuse/openflags.js b/example/provider/javascript/js/webfuse/openflags.js new file mode 100644 index 0000000..b63badf --- /dev/null +++ b/example/provider/javascript/js/webfuse/openflags.js @@ -0,0 +1,28 @@ + +const OpenFlags = { + ACCESS_MODE: 0x03, + RDONLY : 0o00, + WRONLY : 0o01, + RDWR : 0o02, + + APPEND : 0o00002000, + ASYNC : 0o00020000, + CLOEXEC : 0o02000000, + CREAT : 0o00000100, + DIRECT : 0o00040000, + DIRECTORY : 0o00200000, + DSYNC : 0o00010000, + EXCL : 0o00000200, + LARGEFILE : 0o00100000, + NOATIME : 0o01000000, + NOCTTY : 0o00000400, + NOFOLLOW : 0o00400000, + NONBLOCK : 0o00004000, + NDELAY : 0o00004000, + PATH : 0o10000000, + SYNC : 0o04010000, + TMPFILE : 0o20200000, + TRUNC : 0o00001000 +}; + +export { OpenFlags } \ No newline at end of file diff --git a/example/provider/javascript/js/webfuse/webfuse.js b/example/provider/javascript/js/webfuse/webfuse.js index 1c53751..c8e49de 100644 --- a/example/provider/javascript/js/webfuse/webfuse.js +++ b/example/provider/javascript/js/webfuse/webfuse.js @@ -2,6 +2,7 @@ import { MessageWriter } from "./messagewriter.js"; import { MessageReader } from "./messagereader.js"; import { ERRNO } from "./errno.js"; import { AccessMode } from "./accessmode.js"; +import { OpenFlags } from "./openflags.js"; import { BaseFileSystem } from "./basefilesystem.js"; @@ -20,7 +21,7 @@ const Mode = { function fs_access(reader, writer, filesystem) { const path = reader.read_str(); const mode = reader.read_u8(); - result = filesystem.access(path, mode); + const result = filesystem.access(path, mode); writer.write_i32(result); } @@ -156,7 +157,7 @@ function fs_read(reader, writer, filesystem) { const fd = reader.read_u64(); const result = filesystem.read(path, size, offset, fd); if (typeof(result) != "number") { - writer.write_i32(0); + writer.write_i32(result.length); writer.write_bytes(result); } else { @@ -259,10 +260,9 @@ const commands = new Map([ class Webfuse { constructor(url, filesystem) { - console.log('webfuse: ctor') - this.ws = new WebSocket(url, ["webfuse2"]); this.ws.binaryType = 'arraybuffer'; + this.ws.addEventListener('open', (event) => this.on_connected(event)); this.ws.addEventListener('close', (event) => this.on_closed(event)); this.ws.addEventListener('error', (event) => this.on_error(event)); this.ws.addEventListener('message', (event) => this.on_message(event)); @@ -285,21 +285,25 @@ class Webfuse { command(reader, writer, this.filesystem); } else { - console.error(`unknow message type: ${message_type}`); + console.warn(`unknow message type: ${message_type}`); } this.ws.send(writer.get_data()); } + on_connected(event) { + this.filesystem.connectionstatechanged("connected"); + } + on_error(event) { - console.log('error', event); + console.info("connection error"); this.ws.close(); } on_closed(event) { - console.log('closed', event); + this.filesystem.connectionstatechanged("closed"); } } -export { Webfuse, BaseFileSystem, ERRNO, Mode, AccessMode } +export { Webfuse, BaseFileSystem, ERRNO, Mode, AccessMode, OpenFlags } diff --git a/example/provider/javascript/style.css b/example/provider/javascript/style.css new file mode 100644 index 0000000..52ee0d5 --- /dev/null +++ b/example/provider/javascript/style.css @@ -0,0 +1,28 @@ +html, body { + background-color: #c0c0c0; +} + +h1 { + background-color: black; + color: white; +} + +label { + display: inline-block; + width: 150px; +} + +input { + width: 150px; +} + +#stats { + display: inline-block; + text-align: right; + width: 150px; +} + +textarea { + width: 300px; + height: 300px; +} \ No newline at end of file