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; + std::future f; + { + std::promise p; + f = p.get_future(); - try - { - throw std::runtime_error("not implemented"); + 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;