1
0
mirror of https://github.com/falk-werner/webfuse-provider synced 2024-10-27 20:44:10 +00:00

initial commit

This commit is contained in:
Falk Werner 2019-01-27 03:45:03 +01:00
parent a3927be8bf
commit 858fdb862b
24 changed files with 1671 additions and 0 deletions

71
CMakeLists.txt Normal file
View File

@ -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})

103
src/app/main.c Normal file
View File

@ -0,0 +1,103 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#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=<s> Path of www directory (default: not set, www disabled)\n"
" --server_cert_path=<s> Path of servers own certificate (default: not set, TLS disabled)\n"
" --server_key_path=<s> Path of servers private key (default: not set, TLS disabled)\n"
" --vhost_name=<s> Name of virtual host (default: \"localhost\")\n"
" --port=<d> 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;
}

12
src/app/www/index.html Normal file
View File

@ -0,0 +1,12 @@
<html>
<head>
<title>LWS Example</title>
<meta charset='UTF-8' />
<meta http-equiv='Content-Security-Policy' content="script-src localhost:4711/script.js;" />
</head>
<body>
<textarea id='content'></textarea>
<input id='sendButton' type='button' value='send' />
<script nonce='42' type='text/javascript' src='script.js'></script>
</body>
</html>

98
src/app/www/script.js Normal file
View File

@ -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();

87
src/main.c Normal file
View File

@ -0,0 +1,87 @@
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#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=<s> Path of www directory (default: not set, www disabled)\n"
" --server_cert_path=<s> Path of servers own certificate (default: not set, TLS disabled)\n"
" --server_key_path=<s> Path of servers private key (default: not set, TLS disabled)\n"
" --vhost_name=<s> Name of virtual host (default: \"localhost\")\n"
" --port=<d> 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;
}

103
src/wsfs/filesystem.c Normal file
View File

@ -0,0 +1,103 @@
#include "wsfs/filesystem.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jansson.h>
#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;
}

38
src/wsfs/filesystem.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef _WSFS_FILESYSTEM_H
#define _WSFS_FILESYSTEM_H
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#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

8
src/wsfs/fuse.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef _WSFS_FUSE_H
#define _WSFS_FUSE_H
#define FUSE_USE_VERSION 31
#include <fuse.h>
#endif

232
src/wsfs/jsonrpc.c Normal file
View File

@ -0,0 +1,232 @@
#include "wsfs/jsonrpc.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#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);
}
}

41
src/wsfs/jsonrpc.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef _WSFS_JSONRPC_H
#define _WSFS_JSONRPC_H
#include <stdarg.h>
#include <stddef.h>
#include <stdbool.h>
#include <jansson.h>
#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

105
src/wsfs/operations.c Normal file
View File

@ -0,0 +1,105 @@
#include "wsfs/operations.h"
#include <stddef.h>
#include <string.h>
#include <errno.h>
#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;
}

10
src/wsfs/operations.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef _WSFS_OPERATIONS
#define _WSFS_OPERATIONS
#include "wsfs/fuse.h"
extern void wsfs_operations_init(
struct fuse_operations * operations);
#endif

243
src/wsfs/protocol.c Normal file
View File

@ -0,0 +1,243 @@
#include "wsfs/protocol.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#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;
}

37
src/wsfs/protocol.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef _WSFS_PROTOCOL_H
#define _WSFS_PROTOCOL_H
#include <stdbool.h>
#include <libwebsockets.h>
#include <pthread.h>
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

View File

@ -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);
}

View File

@ -0,0 +1,21 @@
#ifndef _WSFS_RESPONSE_PARSER_H
#define _WFSF_RESPONSE_PARSER_H
#include <stddef.h>
#include <jansson.h>
#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

220
src/wsfs/server.c Normal file
View File

@ -0,0 +1,220 @@
#include "wsfs/server.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <libwebsockets.h>
#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);
}

42
src/wsfs/server.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef _WSFS_SERVER_H
#define _WSFS_SERVER_H
#include <stddef.h>
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

18
src/wsfs/status.h Normal file
View File

@ -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

10
src/wsfs/util.h Normal file
View File

@ -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

13
test-src/test_main.c Normal file
View File

@ -0,0 +1,13 @@
#include <stdio.h>
#include <stdlib.h>
#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;
}

View File

@ -0,0 +1,62 @@
#include "test_util.h"
#include <string.h>
#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);
}

15
test-src/test_util.c Normal file
View File

@ -0,0 +1,15 @@
#include "test_util.h"
#include <stdio.h>
#include <stdlib.h>
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);
}

31
test-src/test_util.h Normal file
View File

@ -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