diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb66c7b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build/ +/.deps/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1a076f5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,83 @@ +dist: bionic + +language: c + +compiler: + - gcc + +addons: + apt: + update: true + packages: + - build-essential + - cmake + - pkgconf + - wget + - ca-certificates + - uuid-dev + - udev + - gettext + - python3 + - python3-pip + - python3-setuptools + - python3-wheel + - ninja-build + + +before_install: + - mkdir .deps + - cd .deps + # libfuse + - sudo pip3 install --system meson + - wget https://github.com/libfuse/libfuse/archive/fuse-3.9.0.tar.gz -O fuse.tar.gz + - tar -xf fuse.tar.gz + - cd libfuse-fuse-3.9.0 + - mkdir .build + - cd .build + - meson .. + - ninja + - sudo ninja install + - cd .. + - cd .. + # libwebsockets + - wget https://github.com/warmcat/libwebsockets/archive/v3.2.0.tar.gz -O libwebsockets.tar.gz + - tar -xf libwebsockets.tar.gz + - cd libwebsockets-3.2.0 + - mkdir .build + - cd .build + - cmake .. + - make + - sudo make install + - cd .. + - cd .. + # jansson + - wget https://github.com/akheron/jansson/archive/v2.12.tar.gz -O jansson.tar.gz + - tar -xf jansson.tar.gz + - cd jansson-2.12 + - mkdir .build + - cd .build + - cmake .. + - make + - sudo make install + - cd .. + - cd .. + # libwebfuse + - wget https://github.com/falk-werner/webfuse/archive/master.tar.gz -O webfuse.tar.gz + - tar -xf webfuse.tar.gz + - cd webfuse-master + - mkdir .build + - cd .build + - cmake -DWITHOUT_TESTS=ON -DWITHOUT_EXAMPLE=ON .. + - make + - sudo make install + - cd .. + - cd .. + - cd .. + +before_script: + - mkdir build + - cd build + - cmake .. + +script: + make \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7140f27 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required (VERSION 3.10) +project(webfuse-provider VERSION 0.1.0 DESCRIPTION "Webfuse provider") + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(LWS REQUIRED libwebsockets) +pkg_check_modules(WEBFUSE REQUIRED libwebfuse-provider) + +include_directories( + "src" + ${WEBFUSE_INCLUDE_DIRS} +) + +link_directories( + ${WEBFUSE_LIBRARY_DIRS} +) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(C_WARNINGS -Wall -Wextra) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +add_executable(webfuse-provider-app + src/main.c +) + +set_target_properties(webfuse-provider-app PROPERTIES OUTPUT_NAME webfuse-provider) + +target_link_libraries(webfuse-provider-app PUBLIC webfuse-provider ${WEBFUSE_LIBRARIES}) + +# static-filesystem-provider + +add_executable(static-filesystem-provider + src/static_filesystem.c +) + +target_link_libraries(static-filesystem-provider PUBLIC webfuse-provider ${WEBFUSE_LIBRARIES}) + diff --git a/README.md b/README.md index 43167ef..77127d0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,75 @@ # webfuse-provider + Reference implementation of webfuse provider + +## Build and run + +To install dependecies, see below. + + cd webfused + mkdir build + cd build + cmake .. + make + ./webfuse-provider + +## Dependencies + +- [webfuse](https://github.com/falk-werner/webfuse) + - [libfuse](https://github.com/libfuse/libfuse/) + - [libwebsockets](https://libwebsockets.org/) + - [jansson](https://github.com/akheron/jansson) + +### Installing dependencies + +#### libfuse + +To install libfuse, meson is needed. Please refer to [meson quick guide](https://mesonbuild.com/Quick-guide.html) for setup instructions. + + wget https://github.com/libfuse/libfuse/archive/fuse-3.9.0.tar.gz -O fuse.tar.gz + tar -xf fuse.tar.gz + cd libfuse-fuse-3.9.0 + mkdir .build + cd .build + meson .. + ninja + sudo ninja install + +#### libwebsockets + + wget https://github.com/warmcat/libwebsockets/archive/v3.2.0.tar.gz -O libwebsockets.tar.gz + tar -xf libwebsockets.tar.gz + cd libwebsockets-3.2.0 + mkdir .build + cd .build + cmake .. + make + sudo make install + + +#### jansson + + wget https://github.com/akheron/jansson/archive/v2.12.tar.gz -O jansson.tar.gz + tar -xf jansson.tar.gz + cd jansson-2.12 + mkdir .build + cd .build + cmake .. + make + sudo make install + +#### openssl + + sudo apt update + sudo install openssl libssl-dev + +#### webfuse + + wget https://github.com/falk-werner/webfuse/archive/master.tar.gz -O webfuse.tar.gz + tar -xf webfuse.tar.gz + cd webfuse-master + mkdir .build + cd .build + cmake -DWITHOUT_TESTS=ON -DWITHOUT_EXAMPLE=ON .. + make + sudo make install diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..18fe42e --- /dev/null +++ b/src/main.c @@ -0,0 +1,385 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "webfuse_provider.h" + +#define SERVICE_TIMEOUT (1 * 1000) + +struct config +{ + char * url; + struct wfp_client_config * client_config; + bool show_help; +}; + +enum fs_entry_type +{ + FS_FILE, + FS_DIR +}; + +struct fs_entry +{ + ino_t parent; + ino_t inode; + char const * name; + int mode; + enum fs_entry_type type; + size_t content_length; + char const * content; +}; + +struct fs +{ + struct fs_entry const * entries; +}; + +static void show_help() +{ + printf( + "webfuse-provider, Copyright (c) 2019, webfuse authors \n" + "Example for websocket file system provider\n" + "\n" + "Usage: webfuse-provider -u [-k ] [-c ]\n" + "\n" + "Options:\n" + "\t-u, --url URL of webfuse server (required)\n" + "\t-k, --key_path Path to private key of provider (default: not set, TLS disabled)\n" + "\t-c, --cert_path Path to certificate of provider (defautl: not set, TLS disabled)\n" + "\t-h, --help prints this message\n" + "\n" + "Example:\n" + "\twebfuse-provider -u ws://localhost:8080/\n" + "\n" + ); +} + +static int parse_arguments( + int argc, + char* argv[], + struct config * config) +{ + static struct option const options[] = + { + {"url", required_argument, NULL, 'u'}, + {"key_path", required_argument, NULL, 'k'}, + {"cert_path", required_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + int result = EXIT_SUCCESS; + bool finished = false; + while (!finished) + { + int option_index = 0; + int const c = getopt_long(argc, argv, "u:k:c:h", options, &option_index); + + switch (c) + { + case -1: + finished = true; + break; + case 'h': + config->show_help = true; + finished = true; + break; + case 'u': + free(config->url); + config->url = strdup(optarg); + break; + case 'k': + wfp_client_config_set_keypath(config->client_config, optarg); + break; + case 'c': + wfp_client_config_set_certpath(config->client_config, optarg); + break; + default: + fprintf(stderr, "error: unknown argument\n"); + finished = true; + result = EXIT_FAILURE; + break; + } + + if (NULL == config->url) + { + fprintf(stderr, "error: missing required argument \"-u\"\n"); + result = EXIT_FAILURE; + } + + if (result != EXIT_SUCCESS) + { + config->show_help = true; + } + } + + return result; +} + +static struct fs_entry const * fs_getentry( + struct fs * fs, + ino_t inode) +{ + for (size_t i = 0; 0 != fs->entries[i].inode; i++) + { + struct fs_entry const * entry = &fs->entries[i]; + if (inode == entry->inode) + { + return entry; + } + } + + return NULL; +} + +static struct fs_entry const * fs_getentry_byname( + struct fs * fs, + ino_t parent, + char const * name) +{ + for( size_t i = 0; 0 != fs->entries[i].inode; i++) + { + struct fs_entry const * entry = &fs->entries[i]; + if ((parent == entry->parent) && (0 == strcmp(name, entry->name))) + { + return entry; + } + } + + return NULL; +} + +static void fs_stat( + struct fs_entry const * entry, + struct stat * stat) +{ + memset(stat, 0, sizeof(struct stat)); + + stat->st_ino = entry->inode; + stat->st_mode = entry->mode; + + if (FS_DIR == entry->type) + { + stat->st_mode |= S_IFDIR; + } + + if (FS_FILE == entry->type) + { + stat->st_mode |= S_IFREG; + stat->st_size = entry->content_length; + } +} + +static void fs_lookup( + struct wfp_request * request, + ino_t parent, + char const * name, + void * user_data) +{ + struct fs * fs = (struct fs*) user_data; + struct fs_entry const * entry = fs_getentry_byname(fs, parent, name); + if (NULL != entry) + { + struct stat stat; + fs_stat(entry, &stat); + + wfp_respond_lookup(request, &stat); + } + else + { + wfp_respond_error(request, WF_BAD_NOENTRY); + } +} + + +static void fs_getattr( + struct wfp_request * request, + ino_t inode, + void * user_data) +{ + struct fs * fs = (struct fs*) user_data; + struct fs_entry const * entry = fs_getentry(fs, inode); + + if (NULL != entry) + { + struct stat stat; + fs_stat(entry, &stat); + + wfp_respond_getattr(request, &stat); + } + else + { + wfp_respond_error(request, WF_BAD_NOENTRY); + } +} + +static void fs_readdir( + struct wfp_request * request, + ino_t directory, + void * user_data) +{ + struct fs * fs = (struct fs*) user_data; + + struct fs_entry const * dir = fs_getentry(fs, directory); + if ((NULL != dir) && (FS_DIR == dir->type)) + { + struct wfp_dirbuffer * buffer = wfp_dirbuffer_create(); + wfp_dirbuffer_add(buffer, ".", dir->inode); + wfp_dirbuffer_add(buffer, "..", dir->inode); + + for(size_t i = 0; 0 != fs->entries[i].inode; i++) + { + struct fs_entry const * entry = &fs->entries[i]; + if (directory == entry->parent) + { + wfp_dirbuffer_add(buffer, entry->name, entry->inode); + } + } + + wfp_respond_readdir(request, buffer); + wfp_dirbuffer_dispose(buffer); + } + else + { + wfp_respond_error(request, WF_BAD_NOENTRY); + } +} + +static void fs_open( + struct wfp_request * request, + ino_t inode, + int flags, + void * user_data) +{ + struct fs * fs = (struct fs*) user_data; + + struct fs_entry const * entry = fs_getentry(fs, inode); + if ((NULL != entry) && (FS_FILE == entry->type)) + { + if (O_RDONLY == (flags & O_ACCMODE)) + { + wfp_respond_open(request, 0U); + } + else + { + wfp_respond_error(request, WF_BAD_ACCESS_DENIED); + } + } + else + { + wfp_respond_error(request, WF_BAD_NOENTRY); + } +} + +static size_t min(size_t const a, size_t const b) +{ + return (a < b) ? a : b; +} + +static void fs_read( + struct wfp_request * request, + ino_t inode, + uint32_t handle, + size_t offset, + size_t length, + void * user_data) +{ + (void) handle; + + struct fs * fs = (struct fs*) user_data; + struct fs_entry const * entry = fs_getentry(fs, inode); + if ((NULL != entry) && (FS_FILE == entry->type)) + { + if (entry->content_length > offset) + { + size_t const remaining = entry->content_length - offset; + size_t const count = min(remaining, length); + + wfp_respond_read(request, &entry->content[offset], count); + } + else + { + wfp_respond_error(request, WF_BAD); + } + } + else + { + wfp_respond_error(request, WF_BAD_NOENTRY); + } +} + +static volatile bool shutdown_requested = false; + +static void on_interrupt(int signal_id) +{ + (void) signal_id; + shutdown_requested = true; +} + +int main(int argc, char* argv[]) +{ + struct config config; + config.url = NULL; + config.show_help = false; + config.client_config = wfp_client_config_create(); + int result = parse_arguments(argc, argv, &config); + + if (EXIT_SUCCESS == result) + { + static struct fs_entry const entries[]= + { + {.parent = 0, .inode = 1, .name = "", .mode = 0555, .type = FS_DIR}, + { + .parent = 1, + .inode = 2, + .name = "hello.txt", + .mode = 0444, + .type = FS_FILE, + .content="hello, world!", + .content_length = 13, + }, + {.parent = 0, .inode = 0, .name = NULL} + }; + + struct fs fs = + { + .entries = entries + }; + + signal(SIGINT, &on_interrupt); + + wfp_client_config_set_userdata(config.client_config, &fs); + wfp_client_config_set_onlookup(config.client_config, &fs_lookup); + wfp_client_config_set_ongetattr(config.client_config, &fs_getattr); + wfp_client_config_set_onreaddir(config.client_config, &fs_readdir); + wfp_client_config_set_onopen(config.client_config, &fs_open); + wfp_client_config_set_onread(config.client_config, &fs_read); + + struct wfp_client * client = wfp_client_create(config.client_config); + wfp_client_connect(client, config.url); + + while (!shutdown_requested) + { + wfp_client_service(client, SERVICE_TIMEOUT); + } + + wfp_client_dispose(client); + } + + if (config.show_help) + { + show_help(); + } + + free(config.url); + wfp_client_config_dispose(config.client_config); + return result; +} \ No newline at end of file diff --git a/src/static_filesystem.c b/src/static_filesystem.c new file mode 100644 index 0000000..631274e --- /dev/null +++ b/src/static_filesystem.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include + +#include "webfuse_provider.h" + +#define SERVICE_TIMEOUT (1 * 1000) + +struct args +{ + char const * url; + bool show_help; +}; + +static int +parse_args( + struct args * args, + int argc, + char * argv[]) +{ + int result = EXIT_FAILURE; + args->show_help = true; + args->url = NULL; + + if (2 == argc) + { + result = EXIT_SUCCESS; + + char const * url = argv[1]; + if ((0 != strcmp(url, "-h")) && (0 != strcmp(url, "--help"))) + { + args->show_help = false; + args->url = url; + } + } + else + { + fprintf(stderr, "error: missing argument\n"); + } + + return result; +} + +static volatile bool shutdown_requested = false; + +static void on_interrupt(int signal_id) +{ + (void) signal_id; + shutdown_requested = true; +} + +static void print_usage() +{ + printf( + "static-filesystem-provider Copyright (c) 2019, webfuse authors \n" + "Example of webfuse static filesystem provider\n" + "\n" + "Usage: static-filesystem-provider \n" + "\n" + "Arguments:\n" + "\t URL of webfuse server (required)\n" + "\t-h, --help prints this message\n" + "\n" + "Example:\n" + "\tstatic-filesystem-provider ws://localhost:8080/\n" + "\n" + ); +} + +int main(int argc, char* argv[]) +{ + signal(SIGINT, &on_interrupt); + + struct args args; + int result = parse_args(&args, argc, argv); + if ((EXIT_SUCCESS == result) && (!args.show_help)) + { + struct wfp_client_config * config = wfp_client_config_create(); + + struct wfp_static_filesystem * fs = wfp_static_filesystem_create(config); + wfp_static_filesystem_add_text(fs, "brummni/hello_world.txt", 0444, "Hello, World!\n"); + wfp_static_filesystem_add_text(fs, "brummni/hello_bob.txt", 0444, "Hello, Bob!\n"); + wfp_static_filesystem_add_text(fs, "brummni/hello_bob.txt", 0444, "Hello, Alice!\n"); + wfp_static_filesystem_add_text(fs, "bla/hello_world.txt", 0444, "Hello, World!\n"); + wfp_static_filesystem_add_text(fs, "foo.txt", 0444, "foo\n"); + wfp_static_filesystem_add_text(fs, "bar.txt", 0444, "bar\n"); + + struct wfp_client * client = wfp_client_create(config); + wfp_client_connect(client, args.url); + + while (!shutdown_requested) + { + wfp_client_service(client, SERVICE_TIMEOUT); + } + + wfp_client_dispose(client); + wfp_static_filesystem_dispose(fs); + wfp_client_config_dispose(config); + } + + if (args.show_help) + { + print_usage(); + } + + return result; +}