diff --git a/example/provider/javascript/README.md b/example/provider/javascript/README.md new file mode 100644 index 0000000..629ac37 --- /dev/null +++ b/example/provider/javascript/README.md @@ -0,0 +1,6 @@ +# Webfuse JavaScript example + + mkdir -p /tmp/test + webfuse -f /tmp/test --wf-docroot . + +Visit [http://localhost:8081/](http://localhost:8081/). diff --git a/example/provider/javascript/index.html b/example/provider/javascript/index.html new file mode 100644 index 0000000..8570de9 --- /dev/null +++ b/example/provider/javascript/index.html @@ -0,0 +1,17 @@ + + + + Webfuse Example + + + +

Webfuse

+

+ + +

+

+ +

+ + \ No newline at end of file diff --git a/example/provider/javascript/js/startup.js b/example/provider/javascript/js/startup.js new file mode 100644 index 0000000..8189e5b --- /dev/null +++ b/example/provider/javascript/js/startup.js @@ -0,0 +1,26 @@ + +import { Webfuse } from "./webfuse/webfuse.js"; +import { StaticFileSystem } from "./static_filesystem.js"; + +let webfuse = null; +const filesystem = new StaticFileSystem(new Map([ + ["/foo", "foo"], + ["/bar", "foo"] +])); + +function onConnectButtonClicked() { + if (webfuse) { webfuse.close(); } + + const urlTextfield = document.querySelector('#url'); + const url = urlTextfield.value; + console.log(url); + + webfuse = new Webfuse(url, filesystem); +} + +function startup() { + const connectButton = document.querySelector('#connect'); + connectButton.addEventListener('click', onConnectButtonClicked); +} + +document.addEventListener('DOMContentLoaded', startup(),false); diff --git a/example/provider/javascript/js/static_filesystem.js b/example/provider/javascript/js/static_filesystem.js new file mode 100644 index 0000000..02a81a5 --- /dev/null +++ b/example/provider/javascript/js/static_filesystem.js @@ -0,0 +1,41 @@ + +import { BaseFileSystem, ERRNO, Mode } from "./webfuse/webfuse.js" + +class StaticFileSystem extends BaseFileSystem { + + constructor(files) { + super(); + this.files = files; + } + + getattr(path) { + console.log("getattr", path); + + if (path == "/") { + return { + nlink: 2, + mode: Mode.DIR | 0o555 + }; + } + else if (this.files.has(path)) { + const contents = this.files.get(path); + return { + nlink: 1, + mode: Mode.REG | 0o444, + size: contents.length + } + } + + return ERRNO.ENOENT; + } + + readdir(path) { + if (path == "/") { + return ["foo", "bar"] + } + + return ERRNO.ENOENT; + } +} + +export { StaticFileSystem } diff --git a/example/provider/javascript/js/webfuse/accessmode.js b/example/provider/javascript/js/webfuse/accessmode.js new file mode 100644 index 0000000..dca6300 --- /dev/null +++ b/example/provider/javascript/js/webfuse/accessmode.js @@ -0,0 +1,9 @@ + +const AccessMode = { + F_OK: 0, + R_OK: 4, + W_OK: 2, + X_OK: 1 +}; + +export { AccessMode } diff --git a/example/provider/javascript/js/webfuse/basefilesystem.js b/example/provider/javascript/js/webfuse/basefilesystem.js new file mode 100644 index 0000000..1f987df --- /dev/null +++ b/example/provider/javascript/js/webfuse/basefilesystem.js @@ -0,0 +1,90 @@ +import { ERRNO } from "./errno.js" + +class BaseFileSystem { + + access(path, mode) { + return ERRNO.ENOENT; + } + + getattr(path) { + return ERRNO.ENOENT; + } + + readlink(path) { + return ERRNO.ENOENT; + } + + symlink(target, linkpath) { + return ERRNO.ENOENT; + } + + link(oldpath, newpath) { + return ERRNO.ENOENT; + } + + rename(oldpath, newpath, flags) { + return ERRNO.ENOENT; + } + + chmod(path, mode) { + return ERRNO.ENOENT; + } + + chown(path, uid, gid) { + return ERRNO.ENOENT; + } + + truncate(path, size, fd) { + return ERRNO.ENOENT; + } + + open(path, flags) { + return [ERRNO.ENOENT, 0]; + } + + mknod(path, mode, rdev) { + return ERRNO.ENOENT; + } + + create(path, mode) { + return [ERNNO.ENOEND, 0]; + } + + release(path, fd) { + return ERRNO.ENOENT; + } + + unlink(path) { + return ERRNO.ENOENT; + } + + read(path, size, offset, fd) { + return ERRNO.ENOENT; + } + + write(path, data, offset, fd) { + return ERRNO.ENOENT; + } + + mkdir(path, mode) { + return ERRNO.ENOENT; + } + + readdir(path) { + return ERRNO.ENOENT; + } + + rmdir(path) { + return ERRNO.ENOENT; + } + + statfs(path) { + return ERRNO.ENOENT; + } + + getcreds() { + return ""; + } +} + +export { BaseFileSystem } diff --git a/example/provider/javascript/js/webfuse/errno.js b/example/provider/javascript/js/webfuse/errno.js new file mode 100644 index 0000000..1c6047b --- /dev/null +++ b/example/provider/javascript/js/webfuse/errno.js @@ -0,0 +1,40 @@ +const ERRNO = { + E2BIG : -7, + EACCES : -13, + EAGAIN : -11, + EBADF : -9, + EBUSY : -16, + EDESTADDRREQ : -89, + EDQUOT : -122, + EEXIST : -17, + EFAULT : -14, + EFBIG : -27, + EINTR : -4, + EINVAL : -22, + EIO : -5, + EISDIR : -21, + ELOOP : -40, + EMFILE : -24, + EMLINK : -31, + ENAMETOOLONG : -36, + ENFILE : -23, + ENODATA : -61, + ENODEV : -19, + ENOENT : -2, + ENOMEM : -12, + ENOSPC : -28, + ENOSYS : -38, + ENOTDIR : -20, + ENOTEMPTY : -39, + ENOTSUP : -95, + ENXIO : -6, + EOVERFLOW : -75, + EPERM : -1, + EPIPE : -32, + ERANGE : -34, + EROFS : -30, + ETXTBSY : -26, + EXDEV : -18 +}; + +export { ERRNO } \ No newline at end of file diff --git a/example/provider/javascript/js/webfuse/messagereader.js b/example/provider/javascript/js/webfuse/messagereader.js new file mode 100644 index 0000000..f58958a --- /dev/null +++ b/example/provider/javascript/js/webfuse/messagereader.js @@ -0,0 +1,59 @@ +class MessageReader { + + constructor(data) { + // console.log(new Uint8Array(data)); + this.raw = data; + this.data = new DataView(data); + this.pos = 0; + this.decoder = new TextDecoder('utf-8'); + } + + read_u8() { + const result = this.data.getUint8(this.pos); + this.pos++; + return result; + } + + read_bool() { + return this.read_u8() == 1; + } + + read_u32() { + const result = this.data.getUint32(this.pos); + this.pos += 4; + return result; + } + + read_u64() { + const result = this.data.getBigUint64(this.pos); + this.pos += 8; + return Number(result); + } + + read_str() { + const length = this.read_u32(); + if (length > 0) { + const view = new Uint8Array(this.raw, this.pos, length); + this.pos += length; + return this.decoder.decode(view); + } + else { + return ""; + } + } + + read_bytes() { + const length = this.read_u32(); + if (length > 0) { + const view = new Uint8Array(this.raw, this.pos, length); + this.pos += length; + return view; + } + else { + return []; + } + } + +} + +export { MessageReader } \ No newline at end of file diff --git a/example/provider/javascript/js/webfuse/messagewriter.js b/example/provider/javascript/js/webfuse/messagewriter.js new file mode 100644 index 0000000..ec1f504 --- /dev/null +++ b/example/provider/javascript/js/webfuse/messagewriter.js @@ -0,0 +1,76 @@ +class MessageWriter { + + constructor(message_id, message_type) { + this.data = [ ] + this.write_u32(message_id) + this.write_u8(message_type) + this.encoder = new TextEncoder("utf-8"); + } + + write_u8(value) { + this.data.push(value) + } + + write_u32(value) { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setUint32(0, value); + + const data = new Uint8Array(buffer); + this.data.push(...data); + } + + write_i32(value) { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setInt32(0, value); + + const data = new Uint8Array(buffer); + this.data.push(...data); + } + + write_u64(value) { + const buffer = new ArrayBuffer(8); + const view = new DataView(buffer); + view.setBigUint64(0, BigInt(value)); + + const data = new Uint8Array(buffer); + this.data.push(...data); + } + + // value in milliseconds + write_time(value) { + const seconds = Math.floor(value / 1000); + const millis = value % 1000; + const nanos = millis * 1000 * 1000; + + this.write_u64(seconds); + this.write_u32(nanos); + } + + write_str(value) { + const data = this.encoder.encode(value); + this.write_u32(data.length); + this.data.push(...data); + } + + write_strings(list) { + this.write_u32(list.length); + for(const item of list) { + this.write_str(item); + } + } + + write_bytes(value) { + this.write_u32(value.length); + this.data.push(...value); + } + + get_data() { + // console.log(this.data) + return new Uint8Array(this.data); + } + +} + +export { MessageWriter } \ No newline at end of file diff --git a/example/provider/javascript/js/webfuse/webfuse.js b/example/provider/javascript/js/webfuse/webfuse.js new file mode 100644 index 0000000..1c53751 --- /dev/null +++ b/example/provider/javascript/js/webfuse/webfuse.js @@ -0,0 +1,305 @@ +import { MessageWriter } from "./messagewriter.js"; +import { MessageReader } from "./messagereader.js"; +import { ERRNO } from "./errno.js"; +import { AccessMode } from "./accessmode.js"; +import { BaseFileSystem } from "./basefilesystem.js"; + + +const Mode = { + REG : 0o100000, + DIR : 0o040000, + CHR : 0o020000, + BLK : 0o060000, + FIFO : 0o010000, + LNK : 0o120000, + SOCK : 0o140000 +}; + + + +function fs_access(reader, writer, filesystem) { + const path = reader.read_str(); + const mode = reader.read_u8(); + result = filesystem.access(path, mode); + writer.write_i32(result); +} + +function fs_getattr(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.getattr(path); + if (typeof(result) !== "number") { + writer.write_i32(0); + writer.write_u64(result.ino | 0); + writer.write_u64(result.nlink | 0); + writer.write_u32(result.mode | 0); + writer.write_i32(result.uid | 0); + writer.write_i32(result.gid | 0); + writer.write_u64(result.dev | 0); + writer.write_u64(result.size | 0); + writer.write_u64(result.blocks | 0); + writer.write_time(result.atime | 0); + writer.write_time(result.mtime | 0); + writer.write_time(result.ctime | 0); + } + else { + writer.write_i32(result); + } +} + +function fs_readlink(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.readlink(path); + if (typeof(result) != "number") { + writer.write_i32(0); + writer.write_str(result); + } + else { + writer.write_i32(result); + } +} + +function fs_symlink(reader, writer, filesystem) { + const target = reader.read_str(); + const linkpath = reader.read_str(); + const result = filesystem.symlink(target, linkpath); + writer.write_i32(result); +} + + +function fs_link(reader, writer, filesystem) { + const oldpath = reader.read_str(); + const newpath = reader.read_str(); + const result = filesystem.link(oldpath, newpath); + writer.write_i32(result); +} + +function fs_rename(reader, writer, filesystem) { + const oldpath = reader.read_str(); + const newpath = reader.read_str(); + const flags = reader.read_u8(); + const result = filesystem.rename(oldpath, newpath, flags); + writer.write_i32(result); +} + +function fs_chmod(reader, writer, filesystem) { + const path = reader.read_str(); + const mode = reader.read_u32(); + const result = filesystem.chmod(path, mode); + writer.write_i32(result); +} + +function fs_chown(reader, writer, filesystem) { + const path = reader.read_str(); + const uid = reader.read_u32(); + const gid = reader.read_u32(); + const result = filesystem.chown(path, uid, gid); + writer.write_i32(result); +} + +function fs_truncate(reader, writer, filesystem) { + const path = reader.read_str(); + const size = reader.read_u64(); + const fd = reader.read_u64(); + const result = filesystem.truncate(path, size, fd); + writer.write_i32(result); +} + +function fs_fsync(reader, writer, filesystem) { + const path = reader.read_str(); + const isDataSync = reader.read_bool(); + const fd = reader.read_fd(); + const result = filesystem.fsync(path, isDataSync, fd); + writer.write_i32(result); +} + +function fs_open(reader, writer, filesystem) { + const path = reader.read_str(); + const flags = reader.read_u32(); + const [result, fd] = filesystem.open(path, flags); + writer.write_i32(result); + writer.write_u64(fd); +} + +function fs_mknod(reader, writer, filesystem) { + const path = reader.read_str(); + const mode = reader.read_u32(); + const rdev = reader.read_u64(); + const result = filesystem.mknod(path, mode, rdev); + writer.write_i32(result); +} + +function fs_create(reader, writer, filesystem) { + const path = reader.read_str(); + const mode = reader.read_u32(); + const [result, fd] = filesystem.create(path, mode); + writer.write_i32(result); + writer.write_u64(fd); +} + +function fs_release(reader, writer, filesystem) { + const path = reader.read_str(); + const fd = reader.read_u64(); + const result = filesystem.release(path, fd); + writer.write_i32(result); +} + +function fs_unlink(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.unlink(path); + writer.write_i32(result); +} + +function fs_read(reader, writer, filesystem) { + const path = reader.read_str(); + const size = reader.read_u32(); + const offset = reader.read_u64(); + const fd = reader.read_u64(); + const result = filesystem.read(path, size, offset, fd); + if (typeof(result) != "number") { + writer.write_i32(0); + writer.write_bytes(result); + } + else { + writer.write_i32(result); + } +} + +function fs_write(reader, wriuter, filesystem) { + const path = reader.read_str(); + const data = reader.read_bytes(); + const offset = reader.read_u64(); + const fd = reader.read_u64(); + const result = filesystem.write(path, data, offset, fd); + writer.write_i32(result); +} + +function fs_mkdir(reader, writer, filesystem) { + const path = reader.read_str() + const mode = reader.read_u32(); + const result = filesystem.mkdir(path, mode); + writer.write_i32(result); +} + +function fs_readdir(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.readdir(path); + if (typeof(result) != "number") { + writer.write_i32(0); + writer.write_strings(result); + } + else { + writer.write_i32(result); + } +} + +function fs_rmdir(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.rmdir(path); + writer.write_i32(result); +} + +function fs_statfs(reader, writer, filesystem) { + const path = reader.read_str(); + const result = filesystem.statfs(path); + if (typeof(result) != "number") { + writer.write_i32t(0) + writer.write_u64(result.bsize | 0); + writer.write_u64(result.frsize | 0); + writer.write_u64(result.blocks | 0); + writer.write_u64(result.bfree | 0); + writer.write_u64(result.bavail | 0); + writer.write_u64(result.files | 0); + writer.write_u64(result.ffree | 0); + writer.write_u64(result.namemax | 0); + } + else { + writer.write_i32(result); + } +} + +function fs_utimens(reader, writer, filesystem) { + const path = reader.read_str(); + const atime = reader.read_time(); + const mtime = reader.read_time(); + const result = filesystem.utimens(path, atime, mtime); + writer.write_i32(result); +} + +function fs_getcreds(reader, writer, filesystem) { + const credentials = filesystem.getcreds(); + writer.write_str(credentials); +} + +const commands = new Map([ + [0x01, fs_access], + [0x02, fs_getattr], + [0x03, fs_readlink], + [0x04, fs_symlink], + [0x05, fs_link], + [0x06, fs_rename], + [0x07, fs_chmod], + [0x08, fs_chown], + [0x09, fs_truncate], + [0x0a, fs_fsync], + [0x0b, fs_open], + [0x0c, fs_mknod], + [0x0d, fs_create], + [0x0e, fs_release], + [0x0f, fs_unlink], + [0x10, fs_read], + [0x11, fs_write], + [0x12, fs_mkdir], + [0x13, fs_readdir], + [0x14, fs_rmdir], + [0x15, fs_statfs], + [0x16, fs_utimens], + [0x17, fs_getcreds] +]); + +class Webfuse { + + constructor(url, filesystem) { + console.log('webfuse: ctor') + + this.ws = new WebSocket(url, ["webfuse2"]); + this.ws.binaryType = 'arraybuffer'; + this.ws.addEventListener('close', (event) => this.on_closed(event)); + this.ws.addEventListener('error', (event) => this.on_error(event)); + this.ws.addEventListener('message', (event) => this.on_message(event)); + + this.filesystem = filesystem; + } + + close() { + this.ws.close(); + } + + on_message(event) { + const reader = new MessageReader(event.data); + const message_id = reader.read_u32(); + const message_type = reader.read_u8(); + + const writer = new MessageWriter(message_id, 0x80 + message_type); + if (commands.has(message_type)) { + const command = commands.get(message_type); + command(reader, writer, this.filesystem); + } + else { + console.error(`unknow message type: ${message_type}`); + } + + this.ws.send(writer.get_data()); + } + + on_error(event) { + console.log('error', event); + this.ws.close(); + } + + on_closed(event) { + console.log('closed', event); + } + +} + +export { Webfuse, BaseFileSystem, ERRNO, Mode, AccessMode }