From 3a7c064af70db0ec1e1a707a0f09755374ebc001 Mon Sep 17 00:00:00 2001 From: Falk Werner <47070255+falk-werner@users.noreply.github.com> Date: Wed, 17 Apr 2019 22:51:16 +0200 Subject: [PATCH] feat(webfuse): add multiclient support (#23) * fixes verbosity option when set through command line * adds support for build type and allows to run gdb in container * adds missing toolchain headers to project * renames container macros * adds gdbserver * fixes verbosity option when set through command line * adds support for build type and allows to run gdb in container * adds missing toolchain headers to project * renames container macros * adds gdbserver * removes language settings, which contains alternating values * adds wrapper script to launch gdbserver * fix docker command in wrapper script * fixes run in dind setup * replaces docker's init through dump-init * moves filesystem to session * fixes verbosity option when set through command line * adds support for build type and allows to run gdb in container * renames container macros * adds gdbserver * fixes verbosity option when set through command line * adds support for build type and allows to run gdb in container * renames container macros * adds gdbserver * adds wrapper script to launch gdbserver * fix docker command in wrapper script * fixes run in dind setup * replaces docker's init through dump-init * moves filesystem to session * adds container_of * added dlist * allows multiple clients to connect * removes directory when session is closed * adds dependecy to uuid-dev * allow clients to register filesystems * updates documentation * moves mountpoint handling into filesystem: mountpoints are removed during session cleanup * adds filesystem name/id to request parameters * fixes security issue: add_filesystem did not check name * removes default link, if it is broken * recreates symlink "default", if filesystem is gone * updates documentation * fixes memory leak * makes authentication work .. again * updates provider to support changed protocol * removes execute right of hello.txt * fixes style issues * fixes javascript style issues * fixes flase positive from Flawfinder * fixes some javascript style issues * removes use of PATH_MAX * removes use of GNU extensions in container_of implementation * ignores findings of flawfinder * replaces dlist by slist * removes duplicate implementation of slist (message_queue) --- CMakeLists.txt | 9 + README.md | 58 +++++-- build/amd64-ubuntu-builder.dockerfile | 3 +- build/arm32v7-ubuntu-builder.dockerfile | 3 +- doc/authenticate.png | Bin 37433 -> 28435 bytes doc/authenticate.uml | 18 +- doc/concept.png | Bin 30784 -> 33697 bytes doc/concept.uml | 7 +- doc/filesystem.png | Bin 0 -> 5354 bytes doc/filesystem.uml | 16 ++ example/daemon/main.c | 5 +- example/daemon/www/js/connection_view.js | 30 ++-- example/daemon/www/js/startup.js | 4 +- example/daemon/www/js/webfuse/client.js | 136 +++++++++++---- example/provider/main.c | 2 +- lib/webfuse/adapter/impl/filesystem.c | 159 ++++++++++++++++-- lib/webfuse/adapter/impl/filesystem.h | 23 ++- lib/webfuse/adapter/impl/operation/close.c | 4 +- lib/webfuse/adapter/impl/operation/getattr.c | 4 +- lib/webfuse/adapter/impl/operation/lookup.c | 4 +- lib/webfuse/adapter/impl/operation/open.c | 4 +- lib/webfuse/adapter/impl/operation/read.c | 4 +- lib/webfuse/adapter/impl/operation/readdir.c | 4 +- lib/webfuse/adapter/impl/operations.c | 6 +- lib/webfuse/adapter/impl/operations.h | 8 +- lib/webfuse/adapter/impl/server.c | 17 +- lib/webfuse/adapter/impl/server_protocol.c | 112 ++++++++---- lib/webfuse/adapter/impl/server_protocol.h | 5 +- lib/webfuse/adapter/impl/session.c | 127 +++++++++++--- lib/webfuse/adapter/impl/session.h | 34 +++- lib/webfuse/adapter/impl/session_manager.c | 71 +++++--- lib/webfuse/adapter/impl/session_manager.h | 10 +- lib/webfuse/core/container_of.h | 13 ++ lib/webfuse/core/message.c | 1 - lib/webfuse/core/message.h | 6 +- lib/webfuse/core/message_queue.c | 62 +------ lib/webfuse/core/message_queue.h | 27 +-- lib/webfuse/core/slist.c | 77 +++++++++ lib/webfuse/core/slist.h | 45 +++++ lib/webfuse/core/string.c | 35 ++++ lib/webfuse/core/string.h | 21 +++ lib/webfuse/provider/impl/client_protocol.c | 36 +++- lib/webfuse/provider/impl/client_protocol.h | 4 +- lib/webfuse/provider/impl/operation/close.c | 8 +- lib/webfuse/provider/impl/operation/getattr.c | 4 +- lib/webfuse/provider/impl/operation/lookup.c | 6 +- lib/webfuse/provider/impl/operation/open.c | 6 +- lib/webfuse/provider/impl/operation/read.c | 10 +- lib/webfuse/provider/impl/operation/readdir.c | 4 +- test/test_container_of.cc | 29 ++++ test/test_slist.cc | 35 ++++ test/test_string.cc | 18 ++ 52 files changed, 997 insertions(+), 337 deletions(-) create mode 100644 doc/filesystem.png create mode 100644 doc/filesystem.uml create mode 100644 lib/webfuse/core/container_of.h create mode 100644 lib/webfuse/core/slist.c create mode 100644 lib/webfuse/core/slist.h create mode 100644 lib/webfuse/core/string.c create mode 100644 lib/webfuse/core/string.h create mode 100644 test/test_container_of.cc create mode 100644 test/test_slist.cc create mode 100644 test/test_string.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 082d3b0..774eca6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(FUSE3 REQUIRED fuse3) pkg_check_modules(LWS REQUIRED libwebsockets) pkg_check_modules(JANSSON REQUIRED jansson) +pkg_check_modules(UUID REQUIRED uuid) add_definitions(-D_FILE_OFFSET_BITS=64) @@ -26,6 +27,7 @@ set(EXTRA_INCLUDE_DIRS ${FUSE3_INCLUDE_DIRS} ${LWS_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} + ${UUID_INCLUDE_DIRS} ) set(EXTRA_LIBS @@ -33,6 +35,7 @@ set(EXTRA_LIBS ${FUSE3_LIBRARIES} ${LWS_LIBRARIES} ${JANSSON_LIBRARIES} + ${UUID_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) @@ -41,6 +44,7 @@ set(EXTRA_CFLAGS ${FUSE3_CFLAGS_OTHER} ${LWS_CFLAGS_OTHER} ${JANSSON_CFLAGS_OTHER} + ${UUID_CFLAGS_OTHER} "-pthread" ) @@ -48,9 +52,11 @@ set(EXTRA_CFLAGS # libwebfuse-core add_library(webfuse-core STATIC + lib/webfuse/core/slist.c lib/webfuse/core/message.c lib/webfuse/core/message_queue.c lib/webfuse/core/status.c + lib/webfuse/core/string.c ) set_target_properties(webfuse-core PROPERTIES OUTPUT_NAME webfuse-core) @@ -272,6 +278,7 @@ pkg_check_modules(GMOCK gmock) add_executable(alltests test/msleep.cc test/mock_authenticator.cc + test/test_container_of.cc test/test_response_parser.cc test/test_server.cc test/test_timepoint.cc @@ -280,6 +287,8 @@ add_executable(alltests test/test_credentials.cc test/test_authenticator.cc test/test_authenticators.cc + test/test_string.cc + test/test_slist.cc ) target_link_libraries(alltests PUBLIC webfuse-adapter-static webfuse-provider-static webfuse-core ${EXTRA_LIBS} ${GMOCK_LIBRARIES} ${GTEST_LIBRARIES}) diff --git a/README.md b/README.md index a9e0ca7..e0b54b2 100644 --- a/README.md +++ b/README.md @@ -38,16 +38,35 @@ webfuse solves this problem by using the [WebSocket](https://en.wikipedia.org/wi With webfuse it is possible to implement remote filesystems based on websockets. A reference implementation of such a daemon is provided within the examples. The picture above describes the workflow: -- The websocket filesystem daemon (*webfuse daemon*) mounts a filesystem on startup. - It starts the websocket server and waits for incoming connections. +- The websocket filesystem daemon (*webfuse daemon*) waits for incoming connections. -- A remote filesystem provider connects to webfuse daemon via websocket protocol. - The example includes such a provider implemented in HTML and JavaScript. +- A remote filesystem provider connects to webfuse daemon via websocket protocol and adds one or more filesystems. + *Note: the examples include such a provider implemented in HTML and JavaScript.* - Whenever the user makes filesystem requests, such as *ls*, the request is redirected via webfuse daemon to the connected filesystem provider Currently all requests are initiated by webfuse daemon and responded by filesystem provider. This may change in future, e.g. when authentication is supported. +### Filesystem represenation + +![filesystem](doc/filesystem.png) + +To handle multiple filesystems, that are registered by one or more providers, webfuse daemon maintains a directory structure as shown above. + +- **mount_point** is the entry point of the drectory structure + +- **fwupdate** is a name defined by the provider when filesystem was registered + *Note: the picture above shows two providers, where both registered a filesystem named "fwupdate"* + +- **<uuid>** is the filesystem id choosen by webfuse daemon to distinguish different filesystems + +- **default** is a symbolic link maintained by webfuse daemon to identify the default filesystem + +This directoy structure allows to handle multiple filesystems registered by multiple providers. +It can be used as a kind of service registry, where each filesystem represents a service. +The named subdirectores distinguish differend service types. The symbolic link *default* can be used to identify the +default service and the listing of a named subdirectory can be used to list available services of a particular type. + ## Similar Projects ### Davfs2 @@ -141,7 +160,7 @@ Notfications are used to inform a receiver about something. Unlike requests, not Retrieve information about a filesystem entry by name. - webfuse daemon: {"method": "lookup", "params": [, ], "id": } + webfuse daemon: {"method": "lookup", "params": [, , ], "id": } fs provider: {"result": { "inode": , "mode" : , @@ -154,6 +173,7 @@ Retrieve information about a filesystem entry by name. | Item | Data type | Description | | ----------- | --------------- | ------------------------------------------- | +| filesystem | string | name of the filesystem | | parent | integer | inode of parent directory (1 = root) | | name | string | name of the filesystem object to look up | | inode | integer | inode of the filesystem object | @@ -168,7 +188,7 @@ Retrieve information about a filesystem entry by name. Get file attributes. - webfuse daemon: {"method": "getattr", "params": [], "id": } + webfuse daemon: {"method": "getattr", "params": [, ], "id": } fs provider: {"result": { "mode" : , "type" : , @@ -180,6 +200,7 @@ Get file attributes. | Item | Data type | Description | | ----------- | --------------- | ------------------------------------------- | +| filesystem | string | name of the filesystem | | inode | integer | inode of the filesystem object | | mode | integer | unix file mode | | type | "file" or "dir" | type of filesystem object | @@ -194,7 +215,7 @@ Read directory contents. Result is an array of name-inode pairs for each entry. The generic entries "." and ".." should also be provided. - webfuse daemon: {"method": "readdir", "params": [], "id": } + webfuse daemon: {"method": "readdir", "params": [, ], "id": } fs provider: {"result": [ {"name": , "inode": }, ... @@ -202,6 +223,7 @@ Result is an array of name-inode pairs for each entry. The generic entries | Item | Data type | Description | | ----------- | --------------- | ------------------------------ | +| filesystem | string | name of the filesystem | | dir_inode | integer | inode of the directory to read | | name | integer | name of the entry | | inode | integer | inode of the entry | @@ -210,11 +232,12 @@ Result is an array of name-inode pairs for each entry. The generic entries Open a file. - webfuse daemon: {"method": "readdir", "params": [, ], "id": } + webfuse daemon: {"method": "readdir", "params": [, , ], "id": } fs provider: {"result": {"handle": }, "id": } | Item | Data type | Description | | ----------- | ----------| ----------------------------- | +| filesystem | string | name of the filesystem | | inode | integer | inode of the file | | flags | integer | access mode flags (see below) | | handle | integer | handle of the file | @@ -237,10 +260,11 @@ Open a file. Informs filesystem provider, that a file is closed. Since `close` is a notification, it cannot fail. - webfuse daemon: {"method": "close", "params": [, , ], "id": } + webfuse daemon: {"method": "close", "params": [, , , ], "id": } | Item | Data type | Description | | ----------- | ----------| ---------------------------- | +| filesystem | string | name of the filesystem | | inode | integer | inode of the file | | handle | integer | handle of the file | | flags | integer | access mode flags (see open) | @@ -249,7 +273,7 @@ Since `close` is a notification, it cannot fail. Read from an open file. - webfuse daemon: {"method": "close", "params": [, , , ], "id": } + webfuse daemon: {"method": "close", "params": [, , , , ], "id": } fs provider: {"result": { "data": , "format": , @@ -258,6 +282,7 @@ Read from an open file. | Item | Data type | Description | | ----------- | ----------| ----------------------------- | +| filesystem | string | name of the filesystem | | inode | integer | inode of the file | | handle | integer | handle of the file | | offset | integer | Offet to start read operation | @@ -275,10 +300,21 @@ Read from an open file. ### Requests (Provider -> Adapter) +#### add_filesystem + +Adds a filesystem. + + fs provider: {"method": "add_filesytem", "params": [], "id": } + webfuse daemon: {"result": {"id": }, "id": } + +| Item | Data type | Description | +| ----------- | ----------| ------------------------------- | +| name | string | name and id of filesystem | + #### authtenticate Authenticate the provider. -If authentication is enabled, a provider must be authenticated by the adapter before the adapter will send any messages. +If authentication is enabled, a provider must be authenticated by the adapter before filesystems can be added. fs provider: {"method": "authenticate", "params": [, ], "id": } webfuse daemon: {"result": {}, "id": } diff --git a/build/amd64-ubuntu-builder.dockerfile b/build/amd64-ubuntu-builder.dockerfile index d81fe3f..c28d356 100644 --- a/build/amd64-ubuntu-builder.dockerfile +++ b/build/amd64-ubuntu-builder.dockerfile @@ -14,7 +14,8 @@ RUN set -x \ rsync \ gdb \ gdbserver \ - valgrind + valgrind \ + uuid-dev COPY src /usr/local/src diff --git a/build/arm32v7-ubuntu-builder.dockerfile b/build/arm32v7-ubuntu-builder.dockerfile index a2e1cd7..5c6ad41 100644 --- a/build/arm32v7-ubuntu-builder.dockerfile +++ b/build/arm32v7-ubuntu-builder.dockerfile @@ -17,7 +17,8 @@ RUN set -x \ pkg-config \ rsync \ gdb \ - gdbserver + gdbserver \ + uuid-dev COPY src /usr/local/src diff --git a/doc/authenticate.png b/doc/authenticate.png index b0df17dc81ec922899a15e0dd247fabd58d3d861..b99403320ff244c86aad4837a7854c62e85a2d3a 100644 GIT binary patch literal 28435 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 literal 37433 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 MQcmK7*u5wJ1&vH`$p8QV diff --git a/doc/authenticate.uml b/doc/authenticate.uml index 00a5f52..263ac5b 100644 --- a/doc/authenticate.uml +++ b/doc/authenticate.uml @@ -1,12 +1,10 @@ @startuml participant "Filesystem Provider\n(e.g. Webbrowser)" as provider participant "webfuse\ndaemon" as daemon -actor "user" as user -group directory listing fails without authentication -user -> daemon : ls -daemon -> daemon : is_authenticated -daemon -->x user : error: no entry +group add filesystem fails without authentication +provider -> daemon : add_filesystem +daemon -->x provider : error: access denied end @@ -17,12 +15,10 @@ daemon -> daemon: check(credentials) daemon --> provider: result end -group directory listing succeeds after authentication -user -> daemon : ls -daemon -> daemon : is_authenticated -daemon -> provider : readdir -provider --> daemon : readdir_resp -daemon --> user : [., ..] +group add filesystem succeeds after authentication + provider -> daemon: add_filesystem + daemon -> daemon : fuse_mount + daemon -> provider: okay end @enduml \ No newline at end of file diff --git a/doc/concept.png b/doc/concept.png index 9ab4e9065359a4d6e5909e744a8e8d7e9ec20d7b..64e34750745d6804f0e6e8ca0f831fa06f301000 100644 GIT binary patch literal 33697 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 literal 30784 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 daemon : fuse_mount daemon -> daemon : start ws end ... @@ -16,9 +15,15 @@ provider -> daemon : connect daemon -> daemon: check credentials daemon --> provider: okay end + + provider -> daemon: add_filesystem + daemon -> daemon : fuse_mount + daemon -> provider: okay + end ... + group directory listing user -> daemon : ls daemon -> provider : readdir diff --git a/doc/filesystem.png b/doc/filesystem.png new file mode 100644 index 0000000000000000000000000000000000000000..4b4106a5baa940e2e070fac600acdbbf8f44384c GIT binary patch literal 5354 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 Za@|c+MQhBn1h~Vb(9+OFm8si={SRI2Miu}7 literal 0 HcmV?d00001 diff --git a/doc/filesystem.uml b/doc/filesystem.uml new file mode 100644 index 0000000..1b78837 --- /dev/null +++ b/doc/filesystem.uml @@ -0,0 +1,16 @@ +@startuml + +salt +{ +{T ++ mount_point +++ fwupdate ++++ default -> 7c029f81-6bdf-4d3c-82dc-26f748164012 ++++ 7c029f81-6bdf-4d3c-82dc-26f748164012 +++++ update.raucb ++++ f93de23b-4535-4a47-a287-a381b78a11b8 +++++ update.raucb +} +} + +@enduml \ No newline at end of file diff --git a/example/daemon/main.c b/example/daemon/main.c index 1bafb8c..de6ac51 100644 --- a/example/daemon/main.c +++ b/example/daemon/main.c @@ -50,13 +50,14 @@ static bool authenticate(struct wf_credentials * creds, void * user_data) char const * password = wf_credentials_get(creds, "password"); if ((NULL != username) && (NULL != password)) { - struct userdb * db = userdb_create(""); + struct userdb * db = userdb_create(""); result = userdb_load(db, args->passwd_path); if (result) { result = userdb_check(db, username, password); - userdb_dispose(db); } + + userdb_dispose(db); } return result; diff --git a/example/daemon/www/js/connection_view.js b/example/daemon/www/js/connection_view.js index c46b584..31576ae 100644 --- a/example/daemon/www/js/connection_view.js +++ b/example/daemon/www/js/connection_view.js @@ -1,5 +1,6 @@ export class ConnectionView { - constructor(client) { + constructor(client, provider) { + this._provider = provider; this._client = client; this._client.onopen = () => { this._onConnectionOpened(); }; this._client.onclose = () => { this._onConnectionClosed(); }; @@ -28,6 +29,14 @@ export class ConnectionView { const authenticateBox = document.createElement("div"); this.element.appendChild(authenticateBox); + const authLabel = document.createElement("span"); + authLabel.textContent = "use authentication:"; + authenticateBox.appendChild(authLabel); + + this.authenticateCheckbox = document.createElement("input"); + this.authenticateCheckbox.type = "checkbox"; + authenticateBox.appendChild(this.authenticateCheckbox); + const usernameLabel = document.createElement("span"); usernameLabel.textContent = "user:"; authenticateBox.appendChild(usernameLabel); @@ -45,12 +54,6 @@ export class ConnectionView { this.passwordTextbox.type = "password"; this.passwordTextbox.value = "secret"; authenticateBox.appendChild(this.passwordTextbox); - - this.authenticateButton = document.createElement("input"); - this.authenticateButton.type = "button"; - this.authenticateButton.value = "authenticate"; - this.authenticateButton.addEventListener("click", () => { this._onAuthenticateButtonClicked(); }); - authenticateBox.appendChild(this.authenticateButton); } _onConnectButtonClicked() { @@ -65,14 +68,21 @@ export class ConnectionView { _onAuthenticateButtonClicked() { if (this._client.isConnected()) { - const username = this.usernameTextbox.value; - const password = this.passwordTextbox.value; - this._client.authenticate("username", { username, password }); } } _onConnectionOpened() { + if (this.authenticateCheckbox.checked) { + const username = this.usernameTextbox.value; + const password = this.passwordTextbox.value; + + const promise = this._client.authenticate("username", { username, password }); + promise.then(() => { this._client.addProvider("test", this._provider); }); + } else { + this._client.addProvider("test", this._provider); + } + this.connectButton.value = "disconnect"; } diff --git a/example/daemon/www/js/startup.js b/example/daemon/www/js/startup.js index cdc0796..284d229 100644 --- a/example/daemon/www/js/startup.js +++ b/example/daemon/www/js/startup.js @@ -17,8 +17,8 @@ function startup() { "say_hello.sh": { inode: 3, mode: mode("0555"), type: "file", contents: "#!/bin/sh\necho hello\n"} } }); - const client = new Client(provider); - const connectionView = new ConnectionView(client); + const client = new Client(); + const connectionView = new ConnectionView(client, provider); document.getElementById('connection').appendChild(connectionView.element); } diff --git a/example/daemon/www/js/webfuse/client.js b/example/daemon/www/js/webfuse/client.js index 2838732..65d24f2 100644 --- a/example/daemon/www/js/webfuse/client.js +++ b/example/daemon/www/js/webfuse/client.js @@ -4,7 +4,9 @@ export class Client { static get _PROTOCOL() { return "fs"; } constructor(provider) { - this._provider = provider; + this._provider = { }; + this._pendingRequests = {}; + this._id = 0; this._ws = null; this.onopen = () => { }; this.onclose = () => { }; @@ -24,11 +26,27 @@ export class Client { }; } + _invokeRequest(method, params) { + this._id += 1; + const id = this._id; + const request = {method, params, id}; + + return new Promise((resolve, reject) => { + this._pendingRequests[id] = {resolve, reject}; + this._ws.send(JSON.stringify(request)); + }); + } + authenticate(type, credentials) { + return this._invokeRequest("authenticate", [type, credentials]); + } + + addProvider(name, provider) { + this._provider[name] = provider; const request = { - "method": "authenticate", - "params": [type, credentials], - "id": 42 + "method": "add_filesystem", + "params": [name], + "id": 23 }; this._ws.send(JSON.stringify(request)); @@ -45,27 +63,61 @@ export class Client { return ((this._ws) && (this._ws.readyState === WebSocket.OPEN)); } + _isRequest(request) { + const method = request.method; + + return (("string" === typeof(method)) && ("params" in request)); + } + + _isResponse(response) { + const id = response.id; + + return (("number" === typeof(id)) && (("result" in response) || ("error" in response))); + } + + _removePendingRequest(id) { + let result = null; + + if (id in this._pendingRequests) { + result = this._pendingRequests[id]; + Reflect.deleteProperty(this._pendingRequests, id); + } + + return result; + } + _onmessage(message) { try { - const request = JSON.parse(message.data); - const method = request.method; - const id = request.id; - const params = request.params; + const data = JSON.parse(message.data); - if ("string" !== typeof(method)) { - throw new Error("parse error: missing field: \"method\""); + if (this._isRequest(data)) { + const method = data.method; + const id = data.id; + const params = data.params; + + if ("number" === typeof(id)) { + this._invoke(method, params, id); + } + else { + this._notify(method, params); + } + } + else if (this._isResponse(data)) { + const id = data.id; + const result = data.result; + const error = data.error; + + const request = this._removePendingRequest(id); + if (request) { + if (result) { + request.resolve(result); + } + else { + request.reject(error); + } + } } - if (!params) { - throw new Error("parse error: missing field: \"params\""); - } - - if ("number" === typeof(request.id)) { - this._invoke(method, params, id); - } - else { - this._notify(method, params); - } } catch (ex) { // swallow @@ -114,28 +166,48 @@ export class Client { } } - async _lookup([parent, name]) { - return this._provider.lookup(parent, name); + _getProvider(name) { + if (name in this._provider) { + return this._provider[name]; + } + else { + throw new Error('Unknown provider'); + } } - async _getattr([inode]) { - return this._provider.getattr(inode); + async _lookup([providerName, parent, name]) { + const provider = this._getProvider(providerName); + + return provider.lookup(parent, name); } - async _readdir([inode]) { - return this._provider.readdir(inode); + async _getattr([providerName, inode]) { + const provider = this._getProvider(providerName); + + return provider.getattr(inode); } - async _open([inode, mode]) { - return this._provider.open(inode, mode); + async _readdir([providerName, inode]) { + const provider = this._getProvider(providerName); + + return provider.readdir(inode); } - _close([inode, handle, mode]) { - this._provider.close(inode, handle, mode); + async _open([providerName, inode, mode]) { + const provider = this._getProvider(providerName); + + return provider.open(inode, mode); } - async _read([inode, handle, offset, length]) { - const data = await this._provider.read(inode, handle, offset, length); + _close([providerName, inode, handle, mode]) { + const provider = this._getProvider(providerName); + + provider.close(inode, handle, mode); + } + + async _read([providerName, inode, handle, offset, length]) { + const provider = this._getProvider(providerName); + const data = await provider.read(inode, handle, offset, length); if ("string" === typeof(data)) { return { diff --git a/example/provider/main.c b/example/provider/main.c index 33290cd..1cb27cf 100644 --- a/example/provider/main.c +++ b/example/provider/main.c @@ -340,7 +340,7 @@ int main(int argc, char* argv[]) .parent = 1, .inode = 2, .name = "hello.txt", - .mode = 0555, + .mode = 0444, .type = FS_FILE, .content="hello, world!", .content_length = 13, diff --git a/lib/webfuse/adapter/impl/filesystem.c b/lib/webfuse/adapter/impl/filesystem.c index 0e52b9f..3cfcca7 100644 --- a/lib/webfuse/adapter/impl/filesystem.c +++ b/lib/webfuse/adapter/impl/filesystem.c @@ -1,5 +1,16 @@ #include "webfuse/adapter/impl/filesystem.h" #include "webfuse/adapter/impl/operations.h" +#include "webfuse/adapter/impl/session.h" + +#include "webfuse/core/string.h" + +#include +#include + +#include +#include +#include +#include #include #include @@ -16,23 +27,118 @@ static struct fuse_lowlevel_ops const filesystem_operations = .read = &wf_impl_operation_read }; +static char * wf_impl_filesystem_create_id(void) +{ + uuid_t uuid; + uuid_generate(uuid); + char id[UUID_STR_LEN]; + uuid_unparse(uuid, id); -bool wf_impl_filesystem_init( - struct wf_impl_filesystem * filesystem, - struct wf_impl_session_manager * session_manager, - char * mount_point) + return strdup(id); +} + +static bool wf_impl_filesystem_is_link_broken(char const * path, char const * id) { bool result = false; + char buffer[UUID_STR_LEN]; + ssize_t count = readlink(path, buffer, UUID_STR_LEN); + if ((0 < count) && (count < UUID_STR_LEN)) + { + buffer[count] = '\0'; + result = (0 == strcmp(buffer, id)); + } + + return result; +} + +static bool wf_impl_filesystem_link_first_subdir( + char const * link_path, + char const * path) +{ + bool result = false; + DIR * dir = opendir(path); + if (NULL != dir) + { + struct dirent * entry = readdir(dir); + while (NULL != entry) + { + if ((DT_DIR == entry->d_type) && ('.' != entry->d_name[0])) + { + symlink(entry->d_name, link_path); + result = true; + break; + } + + entry = readdir(dir); + } + + closedir(dir); + } + + return result; +} + +static void wf_impl_filesystem_cleanup( + struct wf_impl_filesystem * filesystem) +{ + fuse_session_reset(filesystem->session); + fuse_session_unmount(filesystem->session); + fuse_session_destroy(filesystem->session); + filesystem->session = NULL; + + free(filesystem->buffer.mem); + fuse_opt_free_args(&filesystem->args); + + rmdir(filesystem->root_path); + + if (wf_impl_filesystem_is_link_broken(filesystem->default_path, filesystem->id)) + { + unlink(filesystem->default_path); + + bool const success = wf_impl_filesystem_link_first_subdir(filesystem->default_path, filesystem->service_path); + if (!success) + { + rmdir(filesystem->service_path); + } + } + + + free(filesystem->user_data.name); + free(filesystem->id); + free(filesystem->root_path); + free(filesystem->default_path); + free(filesystem->service_path); +} + + +static bool wf_impl_filesystem_init( + struct wf_impl_filesystem * filesystem, + struct wf_impl_session * session, + char const * name) +{ + bool result = false; + char * argv[] = {"", NULL}; filesystem->args.argc = 1; filesystem->args.argv = argv; filesystem->args.allocated = 0; - filesystem->user_data.session_manager = session_manager; + filesystem->user_data.session = session; filesystem->user_data.timeout = 1.0; + filesystem->user_data.name = strdup(name); memset(&filesystem->buffer, 0, sizeof(struct fuse_buf)); + filesystem->service_path = wf_create_string("%s/%s", session->mount_point, name); + mkdir(filesystem->service_path, 0755); + + filesystem->id = wf_impl_filesystem_create_id(); + filesystem->root_path = wf_create_string("%s/%s/%s", session->mount_point, name, filesystem->id); + mkdir(filesystem->root_path, 0755); + + filesystem->default_path = wf_create_string("%s/%s/default", session->mount_point, name); + symlink(filesystem->id, filesystem->default_path); + filesystem->session = fuse_session_new( &filesystem->args, &filesystem_operations, @@ -40,31 +146,50 @@ bool wf_impl_filesystem_init( &filesystem->user_data); if (NULL != filesystem->session) { - result = (0 == fuse_session_mount(filesystem->session, mount_point)); + result = (0 == fuse_session_mount(filesystem->session, filesystem->root_path)); + } + + if (result) + { + lws_sock_file_fd_type fd; + fd.filefd = fuse_session_fd(filesystem->session); + struct lws_protocols const * protocol = lws_get_protocol(session->wsi); + filesystem->wsi = lws_adopt_descriptor_vhost(lws_get_vhost(session->wsi), LWS_ADOPT_RAW_FILE_DESC, fd, protocol->name, session->wsi); + + if (NULL == filesystem->wsi) + { + wf_impl_filesystem_cleanup(filesystem); + result = false; + } + } return result; } -void wf_impl_filesystem_cleanup( - struct wf_impl_filesystem * filesystem) +struct wf_impl_filesystem * wf_impl_filesystem_create( + struct wf_impl_session * session, + char const * name) { - if (NULL != filesystem->session) + struct wf_impl_filesystem * filesystem = malloc(sizeof(struct wf_impl_filesystem)); + if (NULL != filesystem) { - fuse_session_reset(filesystem->session); - fuse_session_unmount(filesystem->session); - fuse_session_destroy(filesystem->session); - filesystem->session = NULL; + bool success = wf_impl_filesystem_init(filesystem, session, name); + if (!success) + { + free(filesystem); + filesystem = NULL; + } } - free(filesystem->buffer.mem); - fuse_opt_free_args(&filesystem->args); + return filesystem; } -int wf_impl_filesystem_get_fd( +void wf_impl_filesystem_dispose( struct wf_impl_filesystem * filesystem) { - return fuse_session_fd(filesystem->session); + wf_impl_filesystem_cleanup(filesystem); + free(filesystem); } void wf_impl_filesystem_process_request( diff --git a/lib/webfuse/adapter/impl/filesystem.h b/lib/webfuse/adapter/impl/filesystem.h index af07c32..648dfbd 100644 --- a/lib/webfuse/adapter/impl/filesystem.h +++ b/lib/webfuse/adapter/impl/filesystem.h @@ -7,31 +7,36 @@ #include "webfuse/adapter/impl/fuse_wrapper.h" #include "webfuse/adapter/impl/operations.h" +#include "webfuse/core/slist.h" #ifdef __cplusplus extern "C" { #endif -struct wf_impl_session_manager; +struct wf_impl_session; +struct lws; struct wf_impl_filesystem { + struct wf_slist_item item; struct fuse_args args; struct fuse_session * session; struct fuse_buf buffer; struct wf_impl_operations_context user_data; + struct lws * wsi; + char * name; + char * id; + char * service_path; + char * default_path; + char * root_path; }; -extern bool wf_impl_filesystem_init( - struct wf_impl_filesystem * filesystem, - struct wf_impl_session_manager * session_manager, - char * mount_point); +extern struct wf_impl_filesystem * wf_impl_filesystem_create( + struct wf_impl_session * session, + char const * name); -extern void wf_impl_filesystem_cleanup( - struct wf_impl_filesystem * filesystem); - -extern int wf_impl_filesystem_get_fd( +extern void wf_impl_filesystem_dispose( struct wf_impl_filesystem * filesystem); extern void wf_impl_filesystem_process_request( diff --git a/lib/webfuse/adapter/impl/operation/close.c b/lib/webfuse/adapter/impl/operation/close.c index 6ccefc8..ca63a8e 100644 --- a/lib/webfuse/adapter/impl/operation/close.c +++ b/lib/webfuse/adapter/impl/operation/close.c @@ -13,12 +13,12 @@ void wf_impl_operation_close( struct fuse_file_info * file_info) { struct wf_impl_operations_context * user_data = fuse_req_userdata(request); - struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data, inode); + struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data); if (NULL != rpc) { int handle = (int) (file_info->fh & INT_MAX); - wf_impl_jsonrpc_proxy_notify(rpc, "close", "iii", inode, handle, file_info->flags); + wf_impl_jsonrpc_proxy_notify(rpc, "close", "siii", user_data->name, inode, handle, file_info->flags); } fuse_reply_err(request, 0); diff --git a/lib/webfuse/adapter/impl/operation/getattr.c b/lib/webfuse/adapter/impl/operation/getattr.c index 98993e8..75736a2 100644 --- a/lib/webfuse/adapter/impl/operation/getattr.c +++ b/lib/webfuse/adapter/impl/operation/getattr.c @@ -81,7 +81,7 @@ void wf_impl_operation_getattr ( { struct fuse_ctx const * context = fuse_req_ctx(request); struct wf_impl_operations_context * user_data = fuse_req_userdata(request); - struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data, inode); + struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data); if (NULL != rpc) { @@ -91,7 +91,7 @@ void wf_impl_operation_getattr ( getattr_context->gid = context->gid; getattr_context->timeout = user_data->timeout; - wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_getattr_finished, getattr_context, "getattr", "i", inode); + wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_getattr_finished, getattr_context, "getattr", "si", user_data->name, inode); } else { diff --git a/lib/webfuse/adapter/impl/operation/lookup.c b/lib/webfuse/adapter/impl/operation/lookup.c index 840a754..3749123 100644 --- a/lib/webfuse/adapter/impl/operation/lookup.c +++ b/lib/webfuse/adapter/impl/operation/lookup.c @@ -90,7 +90,7 @@ void wf_impl_operation_lookup ( { struct fuse_ctx const * context = fuse_req_ctx(request); struct wf_impl_operations_context * user_data = fuse_req_userdata(request); - struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data, parent); + struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data); if (NULL != rpc) { @@ -100,7 +100,7 @@ void wf_impl_operation_lookup ( lookup_context->gid = context->gid; lookup_context->timeout = user_data->timeout; - wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_lookup_finished, lookup_context, "lookup", "is", (int) (parent & INT_MAX), name); + wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_lookup_finished, lookup_context, "lookup", "sis", user_data->name, (int) (parent & INT_MAX), name); } else { diff --git a/lib/webfuse/adapter/impl/operation/open.c b/lib/webfuse/adapter/impl/operation/open.c index 51894fa..e82b865 100644 --- a/lib/webfuse/adapter/impl/operation/open.c +++ b/lib/webfuse/adapter/impl/operation/open.c @@ -47,11 +47,11 @@ void wf_impl_operation_open( struct fuse_file_info * file_info) { struct wf_impl_operations_context * user_data = fuse_req_userdata(request); - struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data, inode); + struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data); if (NULL != rpc) { - wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_open_finished, request, "open", "ii", inode, file_info->flags); + wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_open_finished, request, "open", "sii", user_data->name, inode, file_info->flags); } else { diff --git a/lib/webfuse/adapter/impl/operation/read.c b/lib/webfuse/adapter/impl/operation/read.c index 8c956fb..ea89eff 100644 --- a/lib/webfuse/adapter/impl/operation/read.c +++ b/lib/webfuse/adapter/impl/operation/read.c @@ -87,13 +87,13 @@ void wf_impl_operation_read( struct fuse_file_info * file_info) { struct wf_impl_operations_context * user_data = fuse_req_userdata(request); - struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data, inode); + struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data); if (NULL != rpc) { int const length = (size <= WF_MAX_READ_LENGTH) ? (int) size : WF_MAX_READ_LENGTH; int handle = (file_info->fh & INT_MAX); - wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_read_finished, request, "read", "iiii", inode, handle, (int) offset, length); + wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_read_finished, request, "read", "siiii", user_data->name, inode, handle, (int) offset, length); } else { diff --git a/lib/webfuse/adapter/impl/operation/readdir.c b/lib/webfuse/adapter/impl/operation/readdir.c index 104e807..85cfd5b 100644 --- a/lib/webfuse/adapter/impl/operation/readdir.c +++ b/lib/webfuse/adapter/impl/operation/readdir.c @@ -137,7 +137,7 @@ void wf_impl_operation_readdir ( struct fuse_file_info * WF_UNUSED_PARAM(file_info)) { struct wf_impl_operations_context * user_data = fuse_req_userdata(request); - struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data, inode); + struct wf_impl_jsonrpc_proxy * rpc = wf_impl_operations_context_get_proxy(user_data); if (NULL != rpc) { @@ -146,7 +146,7 @@ void wf_impl_operation_readdir ( readdir_context->size = size; readdir_context->offset = offset; - wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_readdir_finished, readdir_context, "readdir", "i", inode); + wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_readdir_finished, readdir_context, "readdir", "si", user_data->name, inode); } else { diff --git a/lib/webfuse/adapter/impl/operations.c b/lib/webfuse/adapter/impl/operations.c index 75949b5..393ab99 100644 --- a/lib/webfuse/adapter/impl/operations.c +++ b/lib/webfuse/adapter/impl/operations.c @@ -4,13 +4,11 @@ #include struct wf_impl_jsonrpc_proxy * wf_impl_operations_context_get_proxy( - struct wf_impl_operations_context * context, - fuse_ino_t inode) + struct wf_impl_operations_context * context) { struct wf_impl_jsonrpc_proxy * proxy = NULL; - struct wf_impl_session_manager * session_manger = context->session_manager; - struct wf_impl_session * session = wf_impl_session_manager_get_by_inode(session_manger, inode); + struct wf_impl_session * session = context->session; if (NULL != session) { proxy = &session->rpc; diff --git a/lib/webfuse/adapter/impl/operations.h b/lib/webfuse/adapter/impl/operations.h index fe1a540..debe4fa 100644 --- a/lib/webfuse/adapter/impl/operations.h +++ b/lib/webfuse/adapter/impl/operations.h @@ -7,13 +7,14 @@ extern "C" { #endif -struct wf_impl_session_manager; +struct wf_impl_session; struct wf_impl_jsonrpc_proxy; struct wf_impl_operations_context { - struct wf_impl_session_manager * session_manager; + struct wf_impl_session * session; double timeout; + char * name; }; extern void wf_impl_operation_lookup ( @@ -49,8 +50,7 @@ extern void wf_impl_operation_read( struct fuse_file_info *fi); extern struct wf_impl_jsonrpc_proxy * wf_impl_operations_context_get_proxy( - struct wf_impl_operations_context * context, - fuse_ino_t inode); + struct wf_impl_operations_context * context); #ifdef __cplusplus } diff --git a/lib/webfuse/adapter/impl/server.c b/lib/webfuse/adapter/impl/server.c index 5bddb10..13250f6 100644 --- a/lib/webfuse/adapter/impl/server.c +++ b/lib/webfuse/adapter/impl/server.c @@ -108,18 +108,11 @@ struct wf_server * wf_impl_server_create( server = malloc(sizeof(struct wf_server)); if (NULL != server) { - if (wf_impl_server_protocol_init(&server->protocol, config->mount_point)) - { - server->shutdown_requested = false; - wf_impl_server_config_clone(config, &server->config); - wf_impl_authenticators_move(&server->config.authenticators, &server->protocol.authenticators); - server->context = wf_impl_server_context_create(server); - } - else - { - free(server); - server = NULL; - } + wf_impl_server_protocol_init(&server->protocol, config->mount_point); + server->shutdown_requested = false; + wf_impl_server_config_clone(config, &server->config); + wf_impl_authenticators_move(&server->config.authenticators, &server->protocol.authenticators); + server->context = wf_impl_server_context_create(server); } } diff --git a/lib/webfuse/adapter/impl/server_protocol.c b/lib/webfuse/adapter/impl/server_protocol.c index 0e89437..27a6fd8 100644 --- a/lib/webfuse/adapter/impl/server_protocol.c +++ b/lib/webfuse/adapter/impl/server_protocol.c @@ -1,12 +1,12 @@ #include "webfuse/adapter/impl/server_protocol.h" #include +#include #include #include "webfuse/core/message.h" #include "webfuse/core/util.h" -#include "webfuse/adapter/impl/filesystem.h" #include "webfuse/adapter/impl/credentials.h" #include "webfuse/adapter/impl/jsonrpc/request.h" @@ -25,23 +25,14 @@ static int wf_impl_server_protocol_callback( switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: - { - lws_sock_file_fd_type fd; - fd.filefd = wf_impl_filesystem_get_fd(&protocol->filesystem); - if (!lws_adopt_descriptor_vhost(lws_get_vhost(wsi), LWS_ADOPT_RAW_FILE_DESC, fd, ws_protocol->name, NULL)) - { - fprintf(stderr, "error: unable to adopt fd"); - } - } - break; case LWS_CALLBACK_ESTABLISHED: session = wf_impl_session_manager_add( &protocol->session_manager, wsi, &protocol->authenticators, &protocol->timeout_manager, - &protocol->server); + &protocol->server, + protocol->mount_point); if (NULL != session) { @@ -64,7 +55,10 @@ static int wf_impl_server_protocol_callback( } break; case LWS_CALLBACK_RAW_RX_FILE: - wf_impl_filesystem_process_request(&protocol->filesystem); + if (NULL != session) + { + wf_impl_session_process_filesystem_request(session, wsi); + } break; default: break; @@ -79,11 +73,7 @@ struct wf_server_protocol * wf_impl_server_protocol_create( struct wf_server_protocol * protocol = malloc(sizeof(struct wf_server_protocol)); if (NULL != protocol) { - if (!wf_impl_server_protocol_init(protocol, mount_point)) - { - free(protocol); - protocol = NULL; - } + wf_impl_server_protocol_init(protocol, mount_point); } return protocol; @@ -140,35 +130,91 @@ static void wf_impl_server_protocol_authenticate( } } -bool wf_impl_server_protocol_init( +static bool wf_impl_server_protocol_check_name(char const * value) +{ + while ('\0' != *value) + { + char const c = * value; + if (!isalpha(c) && !isdigit(c) && ('_' != c)) + { + return false; + } + value++; + } + + return true; +} + +static void wf_impl_server_protocol_add_filesystem( + struct wf_impl_jsonrpc_request * request, + char const * WF_UNUSED_PARAM(method_name), + json_t * params, + void * WF_UNUSED_PARAM(user_data)) +{ + struct wf_impl_session * session = wf_impl_jsonrpc_request_get_userdata(request); + wf_status status = (session->is_authenticated) ? WF_GOOD : WF_BAD_ACCESS_DENIED; + + char const * name = NULL; + if (WF_GOOD == status) + { + json_t * name_holder = json_array_get(params, 0); + if (json_is_string(name_holder)) + { + name = json_string_value(name_holder); + if (wf_impl_server_protocol_check_name(name)) + { + bool const success = wf_impl_session_add_filesystem(session, name); + if (!success) + { + status = WF_BAD; + } + } + else + { + status = WF_BAD_FORMAT; + } + } + else + { + status = WF_BAD_FORMAT; + } + + } + + if (WF_GOOD == status) + { + json_t * result = json_object(); + json_object_set_new(result, "id", json_string(name)); + wf_impl_jsonrpc_respond(request, result); + } + else + { + wf_impl_jsonrpc_respond_error(request, status); + } + + +} + + +void wf_impl_server_protocol_init( struct wf_server_protocol * protocol, char * mount_point) { + protocol->mount_point = strdup(mount_point); + wf_impl_timeout_manager_init(&protocol->timeout_manager); wf_impl_session_manager_init(&protocol->session_manager); wf_impl_authenticators_init(&protocol->authenticators); wf_impl_jsonrpc_server_init(&protocol->server); wf_impl_jsonrpc_server_add(&protocol->server, "authenticate", &wf_impl_server_protocol_authenticate, protocol); - - bool const success = wf_impl_filesystem_init(&protocol->filesystem, &protocol->session_manager, mount_point); - - // cleanup on error - if (!success) - { - wf_impl_jsonrpc_server_cleanup(&protocol->server); - wf_impl_authenticators_cleanup(&protocol->authenticators); - wf_impl_timeout_manager_cleanup(&protocol->timeout_manager); - wf_impl_session_manager_cleanup(&protocol->session_manager); - } - - return success; + wf_impl_jsonrpc_server_add(&protocol->server, "add_filesystem", &wf_impl_server_protocol_add_filesystem, protocol); } void wf_impl_server_protocol_cleanup( struct wf_server_protocol * protocol) { - wf_impl_filesystem_cleanup(&protocol->filesystem); + free(protocol->mount_point); wf_impl_jsonrpc_server_cleanup(&protocol->server); wf_impl_timeout_manager_cleanup(&protocol->timeout_manager); wf_impl_authenticators_cleanup(&protocol->authenticators); diff --git a/lib/webfuse/adapter/impl/server_protocol.h b/lib/webfuse/adapter/impl/server_protocol.h index 8a4b0d7..2ae0fe6 100644 --- a/lib/webfuse/adapter/impl/server_protocol.h +++ b/lib/webfuse/adapter/impl/server_protocol.h @@ -1,7 +1,6 @@ #ifndef WF_ADAPTER_IMPL_SERVER_PROTOCOL_H #define WF_ADAPTER_IMPL_SERVER_PROTOCOL_H -#include "webfuse/adapter/impl/filesystem.h" #include "webfuse/adapter/impl/jsonrpc/proxy.h" #include "webfuse/adapter/impl/time/timeout_manager.h" #include "webfuse/adapter/impl/authenticators.h" @@ -17,14 +16,14 @@ struct lws_protocols; struct wf_server_protocol { + char * mount_point; struct wf_impl_timeout_manager timeout_manager; - struct wf_impl_filesystem filesystem; struct wf_impl_authenticators authenticators; struct wf_impl_session_manager session_manager; struct wf_impl_jsonrpc_server server; }; -extern bool wf_impl_server_protocol_init( +extern void wf_impl_server_protocol_init( struct wf_server_protocol * protocol, char * mount_point); diff --git a/lib/webfuse/adapter/impl/session.c b/lib/webfuse/adapter/impl/session.c index b45027d..ae67cc2 100644 --- a/lib/webfuse/adapter/impl/session.c +++ b/lib/webfuse/adapter/impl/session.c @@ -6,8 +6,12 @@ #include "webfuse/adapter/impl/jsonrpc/request.h" #include "webfuse/adapter/impl/jsonrpc/response.h" +#include "webfuse/core/container_of.h" +#include "webfuse/core/util.h" + #include #include +#include static bool wf_impl_session_send( json_t * request, @@ -20,7 +24,7 @@ static bool wf_impl_session_send( if (result) { - wf_message_queue_push(&session->queue, message); + wf_slist_append(&session->messages, &message->item); lws_callback_on_writable(session->wsi); result = true; @@ -33,31 +37,59 @@ static bool wf_impl_session_send( return result; } -void wf_impl_session_init( - struct wf_impl_session * session, +struct wf_impl_session * wf_impl_session_create( struct lws * wsi, struct wf_impl_authenticators * authenticators, struct wf_impl_timeout_manager * timeout_manager, - struct wf_impl_jsonrpc_server * server) - { - session->wsi = wsi; - session->is_authenticated = false; - session->authenticators = authenticators; - session->server = server; - wf_impl_jsonrpc_proxy_init(&session->rpc, timeout_manager, &wf_impl_session_send, session); - wf_message_queue_init(&session->queue); - } + struct wf_impl_jsonrpc_server * server, + char const * mount_point) +{ -void wf_impl_session_cleanup( + struct wf_impl_session * session = malloc(sizeof(struct wf_impl_session)); + if (NULL != session) + { + wf_slist_init(&session->filesystems); + + session->mount_point = strdup(mount_point); + session->wsi = wsi; + session->is_authenticated = false; + session->authenticators = authenticators; + session->server = server; + wf_impl_jsonrpc_proxy_init(&session->rpc, timeout_manager, &wf_impl_session_send, session); + wf_slist_init(&session->messages); + } + + return session; +} + +static void wf_impl_session_dispose_filesystems( + struct wf_slist * filesystems) +{ + struct wf_slist_item * item = filesystems->first; + while (NULL != item) + { + struct wf_slist_item * next = item->next; + struct wf_impl_filesystem * filesystem = WF_CONTAINER_OF(item, struct wf_impl_filesystem, item); + wf_impl_filesystem_dispose(filesystem); + + item = next; + } +} + +void wf_impl_session_dispose( struct wf_impl_session * session) { + wf_impl_session_dispose_filesystems(&session->filesystems); + wf_impl_jsonrpc_proxy_cleanup(&session->rpc); - wf_message_queue_cleanup(&session->queue); + wf_message_queue_cleanup(&session->messages); session->is_authenticated = false; session->wsi = NULL; session->authenticators = NULL; session->server = NULL; -} + free(session->mount_point); + free(session); +} bool wf_impl_session_authenticate( struct wf_impl_session * session, @@ -68,16 +100,27 @@ bool wf_impl_session_authenticate( return session->is_authenticated; } +bool wf_impl_session_add_filesystem( + struct wf_impl_session * session, + char const * name) +{ + struct wf_impl_filesystem * filesystem = wf_impl_filesystem_create(session, name); + wf_slist_append(&session->filesystems, &filesystem->item); + return (NULL != filesystem); +} + + void wf_impl_session_onwritable( struct wf_impl_session * session) { - if (!wf_message_queue_empty(&session->queue)) - { - struct wf_message * message = wf_message_queue_pop(&session->queue); + if (!wf_slist_empty(&session->messages)) + { + struct wf_slist_item * item = wf_slist_remove_first(&session->messages); + struct wf_message * message = WF_CONTAINER_OF(item, struct wf_message, item); lws_write(session->wsi, (unsigned char*) message->data, message->length, LWS_WRITE_TEXT); wf_message_dispose(message); - if (!wf_message_queue_empty(&session->queue)) + if (!wf_slist_empty(&session->messages)) { lws_callback_on_writable(session->wsi); } @@ -105,4 +148,48 @@ void wf_impl_session_receive( json_decref(message); } -} \ No newline at end of file +} + +static struct wf_impl_filesystem * wf_impl_session_get_filesystem( + struct wf_impl_session * session, + struct lws * wsi) +{ + struct wf_impl_filesystem * result = NULL; + + struct wf_slist_item * item = session->filesystems.first; + while (NULL != item) + { + struct wf_slist_item * next = item->next; + struct wf_impl_filesystem * filesystem = WF_CONTAINER_OF(session->filesystems.first, struct wf_impl_filesystem, item); + if (wsi == filesystem->wsi) + { + result = filesystem; + break; + } + + item = next; + } + + return result; +} + + +bool wf_impl_session_contains_wsi( + struct wf_impl_session * session, + struct lws * wsi) +{ + bool const result = (NULL != wsi) && ((wsi == session->wsi) || (NULL != wf_impl_session_get_filesystem(session, wsi))); + return result; +} + + +void wf_impl_session_process_filesystem_request( + struct wf_impl_session * session, + struct lws * wsi) +{ + struct wf_impl_filesystem * filesystem = wf_impl_session_get_filesystem(session, wsi); + if (NULL != filesystem) + { + wf_impl_filesystem_process_request(filesystem); + } +} diff --git a/lib/webfuse/adapter/impl/session.h b/lib/webfuse/adapter/impl/session.h index 116e196..cb1a19d 100644 --- a/lib/webfuse/adapter/impl/session.h +++ b/lib/webfuse/adapter/impl/session.h @@ -12,6 +12,8 @@ using std::size_t; #include "webfuse/core/message_queue.h" #include "webfuse/adapter/impl/jsonrpc/proxy.h" #include "webfuse/adapter/impl/jsonrpc/server.h" +#include "webfuse/adapter/impl/filesystem.h" +#include "webfuse/core/slist.h" #ifdef __cplusplus extern "C" @@ -26,25 +28,35 @@ struct wf_impl_timeout_manager; struct wf_impl_session { + struct wf_slist_item item; + char * mount_point; struct lws * wsi; bool is_authenticated; - struct wf_message_queue queue; + struct wf_slist messages; struct wf_impl_authenticators * authenticators; struct wf_impl_jsonrpc_server * server; struct wf_impl_jsonrpc_proxy rpc; + struct wf_slist filesystems; }; -extern void wf_impl_session_init( - struct wf_impl_session * session, +extern struct wf_impl_session * wf_impl_session_create( struct lws * wsi, - struct wf_impl_authenticators * authenticators, - struct wf_impl_timeout_manager * timeout_manager, - struct wf_impl_jsonrpc_server * server); + struct wf_impl_authenticators * authenticators, + struct wf_impl_timeout_manager * timeout_manager, + struct wf_impl_jsonrpc_server * server, + char const * mount_point); + +extern void wf_impl_session_dispose( + struct wf_impl_session * session); extern bool wf_impl_session_authenticate( struct wf_impl_session * session, struct wf_credentials * creds); +extern bool wf_impl_session_add_filesystem( + struct wf_impl_session * session, + char const * name); + extern void wf_impl_session_receive( struct wf_impl_session * session, char const * data, @@ -53,8 +65,14 @@ extern void wf_impl_session_receive( extern void wf_impl_session_onwritable( struct wf_impl_session * session); -extern void wf_impl_session_cleanup( - struct wf_impl_session * session); +extern bool wf_impl_session_contains_wsi( + struct wf_impl_session * session, + struct lws * wsi); + +extern void wf_impl_session_process_filesystem_request( + struct wf_impl_session * session, + struct lws * wsi); + #ifdef __cplusplus } diff --git a/lib/webfuse/adapter/impl/session_manager.c b/lib/webfuse/adapter/impl/session_manager.c index be87c2a..e93b6c7 100644 --- a/lib/webfuse/adapter/impl/session_manager.c +++ b/lib/webfuse/adapter/impl/session_manager.c @@ -1,17 +1,26 @@ #include "webfuse/adapter/impl/session_manager.h" #include "webfuse/core/util.h" +#include "webfuse/core/container_of.h" #include void wf_impl_session_manager_init( struct wf_impl_session_manager * manager) { - wf_impl_session_init(&manager->session, NULL, NULL, NULL, NULL); + wf_slist_init(&manager->sessions); } void wf_impl_session_manager_cleanup( struct wf_impl_session_manager * manager) { - wf_impl_session_cleanup(&manager->session); + struct wf_slist_item * item = manager->sessions.first; + while (NULL != item) + { + struct wf_slist_item * next = item->next; + struct wf_impl_session * session = WF_CONTAINER_OF(item, struct wf_impl_session, item); + wf_impl_session_dispose(session); + + item = next; + } } struct wf_impl_session * wf_impl_session_manager_add( @@ -19,13 +28,14 @@ struct wf_impl_session * wf_impl_session_manager_add( struct lws * wsi, struct wf_impl_authenticators * authenticators, struct wf_impl_timeout_manager * timeout_manager, - struct wf_impl_jsonrpc_server * server) + struct wf_impl_jsonrpc_server * server, + char const * mount_point) { - struct wf_impl_session * session = NULL; - if (NULL == manager->session.wsi) + struct wf_impl_session * session = wf_impl_session_create( + wsi, authenticators, timeout_manager, server, mount_point); + if (NULL != session) { - session = &manager->session; - wf_impl_session_init(&manager->session, wsi, authenticators, timeout_manager, server); + wf_slist_append(&manager->sessions, &session->item); } return session; @@ -36,37 +46,42 @@ struct wf_impl_session * wf_impl_session_manager_get( struct lws * wsi) { struct wf_impl_session * session = NULL; - if (wsi == manager->session.wsi) + + struct wf_slist_item * item = manager->sessions.first; + while (NULL != item) { - session = &manager->session; + struct wf_slist_item * next = item->next; + struct wf_impl_session * current = WF_CONTAINER_OF(item, struct wf_impl_session, item); + + if (wf_impl_session_contains_wsi(current, wsi)) { + session = current; + break; + } + + item = next; } return session; } -struct wf_impl_session * wf_impl_session_manager_get_by_inode( - struct wf_impl_session_manager * manager, - fuse_ino_t WF_UNUSED_PARAM(inode)) -{ - // ToDo: use inode to determine session manager - - struct wf_impl_session * session = NULL; - if (NULL != manager->session.wsi) - { - session = &manager->session; - } - - return session; -} - - void wf_impl_session_manager_remove( struct wf_impl_session_manager * manager, struct lws * wsi) { - if (wsi == manager->session.wsi) + struct wf_slist_item * item = manager->sessions.first; + struct wf_slist_item * prev = NULL; + while (NULL != item) { - wf_impl_session_cleanup(&manager->session); - manager->session.wsi = NULL; + struct wf_slist_item * next = item->next; + struct wf_impl_session * session = WF_CONTAINER_OF(item, struct wf_impl_session, item); + if (wsi == session->wsi) + { + wf_slist_remove_after(&manager->sessions, prev); + wf_impl_session_dispose(session); + break; + } + + prev = item; + item = next; } } diff --git a/lib/webfuse/adapter/impl/session_manager.h b/lib/webfuse/adapter/impl/session_manager.h index 6b7a26b..bfe4aa3 100644 --- a/lib/webfuse/adapter/impl/session_manager.h +++ b/lib/webfuse/adapter/impl/session_manager.h @@ -7,6 +7,7 @@ #include "webfuse/adapter/impl/session.h" #include "webfuse/adapter/impl/fuse_wrapper.h" +#include "webfuse/core/slist.h" #ifdef __cplusplus extern "C" @@ -19,7 +20,7 @@ struct wf_impl_jsonrpc_server; struct wf_impl_session_manager { - struct wf_impl_session session; + struct wf_slist sessions; }; extern void wf_impl_session_manager_init( @@ -33,16 +34,13 @@ extern struct wf_impl_session * wf_impl_session_manager_add( struct lws * wsi, struct wf_impl_authenticators * authenticators, struct wf_impl_timeout_manager * timeout_manager, - struct wf_impl_jsonrpc_server * server); + struct wf_impl_jsonrpc_server * server, + char const * mount_point); extern struct wf_impl_session * wf_impl_session_manager_get( struct wf_impl_session_manager * manager, struct lws * wsi); -extern struct wf_impl_session * wf_impl_session_manager_get_by_inode( - struct wf_impl_session_manager * session_manger, - fuse_ino_t inode); - extern void wf_impl_session_manager_remove( struct wf_impl_session_manager * manager, struct lws * wsi); diff --git a/lib/webfuse/core/container_of.h b/lib/webfuse/core/container_of.h new file mode 100644 index 0000000..fa51a26 --- /dev/null +++ b/lib/webfuse/core/container_of.h @@ -0,0 +1,13 @@ +#ifndef WF_CONTAINER_OF_H +#define WF_CONTAINER_OF_H + +#ifndef __cplusplus +#include +#else +#include +#endif + +#define WF_CONTAINER_OF(pointer, type, member) \ + (type *) (((char *) pointer) - offsetof(type, member)) + +#endif diff --git a/lib/webfuse/core/message.c b/lib/webfuse/core/message.c index bc19248..e94d1c6 100644 --- a/lib/webfuse/core/message.c +++ b/lib/webfuse/core/message.c @@ -16,7 +16,6 @@ extern struct wf_message * wf_message_create(json_t const * value) { message->data = &data[sizeof(struct wf_message) + LWS_PRE]; message->length = length; - message->next = NULL; json_dumpb(value, message->data, length, JSON_COMPACT); } diff --git a/lib/webfuse/core/message.h b/lib/webfuse/core/message.h index ea73e5f..7bd346b 100644 --- a/lib/webfuse/core/message.h +++ b/lib/webfuse/core/message.h @@ -9,10 +9,11 @@ using std::size_t; #endif #include +#include "webfuse/core/slist.h" struct wf_message { - struct wf_message * next; + struct wf_slist_item item; char * data; size_t length; }; @@ -22,7 +23,8 @@ extern "C" { #endif -extern struct wf_message * wf_message_create(json_t const * value); +extern struct wf_message * wf_message_create( + json_t const * value); extern void wf_message_dispose( struct wf_message * message); diff --git a/lib/webfuse/core/message_queue.c b/lib/webfuse/core/message_queue.c index b601215..3853f44 100644 --- a/lib/webfuse/core/message_queue.c +++ b/lib/webfuse/core/message_queue.c @@ -1,63 +1,17 @@ #include "webfuse/core/message_queue.h" #include "webfuse/core/message.h" - -void wf_message_queue_init( - struct wf_message_queue * queue) -{ - queue->first = NULL; - queue->last = NULL; - -} +#include "webfuse/core/container_of.h" void wf_message_queue_cleanup( - struct wf_message_queue * queue) + struct wf_slist * queue) { - struct wf_message * message = queue->first; - while (NULL != message) + struct wf_slist_item * item = queue->first; + while (NULL != item) { - struct wf_message * next = message->next; + struct wf_slist_item * next = item->next; + struct wf_message * message = WF_CONTAINER_OF(item, struct wf_message, item); wf_message_dispose(message); - message = next; + item = next; } - wf_message_queue_init(queue); -} - -bool wf_message_queue_empty( - struct wf_message_queue * queue) -{ - return (NULL == queue->first); -} - -void wf_message_queue_push( - struct wf_message_queue * queue, - struct wf_message * message) -{ - message->next = NULL; - - if (NULL != queue->last) - { - queue->last->next = message; - queue->last = message; - } - else - { - queue->first = message; - queue->last = message; - } -} - -struct wf_message * wf_message_queue_pop( - struct wf_message_queue * queue) -{ - struct wf_message * const result = queue->first; - if (NULL != result) - { - queue->first = queue->first->next; - if (NULL == queue->first) - { - queue->last = NULL; - } - } - - return result; + wf_slist_init(queue); } diff --git a/lib/webfuse/core/message_queue.h b/lib/webfuse/core/message_queue.h index 48fdfad..9d549d1 100644 --- a/lib/webfuse/core/message_queue.h +++ b/lib/webfuse/core/message_queue.h @@ -1,39 +1,16 @@ #ifndef WF_MESSAGE_QUEUE_H #define WF_MESSAGE_QUEUE_H -#ifndef __cplusplus -#include -#endif - -struct wf_message_queue; -struct wf_message; - -struct wf_message_queue -{ - struct wf_message * first; - struct wf_message * last; -}; - #ifdef __cplusplus extern "C" { #endif -extern void wf_message_queue_init( - struct wf_message_queue * queue); +struct wf_slist; extern void wf_message_queue_cleanup( - struct wf_message_queue * queue); + struct wf_slist * queue); -extern bool wf_message_queue_empty( - struct wf_message_queue * queue); - -extern void wf_message_queue_push( - struct wf_message_queue * queue, - struct wf_message * message); - -extern struct wf_message * wf_message_queue_pop( - struct wf_message_queue * queue); #ifdef __cplusplus } diff --git a/lib/webfuse/core/slist.c b/lib/webfuse/core/slist.c new file mode 100644 index 0000000..fdc1724 --- /dev/null +++ b/lib/webfuse/core/slist.c @@ -0,0 +1,77 @@ +#include "webfuse/core/slist.h" +#include + +void wf_slist_init( + struct wf_slist * list) +{ + list->first = NULL; + list->last = NULL; +} + +bool wf_slist_empty( + struct wf_slist * list) +{ + return (NULL == list->first); +} + +void wf_slist_append( + struct wf_slist * list, + struct wf_slist_item * item) +{ + item->next = NULL; + + if (NULL != list->last) + { + list->last->next = item; + list->last = item; + } + else + { + list->first = item; + list->last = item; + } +} + +struct wf_slist_item * wf_slist_remove_first( + struct wf_slist * list) +{ + struct wf_slist_item * const result = list->first; + if (NULL != result) + { + list->first = list->first->next; + if (NULL == list->first) + { + list->last = NULL; + } + } + + return result; +} + +struct wf_slist_item * wf_slist_remove_after( + struct wf_slist * list, + struct wf_slist_item * prev) +{ + struct wf_slist_item * result = NULL; + + if (NULL != prev) + { + result = prev->next; + if ((NULL != result) && (NULL != result->next)) + { + prev->next = result->next; + } + else + { + list->last = prev; + prev->next = NULL; + } + + } + else + { + result = wf_slist_remove_first(list); + } + + return result; +} diff --git a/lib/webfuse/core/slist.h b/lib/webfuse/core/slist.h new file mode 100644 index 0000000..5073ff3 --- /dev/null +++ b/lib/webfuse/core/slist.h @@ -0,0 +1,45 @@ +#ifndef WF_SLIST_H +#define WF_SLIST_H + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct wf_slist_item +{ + struct wf_slist_item * next; +}; + +struct wf_slist +{ + struct wf_slist_item * first; + struct wf_slist_item * last; +}; + +extern void wf_slist_init( + struct wf_slist * list); + +extern bool wf_slist_empty( + struct wf_slist * list); + +extern void wf_slist_append( + struct wf_slist * list, + struct wf_slist_item * item); + +extern struct wf_slist_item * wf_slist_remove_first( + struct wf_slist * list); + +extern struct wf_slist_item * wf_slist_remove_after( + struct wf_slist * list, + struct wf_slist_item * prev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/webfuse/core/string.c b/lib/webfuse/core/string.c new file mode 100644 index 0000000..b5ead74 --- /dev/null +++ b/lib/webfuse/core/string.c @@ -0,0 +1,35 @@ +#include "webfuse/core/string.h" + +#include +#include + +char * wf_create_string(char const * format, ...) +{ + char * result = NULL; + + va_list measure_args; + va_start(measure_args, format); + char buffer; + int needed = vsnprintf(&buffer, 1, format, measure_args); /* Flawfinder: ignore */ + va_end(measure_args); + + if (0 <= needed) + { + result = malloc(needed + 1); + if (NULL != result) + { + va_list args; + va_start(args, format); + int count = vsnprintf(result, needed + 1, format, args); /* Flawfinder: ignore */ + va_end(args); + + if ((count < 0) || (needed < count)) + { + free(result); + result = NULL; + } + } + } + + return result; +} diff --git a/lib/webfuse/core/string.h b/lib/webfuse/core/string.h new file mode 100644 index 0000000..00a686b --- /dev/null +++ b/lib/webfuse/core/string.h @@ -0,0 +1,21 @@ +#ifndef WF_CORE_STRING_H +#define WF_CORE_STRING_H + +#ifndef __cplusplus +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern char * wf_create_string(char const * format, ...); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/webfuse/provider/impl/client_protocol.c b/lib/webfuse/provider/impl/client_protocol.c index f6c34be..a5dd360 100644 --- a/lib/webfuse/provider/impl/client_protocol.c +++ b/lib/webfuse/provider/impl/client_protocol.c @@ -10,6 +10,8 @@ #include "webfuse/provider/impl/provider.h" #include "webfuse/core/util.h" #include "webfuse/core/message.h" +#include "webfuse/core/message_queue.h" +#include "webfuse/core/container_of.h" static void wfp_impl_client_protocol_respond( json_t * response, @@ -20,7 +22,7 @@ static void wfp_impl_client_protocol_respond( struct wf_message * message = wf_message_create(response); if (NULL != message) { - wf_message_queue_push(&protocol->queue, message); + wf_slist_append(&protocol->messages, &message->item); lws_callback_on_writable(protocol->wsi); } } @@ -45,6 +47,26 @@ static void wfp_impl_client_protocol_process_request( } } +static void wfp_impl_client_protocol_add_filesystem( + struct wfp_client_protocol * protocol) +{ + json_t * params = json_array(); + json_array_append_new(params, json_string("cprovider")); + + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("add_filesystem")); + json_object_set_new(request, "params", params); + json_object_set_new(request, "id", json_integer(42)); + + struct wf_message * message = wf_message_create(request); + if (NULL != message) + { + wf_slist_append(&protocol->messages, &message->item); + lws_callback_on_writable(protocol->wsi); + } + + json_decref(request); +} static int wfp_impl_client_protocol_callback( struct lws * wsi, @@ -61,6 +83,7 @@ static int wfp_impl_client_protocol_callback( switch (reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: + wfp_impl_client_protocol_add_filesystem(protocol); protocol->provider.connected(protocol->user_data); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: @@ -75,13 +98,14 @@ static int wfp_impl_client_protocol_callback( case LWS_CALLBACK_SERVER_WRITEABLE: // fall-through case LWS_CALLBACK_CLIENT_WRITEABLE: - if ((wsi == protocol->wsi) && (!wf_message_queue_empty(&protocol->queue))) + if ((wsi == protocol->wsi) && (!wf_slist_empty(&protocol->messages))) { - struct wf_message * message = wf_message_queue_pop(&protocol->queue); + struct wf_slist_item * item = wf_slist_remove_first(&protocol->messages); + struct wf_message * message = WF_CONTAINER_OF(item, struct wf_message, item); lws_write(wsi, (unsigned char*) message->data, message->length, LWS_WRITE_TEXT); wf_message_dispose(message); - if (!wf_message_queue_empty(&protocol->queue)) + if (!wf_slist_empty(&protocol->messages)) { lws_callback_on_writable(wsi); @@ -102,7 +126,7 @@ void wfp_impl_client_protocol_init( struct wfp_provider const * provider, void * user_data) { - wf_message_queue_init(&protocol->queue); + wf_slist_init(&protocol->messages); protocol->wsi = NULL; @@ -116,7 +140,7 @@ void wfp_impl_client_protocol_init( void wfp_impl_client_protocol_cleanup( struct wfp_client_protocol * protocol) { - wf_message_queue_cleanup(&protocol->queue); + wf_message_queue_cleanup(&protocol->messages); } struct wfp_client_protocol * wfp_impl_client_protocol_create( diff --git a/lib/webfuse/provider/impl/client_protocol.h b/lib/webfuse/provider/impl/client_protocol.h index c4181fe..54929c3 100644 --- a/lib/webfuse/provider/impl/client_protocol.h +++ b/lib/webfuse/provider/impl/client_protocol.h @@ -4,7 +4,7 @@ #include "webfuse/provider/impl/provider.h" #include "webfuse/provider/impl/request.h" -#include "webfuse/core/message_queue.h" +#include "webfuse/core/slist.h" #ifdef __cplusplus extern "C" @@ -20,7 +20,7 @@ struct wfp_client_protocol struct wfp_provider provider; void * user_data; struct lws * wsi; - struct wf_message_queue queue; + struct wf_slist messages; }; extern void wfp_impl_client_protocol_init( diff --git a/lib/webfuse/provider/impl/operation/close.c b/lib/webfuse/provider/impl/operation/close.c index 2dfaf9f..3346fbc 100644 --- a/lib/webfuse/provider/impl/operation/close.c +++ b/lib/webfuse/provider/impl/operation/close.c @@ -8,11 +8,11 @@ void wfp_impl_close( int WF_UNUSED_PARAM(id)) { size_t const param_count = json_array_size(params); - if (3 == param_count) + if (4 == param_count) { - json_t * inode_holder = json_array_get(params, 0); - json_t * handle_holder = json_array_get(params, 1); - json_t * flags_holder = json_array_get(params, 2); + json_t * inode_holder = json_array_get(params, 1); + json_t * handle_holder = json_array_get(params, 2); + json_t * flags_holder = json_array_get(params, 3); if (json_is_integer(inode_holder) && json_is_integer(handle_holder) && diff --git a/lib/webfuse/provider/impl/operation/getattr.c b/lib/webfuse/provider/impl/operation/getattr.c index f83c851..467e99b 100644 --- a/lib/webfuse/provider/impl/operation/getattr.c +++ b/lib/webfuse/provider/impl/operation/getattr.c @@ -13,9 +13,9 @@ void wfp_impl_getattr( int id) { size_t const count = json_array_size(params); - if (1 == count) + if (2 == count) { - json_t * inode_holder = json_array_get(params, 0); + json_t * inode_holder = json_array_get(params, 1); if (json_is_integer(inode_holder)) { diff --git a/lib/webfuse/provider/impl/operation/lookup.c b/lib/webfuse/provider/impl/operation/lookup.c index 17dc2c1..d2e89aa 100644 --- a/lib/webfuse/provider/impl/operation/lookup.c +++ b/lib/webfuse/provider/impl/operation/lookup.c @@ -12,10 +12,10 @@ void wfp_impl_lookup( int id) { size_t const count = json_array_size(params); - if (2 == count) + if (3 == count) { - json_t * inode_holder = json_array_get(params, 0); - json_t * name_holder = json_array_get(params, 1); + json_t * inode_holder = json_array_get(params, 1); + json_t * name_holder = json_array_get(params, 2); if (json_is_integer(inode_holder) && json_is_string(name_holder)) diff --git a/lib/webfuse/provider/impl/operation/open.c b/lib/webfuse/provider/impl/operation/open.c index 6beb974..25c8dff 100644 --- a/lib/webfuse/provider/impl/operation/open.c +++ b/lib/webfuse/provider/impl/operation/open.c @@ -9,10 +9,10 @@ void wfp_impl_open( int id) { size_t const count = json_array_size(params); - if (2 == count) + if (3 == count) { - json_t * inode_holder = json_array_get(params, 0); - json_t * flags_holder = json_array_get(params, 1); + json_t * inode_holder = json_array_get(params, 1); + json_t * flags_holder = json_array_get(params, 2); if (json_is_integer(inode_holder) && json_is_integer(flags_holder)) diff --git a/lib/webfuse/provider/impl/operation/read.c b/lib/webfuse/provider/impl/operation/read.c index 8ea3801..19a9926 100644 --- a/lib/webfuse/provider/impl/operation/read.c +++ b/lib/webfuse/provider/impl/operation/read.c @@ -13,12 +13,12 @@ void wfp_impl_read( int id) { size_t const count = json_array_size(params); - if (4 == count) + if (5 == count) { - json_t * inode_holder = json_array_get(params, 0); - json_t * handle_holder = json_array_get(params, 1); - json_t * offset_holder = json_array_get(params, 2); - json_t * length_holder = json_array_get(params, 3); + json_t * inode_holder = json_array_get(params, 1); + json_t * handle_holder = json_array_get(params, 2); + json_t * offset_holder = json_array_get(params, 3); + json_t * length_holder = json_array_get(params, 4); if (json_is_integer(inode_holder) && json_is_integer(handle_holder) && diff --git a/lib/webfuse/provider/impl/operation/readdir.c b/lib/webfuse/provider/impl/operation/readdir.c index 96d0362..4fc3db7 100644 --- a/lib/webfuse/provider/impl/operation/readdir.c +++ b/lib/webfuse/provider/impl/operation/readdir.c @@ -10,9 +10,9 @@ void wfp_impl_readdir( int id) { size_t const count = json_array_size(params); - if (1 == count) + if (2 == count) { - json_t * inode_holder = json_array_get(params, 0); + json_t * inode_holder = json_array_get(params, 1); if ((NULL != inode_holder) && (json_is_integer(inode_holder))) { diff --git a/test/test_container_of.cc b/test/test_container_of.cc new file mode 100644 index 0000000..91a96ef --- /dev/null +++ b/test/test_container_of.cc @@ -0,0 +1,29 @@ +#include +#include "webfuse/core/container_of.h" + +namespace +{ + +struct MyStruct +{ + int first; + int second; +}; + +} + +TEST(ContainerOf, FirstMember) +{ + MyStruct my_struct = {23, 42}; + + int * first = &my_struct.first; + ASSERT_EQ(&my_struct, WF_CONTAINER_OF(first, MyStruct, first)); +} + +TEST(ContainerOf, SecondMember) +{ + MyStruct my_struct = {23, 42}; + + int * second = &my_struct.second; + ASSERT_EQ(&my_struct, WF_CONTAINER_OF(second, MyStruct, second)); +} \ No newline at end of file diff --git a/test/test_slist.cc b/test/test_slist.cc new file mode 100644 index 0000000..c6995fa --- /dev/null +++ b/test/test_slist.cc @@ -0,0 +1,35 @@ +#include +#include "webfuse/core/slist.h" + +TEST(wf_slist_remove_after, RemoveFirst) +{ + struct wf_slist list; + struct wf_slist_item item[10]; + + wf_slist_init(&list); + wf_slist_append(&list, &item[0]); + + wf_slist_item * removed = wf_slist_remove_after(&list, NULL); + ASSERT_TRUE(wf_slist_empty(&list)); + ASSERT_EQ(nullptr, list.first); + ASSERT_EQ(nullptr, list.last); + ASSERT_EQ(&item[0], removed); +} + +TEST(wf_slist_remove_after, RemoveLast) +{ + struct wf_slist list; + struct wf_slist_item item[10]; + + wf_slist_init(&list); + wf_slist_append(&list, &item[0]); + wf_slist_append(&list, &item[1]); + wf_slist_append(&list, &item[2]); + + wf_slist_item * removed = wf_slist_remove_after(&list, &item[1]); + ASSERT_FALSE(wf_slist_empty(&list)); + ASSERT_EQ(&item[0], list.first); + ASSERT_EQ(&item[1], list.last); + ASSERT_EQ(nullptr, item[1].next); + ASSERT_EQ(&item[2], removed); +} diff --git a/test/test_string.cc b/test/test_string.cc new file mode 100644 index 0000000..17e501a --- /dev/null +++ b/test/test_string.cc @@ -0,0 +1,18 @@ +#include +#include + +#include "webfuse/core/string.h" + +TEST(wf_string_create, Default) +{ + char * value = wf_create_string("test %s/%d", "hello", 42); + ASSERT_STREQ("test hello/42", value); + free(value); +} + +TEST(wf_string_create, EmptyString) +{ + char * value = wf_create_string(""); + ASSERT_STREQ("", value); + free(value); +}