mirror of
https://github.com/falk-werner/webfuse-provider
synced 2026-03-02 04:09:18 +00:00
added example for provider
This commit is contained in:
166
example/daemon/main.c
Normal file
166
example/daemon/main.c
Normal file
@@ -0,0 +1,166 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "wsfs/server.h"
|
||||
#include "wsfs/server_config.h"
|
||||
|
||||
struct args
|
||||
{
|
||||
struct wsfs_server_config config;
|
||||
bool show_help;
|
||||
};
|
||||
|
||||
static struct wsfs_server * server;
|
||||
|
||||
static void show_help(void)
|
||||
{
|
||||
printf(
|
||||
"wsfsd, Copyright (c) 2019, Falk Werner\n"
|
||||
"Websocket file system daemon\n"
|
||||
"\n"
|
||||
"Usage: wsfsd [m <mount_point>] [-d <document_root] [-n <vhost_name>] [-p <port>]\n"
|
||||
" [-c <server_cert_path> -k] [<server_key_path>]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
"\t-m, --mount_point Path of mount point (required)\n"
|
||||
"\t-d, --document_root Path of www directory (default: not set, www disabled)\n"
|
||||
"\t-c, --server_cert_path Path of servers own certificate (default: not set, TLS disabled)\n"
|
||||
"\t-k, --server_key_path Path of servers private key (default: not set, TLS disabled)\n"
|
||||
"\t-n, --vhost_name Name of virtual host (default: \"localhost\")\n"
|
||||
"\t-p, --port Number of servers port (default: 8080)\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
static int parse_arguments(int argc, char * argv[], struct args * args)
|
||||
{
|
||||
static struct option const options[] =
|
||||
{
|
||||
{"mount_point", required_argument, NULL, 'm'},
|
||||
{"document_root", required_argument, NULL, 'd'},
|
||||
{"server_cert_path", required_argument, NULL, 'c'},
|
||||
{"server_key_path", required_argument, NULL, 'k'},
|
||||
{"vhost_name", required_argument, NULL, 'n'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
bool result = EXIT_SUCCESS;
|
||||
bool finished = false;
|
||||
while ((!finished) && (EXIT_SUCCESS == result))
|
||||
{
|
||||
int option_index = 0;
|
||||
int const c = getopt_long(argc, argv, "m:d:c:k:n:p:h", options, &option_index);
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case -1:
|
||||
finished = true;
|
||||
break;
|
||||
case 'h':
|
||||
args->show_help = true;
|
||||
finished = true;
|
||||
break;
|
||||
case 'm':
|
||||
free(args->config.mount_point);
|
||||
args->config.mount_point = strdup(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
free(args->config.document_root);
|
||||
args->config.document_root = strdup(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
free(args->config.cert_path);
|
||||
args->config.cert_path = strdup(optarg);
|
||||
break;
|
||||
case 'k':
|
||||
free(args->config.key_path);
|
||||
args->config.key_path = strdup(optarg);
|
||||
break;
|
||||
case 'n':
|
||||
free(args->config.vhost_name);
|
||||
args->config.vhost_name = strdup(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
args->config.port = atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "error: unknown argument\n");
|
||||
result = EXIT_FAILURE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((EXIT_SUCCESS == result) && (!args->show_help))
|
||||
{
|
||||
if (NULL == args->config.mount_point)
|
||||
{
|
||||
fprintf(stderr, "error: missing mount point\n");
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (EXIT_SUCCESS != result)
|
||||
{
|
||||
args->show_help = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void on_interrupt(int signal_id)
|
||||
{
|
||||
(void) signal_id;
|
||||
|
||||
wsfs_server_shutdown(server);
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
struct args args =
|
||||
{
|
||||
.config =
|
||||
{
|
||||
.mount_point = NULL,
|
||||
.document_root = NULL,
|
||||
.cert_path = NULL,
|
||||
.key_path = NULL,
|
||||
.vhost_name = strdup("localhost"),
|
||||
.port = 8080,
|
||||
},
|
||||
.show_help = 0
|
||||
};
|
||||
|
||||
int result = parse_arguments(argc, argv, &args);
|
||||
|
||||
if (!args.show_help)
|
||||
{
|
||||
signal(SIGINT, on_interrupt);
|
||||
server = wsfs_server_create(&args.config);
|
||||
if (NULL != server)
|
||||
{
|
||||
wsfs_server_run(server);
|
||||
wsfs_server_dispose(server);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "fatal: unable start server\n");
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
show_help();
|
||||
}
|
||||
|
||||
wsfs_server_config_cleanup(&args.config);
|
||||
return result;
|
||||
}
|
||||
|
||||
22
example/daemon/www/index.html
Normal file
22
example/daemon/www/index.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LWS Example</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="style/main.css">
|
||||
<script type="text/javascript" src="js/connection.js"></script>
|
||||
<script type="text/javascript" src="js/connection_view.js"></script>
|
||||
<script type="text/javascript" src="js/filesystem.js"></script>
|
||||
<script type="text/javascript" src="js/filesystem_handler.js"></script>
|
||||
<script type="text/javascript" src="js/startup.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page">
|
||||
<div class="window">
|
||||
<div class="title">Connection</div>
|
||||
<div id="connection"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
36
example/daemon/www/js/connection.js
Normal file
36
example/daemon/www/js/connection.js
Normal file
@@ -0,0 +1,36 @@
|
||||
class Connection {
|
||||
|
||||
constructor() {
|
||||
this.ws = null;
|
||||
this.isConnected = false;
|
||||
this.onopen = () => {};
|
||||
this.onclose = () => {};
|
||||
this.onmessage = () => {};
|
||||
}
|
||||
|
||||
connectTo(url) {
|
||||
if (this.ws) { this.close(); }
|
||||
|
||||
this.ws = new WebSocket(url, "fs");
|
||||
this.ws.onopen = () => {
|
||||
this.isConnected = true;
|
||||
this.onopen();
|
||||
};
|
||||
this.ws.onclose = () => {
|
||||
this.isConnected = false;
|
||||
this.onclose();
|
||||
};
|
||||
this.ws.onmessage = (message) => {
|
||||
this.onmessage(message);
|
||||
};
|
||||
}
|
||||
|
||||
send(message) {
|
||||
this.ws.send(message);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.ws.close();
|
||||
}
|
||||
|
||||
}
|
||||
42
example/daemon/www/js/connection_view.js
Normal file
42
example/daemon/www/js/connection_view.js
Normal file
@@ -0,0 +1,42 @@
|
||||
class ConnectionView {
|
||||
constructor(connection) {
|
||||
this.connection = connection;
|
||||
this.connection.onclose = () => { this.onConnectionClosed(); };
|
||||
this.connection.onopen = () => { this.onConnectionOpened(); };
|
||||
|
||||
this.element = document.createElement("div");
|
||||
|
||||
let urlLabel = document.createElement("span");
|
||||
urlLabel.textContent = "URL:";
|
||||
this.element.appendChild(urlLabel);
|
||||
|
||||
this.urlTextbox = document.createElement("input");
|
||||
this.urlTextbox.type = "text";
|
||||
this.urlTextbox.value = window.location.href.replace(/^http/, "ws");
|
||||
this.element.appendChild(this.urlTextbox);
|
||||
|
||||
this.connectButton = document.createElement("input");
|
||||
this.connectButton.type = "button";
|
||||
this.connectButton.value = "connect";
|
||||
this.connectButton.addEventListener("click", () => { this.onConnectButtonClicked(); });
|
||||
this.element.appendChild(this.connectButton);
|
||||
}
|
||||
|
||||
onConnectButtonClicked() {
|
||||
if (!this.connection.isConnected) {
|
||||
let url = this.urlTextbox.value;
|
||||
this.connection.connectTo(url);
|
||||
}
|
||||
else {
|
||||
this.connection.close();
|
||||
}
|
||||
}
|
||||
onConnectionOpened() {
|
||||
this.connectButton.value = "disconnect";
|
||||
}
|
||||
|
||||
onConnectionClosed() {
|
||||
this.connectButton.value = "connect";
|
||||
}
|
||||
|
||||
}
|
||||
128
example/daemon/www/js/filesystem.js
Normal file
128
example/daemon/www/js/filesystem.js
Normal file
@@ -0,0 +1,128 @@
|
||||
class FileSystem {
|
||||
static get GOOD() { return 0; }
|
||||
static get BAD() { return 1; }
|
||||
|
||||
static get BAD_NOTIMPLEMENTED() { return 2; }
|
||||
static get BAD_TIMEOUT() { return 3; }
|
||||
static get BAD_FORMAT() { return 4; }
|
||||
|
||||
static get BAD_NOENTRY() { return 101; }
|
||||
static get BAD_NOACCESS() { return 102; }
|
||||
|
||||
static get O_ACCMODE() { return 0x003; }
|
||||
static get O_RDONLY() { return 0x000; }
|
||||
static get O_WRONLY() { return 0x001; }
|
||||
static get O_RDWR() { return 0x002; }
|
||||
static get O_CREAT() { return 0x040; }
|
||||
static get O_EXCL() { return 0x080; }
|
||||
static get O_TRUNK() { return 0x200; }
|
||||
static get O_APPEND() { return 0x400; }
|
||||
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this._inodes = { };
|
||||
|
||||
this._walk(this.root, (entry) => { this._inodes[entry.inode] = entry; });
|
||||
}
|
||||
|
||||
_walk(node, callback) {
|
||||
callback(node);
|
||||
|
||||
const entries = node.entries;
|
||||
if (entries) {
|
||||
for(let entry of Object.entries(entries)) {
|
||||
this._walk(entry[1], callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lookup(parent, name) {
|
||||
const parentEntry = this._inodes[parent];
|
||||
const entry = (parentEntry && parentEntry.entries && parentEntry.entries[name]) || null;
|
||||
if (entry) {
|
||||
return {
|
||||
inode: entry.inode,
|
||||
mode: entry.mode || parseInt("755", 8),
|
||||
type: entry.type || "file",
|
||||
size: entry.size || (entry.contents && entry.contents.length) || 0,
|
||||
atime: entry.atime || 0,
|
||||
mtime: entry.mtime || 0,
|
||||
ctime: entry.ctime || 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
return FileSystem.BAD_NOENTRY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getattr(inode) {
|
||||
let entry = this._inodes[inode];
|
||||
if (entry) {
|
||||
return {
|
||||
mode: entry.mode || parseInt("755", 8),
|
||||
type: entry.type || "file",
|
||||
size: entry.size || (entry.contents && entry.contents.length) || 0,
|
||||
atime: entry.atime || 0,
|
||||
mtime: entry.mtime || 0,
|
||||
ctime: entry.ctime || 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
return FileSystem.BAD_NOENTRY;
|
||||
}
|
||||
}
|
||||
|
||||
readdir(inode) {
|
||||
let result = FileSystem.BAD_NOENTRY;
|
||||
let entry = this._inodes[inode];
|
||||
|
||||
if ((entry) && ("dir" === entry.type)) {
|
||||
result = [
|
||||
{name: ".", inode: entry.inode},
|
||||
{name: "..", inode: entry.inode}
|
||||
];
|
||||
for(let subdir of Object.entries(entry.entries)) {
|
||||
const name = subdir[0];
|
||||
const inode = subdir[1].inode;
|
||||
result.push({name, inode});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
open(inode, mode) {
|
||||
let result = FileSystem.BAD_NOENTRY;
|
||||
let entry = this._inodes[inode];
|
||||
|
||||
if (entry.type === "file") {
|
||||
result = ((mode & FileSystem.O_ACCMODE) === FileSystem.O_RDONLY) ? {handle: 1337} : FileSystem.BAD_NOACCESS;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
close(inode, handle, mode) {
|
||||
// do nothing
|
||||
return true;
|
||||
}
|
||||
|
||||
read(inode, handle, offset, length) {
|
||||
let result = FileSystem.BAD_NOENTRY;
|
||||
let entry = this._inodes[inode];
|
||||
|
||||
if (entry.type === "file") {
|
||||
let end = Math.min(offset + length, entry.contents.length);
|
||||
let data = (offset < entry.contents.length) ? entry.contents.substring(offset, end) : "";
|
||||
result = {
|
||||
data: btoa(data),
|
||||
format: "base64",
|
||||
count: data.length
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
87
example/daemon/www/js/filesystem_handler.js
Normal file
87
example/daemon/www/js/filesystem_handler.js
Normal file
@@ -0,0 +1,87 @@
|
||||
|
||||
class FileSystemHandler {
|
||||
|
||||
constructor(filesystem, connection) {
|
||||
this._fs = filesystem;
|
||||
this._connection = connection;
|
||||
this._connection.onmessage = (message) => {
|
||||
this._onmessage(message);
|
||||
};
|
||||
}
|
||||
|
||||
_onmessage(message) {
|
||||
|
||||
try {
|
||||
let request = JSON.parse(message.data);
|
||||
let result = -42;
|
||||
let response;
|
||||
|
||||
console.log(request);
|
||||
if (("string" === typeof(request.method)) &&
|
||||
("number" === typeof(request.id)) &&
|
||||
(request.params)) {
|
||||
switch(request.method)
|
||||
{
|
||||
case "lookup":
|
||||
{
|
||||
const parent = request.params[0];
|
||||
const name = request.params[1];
|
||||
result = this._fs.lookup(parent, name);
|
||||
}
|
||||
break;
|
||||
case "getattr":
|
||||
{
|
||||
const inode = request.params[0];
|
||||
result = this._fs.getattr(inode);
|
||||
}
|
||||
break;
|
||||
case "readdir":
|
||||
{
|
||||
const inode = request.params[0];
|
||||
result = this._fs.readdir(inode);
|
||||
}
|
||||
break;
|
||||
case "open":
|
||||
{
|
||||
const inode = request.params[0];
|
||||
const mode = request.params[1];
|
||||
result = this._fs.open(inode, mode);
|
||||
}
|
||||
break;
|
||||
case "close":
|
||||
{
|
||||
const inode = request.params[0];
|
||||
const handle = request.params[1];
|
||||
const mode = request.params[2];
|
||||
this._fs.close(inode, handle, mode);
|
||||
}
|
||||
break;
|
||||
case "read":
|
||||
{
|
||||
const inode = request.params[0];
|
||||
const handle = request.params[1];
|
||||
const offset = request.params[2];
|
||||
const length = request.params[3];
|
||||
result = this._fs.read(inode, handle, offset, length);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ("number" == typeof(request.id))
|
||||
{
|
||||
if ("number" !== typeof(result)) {
|
||||
response = {result: result, id: request.id};
|
||||
}
|
||||
else {
|
||||
response = {error: {code: result}, id: request.id};
|
||||
}
|
||||
console.log(response);
|
||||
this._connection.send(JSON.stringify(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex) { console.log(ex, message); }
|
||||
}
|
||||
}
|
||||
20
example/daemon/www/js/startup.js
Normal file
20
example/daemon/www/js/startup.js
Normal file
@@ -0,0 +1,20 @@
|
||||
function startup() {
|
||||
let connection = new Connection();
|
||||
let connectionView = new ConnectionView(connection);
|
||||
document.getElementById('connection').appendChild(connectionView.element);
|
||||
|
||||
let fs = new FileSystem({
|
||||
inode: 1,
|
||||
mode: 0755,
|
||||
type: "dir",
|
||||
entries: {
|
||||
"hello.txt" : { inode: 2, mode: 0444, type: "file", contents: "Hello, World!"},
|
||||
"say_hello.sh": { inode: 3, mode: 0555, type: "file", contents: "#!/bin/sh\necho hello\n"}
|
||||
}
|
||||
});
|
||||
let handler = new FileSystemHandler(fs, connection);
|
||||
|
||||
|
||||
}
|
||||
|
||||
window.onload = startup;
|
||||
47
example/daemon/www/style/main.css
Normal file
47
example/daemon/www/style/main.css
Normal file
@@ -0,0 +1,47 @@
|
||||
html, body {
|
||||
font-family: monospace;
|
||||
background-color: #c0c0c0;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin-left: 50px;
|
||||
margin-right: 50px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.window {
|
||||
border: 1px solid black;
|
||||
background-color: black;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 25px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.window .title {
|
||||
text-align: center;
|
||||
color: #dba329;
|
||||
font-weight: bold;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #dba329;
|
||||
}
|
||||
|
||||
.commands {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.content {
|
||||
column-count: 2;
|
||||
column-width: 50%;
|
||||
}
|
||||
|
||||
.content > div {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#connection {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user