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