From 858fdb862b04c8ac2f834673d5c0d56d2f953af6 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 27 Jan 2019 03:45:03 +0100 Subject: [PATCH] initial commit --- CMakeLists.txt | 71 ++++++++++ src/app/main.c | 103 ++++++++++++++ src/app/www/index.html | 12 ++ src/app/www/script.js | 98 +++++++++++++ src/main.c | 87 ++++++++++++ src/wsfs/filesystem.c | 103 ++++++++++++++ src/wsfs/filesystem.h | 38 +++++ src/wsfs/fuse.h | 8 ++ src/wsfs/jsonrpc.c | 232 ++++++++++++++++++++++++++++++ src/wsfs/jsonrpc.h | 41 ++++++ src/wsfs/operations.c | 105 ++++++++++++++ src/wsfs/operations.h | 10 ++ src/wsfs/protocol.c | 243 ++++++++++++++++++++++++++++++++ src/wsfs/protocol.h | 37 +++++ src/wsfs/response_parser.c | 51 +++++++ src/wsfs/response_parser.h | 21 +++ src/wsfs/server.c | 220 +++++++++++++++++++++++++++++ src/wsfs/server.h | 42 ++++++ src/wsfs/status.h | 18 +++ src/wsfs/util.h | 10 ++ test-src/test_main.c | 13 ++ test-src/test_response_parser.c | 62 ++++++++ test-src/test_util.c | 15 ++ test-src/test_util.h | 31 ++++ 24 files changed, 1671 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 src/app/main.c create mode 100644 src/app/www/index.html create mode 100644 src/app/www/script.js create mode 100644 src/main.c create mode 100644 src/wsfs/filesystem.c create mode 100644 src/wsfs/filesystem.h create mode 100644 src/wsfs/fuse.h create mode 100644 src/wsfs/jsonrpc.c create mode 100644 src/wsfs/jsonrpc.h create mode 100644 src/wsfs/operations.c create mode 100644 src/wsfs/operations.h create mode 100644 src/wsfs/protocol.c create mode 100644 src/wsfs/protocol.h create mode 100644 src/wsfs/response_parser.c create mode 100644 src/wsfs/response_parser.h create mode 100644 src/wsfs/server.c create mode 100644 src/wsfs/server.h create mode 100644 src/wsfs/status.h create mode 100644 src/wsfs/util.h create mode 100644 test-src/test_main.c create mode 100644 test-src/test_response_parser.c create mode 100644 test-src/test_util.c create mode 100644 test-src/test_util.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..577f132 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,71 @@ +cmake_minimum_required (VERSION 2.8) +project(fuse-wsfs) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(FUSE3 REQUIRED fuse3) +pkg_check_modules(LWS REQUIRED libwebsockets) +pkg_check_modules(JANSSON REQUIRED jansson) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Wextra") + +set(EXTRA_INCLUDE_DIRS + ${FUSE3_INCLUDE_DIRS} + ${LWS_INCLUDE_DIRS} + ${JANSSON_INCLUDE_DIRS} +) + +set(EXTRA_LIBS + ${EXTRA_LIBS} + ${FUSE3_LIBRARIES} + ${LWS_LIBRARIES} + ${JANSSON_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} +) + +set(EXTRA_CFLAGS + ${FUSE3_CFLAGS_OTHER} + ${LWS_CFLAGS_OTHER} + ${JANSSON_CFLAGS_OTHER} + "-pthread" +) + + +# libfuse-wsfs + +add_library(fuse-wsfs + src/wsfs/operations.c + src/wsfs/filesystem.c + src/wsfs/response_parser.c + src/wsfs/server.c + src/wsfs/protocol.c + src/wsfs/jsonrpc.c +) + +target_include_directories(fuse-wsfs PUBLIC src ${EXTRA_INCLUDE_DIRS}) +target_compile_options(fuse-wsfs PUBLIC ${EXTRA_CFLAGS}) + +# app + +add_executable(wsfs + src/app/main.c +) + +target_link_libraries(wsfs PUBLIC fuse-wsfs ${EXTRA_LIBS}) +target_include_directories(wsfs PUBLIC src ${EXTRA_INCLUDE_DIRS}) +target_compile_options(wsfs PUBLIC ${EXTRA_CFLAGS}) + +# tests + +add_executable(alltests + test-src/test_main.c + test-src/test_util.c + test-src/test_response_parser.c +) + +target_link_libraries(alltests PUBLIC fuse-wsfs ${EXTRA_LIBS}) +target_include_directories(alltests PUBLIC src ${EXTRA_INCLUDE_DIRS}) +target_compile_options(alltests PUBLIC ${EXTRA_CFLAGS}) + diff --git a/src/app/main.c b/src/app/main.c new file mode 100644 index 0000000..a1b033e --- /dev/null +++ b/src/app/main.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include + +#include "wsfs/fuse.h" +#include "wsfs/operations.h" +#include "wsfs/server.h" + +struct options +{ + struct wsfs_server_config config; + int show_help; +}; + +static struct fuse_opt const option_spec[] = +{ + {"--document_root=%s" , offsetof(struct options, config.document_root), 1}, + {"--server_cert_path=%s", offsetof(struct options, config.cert_path), 1}, + {"--server_key_path=%s" , offsetof(struct options, config.key_path), 1}, + {"--vhost_name=%s" , offsetof(struct options, config.vhost_name), 1}, + {"--port=%d" , offsetof(struct options, config.port), 1}, + {"-h" , offsetof(struct options, show_help), 1}, + {"--help" , offsetof(struct options, show_help), 1}, + FUSE_OPT_END +}; + +static void show_help(void) +{ + printf( + "\n" + "File-system specific options:\n" + " --document_root= Path of www directory (default: not set, www disabled)\n" + " --server_cert_path= Path of servers own certificate (default: not set, TLS disabled)\n" + " --server_key_path= Path of servers private key (default: not set, TLS disabled)\n" + " --vhost_name= Name of virtual host (default: \"localhost\")\n" + " --port= Number of servers port (default: 8080)\n" + "\n"); +} + +int main(int argc, char * argv[]) +{ + struct options options = + { + .config = + { + .document_root = NULL, + .cert_path = NULL, + .key_path = NULL, + .vhost_name = strdup("localhost"), + .port = 8080, + }, + .show_help = 0 + }; + + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + if (-1 == fuse_opt_parse(&args, &options, option_spec, NULL)) + { + return EXIT_FAILURE; + } + + struct fuse_operations operations; + wsfs_operations_init(&operations); + + int result; + if (!options.show_help) + { + struct wsfs_server * server = wsfs_server_create(&options.config); + if (NULL != server) + { + wsfs_server_start(server); + struct wsfs_jsonrpc * const rpc = wsfs_server_get_jsonrpc_service(server); + + result = fuse_main(args.argc, args.argv, &operations, rpc); + wsfs_server_dispose(server); + } + else + { + fprintf(stderr, "fatal: unable start server\n"); + result = EXIT_FAILURE; + } + } + else + { + if (0 == fuse_opt_add_arg(&args, "--help")) + { + fuse_main(args.argc, args.argv, &operations, NULL); + show_help(); + } + else + { + fprintf(stderr, "fatal: unable to show help\n"); + } + + result = EXIT_FAILURE; + } + + fuse_opt_free_args(&args); + wsfs_server_config_clear(&options.config); + + return result; +} + diff --git a/src/app/www/index.html b/src/app/www/index.html new file mode 100644 index 0000000..d3c4b39 --- /dev/null +++ b/src/app/www/index.html @@ -0,0 +1,12 @@ + + + LWS Example + + + + + + + + + diff --git a/src/app/www/script.js b/src/app/www/script.js new file mode 100644 index 0000000..b5579a3 --- /dev/null +++ b/src/app/www/script.js @@ -0,0 +1,98 @@ +function startup() +{ + var FileSystem = function(root) { + this.root = root; + }; + + FileSystem.BAD_NO_ENTRY = -1; + + FileSystem.prototype.getEntry = function(path) { + var items = path.split('/'); + var curItem = this.root; + var item; + var i, len; + + for(i = 0, len = items.length; curItem && (i < len); i++) { + item = items[i]; + if ("" !== item) { + curItem = curItem.entries && curItem.entries[item]; + } + } + + return curItem; + } + + FileSystem.prototype.getattr = function(path) { + entry = this.getEntry(path); + if (entry) { + return { + mode: entry.mode || 0755, + type: entry.type || 'file', + size: entry.size || 0, + atime: entry.atime || 0, + mtime: entry.mtime || 0, + ctime: entry.ctime || 0 + }; + } + else { + return FileSystem.BAD_NO_ENTRY; + } + }; + + var fs = new FileSystem({ + mode: 0755, + type: "dir", + entries: { + "hello": { mode: 0755, type: "file", size: 10, contents: "Hello, World!"} + } + }); + + var ws = new WebSocket('ws://localhost:4711/', 'fs'); + ws.onopen = function() { + console.log('open'); + }; + ws.onclose = function() { + console.log('close'); + }; + ws.onmessage = function(message) { + console.log(message); + + try { + var request = JSON.parse(message.data); + var result = -42; + var response; + + if (("string" === typeof(request.method)) && + ("number" === typeof(request.id)) && + (request.params)) { + switch(request.method) + { + case "getattr": + result = fs.getattr(request.params[0]); + break; + default: + break; + } + + if ("number" !== typeof(result)) { + response = {result: result, id: request.id}; + } + else { + response = {error: {code: result}, id: request.id}; + } + console.log(response); + ws.send(JSON.stringify(response)); + } + } + catch (ex) { console.log(ex); } + }; + + var sendButton = document.getElementById('sendButton'); + sendButton.addEventListener('click', function() { + var content = document.getElementById('content').value; + ws.send(content); + console.log(content); + }); +} + +document.onload=startup(); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..bb00521 --- /dev/null +++ b/src/main.c @@ -0,0 +1,87 @@ +#include +#include +#include + +#include "wsfs/fuse.h" +#include "wsfs/operations.h" + +struct options +{ + char const * document_root; + char const * server_cert_path; + char const * server_key_path; + char const * vhost_name; + int port; + int show_help; +}; + +static struct fuse_opt const option_spec[] = +{ + {"--document_root=%s" , offsetof(struct options, document_root), 1}, + {"--server_cert_path=%s", offsetof(struct options, server_cert_path), 1}, + {"--server_key_path=%s" , offsetof(struct options, server_key_path), 1}, + {"--vhost_name=%s" , offsetof(struct options, vhost_name), 1}, + {"--port=%d" , offsetof(struct options, port), 1}, + {"-h" , offsetof(struct options, show_help), 1}, + {"--help" , offsetof(struct options, show_help), 1}, + FUSE_OPT_END +}; + +static void show_help(void) +{ + printf( + "\n" + "File-system specific options:\n" + " --document_root= Path of www directory (default: not set, www disabled)\n" + " --server_cert_path= Path of servers own certificate (default: not set, TLS disabled)\n" + " --server_key_path= Path of servers private key (default: not set, TLS disabled)\n" + " --vhost_name= Name of virtual host (default: \"localhost\")\n" + " --port= Number of servers port (default: 8080)\n" + "\n"); +} + +int main(int argc, char * argv[]) +{ + struct options options = + { + .document_root = NULL, + .server_cert_path = NULL, + .server_key_path = NULL, + .vhost_name = "localhost", + .port = 8080, + .show_help = 0 + }; + + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + if (-1 == fuse_opt_parse(&args, &options, option_spec, NULL)) + { + return EXIT_FAILURE; + } + + struct fuse_operations operations; + wsfs_operations_init(&operations); + + int result; + if (!options.show_help) + { + result = fuse_main(args.argc, args.argv, &operations, NULL); + } + else + { + if (0 == fuse_opt_add_arg(&args, "--help")) + { + fuse_main(args.argc, args.argv, &operations, NULL); + show_help(); + } + else + { + fprintf(stderr, "fatal: unable to show help\n"); + } + + result = EXIT_FAILURE; + } + + fuse_opt_free_args(&args); + return result; +} + diff --git a/src/wsfs/filesystem.c b/src/wsfs/filesystem.c new file mode 100644 index 0000000..19855fe --- /dev/null +++ b/src/wsfs/filesystem.c @@ -0,0 +1,103 @@ +#include "wsfs/filesystem.h" + +#include +#include +#include +#include + +#include "wsfs/util.h" +#include "wsfs/jsonrpc.h" + +struct wsfs_filesystem +{ + struct wsfs_jsonrpc * rpc; +}; + +struct wsfs_filesystem * wsfs_filesystem_create( + struct wsfs_jsonrpc * rpc) +{ + struct wsfs_filesystem * filesystem = malloc(sizeof(struct wsfs_filesystem)); + if (NULL != filesystem) + { + filesystem->rpc = rpc; + } + + return filesystem; +} + +void wsfs_filesystem_dispose( + struct wsfs_filesystem * filesystem) +{ + free(filesystem); +} + +static int wsfs_json_get_int(json_t * object, char const * key, int default_value) +{ + int result = default_value; + + json_t * holder = json_object_get(object, key); + if ((NULL != holder) && (json_is_integer(holder))) + { + result = json_integer_value(holder); + } + + return result; +} + +wsfs_status wsfs_filesystem_getattr( + struct wsfs_filesystem * filesystem, + char const * path, + struct stat * result) +{ + json_t * data = NULL; + wsfs_status status = wsfs_jsonrpc_invoke(filesystem->rpc, &data, "getattr", "s", path); + if (NULL != result) + { + json_t * mode_holder = json_object_get(data, "mode"); + json_t * type_holder = json_object_get(data, "type"); + if ((NULL != mode_holder) && (json_is_integer(mode_holder)) && + (NULL != type_holder) && (json_is_string(type_holder))) + { + result->st_mode = json_integer_value(mode_holder) & 0777; + char const * type = json_string_value(type_holder); + if (0 == strcmp("file", type)) + { + result->st_mode |= S_IFREG; + } + else if (0 == strcmp("dir", type)) + { + result->st_mode |= S_IFDIR; + } + + result->st_atime = wsfs_json_get_int(data, "atime", 0); + result->st_mtime = wsfs_json_get_int(data, "mtime", 0); + result->st_ctime = wsfs_json_get_int(data, "ctime", 0); + } + else + { + status = WSFS_BAD_PARSEERROR; + } + + + json_decref(data); + } + + return status; +} + +wsfs_status wsfs_filesystem_readdir( + struct wsfs_filesystem * filesystem, + char const * path, + void * WSFS_UNUSED_PARAM(buffer), + wsfs_add_entry_fn * WSFS_UNUSED_PARAM(add_entry)) +{ + json_t * result = NULL; + wsfs_status const status = wsfs_jsonrpc_invoke(filesystem->rpc, &result, "readdir", "s", path); + if (NULL != result) + { + json_decref(result); + } + + return status; +} + diff --git a/src/wsfs/filesystem.h b/src/wsfs/filesystem.h new file mode 100644 index 0000000..1ffe5fe --- /dev/null +++ b/src/wsfs/filesystem.h @@ -0,0 +1,38 @@ +#ifndef _WSFS_FILESYSTEM_H +#define _WSFS_FILESYSTEM_H + +#include + +#include +#include +#include + +#include "wsfs/status.h" + + +struct wsfs_filesystem; +struct wsfs_jsonrpc; + +typedef bool wsfs_add_entry_fn(void * buffer, char const * path); + +extern struct wsfs_filesystem * wsfs_filesystem_create( + struct wsfs_jsonrpc * service); + +extern void wsfs_filesystem_dispose( + struct wsfs_filesystem * filesystem); + +extern wsfs_status wsfs_filesystem_getattr( + struct wsfs_filesystem * filesystem, + char const * path, + struct stat * result); + +extern wsfs_status wsfs_filesystem_readdir( + struct wsfs_filesystem * filesystem, + char const * path, + void * buffer, + wsfs_add_entry_fn * add_entry); + + + +#endif + diff --git a/src/wsfs/fuse.h b/src/wsfs/fuse.h new file mode 100644 index 0000000..3a12825 --- /dev/null +++ b/src/wsfs/fuse.h @@ -0,0 +1,8 @@ +#ifndef _WSFS_FUSE_H +#define _WSFS_FUSE_H + +#define FUSE_USE_VERSION 31 +#include + +#endif + diff --git a/src/wsfs/jsonrpc.c b/src/wsfs/jsonrpc.c new file mode 100644 index 0000000..3ad70ef --- /dev/null +++ b/src/wsfs/jsonrpc.c @@ -0,0 +1,232 @@ +#include "wsfs/jsonrpc.h" + +#include +#include +#include + +#include + +#include "wsfs/response_parser.h" + +#define DEFAULT_TIMEOUT_SECS 10 + +struct wsfs_jsonrpc_response +{ + int id; + wsfs_status status; + json_t * result; +}; + +struct wsfs_jsonrpc +{ + pthread_mutex_t lock; + pthread_cond_t finished; + pthread_condattr_t finished_attributes; + + wsfs_create_message_fn * create_message; + wsfs_send_message_fn * send_message; + void * user_data; + + bool is_finished; + struct wsfs_jsonrpc_response response; +}; + +static json_t * wsfs_request_create( + char const * method, + int id, + char const * param_info, + va_list args) +{ + json_t * request = json_object(); + json_object_set_new(request, "method", json_string(method)); + json_t * params = json_array(); + + for (char const * param_type = param_info; '\0' != *param_type; param_type++) + { + switch(*param_type) + { + case 's': + { + char const * const value = va_arg(args, char const *); + json_array_append_new(params, json_string(value)); + } + break; + case 'i': + { + int const value = va_arg(args, int); + json_array_append_new(params, json_integer(value)); + } + break; + default: + fprintf(stderr, "fatal: unknown param_type '%c'\n", *param_type); + exit(EXIT_FAILURE); + break; + } + } + + + json_object_set_new(request, "params", params); + json_object_set_new(request, "id", json_integer(id)); + + return request; +} + + +wsfs_status wsfs_jsonrpc_invoke( + struct wsfs_jsonrpc * rpc, + json_t * * result, + char const * method, + char const * param_info, + ... +) +{ + // enqueue message + pthread_mutex_lock(&rpc->lock); + wsfs_status status = WSFS_BAD; + + char * message = NULL; + size_t length = 0; + if (-1 == rpc->response.id) { + + va_list args; + va_start(args, param_info); + json_t * request = wsfs_request_create(method, 42, param_info, args); + va_end(args); + + + length = json_dumpb(request, NULL, 0, JSON_COMPACT); + if (0 < length) + { + rpc->is_finished = false; + rpc->response.id = 42; + rpc->response.result = NULL; + rpc->response.status = WSFS_GOOD; + + message = rpc->create_message(length); + json_dumpb(request, message, length, JSON_COMPACT); + + } + json_decref(request); + } + pthread_mutex_unlock(&rpc->lock); + + + if (NULL != message) + { + bool const success = rpc->send_message(message, length, rpc->user_data); + + // wait for answer + pthread_mutex_lock(&rpc->lock); + + if (success) + { + struct timespec timeout; + clock_gettime(CLOCK_MONOTONIC, &timeout); + timeout.tv_sec += DEFAULT_TIMEOUT_SECS; + int rc = 0; + while ((0 == rc) && (!rpc->is_finished)) { + rc = pthread_cond_timedwait(&rpc->finished, &rpc->lock, &timeout); + } + + if (rpc->is_finished) + { + status = rpc->response.status; + *result = rpc->response.result; + } + else + { + status = WSFS_BAD_TIMEOUT; + } + } + + rpc->response.id = -1; + rpc->response.result = NULL; + rpc->response.status = WSFS_GOOD; + pthread_mutex_unlock(&rpc->lock); + } + + return status; +} + +struct wsfs_jsonrpc * wsfs_jsonrpc_create( + wsfs_create_message_fn * create_message, + wsfs_send_message_fn * send_message, + void * user_data) +{ + struct wsfs_jsonrpc * rpc = malloc(sizeof(struct wsfs_jsonrpc)); + if (NULL != rpc) + { + pthread_mutex_init(&rpc->lock, NULL); + + pthread_condattr_init(&rpc->finished_attributes); + pthread_condattr_setclock(&rpc->finished_attributes, CLOCK_MONOTONIC); + pthread_cond_init(&rpc->finished, &rpc->finished_attributes); + + rpc->create_message = create_message; + rpc->send_message = send_message; + rpc->user_data = user_data; + rpc->is_finished = true; + rpc->response.id = -1; + rpc->response.status = WSFS_GOOD; + rpc->response.result = NULL; + } + + return rpc; +} + +void wsfs_jsonrpc_set_user_data( + struct wsfs_jsonrpc * rpc, + void * user_data) +{ + rpc->user_data = user_data; +} + +void wsfs_jsonrpc_dispose( + struct wsfs_jsonrpc * rpc) +{ + if (NULL != rpc->response.result) + { + json_decref(rpc->response.result); + } + + pthread_cond_destroy(&rpc->finished); + pthread_condattr_destroy(&rpc->finished_attributes); + pthread_mutex_destroy(&rpc->lock); + + + free(rpc); +} + +void wsfs_jsonrpc_on_message( + char const * message, + size_t length, + void * user_data) +{ + struct wsfs_jsonrpc * rpc = user_data; + + struct wsfs_response response; + wsfs_response_parse(message, length, &response); + + if (-1 != response.id) + { + pthread_mutex_lock(&rpc->lock); + + if (response.id == rpc->response.id) + { + rpc->is_finished = true; + rpc->response.status = response.status; + rpc->response.result = response.result; + } + else + { + if (NULL != response.result) + { + json_decref(response.result); + } + } + + pthread_cond_signal(&rpc->finished); + pthread_mutex_unlock(&rpc->lock); + } +} + diff --git a/src/wsfs/jsonrpc.h b/src/wsfs/jsonrpc.h new file mode 100644 index 0000000..52e8b83 --- /dev/null +++ b/src/wsfs/jsonrpc.h @@ -0,0 +1,41 @@ +#ifndef _WSFS_JSONRPC_H +#define _WSFS_JSONRPC_H + +#include +#include +#include +#include +#include "wsfs/status.h" + +struct wsfs_jsonrpc; + +typedef char * wsfs_create_message_fn(size_t size); +typedef bool wsfs_send_message_fn(char * message, size_t length, void * user_data); + +extern struct wsfs_jsonrpc * wsfs_jsonrpc_create( + wsfs_create_message_fn * create_message, + wsfs_send_message_fn * send_message, + void * user_data); + +extern void wsfs_jsonrpc_set_user_data( + struct wsfs_jsonrpc * rpc, + void * user_data); + +extern void wsfs_jsonrpc_dispose( + struct wsfs_jsonrpc * rpc); + +extern wsfs_status wsfs_jsonrpc_invoke( + struct wsfs_jsonrpc * rpc, + json_t * * result, + char const * method, + char const * param_info, + ... +); + +extern void wsfs_jsonrpc_on_message( + char const * message, + size_t length, + void * user_data); + +#endif + diff --git a/src/wsfs/operations.c b/src/wsfs/operations.c new file mode 100644 index 0000000..528f347 --- /dev/null +++ b/src/wsfs/operations.c @@ -0,0 +1,105 @@ +#include "wsfs/operations.h" + +#include +#include +#include + +#include "wsfs/util.h" +#include "wsfs/filesystem.h" + +struct wsfs_readdir_context +{ + void * buffer; + fuse_fill_dir_t filler; +}; + +static int wsfs_result_from_status(wsfs_status status) +{ + switch(status) + { + case WSFS_GOOD: return 0; + case WSFS_BAD_NOENTRY: return -ENOENT; + default: return -ENOENT; + } +} + +static struct wsfs_filesystem * wsfs_get_filesystem(void) +{ + struct fuse_context * const context = fuse_get_context(); + struct wsfs_filesystem * const filesystem = context->private_data; + + return filesystem; +} + +static bool wsfs_add_entry(void * buffer, char const * path) +{ + struct wsfs_readdir_context * context = buffer; + int const result = context->filler(context->buffer, path, NULL, 0, 0); + + return (0 == result); +} + +static void* wsfs_operation_init( + struct fuse_conn_info * WSFS_UNUSED_PARAM(connection), + struct fuse_config * config) +{ + struct fuse_context * const context = fuse_get_context(); + config->kernel_cache = 1; + + return wsfs_filesystem_create(context->private_data); +} + +static void wsfs_operation_destroy(void * private_data) +{ + struct wsfs_filesystem * const filesystem = private_data; + wsfs_filesystem_dispose(filesystem); +} + +static int wsfs_operation_getattr( + char const * path, + struct stat * buffer, + struct fuse_file_info * WSFS_UNUSED_PARAM(file_info)) +{ + struct fuse_context * const context = fuse_get_context(); + struct wsfs_filesystem * const filesystem = wsfs_get_filesystem(); + + wsfs_status const status = wsfs_filesystem_getattr(filesystem, path, buffer); + if (WSFS_GOOD == status) + { + buffer->st_uid = context->uid; + buffer->st_gid = context->gid; + buffer->st_nlink = 1; + } + + return wsfs_result_from_status(status); +} + +static int wsfs_operation_readdir( + char const * path, + void * buffer, + fuse_fill_dir_t filler, + off_t WSFS_UNUSED_PARAM(offset), + struct fuse_file_info * WSFS_UNUSED_PARAM(file_info), + enum fuse_readdir_flags WSFS_UNUSED_PARAM(flags)) +{ + struct wsfs_filesystem * filesystem = wsfs_get_filesystem(); + struct wsfs_readdir_context context = + { + .buffer = buffer, + .filler = filler + }; + + wsfs_status const status = wsfs_filesystem_readdir(filesystem, path, &context, &wsfs_add_entry); + return wsfs_result_from_status(status); +} + +void wsfs_operations_init( + struct fuse_operations * operations) +{ + memset(operations, 0, sizeof(struct fuse_operations)); + operations->init = &wsfs_operation_init; + operations->destroy = &wsfs_operation_destroy; + operations->getattr = &wsfs_operation_getattr; + operations->readdir = &wsfs_operation_readdir; +} + diff --git a/src/wsfs/operations.h b/src/wsfs/operations.h new file mode 100644 index 0000000..58d94b1 --- /dev/null +++ b/src/wsfs/operations.h @@ -0,0 +1,10 @@ +#ifndef _WSFS_OPERATIONS +#define _WSFS_OPERATIONS + +#include "wsfs/fuse.h" + +extern void wsfs_operations_init( + struct fuse_operations * operations); + +#endif + diff --git a/src/wsfs/protocol.c b/src/wsfs/protocol.c new file mode 100644 index 0000000..967f598 --- /dev/null +++ b/src/wsfs/protocol.c @@ -0,0 +1,243 @@ +#include "wsfs/protocol.h" + +#include +#include +#include + +#include + +#include "wsfs/util.h" +#include "wsfs/server.h" + +struct wsfs_message +{ + char * content; + size_t length; +}; + +struct wsfs_protocol +{ + pthread_mutex_t lock; + struct lws * wsi; + struct wsfs_message pending_message; + struct wsfs_server * server; +}; + +static struct wsfs_protocol * wsfs_protocol_from_wsi( + struct lws * wsi) +{ + struct lws_protocols const * protocol = lws_get_protocol(wsi); + return protocol->user; +} + +static bool wsfs_protocol_connect( + struct wsfs_protocol * protocol, + struct lws * wsi) +{ + pthread_mutex_lock(&protocol->lock); + + bool const success = (NULL == protocol->wsi); + if (success) + { + protocol->wsi = wsi; + } + + pthread_mutex_unlock(&protocol->lock); + + return success; +} + +static bool wsfs_protocol_is_wsi_connected( + struct wsfs_protocol * protocol, + struct lws * wsi) +{ + pthread_mutex_lock(&protocol->lock); + bool const result = (wsi == protocol->wsi); + pthread_mutex_unlock(&protocol->lock); + + return result; +} + +static void wsfs_protocol_disconnect( + struct wsfs_protocol * protocol, + struct lws * wsi) +{ + pthread_mutex_lock(&protocol->lock); + if (wsi == protocol->wsi) + { + protocol->wsi = NULL; + } + pthread_mutex_unlock(&protocol->lock); +} + +static void wsfs_protocol_get_message( + struct wsfs_protocol * protocol, + struct wsfs_message * message) +{ + pthread_mutex_lock(&protocol->lock); + + message->content = protocol->pending_message.content; + message->length = protocol->pending_message.length; + + protocol->pending_message.content = NULL; + protocol->pending_message.length = 0; + + pthread_mutex_unlock(&protocol->lock); +} + + +static int wsfs_protocol_callback( + struct lws *wsi, + enum lws_callback_reasons reason, + void * WSFS_UNUSED_PARAM(user), + void *in, + size_t len) +{ + int result = 0; + struct wsfs_protocol * const protocol = wsfs_protocol_from_wsi(wsi); + + switch (reason) + { + case LWS_CALLBACK_ESTABLISHED: + { + if (!wsfs_protocol_connect(protocol, wsi)) + { + puts("connect failed"); + lws_callback_on_writable(wsi); + result = -1; + } + } + break; + case LWS_CALLBACK_CLOSED: + { + wsfs_protocol_disconnect(protocol, wsi); + } + break; + case LWS_CALLBACK_SERVER_WRITEABLE: + { + if (wsfs_protocol_is_wsi_connected(protocol, wsi)) + { + struct wsfs_message message; + wsfs_protocol_get_message(protocol, &message); + if (NULL != message.content) + { + lws_write(wsi, (unsigned char*) message.content, message.length, LWS_WRITE_TEXT); + wsfs_protocol_message_dispose(message.content); + } + } + else + { + result = -1; + } + } + break; + case LWS_CALLBACK_RECEIVE: + { + wsfs_server_handle_message(protocol->server, in, len); + } + break; + default: + break; + } + + return result; +} + + +struct wsfs_protocol * wsfs_protocol_create( + struct wsfs_server * server) +{ + struct wsfs_protocol * protocol = malloc(sizeof(struct wsfs_protocol)); + if (NULL != protocol) + { + pthread_mutex_init(&protocol->lock, NULL); + protocol->wsi = NULL; + protocol->pending_message.content = NULL; + protocol->pending_message.length = 0; + protocol->server = server; + } + + return protocol; +} + +void wsfs_protocol_dispose( + struct wsfs_protocol * protocol) +{ + pthread_mutex_destroy(&protocol->lock); + + if (NULL != protocol->pending_message.content) + { + wsfs_protocol_message_dispose(protocol->pending_message.content); + protocol->pending_message.content = NULL; + } + + free(protocol); +} + +void wsfs_protocol_check( + struct wsfs_protocol * protocol) +{ + pthread_mutex_lock(&protocol->lock); + + if ((NULL != protocol->wsi) && (NULL != protocol->pending_message.content)) + { + lws_callback_on_writable(protocol->wsi); + } + + pthread_mutex_unlock(&protocol->lock); +} + +void wsfs_protocol_init_lws( + struct wsfs_protocol * protocol, + struct lws_protocols * lws_protocol) +{ + lws_protocol->callback = &wsfs_protocol_callback; + lws_protocol->per_session_data_size = 1; + lws_protocol->user = protocol; +} + +char * wsfs_protocol_message_create( + size_t size) +{ + char * buffer = malloc(LWS_PRE + size); + return &buffer[LWS_PRE]; +} + +void wsfs_protocol_message_dispose( + char * message) +{ + char * buffer = message - LWS_PRE; + free(buffer); +} + +bool wsfs_protocol_send( + char * message, + size_t length, + void * user_data) +{ + struct wsfs_protocol * protocol = user_data; + + pthread_mutex_lock(&protocol->lock); + + struct wsfs_server * server = protocol->server; + bool result = (NULL != protocol->wsi) && (NULL == protocol->pending_message.content); + if (result) + { + protocol->pending_message.content = message; + protocol->pending_message.length = length; + } + else + { + wsfs_protocol_message_dispose(message); + } + + pthread_mutex_unlock(&protocol->lock); + + if (result) + { + wsfs_server_wakeup(server); + } + + return result; +} + diff --git a/src/wsfs/protocol.h b/src/wsfs/protocol.h new file mode 100644 index 0000000..232dadd --- /dev/null +++ b/src/wsfs/protocol.h @@ -0,0 +1,37 @@ +#ifndef _WSFS_PROTOCOL_H +#define _WSFS_PROTOCOL_H + +#include +#include +#include + +struct wsfs_protocol; +struct wsfs_server; + +extern struct wsfs_protocol * wsfs_protocol_create( + struct wsfs_server * server); + +extern void wsfs_protocol_dispose( + struct wsfs_protocol * protocol); + +extern void wsfs_protocol_check( + struct wsfs_protocol * protocol); + +extern void wsfs_protocol_init_lws( + struct wsfs_protocol * protocl, + struct lws_protocols * lws_protocol); + +extern char * wsfs_protocol_message_create( + size_t size); + +extern void wsfs_protocol_message_dispose( + char * message); + +extern bool wsfs_protocol_send( + char * message, + size_t length, + void * user_data); + + +#endif + diff --git a/src/wsfs/response_parser.c b/src/wsfs/response_parser.c new file mode 100644 index 0000000..f77bf6f --- /dev/null +++ b/src/wsfs/response_parser.c @@ -0,0 +1,51 @@ +#include "wsfs/response_parser.h" + +void wsfs_response_parse( + char const * buffer, + size_t length, + struct wsfs_response * result) +{ + result->status = WSFS_BAD; + result->id = -1; + result->result = NULL; + + json_t * response = json_loadb(buffer, length, 0, NULL); + if (NULL == response) + { + result->status = WSFS_BAD_PARSEERROR; + return; + } + + json_t * id_holder = json_object_get(response, "id"); + if ((NULL == id_holder) || (!json_is_integer(id_holder))) + { + result->status = WSFS_BAD_INVALIDID; + json_decref(response); + return; + } + + result->status = WSFS_GOOD; + result->id = json_integer_value(id_holder); + result->result = json_object_get(response, "result"); + if (NULL != result->result) + { + json_incref(result->result); + } + else + { + result->status = WSFS_BAD_NODATA; + + json_t * error = json_object_get(response, "error"); + if (NULL != error) + { + json_t * error_code = json_object_get(error, "code"); + if ((NULL != error_code) && (json_is_integer(error_code))) + { + result->status = json_integer_value(error_code); + } + } + } + + json_decref(response); +} + diff --git a/src/wsfs/response_parser.h b/src/wsfs/response_parser.h new file mode 100644 index 0000000..b976621 --- /dev/null +++ b/src/wsfs/response_parser.h @@ -0,0 +1,21 @@ +#ifndef _WSFS_RESPONSE_PARSER_H +#define _WFSF_RESPONSE_PARSER_H + +#include +#include +#include "wsfs/status.h" + +struct wsfs_response +{ + wsfs_status status; + int id; + json_t * result; +}; + +extern void wsfs_response_parse( + char const * buffer, + size_t buffer_length, + struct wsfs_response * response); + +#endif + diff --git a/src/wsfs/server.c b/src/wsfs/server.c new file mode 100644 index 0000000..6e16039 --- /dev/null +++ b/src/wsfs/server.c @@ -0,0 +1,220 @@ +#include "wsfs/server.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#include "wsfs/util.h" +#include "wsfs/protocol.h" +#include "wsfs/jsonrpc.h" + +#define WSFS_SERVICE_INTERVAL (1 * 1000) + +struct wsfs_server +{ + pthread_t thread; + bool shutdown_flag; + pthread_mutex_t lock; + struct wsfs_server_config config; + struct wsfs_protocol * protocol; + struct wsfs_jsonrpc * rpc; +}; + +static char * wsfs_strdup(char const * value) +{ + char * result = NULL; + if (NULL != value) + { + result = strdup(value); + } + + return result; +} + + +static bool wsfs_server_isshutdown( + struct wsfs_server * server) +{ + bool result; + + pthread_mutex_lock(&server->lock); + result = server->shutdown_flag; + pthread_mutex_unlock(&server->lock); + + return result; +} + +static bool wsfs_server_tls_enabled( + struct wsfs_server * server) +{ + return ((server->config.key_path != NULL) && (server->config.cert_path != NULL)); +} + +static void wsfs_server_request_shutdown( + struct wsfs_server * server) +{ + pthread_mutex_lock(&server->lock); + server->shutdown_flag = true; + pthread_mutex_unlock(&server->lock); +} + +static void wsfs_ignore_signal(int WSFS_UNUSED_PARAM(signal_id)) +{ +} + +static void* wsfs_server_run(void * arg) +{ + struct wsfs_server * const server = arg; + + struct lws_protocols protocols[] = + { + {"http", lws_callback_http_dummy, 0, 0, 0, NULL, 0}, + { "fs", NULL, 0 , 0, 0, NULL, 0}, + { NULL, NULL, 0 , 0, 0, NULL, 0} + }; + + struct lws_http_mount mount = + { + .mount_next = NULL, + .mountpoint = "/", + .origin = server->config.document_root, + .def = "index.html", + .protocol = NULL, + .cgienv = NULL, + .extra_mimetypes = NULL, + .interpret = NULL, + .cgi_timeout = 0, + .cache_max_age = 0, + .auth_mask = 0, + .cache_reusable = 0, + .cache_intermediaries = 0, + .origin_protocol = LWSMPRO_FILE, + .mountpoint_len = 1, + .basic_auth_login_file = NULL + }; + + wsfs_protocol_init_lws(server->protocol, &protocols[1]); + + struct lws_context_creation_info info; + memset(&info, 0, sizeof(info)); + info.port = server->config.port; + info.mounts = &mount; + info.protocols = protocols; + info.vhost_name = server->config.vhost_name; + info.ws_ping_pong_interval = 10; + info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + + if (NULL == server->config.document_root) + { + // disable http + info.protocols = &(protocols[1]); + info.mounts = NULL; + } + + if (wsfs_server_tls_enabled(server)) + { + info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.ssl_cert_filepath = server->config.cert_path; + info.ssl_private_key_filepath = server->config.key_path; + } + + struct lws_context * context = lws_create_context(&info); + if (NULL == context) + { + fprintf(stderr, "error: unable to start websocket server\n"); + return NULL; + } + + int n = 0; + while ((0 <= n) && (!wsfs_server_isshutdown(server))) + { + wsfs_protocol_check(server->protocol); + n = lws_service(context, WSFS_SERVICE_INTERVAL); + } + + lws_context_destroy(context); + return NULL; +} + +static void wsfs_server_join(struct wsfs_server * server) +{ + wsfs_server_request_shutdown(server); + pthread_join(server->thread, NULL); +} + +struct wsfs_server * wsfs_server_create( + struct wsfs_server_config * config) +{ + signal(SIGUSR1, &wsfs_ignore_signal); + + struct wsfs_server * server = malloc(sizeof(struct wsfs_server)); + if (NULL != server) + { + pthread_mutex_init(&server->lock, NULL); + server->shutdown_flag = false; + server->config.document_root = wsfs_strdup(config->document_root); + server->config.cert_path = wsfs_strdup(config->cert_path); + server->config.key_path = wsfs_strdup(config->key_path); + server->config.vhost_name = wsfs_strdup(config->vhost_name); + server->config.port = config->port; + + server->rpc = wsfs_jsonrpc_create(&wsfs_protocol_message_create, &wsfs_protocol_send, NULL); + server->protocol = wsfs_protocol_create(server); + wsfs_jsonrpc_set_user_data(server->rpc, server->protocol); + } + + return server; +} + +void wsfs_server_dispose( + struct wsfs_server * server) +{ + wsfs_server_join(server); + + wsfs_jsonrpc_dispose(server->rpc); + wsfs_protocol_dispose(server->protocol); + wsfs_server_config_clear(&server->config); + pthread_mutex_destroy(&server->lock); + free(server); +} + +void wsfs_server_start( + struct wsfs_server * server) +{ + pthread_create(&server->thread, NULL, &wsfs_server_run, server); +} + +void wsfs_server_config_clear(struct wsfs_server_config * config) +{ + free(config->document_root); + free(config->cert_path); + free(config->key_path); + free(config->vhost_name); +} + +struct wsfs_jsonrpc * wsfs_server_get_jsonrpc_service( + struct wsfs_server * server) +{ + return server->rpc; +} + +void wsfs_server_wakeup( + struct wsfs_server * server) +{ + pthread_kill(server->thread, SIGUSR1); +} + +void wsfs_server_handle_message( + struct wsfs_server * server, + char const * message, + size_t length) +{ + wsfs_jsonrpc_on_message(message, length, server->rpc); +} + diff --git a/src/wsfs/server.h b/src/wsfs/server.h new file mode 100644 index 0000000..9b5762c --- /dev/null +++ b/src/wsfs/server.h @@ -0,0 +1,42 @@ +#ifndef _WSFS_SERVER_H +#define _WSFS_SERVER_H + +#include + +struct wsfs_server; +struct wsfs_jsonrpc; + +struct wsfs_server_config +{ + char * document_root; + char * key_path; + char * cert_path; + char * vhost_name; + int port; +}; + +extern void wsfs_server_config_clear( + struct wsfs_server_config * config); + +extern struct wsfs_server * wsfs_server_create( + struct wsfs_server_config * config); + +extern void wsfs_server_dispose( + struct wsfs_server * server); + +extern struct wsfs_jsonrpc * wsfs_server_get_jsonrpc_service( + struct wsfs_server * server); + +extern void wsfs_server_start( + struct wsfs_server * server); + +extern void wsfs_server_wakeup( + struct wsfs_server * server); + +extern void wsfs_server_handle_message( + struct wsfs_server * server, + char const * message, + size_t length); + +#endif + diff --git a/src/wsfs/status.h b/src/wsfs/status.h new file mode 100644 index 0000000..6e7c3f2 --- /dev/null +++ b/src/wsfs/status.h @@ -0,0 +1,18 @@ +#ifndef _WSFS_STATUS_H +#define _WSFS_STATUS_H + +#define WSFS_GOOD 0 +#define WSFS_BAD 1 + +#define WSFS_BAD_NOENTRY 101 +#define WSFS_BAD_TIMEOUT 102 + +#define WSFS_BAD_PARSEERROR 200 +#define WSFS_BAD_INVALIDID 201 +#define WSFS_BAD_NODATA 202 + +typedef int wsfs_status; + + +#endif + diff --git a/src/wsfs/util.h b/src/wsfs/util.h new file mode 100644 index 0000000..3146097 --- /dev/null +++ b/src/wsfs/util.h @@ -0,0 +1,10 @@ +#ifndef _WSFS_UTIL_H +#define _WSFS_UTIL_H + +#ifdef __GNUC__ +#define WSFS_UNUSED_PARAM(param) param __attribute__((unused)) +#else +#define WSFS_UNUSED_PARAM(param) +#endif + +#endif diff --git a/test-src/test_main.c b/test-src/test_main.c new file mode 100644 index 0000000..0b3e468 --- /dev/null +++ b/test-src/test_main.c @@ -0,0 +1,13 @@ +#include +#include + +#include "test_util.h" +#include "wsfs/util.h" + +extern void test_request_parser(); + +int main(int WSFS_UNUSED_PARAM(argc), char* WSFS_UNUSED_PARAM(argv[])) +{ + test_request_parser(); + return EXIT_SUCCESS; +} diff --git a/test-src/test_response_parser.c b/test-src/test_response_parser.c new file mode 100644 index 0000000..06aff53 --- /dev/null +++ b/test-src/test_response_parser.c @@ -0,0 +1,62 @@ +#include "test_util.h" + +#include + +#include "wsfs/response_parser.h" + +static void wsfs_response_parse_str( + char const * buffer, + struct wsfs_response * response) +{ + size_t length = strlen(buffer); + wsfs_response_parse(buffer, length, response); +} + +void test_request_parser() +{ + struct wsfs_response response; + + // invalid json + wsfs_response_parse_str("", &response); + ASSERT_NE(WSFS_GOOD, response.status); + ASSERT_EQ(-1, response.id); + ASSERT_EQ(NULL, response.result); + + // invalid json + wsfs_response_parse_str("invalid_json", &response); + ASSERT_NE(WSFS_GOOD, response.status); + ASSERT_EQ(-1, response.id); + ASSERT_EQ(NULL, response.result); + + // no object + wsfs_response_parse_str("[]", &response); + ASSERT_NE(WSFS_GOOD, response.status); + ASSERT_EQ(-1, response.id); + ASSERT_EQ(NULL, response.result); + + // empty + wsfs_response_parse_str("{}", &response); + ASSERT_NE(WSFS_GOOD, response.status); + ASSERT_EQ(-1, response.id); + ASSERT_EQ(NULL, response.result); + + // no data + wsfs_response_parse_str("{\"id\":42}", &response); + ASSERT_NE(WSFS_GOOD, response.status); + ASSERT_EQ(42, response.id); + ASSERT_EQ(NULL, response.result); + + // custom error code + wsfs_response_parse_str("{\"error\":{\"code\": 42}, \"id\": 42}", &response); + ASSERT_NE(WSFS_GOOD, response.status); + ASSERT_EQ(42, response.status); + ASSERT_EQ(42, response.id); + ASSERT_EQ(NULL, response.result); + + // valid response + wsfs_response_parse_str("{\"result\": true, \"id\": 42}", &response); + ASSERT_EQ(WSFS_GOOD, response.status); + ASSERT_EQ(42, response.id); + ASSERT_NE(NULL, response.result); + json_decref(response.result); +} diff --git a/test-src/test_util.c b/test-src/test_util.c new file mode 100644 index 0000000..b021c8d --- /dev/null +++ b/test-src/test_util.c @@ -0,0 +1,15 @@ +#include "test_util.h" + +#include +#include + +void fail( + char const * file_name, + int line, + char const * message +) +{ + fprintf(stderr, "error: %s:%d: %s\n", file_name, line, message); + exit(EXIT_FAILURE); +} + diff --git a/test-src/test_util.h b/test-src/test_util.h new file mode 100644 index 0000000..28e6aff --- /dev/null +++ b/test-src/test_util.h @@ -0,0 +1,31 @@ +#ifndef _WSFS_TEST_UTIL_H +#define _WSFS_TEST_UTIL_H + +#define ASSERT_EQ(expected, actual) \ + do \ + { \ + if ((expected) != (actual)) \ + { \ + fail(__FILE__, __LINE__, "expected " #expected ", but was " #actual); \ + } \ + } \ + while (0) + +#define ASSERT_NE(expected, actual) \ + do \ + { \ + if ((expected) == (actual)) \ + { \ + fail(__FILE__, __LINE__, "expected " #expected ", but was " #actual); \ + } \ + } \ + while (0) + +extern void fail( + char const * file_name, + int line, + char const * message +); + +#endif +