Merge pull request #105 from falk-werner/webfuse2

Activate re-implementation of  webfuse

Major changes:
- read- and write-access to the filesystem
- provide full access to all fuse options
- a single repository containing libraries, executables and examples  

Incompatible changes:
- change webfuse protocol from JSON to binary
- replace webfuse daemon by webfuse filesystem executable
- remove webfuse C libraries
pull/111/head
Falk Werner 1 year ago committed by GitHub
commit e14f6e273c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -18,10 +18,16 @@ jobs:
- uses: actions/checkout@v3
- name: Install APT dependencies
run: sudo apt install libfuse3-dev libwecksockets-dev
run: sudo apt install libfuse3-dev libwebsockets-dev libgtest-dev libgmock-dev clang-tidy valgrind
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Unit Test
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target test
- name: Memcheck
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target memcheck

4
.gitignore vendored

@ -1 +1,3 @@
/build/
/build/
/.vscode/
*.pem

@ -1,5 +1,129 @@
cmake_minimum_required(VERSION 3.10)
project(webfuse VERSION 2.0.0)
add_executable(webfuse
src/main.cpp)
option(WITHOUT_PROVIDER "Disabled build of webfuse provider" OFF)
option(WITHOUT_TEST "Disables unit and integration tests" OFF)
option(WITHOUT_CLANG_TIDY "Disables clang tidy" OFF)
set (CMAKE_CXX_STANDARD 17)
find_package(PkgConfig REQUIRED)
pkg_check_modules(FUSE REQUIRED IMPORTED_TARGET fuse3)
pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets)
configure_file(src/webfuse/version.cpp.in version.cpp)
add_library(webfuse_static STATIC
${CMAKE_CURRENT_BINARY_DIR}/version.cpp
src/webfuse/webfuse.cpp
src/webfuse/provider.cpp
src/webfuse/fuse.cpp
src/webfuse/request_type.cpp
src/webfuse/response_type.cpp
src/webfuse/util/commandline_args.cpp
src/webfuse/util/commandline_reader.cpp
src/webfuse/util/authenticator.cpp
src/webfuse/filesystem.cpp
src/webfuse/filesystem/status.cpp
src/webfuse/filesystem/accessmode.cpp
src/webfuse/filesystem/openflags.cpp
src/webfuse/filesystem/filemode.cpp
src/webfuse/filesystem/filesystem_statistics.cpp
src/webfuse/filesystem/empty_filesystem.cpp
src/webfuse/ws/config.cpp
src/webfuse/ws/server.cpp
src/webfuse/ws/server_handler.cpp
src/webfuse/ws/client.cpp
src/webfuse/ws/messagewriter.cpp
src/webfuse/ws/messagereader.cpp
src/webfuse/ws/url.cpp
)
target_include_directories(webfuse_static PUBLIC src)
target_link_libraries(webfuse_static PUBLIC PkgConfig::FUSE PkgConfig::LWS)
if(NOT(WITHOUT_CLANG_TIDY))
set_property(
TARGET webfuse_static
PROPERTY CXX_CLANG_TIDY clang-tidy -checks=readability-*,-readability-identifier-length -warnings-as-errors=*)
endif()
add_executable(webfuse src/main.cpp)
target_link_libraries(webfuse PRIVATE webfuse_static)
install(TARGETS webfuse DESTINATION bin)
if(NOT(WITHOUT_PROVIDER))
add_executable(webfuse_provider src/provider_main.cpp)
target_link_libraries(webfuse_provider PRIVATE webfuse_static)
install(TARGETS webfuse_provider DESTINATION bin)
endif()
if(NOT(WITHOUT_TEST))
pkg_check_modules(GTEST REQUIRED gtest_main)
pkg_check_modules(GMOCK REQUIRED gmock)
add_executable(unit_tests
test-src/unit/webfuse/test_app.cpp
test-src/unit/webfuse/test_request_type.cpp
test-src/unit/webfuse/test_response_type.cpp
test-src/unit/webfuse/filesystem/test_status.cpp
test-src/unit/webfuse/filesystem/test_accessmode.cpp
test-src/unit/webfuse/filesystem/test_openflags.cpp
test-src/unit/webfuse/filesystem/test_filemode.cpp
)
target_include_directories(unit_tests PRIVATE test-src/unit ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS})
target_compile_options(unit_tests PRIVATE
${GTEST_CFLAGS} ${GTEST_CFLAGS_OTHER}
${GMOCK_CFLAGS} ${GMOCK_CFLAGS_OTHER}
)
target_link_libraries(unit_tests PRIVATE webfuse_static ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
add_executable(integration_tests
test-src/integration/webfuse/test/tempdir.cpp
test-src/integration/webfuse/test/fixture.cpp
test-src/integration/webfuse/test/process.cpp
test-src/integration/webfuse/test/daemon.cpp
test-src/integration/test_access.cpp
test-src/integration/test_readdir.cpp
test-src/integration/test_readlink.cpp
test-src/integration/test_symlink.cpp
test-src/integration/test_link.cpp
test-src/integration/test_rename.cpp
test-src/integration/test_chmod.cpp
test-src/integration/test_chown.cpp
test-src/integration/test_truncate.cpp
test-src/integration/test_fsync.cpp
test-src/integration/test_utimens.cpp
test-src/integration/test_open.cpp
test-src/integration/test_mknod.cpp
test-src/integration/test_unlink.cpp
test-src/integration/test_read.cpp
test-src/integration/test_write.cpp
test-src/integration/test_mkdir.cpp
test-src/integration/test_rmdir.cpp
test-src/integration/test_statfs.cpp
)
target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS})
target_compile_options(integration_tests PRIVATE
${GTEST_CFLAGS} ${GTEST_CFLAGS_OTHER}
${GMOCK_CFLAGS} ${GMOCK_CFLAGS_OTHER}
)
target_link_libraries(integration_tests PRIVATE webfuse_static ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
enable_testing()
add_test(NAME unit_tests COMMAND unit_tests)
add_test(NAME integration_tests COMMAND integration_tests)
find_program(VALGRIND valgrind REQUIRED)
if(VALGRIND)
add_custom_target(memcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./unit_tests)
endif()
endif()

@ -1,10 +1,75 @@
# webfuse2
[![build](https://github.com/falk-werner/webfuse/actions/workflows/build.yml/badge.svg)](https://github.com/falk-werner/webfuse/actions/workflows/build.yml)
Reimplementation of webfuse.
# webfuse
## Build
webfuse combines libwebsockets and libfuse. It allows to attach a remote filesystem via websockets.
````
cmake -B build
cmake --build build
````
## Motivation
Many embedded devices, such as smart home or [IoT](https://en.wikipedia.org/wiki/Internet_of_things) devices are very limited regarding to their (non-volatile) memory resources. Such devices are typically comprised of an embedded linux and a small web server, providing an interface for maintenance purposes.
Some use cases, such as firmware update, require to transfer (larger) files to the device. The firmware file is often stored multiple times on the device:
1. cached by the web server, e.g. [lighttpd](https://redmine.lighttpd.net/boards/2/topics/3451)
2. copied to locally, e.g. /tmp
3. uncompressed, also to /tmp
Techniques like [SquashFS](https://en.wikipedia.org/wiki/SquashFS) help to avoid the third step, since the upgrade file can be mounted directly. [RAUC](https://rauc.io/) shows the use of SquashFS within an update facility.
However at least one (unecessary) copy of the upload file is needed on the device.
To avoid Steps 1 and 2, it would be great to keep the update file entirely in web server, just like [NFS](https://en.wikipedia.org/wiki/Network_File_System) or [WebDAV](https://wiki.archlinux.org/index.php/WebDAV). Unfortunately, NFS is not based on any protocol, natively usable by a web application. WebDAV is based on HTTP, but it needs a server providing the update file.
webfuse solves this problem by using the [WebSocket](https://en.wikipedia.org/wiki/WebSocket) protocol. The emdedded device runs a service, known as webfuse adapter, awaiting incoming connections, e.g. from a web browser. The browser acts as a file system provider, providing the update file to the device.
## Concept
![concept](doc/concept.png)
With webfuse it is possible to implement remote filesystems based on websockets.
Therefore, webfuse defined two roles participating in a webfuse connection:
- webfuse service
- webfuse provider
### Webfuse Service
A `webfuse service` is both,
- a [websocket](https://en.wikipedia.org/wiki/WebSocket) service providing the `webfuse` protocol
- a [fuse](https://github.com/libfuse/libfuse) filesystem attached to a local mountpoint
The `webfuse service` awaits incoming connections from a `webfuse provider`. Once connected, it communicates all the filesystem requests originated by the `libfuse` to the connected `webfuse provider` using the `websocket`-based `webfuse protocol`.
By doing so, `webfuse` allows to inject a filesystem to a remote device.
### Webfuse Provider
A `webfuse provider` provides a filesystem to a remote device using the `websocket`-based `webfuse protocol`. Therefore, a `webfuse provider` implements a `websocket` client.
## Similar Projects
### Davfs2
[davfs2](http://savannah.nongnu.org/projects/davfs2) is a Linux file system driver that allows to mount a [WebDAV](https://wiki.archlinux.org/index.php/WebDAV) resource. WebDAV is an extension to HTTP/1.1 that allows remote collaborative authoring of Web resources.
Unlike webfuse, davfs2 mounts a remote filesystem locally, that is provided by a WebDAV server. In contrast, webfuse starts a server awaiting client connections to attach the remote file system.
## Further Documentation
- [Build instructions](doc/build.md)
- [Webfuse command line options](doc/webfuse.md)
- [Webfuse provider command line options](doc/webfuse_provider.md)
- [Webfuse Protocol](doc/protocol.md)
- [Authentication](doc/authentication.md)
## webfuse legacy
`webfuse2` is a complete re-implementation of the original idea behind `webfuse`. In contrast to the original `webfuse` implementation, `webfuse2` provides also write access to the filesystem and allows to use all standard options of a fuse filesystem.
But `webfuse2` marks also some breaking changes:
- `webfuse2` uses a new, binary protocol which is not compatible to the JSON-based protocol of legacy `webfuse`
- `webfuse` does not provide an API nor a library to program against
_(if you are interested in an API or a library for webfuse2 feel free to create an issue)_
When you are interested in the original `webfuse` implementation take a look at this [branch](https://github.com/falk-werner/webfuse/tree/master).

@ -0,0 +1,7 @@
# webfuse developer documentation
- [Build instructions](build.md)
- [Webfuse command line options](webfuse.md)
- [Webfuse provider command line options](webfuse_provider.md)
- [Webfuse2 protocol](protocol.md)
- [Authentication](authentication.md)

@ -0,0 +1,34 @@
# Authentication
Webfuse supports two authentications mechanisms:
- token-based authentication using HTTP headers
- in-protocol authentication
To activate authentication, two command line option can be specified:
- `--wf-authenticator PATH`
allows to specify an executable used for authentication
- `--wf-auth-header HEADER` _(optional)_
allows to specify the HTTP header used for authentication
When `--wf-auth-header` is not specifiend or the header is not contained
in the HTTP request, the in-protocol solutions is used: Before any other
operation, the credentials are queried via `getcreds`request.
## Authenticator
An authenticator is an executable or script used for token-based
authentication. Credentials are passed to the authenticator via `stdin`.
## Header restrictions
Note that not any HTTP header can be specified using `--wf-auth-header`
option. The following headers are supported:
- `X-Auth-Token`
- `Authorization`
In addition to that, any non-standard header can be specified.
Due to implementation limitation, most standard headers can not be used by now. Please let us know, when you intend to use a header that is not supported yet. Please create an issue in that case.

@ -0,0 +1,35 @@
# webfuse build instructions
## Build
````
cmake -B build
cmake --build build
sudo cmake --install build
````
## Build options
| Options | Default | Description |
| ------------------ | -------- | ----------- |
| WITHOUT_PROVIDER | OFF | Disables build of webfuse provider |
| WITHOUT_TEST | OFF | Disables build of unit and integration tests |
| WITHOUT_CLANG_TIDY | OFF | Disables running clang tidy on build |
## Dependencies
- [libfuse](https://github.com/libfuse/libfuse)
- [libwebsockets](https://libwebsockets.org/)
### Optional dependencies
- [googletest](https://github.com/google/googletest)
- [valgrind](https://valgrind.org/)
- [clang-tidy](https://clang.llvm.org/extra/clang-tidy/)
## Additional cmake targets
| Target | Description |
| -------- | ----------- |
| test | runs unit and integration tests |
| memcheck | runs unit test with valgrind/memcheck |

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@ -0,0 +1,26 @@
@startuml
participant "webfuse provider" as provider
participant "webfuse service" as service
actor "user" as user
group startup
service -> service : fuse_mount
service -> service : start websocket server
end
...
group connect
provider -> service : connect
end
...
group directory listing
user -> service : ls
service -> provider : readdir request
provider --> service : readdir response
service --> user : [., ..]
end
...
@enduml

@ -0,0 +1,916 @@
# Webfuse2 Protocol
## Scope
This document describes the webfuse 2 communication protocol. The protocol is used to transfer messages between a `webfuse service` and a `webfuse provider`. In contrast to `legacy webfuse`, which is based on `JSON RPC` the `webfuse2 protocol` is a binary protocol.
## Definitions
### Webfuse Service
A `webfuse service` is both,
- a [websocket](https://en.wikipedia.org/wiki/WebSocket) service providing the `webfuse` protocol
- a [fuse](https://github.com/libfuse/libfuse) filesystem attached to a local mountpoint
The `webfuse service` awaits incoming connections from a `webfuse provider`. Once connected, it communicates all the filesystem requests originated by the `libfuse` to the connected `webfuse provider` using the `websocket`-based `webfuse protocol`.
By doing so, `webfuse` allows to inject a filesystem to a remote device.
### Webfuse Provider
A `webfuse provider` provides a filesystem to a remote device using the `websocket`-based `webfuse protocol`. Therefore, a `webfuse provider` implements a `websocket` client.
## Websocket protocol name
The webfuse2 protocol uses the following websocket protocol name: `webfuse2`.
## Message exchange
Once connected, the `webfuse2 protocol` implements a strict request-response scheme, where
- all requests are send by the `webfuse service`,
- all requests require a response and
- all responses are send by the `webfuse provider`
Note that this communication is reversed the typical client-server-communication scheme. In `webfuse` the `webfuse service` (server) sends all the requests and the `webfuse provider` (client) sends the responses.
For message transfer, the [websocket](https://en.wikipedia.org/wiki/WebSocket) protocol is used. All messages are in binary form, plain text messages are never used by the `webfuse protocol`.
## Endianness
All numeric data types are transferred in [Big Endian](https://en.wikipedia.org/wiki/Endianness).
For instance, the uint32 value 1 will be transferred as
00 00 00 01
## Data Types
### Basic data types
| Data Type | Width | Description |
| --------- | ------ | ----------- |
| bool | 8 bit | boolean value |
| u8 | 8 bit | 8 bit unsigned integer |
| u32 | 32 bit | 32 bit unsigned integer |
| u64 | 64 bit | 64 bit unsigned integer |
| i32 | 32 bit | 32 bit signed integer |
### Derrived integral types
| Data Type | Base Type | Description |
| --------- | --------- | ----------- |
| uid | u32 | user ID |
| gid | u32 | group ID |
| dev | u64 | device ID |
| handle | u64 | file handle |
### Flags and Enums
| Data Type | Base Type | Description |
| ------------ | --------- | ------------------------- |
| result | i32 | result type of methods |
| access_mode | u32 | mode of `access` method |
| mode | u32 | file type and permissions |
| open_flags | i32 | flags of `open` method |
| rename_flags | u8 | flags of `rename` method |
#### result
| Value Range | Description |
| -------------- | ---------------------- |
| 0 | method call succeed |
| negative value | error code (see below) |
| positive value | amount of bytes read or written (`read` and `write` only) |
| Error Code | Value | Description |
| ------------ | ----- | ----------- |
| E2BIG | -7 | argument list too long |
| EACCES | -13 | permission denied |
| EAGAIN | -11 | resource temporarily unavailable |
| EBADF | -9 | bad file descriptor |
| EBUSY | -16 | device or resource busy |
| EDESTADDRREQ | -89 | destination address required |
| EDQUOT | -122 | disk quota exceeded |
| EEXIST | -17 | file exists |
| EFAULT | -14 | bad address |
| EFBIG | -27 | file too large |
| EINTR | -4 | interrupt function call |
| EINVAL | -22 | invalid argument |
| EIO | -5 | input / output error |
| EISDIR | -21 | is a directory
| ELOOP | -40 | too many levels of symbolic links |
| EMFILE | -24 | too many open files |
| EMLINK | -31 | too many links |
| ENAMETOOLONG | -36 | filename too long |
| ENFILE | -23 | too many open files in system |
| ENODATA | -61 | the named attribute does not exist, or the process has not access to this attribute |
| ENODEV | -19 | no such device |
| ENOENT | -2 | no such file or directory |
| ENOMEM | -12 | not enough space / cannot allocate memory |
| ENOSPC | -28 | no space left on device |
| ENOSYS | -38 | function not implemented |
| ENOTDIR | -20 | not a directory |
| ENOTEMPTY | -39 | directory not empty |
| ENOTSUP | -95 | operation not supported |
| ENXIO | -6 | no such device or address |
| EOVERFLOW | -75 | value too large to be stored in data type |
| EPERM | -1 | operation not permitted |
| EPIPE | -32 | broken pipe |
| ERANGE | -34 | result too large |
| EROFS | -30 | read-only filesystem |
| ETXTBSY | -26 | text file busy |
| EXDEV | -18 | improper link |
| EWOULDBLOCK | -11 | resource temporarily unavailable |
#### access mode
| Mode | Value | Description |
| ---- | ----- | ----------- |
| F_OK | 0 | Tests, whether the file exists |
| X_OK | 1 | Tests, whether the file is executable |
| W_OK | 2 | Tests, whether the file is writable |
| R_OK | 4 | Tests, whether the file is readable |
#### mode
_Note that the following numbers are in `octal` notation._
| Fields and Flags | Mask | Description |
| ---------------- | -------- | ----------- |
| Protection mask | 0o000777 | Cointains the file protection flags (rwx for owner, group and others) |
| Sticky mask | 0o007000 | Sticky bits |
| S_ISVTX | 0o001000 | Sticky bit |
| S_ISGID | 0o002000 | Set-Group-ID bit |
| S_ISUID | 0o004000 | Set-User-ID bit |
| Filetype mask | 0o170000 | Filetype mask |
| S_IFREG | 0o100000 | regular file |
| S_IFDIR | 0o040000 | directory |
| S_IFCHR | 0o020000 | character device |
| S_IFBLK | 0o060000 | block device |
| S_IFIFO | 0o010000 | named pipe |
| S_IFLNK | 0o120000 | link |
| S_IFSOCK | 0o140000 | socket |
#### open_flags
_Note that the following numbers are in `octal` notation._
| Flags | Value | Description |
| ---------------- | -------- | ----------- |
| O_ACCMODE | 0o03 | Access mode mask |
| O_RDONLY | 0o00 | open file read-only |
| O_WRONLY | 0o01 | open file write-only |
| O_RDWR | 0o02 | open file for reading and writing |
| O_APPEND | 0o000002000 | open file in append mode |
| O_ASYNC | 0o000020000 | enable signal-driven I/O |
| O_CLOEXEC | 0o002000000 | enable close-on-exec |
| O_CREAT | 0o000000100 | create file if path does not exists |
| O_DIRECT | 0o000040000 | try to minimize cache effects on I/O |
| O_DIRECTORY | 0o000200000 | open a directory |
| O_DSYNC | 0o000010000 | write with synchronized I/O data integrity |
| O_EXCL | 0o000000200 | ensure that file exists when specified in conjunction with O_CREATE |
| O_LARGEFILE | 0o000100000 | allow large files to be opened |
| O_NOATIME | 0o001000000 | do not update file last access time |
| O_NOCTTY | 0o000000400 | make device the process's controlling terminal |
| O_NOFOLLOW | 0o000400000 | fail to open, if basename is a symbolic link |
| O_NONBLOCK | 0o000004000 | open in nonblocking mode |
| O_NDELAY | 0o000004000 | open in nonblocking mode |
| O_PATH | 0o010000000 | see manual entry of `open(2)` for details |
| O_SYNC | 0o004010000 | write using synchronized I/O |
| O_TMPFILE | 0o020200000 | create an unnamed temporary file |
| O_TRUNC | 0o000001000 | truncate fole to length 0 |
#### rename_flags
| Flag | Value | Description |
| ---------------- | ----- | ----------- |
| RENAME_NOREPLACE | 1 | do not overwrite the new file |
| RENAME_EXCHANGE | 2 | atomically exchange the files |
### Complex Types
| Data Type | Description |
| ---------- | --------------------- |
| string | UTF-8 string |
| strings | list of strings |
| bytes | array of bytes |
| timestamp | date and time |
| attributes | file attributes |
| statistics | filesystem statistics |
#### string
| Field | Data Type | Description |
| ----- | --------- | ----------------------------- |
| size | u32 | length of the string in bytes |
| data | u8[] | string data (UTF-8) |
#### strings
| Field | Data Type | Description |
| ----- | --------- | --------------------------------- |
| size | u32 | count of the elements in the list |
| data | string[] | strings |
#### bytes
| Field | Data Type | Description |
| ----- | --------- | ------------------------ |
| size | u32 | length of the byte array |
| data | u8[] | array data |
#### timestamp
| Field | Data Type | Description |
| ------- | --------- | ----------------------------- |
| seconds | u64 | seconds sind epoch (1.1.1970) |
| nsec | u32 | nano seconds |
#### attributes
| Field | Data Type | Description |
| ------ | ---------- | -------------------------- |
| inode | u64 | Inode value |
| nlink | u64 | Number of hard links |
| mode | mode (u32) | file mode flags |
| uid | uid (u32) | user ID |
| gid | gid (u32) | group ID |
| rdev | dev (u64) | device ID |
| size | u64 | file size |
| blocks | u64 | number 512-byte blocks |
| atime | timestamp | time of last access |
| mtime | timestamp | time of last modification |
| ctime | timestamp | time of last status change |
#### statistics
| Field | Data Type | Description |
| ------ | ---------- | -------------------------- |
| bsize | u64 | Filesystem block size |
| frsize | u64 | Fragment size |
| blocks | u64 | Size of the filesystem if `frsize` units |
| bfree | u64 | Number of free blocks |
| bavail | u64 | Number of free blocks for unprivileged users |
| files | u64 | Number of inodes |
| ffree | u64 | Number of free inodes |
| namemax | u64 | Maximum filename length |
## Messages
| Field | Type | Descripton |
| ------- | ---- | ---------- |
| id | u32 | Unique ID of the message |
| type | u8 | Type of the message |
| payload | u8[] | Payload according to the message type |
The `id` is just a number without any meaning for the `webfuse provider`. It is set by the `webfuse service` of a request and is copied by the `webfuse provider` to the response. A `webfuse service` implementation might choose to keep track on pending requests using the `id`.
### Erroneous Responses
Most responses contain a `result` encoding the status of the operation. While successful responses may contain additional data, erroneous responses must not be decoded by a `webfuse service` implementation beyond the `result` value.
### Unknown requests
There are two reserved message types:
- **0x00:** Unknown request
- **0x80:** Unknown response
A `webfuse service` may send a request of type `unknown request` for conformance testing reasons.
Since each request requires a response, a `webfuse provider` must respond to any unknown requests with a message of `unknown response` type. This allows to add new request types in future.
### Accept additional data in requests
Both, a `webfuse provider` and a `webfuse service` must accept messages that contain more data than specified. This allows to add optional fields to existing requests and / or responses in future.
_Note there are no optional fields in the current revision of the `webfuse2 protocol` yet._
### Message Types
_Note that the following numbers are in `hexadecimal` notation._
| Method | Request | Response |
| -------- | ------- | -------- |
| access | 0x01 | 0x81 |
| getattr | 0x02 | 0x82 |
| readlink | 0x03 | 0x83 |
| symlink | 0x04 | 0x84 |
| link | 0x05 | 0x85 |
| rename | 0x06 | 0x86 |
| chmod | 0x07 | 0x87 |
| chown | 0x08 | 0x88 |
| truncate | 0x09 | 0x89 |
| fsync | 0x0a | 0x8a |
| open | 0x0b | 0x8b |
| mknod | 0x0c | 0x8c |
| create | 0x0d | 0x8d |
| release | 0x0e | 0x8e |
| unlink | 0x0f | 0x8f |
| read | 0x10 | 0x90 |
| write | 0x11 | 0x91 |
| mkdir | 0x12 | 0x92 |
| readdir | 0x13 | 0x93 |
| rmdir | 0x14 | 0x94 |
| statfs | 0x15 | 0x95 |
| utimens | 0x16 | 0x96 |
| getcreds | 0x17 | 0x97 |
## Methods
Since `webfuse` aims to communicate the `libfuse API` over a `websocket` connection, `webfuse` methods are tightly connected to [fuse operations](https://libfuse.github.io/doxygen/structfuse__operations.html) which itself have a tight connection to `posix filesystem operations`. Therefore, additional information about most `webfuse` operations can be found in the [fuse operations documentation](https://libfuse.github.io/doxygen/structfuse__operations.html) and / or the [man pages](https://man7.org/index.html).
### access
Checks the user's permissions for a file (see [man access(2)](https://man7.org/linux/man-pages/man2/access.2.html)).
#### Request
| Field | Data Type | Description |
| ----- | ---------------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x01) |
| path | string | path of file to check |
| mode | access_mode (i8) | access mode to check |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x81) |
| result | result | operation status |
### getattr
Retrieve file attributes (see [man getattr(2)](https://man7.org/linux/man-pages/man2/getxattr.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x02) |
| path | string | path |
#### Response
| Field | Data Type | Description |
| --------- | ---------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x82) |
| result | result | operation status |
| attibutes | attributes | attributes of file |
### readlink
Read value of a symbolik link (see [man readlink(2)](https://man7.org/linux/man-pages/man2/readlink.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x03) |
| path | string | path of link |
#### Response
| Field | Data Type | Description |
| -------- | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x83) |
| result | result | operation status |
| resolved | string | resolved path |
### symlink
Make a new name of a file (see [man symlink(2)](https://man7.org/linux/man-pages/man2/symlink.2.html)).
#### Request
| Field | Data Type | Description |
| -------- | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x04) |
| target | string | target of link |
| linkpath | string | name of the link |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x84) |
| result | result | operation status |
### link
Make a new name for a file (see [man link(2)](https://man7.org/linux/man-pages/man2/link.2.html)).
#### Request
| Field | Data Type | Description |
| -------- | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x05) |
| old_path | string | path of the existing file |
| new_path | string | new name of the file |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x85) |
| result | result | operation status |
### rename
Change the name of a file (see [man rename(2)](https://man7.org/linux/man-pages/man2/rename.2.html)).
#### Request
| Field | Data Type | Description |
| -------- | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x06) |
| old_path | string | old name of the file |
| new_path | string | new name of the file |
| flags | rename_flags (u8) | flags to control the rename operation |
The following `flags` are defined:
- **0x00:** move the file from `old_path` to `new_path`
- **0x01 (RENAME_NOREPLACE):** do not override `new_path`
This results in an error, when `new_path` already exists.
- **0x02 (RENAME_EXCHANGE):** atomically exchange the files
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x86) |
| result | result | operation status |
### chmod
Change permissions of a file (see [man chmod(2)](https://man7.org/linux/man-pages/man2/chmod.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | ---------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x07) |
| path | string | path of the file |
| mode | mode (u32) | new file permissions |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x87) |
| result | result | operation status |
### chown
Change ownership of a file (see [man chown(2)](https://man7.org/linux/man-pages/man2/chown.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x08) |
| path | string | path of the file |
| uid | uid (u32) | user id of the new owner |
| gid | gid (u32) | group id of the new owning group |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x88) |
| result | result | operation status |
### truncate
Truncate a file to a specified length (see [man truncate(2)](https://man7.org/linux/man-pages/man2/truncate64.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | ------------ | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x09) |
| path | string | path of the file |
| size | u64 | new file size |
| handle | handle (u64) | handle of the file |
_Note that handle might be invalid (-1), even if the file is open._
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x89) |
| result | result | operation status |
### fsync
Sychronize a file's in-core state with storage device (see [man fsync(2)](https://man7.org/linux/man-pages/man2/fsync.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x0a) |
| path | string | path of the file |
| is_datasync | bool | if true, sync only user data |
| handle | handle (u64) | handle of the file |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x8a) |
| result | result | operation status |
### open
Open and possibly create a file ([man open(2)](https://man7.org/linux/man-pages/man2/open.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x0b) |
| path | string | path of the file |
| flags | open_flags (i32) | flags |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x8b) |
| result | result | operation status |
| handle | handle (u64) | handle of the file |
### mknod
Create a special or ordinary file (see [man mknod(2)](https://man7.org/linux/man-pages/man2/mknod.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x0c) |
| path | string | path of the file |
| mode | mode (u32) | mode of the file |
| dev | dev (64) | device type |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x8c) |
| result | result | operation status |
### create
Create a new file or rewrite an existing one (see [man creat(3p)](https://man7.org/linux/man-pages/man3/creat.3p.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x0d) |
| path | string | path of the file |
| mode | mode (u32) | mode of the file |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x8d) |
| result | result | operation status |
| handle | handle (u64) | handle of the file |
### release
Releases a file handle (see [man close(2)](https://man7.org/linux/man-pages/man2/close.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x0e) |
| path | string | path of the file |
| handle | handle (u64) | handle of the file |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x8e) |
| result | result | operation status |
### unlink
Delete a name and possibly the file it refers to ([man unlink(2)](https://man7.org/linux/man-pages/man2/unlink.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x0f) |
| path | string | path of the file |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x8f) |
| result | result | operation status |
### read
Read from a file description (see [man read(2)](https://man7.org/linux/man-pages/man2/read.2.html), [man pread(2)](https://man7.org/linux/man-pages/man2/pread.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x10) |
| path | string | path of the file |
| buffer_size | u32 | max. amount of bytes requested |
| offset | u64 | offset of the file |
| handle | handle (u64) | handle of the file |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x90) |
| result | result | amount of byte read or error code |
| data | bytes | requested data |
_Note that results returns the amount of bytes read on success._
### write
Write to a file (see [man write(2)](https://man7.org/linux/man-pages/man2/write.2.html), [man pread(2)](https://man7.org/linux/man-pages/man2/pread.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x17) |
| data | bytes | data to write |
| offset | u64 | offset to write to |
| handle | handle (u64) | handle of the file |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x91) |
| result | result | amount of bytes written or error code |
_Note that results returns the amount of bytes written on success._
### mkdir
Create a directory (see [man mkdir(2)](https://man7.org/linux/man-pages/man2/mkdir.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x12) |
| path | string | path of the directory |
| mode | mode (u32) | directory permissions |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x92) |
| result | result | operation status |
### readdir
Reads the contents of a directory.
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x13) |
| path | string | path of the directory |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x93) |
| result | result | operation status |
| items | strings | names of the directory entries |
### rmdir
Delete a directory (see [man rmdir(2)](https://man7.org/linux/man-pages/man2/rmdir.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x14) |
| path | string | path of the directory |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x94) |
| result | result | operation status |
### statfs
Get filesystem statistics (see [man statvfs(3)](https://man7.org/linux/man-pages/man3/statvfs.3.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x15) |
| path | string | path of the file |
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x95) |
| result | result | operation status |
| statistics | statistics | filesystem statistics |
### utimens
Change the file timestamps with nanosecond precision ([man utimesat(2)](https://man7.org/linux/man-pages/man2/utimensat.2.html)).
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x16) |
| path | string | path of the file |
| atime | timestamp | new last access time |
| mtime | timestamp | new last modified time |
| handle | handle (u64) | handle of the file |
_Note that handle might be invalid (-1), even if the file is open._
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x96) |
| result | result | operation status |
### getcreds
Query credentials. When authentication is active and the in-protocol
authentication mechanism is used, this is the first request a
webfuse service sends to a provider.
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x17) |
_Note that handle might be invalid (-1), even if the file is open._
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x97) |
| creds | str | credentials |
## Examples
### Get file attributes
````
service -> provider:
00 00 00 01 # message id = 1
02 # message type = getattr request
00 00 00 01 # path.size = 1
'/' # path = "/"
provider -> service:
00 00 00 01 # message id = 1
82 # message type = getattr response
00 00 00 00 # result = 0 (OK)
00 00 00 00 00 00 00 01 # attributes.inode = 1
00 00 00 00 00 00 00 02 # attributes.nlink = 2
00 00 41 a4 # attributes.mode = 0o40644 (S_IDDIR | 0o0644)
00 00 03 e8 # attributes.uid = 1000
00 00 03 e8 # attributes.gid = 1000
00 00 00 00 00 00 00 00 # attributes.size = 0
00 00 00 00 00 00 00 00 # attributes.blocks = 0
00 00 00 00 00 00 00 00 # attrbites.atime.sec = 0
00 00 00 00 00 # attributs.atime.nsec = 0
00 00 00 00 00 00 00 00 # attrbites.mtime.sec = 0
00 00 00 00 00 # attributs.mtime.nsec = 0
00 00 00 00 00 00 00 00 # attrbites.ctime.sec = 0
00 00 00 00 00 # attributs.ctime.nsec = 0
````
### Get file attributes (Failure)
_Note that attributs are skipped in case of an error._
````
service -> provider:
00 00 00 01 # message id = 1
02 # message type = getattr request
00 00 00 04 # path.size = 4
"/foo" # path = "/foo"
provider -> service:
00 00 00 01 # message id = 1
82 # message type = getattr response
ff ff ff fe # result = -2 (ENOENT)
````
### List directory contents
_Note that '.' and '..' should not be included in the response._
````
service -> provider:
00 00 00 02 # message id = 2
13 # message type = readdir request
00 00 00 04 # path.size = 4
'/dir' # path = "/dir"
provider -> service:
00 00 00 02 # message id = 2
93 # message type = readdir response
00 00 00 00 # result = 0 (OK)
00 00 00 03 # items.size = 3
00 00 00 03 # items[0].size = 3
"foo" # items[0] = "foo"
00 00 00 03 # items[0].size = 3
"bar" # items[0] = "bar"
00 00 00 03 # items[0].size = 3
"baz" # items[0] = "baz"
````
### Unknown request
````
service -> provider:
00 00 00 23 # message id = 0x23
42 # message type = ??? (not specified yet)
... # some more data
provider -> service:
00 00 00 23 # message id = 0x23
80 # message type = unknown response
````

@ -0,0 +1,79 @@
# webfuse command line options
In order to inject a remote filesystem, webfuse mounts a local
filesystem via fuse and exposes it's API via websockets.
## Usage
webfuse [options] <mountpoint>
## Webfuse specific options
| Option | Argument | Default | Description |
| ------------ | -------- | --------- | ----------- |
| --wf-port | port | 8081 | Specify the port of the websocket server |
| --wf-vhost | vhost | localhost | Specify the name of the websocket server's virtual host |
| --wf-cert | path | - | Optional. Specify the file path of the server's public certificate |
| --wf-key | path | - | Optional. Specify the file path of the server's private key |
| --wf-authenticator | path | - | Optional. Specify the file path of the authenticator executable |
| --wf-auth-header | name | - | Optional. Specify the name of the HTTP header used for authentication |
| --wf-docroot | path | - | Optional. Enabled HTTP server with given document root. |
| --wf-timeout | timeout | 10 | Optional. Specify the communication timeout. |
| --wf-version | - | - | Print version and exit. |
**Note:** All paths must be absolute _(this might be relaxed if future versions)_.
## Fuse options
| Option | Descripion |
| --------------------- | ---------- |
| -h, --help | print help |
| -V --version | print version |
| -d -o debug | enable debug output (implies -f) |
| -f | foreground operation |
| -s | disable multi-threaded operation |
| -o clone_fd | use separate fuse device fd for each thread |
| | (may improve performance) |
| -o max_idle_threads | the maximum number of idle worker threads |
| | allowed (default: 10) |
| -o kernel_cache | cache files in kernel |
| -o [no]auto_cache | enable caching based on modification times (off) |
| -o umask=M | set file permissions (octal) |
| -o uid=N | set file owner |
| -o gid=N | set file group |
| -o entry_timeout=T | cache timeout for names (1.0s) |
| -o negative_timeout=T | cache timeout for deleted names (0.0s) |
| -o attr_timeout=T | cache timeout for attributes (1.0s) |
| -o ac_attr_timeout=T | auto cache timeout for attributes (attr_timeout) |
| -o noforget | never forget cached inodes |
| -o remember=T | remember cached inodes for T seconds (0s) |
| -o modules=M1[:M2...] | names of modules to push onto filesystem stack |
| -o allow_other | allow access by all users |
| -o allow_root | allow access by root |
| -o auto_unmount | auto unmount on process termination |
### Options for subdir module
| Option | Descripion |
| --------------------- | ---------- |
| -o subdir=DIR | prepend this directory to all paths (mandatory) |
| -o [no]rellinks | transform absolute symlinks to relative |
### Options for iconv module
| Option | Descripion |
| --------------------- | ---------- |
| -o from_code=CHARSET | original encoding of file names (default: UTF-8) |
| -o to_code=CHARSET | new encoding of the file names (default: UTF-8) |
## Examples
- run webfuse in foreground on default port:
`webfuse -f /path/to/mointpoint`
- run webfuse in forground on port 8080:
`webfuse -f --wf-port 8080 /path/to/mountpoint`
- run webfuse using TLS:
`webfuse -f --wf-cert /path/to/cert --wf-key /path/to/key /path/to/mountpoint`
- run webfuse using authentication via `X-Auth-Token` header:
`webfuse -f --wf-authenticator /path/to/authenticator \`
` --wf-auth-header X-Auth-Token /path/to/mountpoint`

@ -0,0 +1,39 @@
# webfuse_provider command line options
Inject a remote filesystem via webfuse.
## Usage
webfuse_provider -u <url> [-p <path>] [-a <cert_path>]
## Options
| Short Option | Long Option | Argument | Description |
| ------------ | ----------- | -------- | ----------- |
| -h | --help | - | print usage and exit |
| -v | --version | - | print version an exit |
| -p | --path | path | path of local filesystem to inject (default: .) |
| -u | --url | url | url of webfuse server |
| -a | --ca-path | path | path of ca file |
| -t | --token | token | authentication token (overrides WEBFUSE_TOKEN) |
## Environment variables
| Variable | Description |
| -------- | ----------- |
| WEBFUSE_TOKEN | Default value of webfuse token |
## Examples
- inject current directory:
`webfuse_provider -u ws://localhost/`
- inject a given directory:
`webfuse_provider -u ws://localhost/ -p /path/to/directory`
- inject current directory to port 8080:
`webfuse_provider -u ws://localhost:8080/`
- inject current directory via TLS:
`webfuse_provider -u wss://localhost/`
- inject current diectory via TLS using a specific ca:
`webfuse_provider -u wss://localhost/ -a /path/to/server-cert.pem`
- inject current directory, authenticate via token:
`webfuse_provider -u wss://localhost/ -t my_token`

@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.10)
project(webfuse VERSION 2.0.0)
option(WITHOUT_CLANG_TIDY "Disables clang tidy" OFF)
set (CMAKE_CXX_STANDARD 17)
find_library(PAM pam REQUIRED)
find_library(B64 b64 REQUIRED)
add_executable(webfuse_pam_authenticator src/main.cpp)
target_link_libraries(webfuse_pam_authenticator pam b64)
install(TARGETS webfuse_pam_authenticator DESTINATION bin)
install(FILES etc/pam.d/webfuse DESTINATION /etc/pam.d)
add_executable(webfuse_pam_token_encode src/token_encode.cpp)
target_link_libraries(webfuse_pam_token_encode b64)
add_executable(webfuse_pam_token_decode src/token_decode.cpp)
target_link_libraries(webfuse_pam_token_decode b64)
if(NOT(WITHOUT_CLANG_TIDY))
set_property(
TARGET webfuse_pam_authenticator webfuse_pam_token_encode webfuse_pam_token_decode
PROPERTY CXX_CLANG_TIDY clang-tidy -checks=readability-*,-readability-identifier-length -warnings-as-errors=*)
endif()

@ -0,0 +1,31 @@
# webfuse PAM authenticator
This directory contains an example of a webfuse authenticator using PAM.
The authenticator uses `username` and `password` for authentication.
Since webfuse only provides a token, username and password are encoded as follows:
TOKEN := base64 ( USERNAME ":" PASSWORD )
Example:
USERNAME := "user"
PASSWORD := "secret"
TOKEN := base64 ( "user:secret" ) = "XNlcjpzZWNyZXQ="
The utilities `webfuse_pam_token_encode` and `webfuse_pam_token_decode` can be used
to encode and decode tokens.
## Build
cmake -b build
cmake build
## Dependencies
- libpam
- libb64
## Notes
- in order to make the authenticator work, read access to /etc/shadow is needed

@ -0,0 +1,7 @@
# Use unix authentication for webfuse authentication.
#
# NOTE:
# - in order to use unix authentication, read access to /etc/shadow is required
auth required pam_unix.so
account required pam_unix.so

@ -0,0 +1,181 @@
#include <b64/decode.h>
#include <security/pam_appl.h>
#include <syslog.h>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>
#include <sstream>
namespace
{
bool decode(std::string const & token, std::string & username, std::string & password)
{
std::istringstream encoded(token);
std::stringstream decoded;
base64::decoder decoder;
decoder.decode(encoded, decoded);
auto const plain = decoded.str();
auto const pos = plain.find(':');
bool const success = (pos != std::string::npos);
if (success)
{
username = plain.substr(0, pos);
password = plain.substr(pos + 1);
}
return success;
}
struct credentials
{
std::string username;
std::string password;
};
extern "C" int conversation(
int count,
struct pam_message const ** messages,
struct pam_response ** ret_responses,
void * user_data)
{
if (count <= 0) { return PAM_CONV_ERR; }
int result = PAM_SUCCESS;
auto * responses = reinterpret_cast<struct pam_response *>(malloc(count * sizeof(struct pam_response)));
auto * creds = reinterpret_cast<credentials*>(user_data);
for(int i = 0; (PAM_SUCCESS == result) && (i < count); i++)
{
auto * response = &responses[i];
auto const * message = messages[i];
response->resp_retcode = 0;
response->resp = nullptr;
switch(message->msg_style)
{
case PAM_PROMPT_ECHO_ON:
response->resp = strdup(creds->username.c_str());
break;
case PAM_PROMPT_ECHO_OFF:
response->resp = strdup(creds->password.c_str());
break;
case PAM_TEXT_INFO:
break;
case PAM_ERROR_MSG:
break;
default:
free(responses);
result = PAM_CONV_ERR;
break;
}
}
if (PAM_SUCCESS == result)
{
*ret_responses = responses;
}
return result;
}
bool authenticate(std::string const & username, std::string const & password)
{
credentials creds;
creds.username = username;
creds.password = password;
struct pam_conv conv;
conv.conv = &conversation;
conv.appdata_ptr = reinterpret_cast<void*>(&creds);
pam_handle_t * handle = nullptr;
bool cleanup_handle = false;
bool result = true;
{
auto const rc = pam_start("webfuse", nullptr, &conv, &handle);
result = (PAM_SUCCESS == rc);
cleanup_handle = result;
if (!result)
{
syslog(LOG_AUTH, "failed to start PAM conversation");
}
}
if (result)
{
pam_set_item(handle, PAM_USER, reinterpret_cast<void const*>(username.c_str()));
auto const rc = pam_authenticate(handle, PAM_DISALLOW_NULL_AUTHTOK);
result = (PAM_SUCCESS == rc);
}
if (result)
{
auto const rc = pam_acct_mgmt(handle, PAM_DISALLOW_NULL_AUTHTOK);
result = (PAM_SUCCESS == rc);
}
if (cleanup_handle)
{
pam_end(handle, 0);
}
return result;
}
}
int main(int argc, char* argv[])
{
int exit_code = EXIT_FAILURE;
if (argc == 1)
{
std::string token;
std::getline(std::cin, token);
openlog("webfuse_pam_auth", 0, LOG_AUTH);
std::string username;
std::string password;
auto const decode_valid = decode(token, username, password);
if (decode_valid)
{
auto const is_authenticated = authenticate(username, password);
if (is_authenticated)
{
syslog(LOG_AUTH, "authenticate user \"%s\"", username.c_str());
exit_code = EXIT_SUCCESS;
}
else
{
syslog(LOG_AUTH, "failed to authenticate user \"%s\"", username.c_str());
}
}
closelog();
}
else
{
std::cout << R"(webfuse_pam_authenticator, (c) 2023 Falk Werner
webfuse PAM authenticator
Usage:
webfuse_pam_authenticator [-h]
Options:
--help, -h print this message and exit
Credentials:
Credentials are passed as based64-encoded token via stdin:
token := base64(<username> ":" <password>)
)";
}
return exit_code;
}

@ -0,0 +1,47 @@
#include <b64/decode.h>
#include <iostream>
#include <string>
#include <sstream>
int main(int argc, char* argv[])
{
int exit_code = EXIT_FAILURE;
if (argc == 2)
{
std::istringstream token(argv[1]);
base64::decoder decoder;
std::stringstream plain;
decoder.decode(token, plain);
auto const plain_str = plain.str();
auto const pos = plain_str.find(':');
if (pos != std::string::npos)
{
auto const username = plain_str.substr(0, pos);
auto const password = plain_str.substr(pos + 1);
std::cout << "username: " << username << std::endl;
std::cout << "password: " << password << std::endl;
exit_code = EXIT_SUCCESS;
}
if (exit_code != EXIT_SUCCESS)
{
std::cerr << "error: failed to decode" << std::endl;
}
}
else
{
std::cout << R"(webfuse_pam_token_encode, (c) 2023 Falk Werner
Encode token for webfuse PAM authenticator
Usage:
webfuse_pam_token_encode <username> <password>
)";
}
return exit_code;
}

@ -0,0 +1,32 @@
#include <b64/encode.h>
#include <iostream>
#include <string>
#include <sstream>
int main(int argc, char* argv[])
{
if (argc == 3)
{
std::string const username = argv[1];
std::string const password = argv[2];
base64::encoder encoder;
std::istringstream plain(username + ':' + password);
std::stringstream token;
encoder.encode(plain, token);
std::cout << token.str();
}
else
{
std::cout << R"(webfuse_pam_token_encode, (c) 2023 Falk Werner
Encode token for webfuse PAM authenticator
Usage:
webfuse_pam_token_encode <username> <password>
)";
}
return EXIT_SUCCESS;
}

@ -0,0 +1,13 @@
#!/usr/bin/bash
read AUTH_TOKEN
if [[ "$AUTH_TOKEN" == "simple_token" ]]
then
echo "$(date): webfuse: auth granted: $AUTH_TOKEN" >> /tmp/webfuse_auth.log
else
echo "$(date): webfuse: auth denied: $AUTH_TOKEN" >> /tmp/webfuse_auth.log
exit 1
fi

@ -0,0 +1,6 @@
# Webfuse JavaScript example
mkdir -p /tmp/test
webfuse -f /tmp/test --wf-docroot .
Visit [http://localhost:8081/](http://localhost:8081/).

@ -0,0 +1,37 @@
<!doctype html>
<html>
<head>
<title>Webfuse Example</title>
<script type="module" src="js/startup.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Webfuse Example</h1>
<p>
This example provides a single file "README.md", which
contents can be altered below.
</p>
<h2>Connection</h2>
<p>
<label for="url">Url:</label>
<input type="text" id="url" value="ws://localhost:8081"/>
</p>
<p>
<label for="token">Token:</label>
<input type="text" id="token" value="" />
</p>
<p>
<input type="button" id="connect" value="Connect"/>
<span id="state">disconnected</span>
</p>
<h2>README.md</h2>
<p>
<textarea id="contents"># Webfuse
This is a sample text.
</textarea>
</p>
</body>
</html>

@ -0,0 +1,104 @@
import { BaseFileSystem, ERRNO, Mode, AccessMode, OpenFlags } from "./webfuse/webfuse.js"
class FileSystem extends BaseFileSystem {
constructor(tokenProvider, stateListener, files) {
super();
this.tokenProvider = tokenProvider;
this.stateListener = stateListener
this.files = new Map();
for(const file of files) {
this.files.set("/" + file.name, file);
}
}
access(path, mode) {
// we do not allow write or execute
if ((mode & AccessMode.W_OK) || (mode & AccessMode.X_OK)) {
return ERRNO.EACCES;
}
if ((path = "/") || (this.files.has(path))) {
return 0;
}
return ERRNO.ENOENT;
}
getattr(path) {
if (path == "/") {
return {
nlink: 2,
mode: Mode.DIR | 0o555
};
}
else if (this.files.has(path)) {
const file = this.files.get(path);
const contents = file.contents();
return {
nlink: 1,
mode: Mode.REG | 0o444,
size: contents.length
};
}
return ERRNO.ENOENT;
}
readdir(path) {
if (path == "/") {
const list = [];
for(const file of this.files.values()) {
list.push(file.name);
}
return list;
}
return ERRNO.ENOENT;
}
open(path, flags) {
if (this.files.has(path)) {
const accessMode = flags & OpenFlags.ACCESS_MODE;
if (accessMode == OpenFlags.RDONLY) {
return [0, 0];
}
else {
return [ERRNO.EPERM, 0];
}
}
return [ERRNO.ENOENT, 0];
}
read(path, size, offset, fd) {
if (this.files.has(path)) {
const file = this.files.get(path);
const contents = file.contents();
if (offset < contents.length) {
const available = contents.length - offset;
const length = (size < available) ? size : available;
const data = contents.slice(offset, offset + length);
return data;
}
return [];
}
else {
return ERRNO.EBADF;
}
}
getcreds() {
const token = this.tokenProvider();
return token;
}
connectionstatechanged(state) {
this.stateListener(state);
}
}
export { FileSystem }

@ -0,0 +1,47 @@
import { Webfuse } from "./webfuse/webfuse.js";
import { FileSystem } from "./filesystem.js";
function encode(value) {
const encoder = new TextEncoder('utf-8');
return encoder.encode(value);
}
function get_contents() {
const contentTextArea = document.querySelector("#contents");
const contents = contentTextArea.value;
return encode(contents);
}
function get_token() {
const tokenTextfield = document.querySelector('#token');
const token = tokenTextfield.value;
return token;
}
function update_state(state) {
const stateTextField = document.querySelector("#state");
stateTextField.textContent = (state == "connected") ? "connected" : "disconnected";
}
let webfuse = null;
const filesystem = new FileSystem(get_token, update_state, [
{name: "README.md", contents: get_contents }
]);
function onConnectButtonClicked() {
if (webfuse) { webfuse.close(); }
const urlTextfield = document.querySelector('#url');
const url = urlTextfield.value;
webfuse = new Webfuse(url, filesystem);
}
function startup() {
const connectButton = document.querySelector('#connect');
connectButton.addEventListener('click', onConnectButtonClicked);
}
document.addEventListener('DOMContentLoaded', startup(),false);

@ -0,0 +1,9 @@
const AccessMode = {
F_OK: 0,
R_OK: 4,
W_OK: 2,
X_OK: 1
};
export { AccessMode }

@ -0,0 +1,102 @@
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.EPERM;
}
chown(path, uid, gid) {
return ERRNO.EPERM;
}
truncate(path, size, fd) {
return ERRNO.EPERM;
}
fsync(path, isDataSync, fd) {
return 0;
}
open(path, flags) {
return [ERRNO.ENOENT, 0];
}
mknod(path, mode, rdev) {
return ERRNO.EPERM;
}
create(path, mode) {
return [ERRNO.EPERM, 0];
}
release(path, fd) {
return 0;
}
unlink(path) {
return ERRNO.EPERM;
}
read(path, size, offset, fd) {
return ERRNO.EBADF;
}
write(path, data, offset, fd) {
return ERRNO.EBADF;
}
mkdir(path, mode) {
return ERRNO.EPERM;
}
readdir(path) {
return ERRNO.ENOENT;
}
rmdir(path) {
return ERRNO.EPERM;
}
statfs(path) {
return ERRNO.ENOSYS;
}
utimens(path, atime, mtime) {
return ERRNO.ENOSYS;
}
getcreds() {
return "";
}
connectionstatechanged(state) {
// pass
}
}
export { BaseFileSystem }

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

@ -0,0 +1,58 @@
class MessageReader {
constructor(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 }

@ -0,0 +1,75 @@
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() {
return new Uint8Array(this.data);
}
}
export { MessageWriter }

@ -0,0 +1,28 @@
const OpenFlags = {
ACCESS_MODE: 0x03,
RDONLY : 0o00,
WRONLY : 0o01,
RDWR : 0o02,
APPEND : 0o00002000,
ASYNC : 0o00020000,
CLOEXEC : 0o02000000,
CREAT : 0o00000100,
DIRECT : 0o00040000,
DIRECTORY : 0o00200000,
DSYNC : 0o00010000,
EXCL : 0o00000200,
LARGEFILE : 0o00100000,
NOATIME : 0o01000000,
NOCTTY : 0o00000400,
NOFOLLOW : 0o00400000,
NONBLOCK : 0o00004000,
NDELAY : 0o00004000,
PATH : 0o10000000,
SYNC : 0o04010000,
TMPFILE : 0o20200000,
TRUNC : 0o00001000
};
export { OpenFlags }

@ -0,0 +1,309 @@
import { MessageWriter } from "./messagewriter.js";
import { MessageReader } from "./messagereader.js";
import { ERRNO } from "./errno.js";
import { AccessMode } from "./accessmode.js";
import { OpenFlags } from "./openflags.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();
const 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(result.length);
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) {
this.ws = new WebSocket(url, ["webfuse2"]);
this.ws.binaryType = 'arraybuffer';
this.ws.addEventListener('open', (event) => this.on_connected(event));
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.warn(`unknow message type: ${message_type}`);
}
this.ws.send(writer.get_data());
}
on_connected(event) {
this.filesystem.connectionstatechanged("connected");
}
on_error(event) {
console.info("connection error");
this.ws.close();
}
on_closed(event) {
this.filesystem.connectionstatechanged("closed");
}
}
export { Webfuse, BaseFileSystem, ERRNO, Mode, AccessMode, OpenFlags }

@ -0,0 +1,28 @@
html, body {
background-color: #c0c0c0;
}
h1 {
background-color: black;
color: white;
}
label {
display: inline-block;
width: 150px;
}
input {
width: 150px;
}
#stats {
display: inline-block;
text-align: right;
width: 150px;
}
textarea {
width: 300px;
height: 300px;
}

@ -0,0 +1,17 @@
[BASIC]
good-names=i,j,k,ex,Run,_,f,fd
[MESSAGES CONTROL]
disable=raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
missing-function-docstring

@ -0,0 +1,600 @@
#!/usr/bin/env python3
"""Example webfuse provider written in python."""
import asyncio
import os
import stat
import errno
import getpass
import argparse
import websockets
INVALID_FD = 0xffffffffffffffff
F_OK = 0
R_OK = 4
W_OK = 2
X_OK = 1
RESPONSE = 0x80
ERRNO = {
-errno.E2BIG : -7,
-errno.EACCES : -13,
-errno.EAGAIN : -11,
-errno.EBADF : -9,
-errno.EBUSY : -16,
-errno.EDESTADDRREQ : -89,
-errno.EDQUOT : -122,
-errno.EEXIST : -17,
-errno.EFAULT : -14,
-errno.EFBIG : -27,
-errno.EINTR : -4,
-errno.EINVAL : -22,
-errno.EIO : -5,
-errno.EISDIR : -21,
-errno.ELOOP : -40,
-errno.EMFILE : -24,
-errno.EMLINK : -31,
-errno.ENAMETOOLONG : -36,
-errno.ENFILE : -23,
-errno.ENODATA : -61,
-errno.ENODEV : -19,
-errno.ENOENT : -2,
-errno.ENOMEM : -12,
-errno.ENOSPC : -28,
-errno.ENOSYS : -38,
-errno.ENOTDIR : -20,
-errno.ENOTEMPTY : -39,
-errno.ENOTSUP : -95,
-errno.ENXIO : -6,
-errno.EOVERFLOW : -75,
-errno.EPERM : -1,
-errno.EPIPE : -32,
-errno.ERANGE : -34,
-errno.EROFS : -30,
-errno.ETXTBSY : -26,
-errno.EXDEV : -18
}
RENAME_NOREPLACE = 0x01
RENAME_EXCHANGE = 0x02
MODE_REG = 0o100000
MODE_DIR = 0o040000
MODE_CHR = 0o020000
MODE_BLK = 0o060000
MODE_FIFO = 0o010000
MODE_LNK = 0o120000
MODE_SOCK = 0o140000
O_RDONLY = 0o00
O_WRONLY = 0o01
O_RDWR = 0o02
O_APPEND = 0o00002000
O_ASYNC = 0o00020000
O_CLOEXEC = 0o02000000
O_CREAT = 0o00000100
O_DIRECT = 0o00040000
O_DIRECTORY = 0o00200000
O_DSYNC = 0o00010000
O_EXCL = 0o00000200
O_LARGEFILE = 0o00100000
O_NOATIME = 0o01000000
O_NOCTTY = 0o00000400
O_NOFOLLOW = 0o00400000
O_NONBLOCK = 0o00004000
O_NDELAY = 0o00004000
O_PATH = 0o10000000
O_SYNC = 0o04010000
O_TMPFILE = 0o20200000
O_TRUNC = 0o00001000
class MessageReader:
"""Reads webfuse messages from buffer."""
def __init__(self, buffer):
self.buffer = buffer
self.offset = 0
def read_u8(self):
value = self.buffer[self.offset]
self.offset += 1
return value
def read_bool(self):
return self.read_u8() == 1
def read_u32(self):
value = (self.buffer[self.offset] << 24) + (self.buffer[self.offset + 1] << 16) + \
(self.buffer[self.offset + 2] << 8) + self.buffer[self.offset + 3]
self.offset += 4
return value
def read_u64(self):
value = (
(self.buffer[self.offset ] << 56) +
(self.buffer[self.offset + 1] << 48) +
(self.buffer[self.offset + 2] << 40) +
(self.buffer[self.offset + 3] << 32) +
(self.buffer[self.offset + 4] << 24) +
(self.buffer[self.offset + 5] << 16) +
(self.buffer[self.offset + 6] << 8) +
self.buffer[self.offset + 7])
self.offset += 8
return value
def read_str(self):
return self.read_bytes().decode()
def read_bytes(self):
size = self.read_u32()
value = self.buffer[self.offset : self.offset + size]
self.offset += size
return value
def read_path(self, base_path):
local_path = self.read_str().lstrip('/')
return os.path.join(base_path, local_path)
def read_access_mode(self):
value = self.read_u8()
mode = os.F_OK if F_OK == (value & F_OK) else 0
mode |= os.R_OK if R_OK == (value & R_OK) else 0
mode |= os.W_OK if W_OK == (value & W_OK) else 0
mode |= os.X_OK if X_OK == (value & X_OK) else 0
return mode
def read_rename_flags(self):
return self.read_u8()
def read_mode(self):
value = self.read_u32()
mode = value & 0o7777
mode |= stat.S_IFREG if MODE_REG == (value & MODE_REG ) else 0
mode |= stat.S_IFDIR if MODE_DIR == (value & MODE_DIR ) else 0
mode |= stat.S_IFCHR if MODE_CHR == (value & MODE_CHR ) else 0
mode |= stat.S_IFBLK if MODE_BLK == (value & MODE_BLK ) else 0
mode |= stat.S_IFIFO if MODE_FIFO == (value & MODE_FIFO) else 0
mode |= stat.S_IFLNK if MODE_LNK == (value & MODE_LNK ) else 0
mode |= stat.S_IFSOCK if MODE_SOCK == (value & MODE_SOCK) else 0
return mode
def read_openflags(self):
value = self.read_u32()
flags = 0
# Access Mode
flags |= os.O_RDONLY if O_RDONLY == (value & O_RDONLY) else 0
flags |= os.O_WRONLY if O_WRONLY == (value & O_WRONLY) else 0
flags |= os.O_RDWR if O_RDWR == (value & O_RDWR ) else 0
# Flags
flags |= os.O_APPEND if O_APPEND == (value & O_APPEND ) else 0
flags |= os.O_ASYNC if O_ASYNC == (value & O_ASYNC ) else 0
flags |= os.O_CLOEXEC if O_CLOEXEC == (value & O_CLOEXEC ) else 0
flags |= os.O_CREAT if O_CREAT == (value & O_CREAT ) else 0
flags |= os.O_DIRECT if O_DIRECT == (value & O_DIRECT ) else 0
flags |= os.O_DIRECTORY if O_DIRECTORY == (value & O_DIRECTORY) else 0
flags |= os.O_DSYNC if O_DSYNC == (value & O_DSYNC ) else 0
flags |= os.O_EXCL if O_EXCL == (value & O_EXCL ) else 0
flags |= os.O_LARGEFILE if O_LARGEFILE == (value & O_LARGEFILE) else 0
flags |= os.O_NOCTTY if O_NOCTTY == (value & O_NOCTTY ) else 0
flags |= os.O_NOFOLLOW if O_NOFOLLOW == (value & O_NOFOLLOW ) else 0
flags |= os.O_NONBLOCK if O_NONBLOCK == (value & O_NONBLOCK ) else 0
flags |= os.O_NDELAY if O_NDELAY == (value & O_NDELAY ) else 0
flags |= os.O_PATH if O_PATH == (value & O_PATH ) else 0
flags |= os.O_SYNC if O_SYNC == (value & O_SYNC ) else 0
flags |= os.O_TMPFILE if O_TMPFILE == (value & O_TMPFILE ) else 0
flags |= os.O_TRUNC if O_TRUNC == (value & O_TRUNC ) else 0
return flags
class MessageWriter:
""""Writes webfuse messages into buffer."""
def __init__(self, message_id, message_type):
self.buffer = []
self.write_u32(message_id)
self.write_u8(message_type)
def write_u8(self, value):
self.buffer.append(value)
def write_u32(self, value):
self.buffer.extend([
(value >> 24) & 0xff,
(value >> 16) & 0xff,
(value >> 8) & 0xff,
value & 0xff
])
def write_u64(self, value):
self.buffer.extend([
(value >> 56) & 0xff,
(value >> 48) & 0xff,
(value >> 40) & 0xff,
(value >> 32) & 0xff,
(value >> 24) & 0xff,
(value >> 16) & 0xff,
(value >> 8) & 0xff,
value & 0xff
])
def write_i32(self, value):
self.write_u32(value & 0xffffffff)
def write_result(self, value):
if 0 > value:
value = ERRNO.get(value, value)
self.write_i32(value)
def write_str(self, value):
data = value.encode('utf-8')
self.write_bytes(data)
def write_bytes(self, value):
size = len(value)
self.write_u32(size)
self.buffer.extend(value)
def write_strings(self, values):
count = len(values)
self.write_u32(count)
for value in values:
self.write_str(value)
def get_bytes(self):
return bytearray(self.buffer)
# pylint: disable=too-many-public-methods
class FilesystemProvider:
"""Webfuse filesystem provider."""
def __init__(self, path, url, token):
self.root = os.path.abspath(path)
self.url = url
self.token = token
self.commands = {
0x01: FilesystemProvider.access,
0x02: FilesystemProvider.getattr,
0x03: FilesystemProvider.readlink,
0x04: FilesystemProvider.symlink,
0x05: FilesystemProvider.link,
0x06: FilesystemProvider.rename,
0x07: FilesystemProvider.chmod,
0x08: FilesystemProvider.chown,
0x09: FilesystemProvider.truncate,
0x0a: FilesystemProvider.fsync,
0x0b: FilesystemProvider.open,
0x0c: FilesystemProvider.mknod,
0x0d: FilesystemProvider.create,
0x0e: FilesystemProvider.release,
0x0f: FilesystemProvider.unlink,
0x10: FilesystemProvider.read,
0x11: FilesystemProvider.write,
0x12: FilesystemProvider.mkdir,
0x13: FilesystemProvider.readdir,
0x14: FilesystemProvider.rmdir,
0x15: FilesystemProvider.statfs,
0x16: FilesystemProvider.utimens,
0x17: FilesystemProvider.getcreds,
}
async def run(self):
extra_headers = [("X-Auth-Token", self.token)] if self.token != "" else []
# pylint: disable-next=no-member
async with websockets.connect(self.url, extra_headers=extra_headers) as connection:
while True:
request = await connection.recv()
reader = MessageReader(request)
message_id = reader.read_u32()
message_type = reader.read_u8()
writer = MessageWriter(message_id, RESPONSE + message_type)
if message_type in self.commands:
method = self.commands[message_type]
method(self, reader, writer)
else:
print(f"unknown message type: {message_type}")
response = writer.get_bytes()
await connection.send(response)
def access(self, reader, writer):
path = reader.read_path(self.root)
mode = reader.read_access_mode()
result = -errno.EACCES
try:
if os.access(path, mode) is True:
result = 0
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def getattr(self, reader, writer):
path = reader.read_path(self.root)
try:
attr = os.lstat(path)
except OSError as ex:
writer.write_result(-ex.errno)
return
writer.write_result(0)
writer.write_u64(attr.st_ino)
writer.write_u64(attr.st_nlink)
writer.write_u32(attr.st_mode)
writer.write_i32(attr.st_uid)
writer.write_i32(attr.st_gid)
writer.write_u64(attr.st_dev)
writer.write_u64(attr.st_size)
writer.write_u64(attr.st_blocks)
writer.write_u64(int(attr.st_atime))
writer.write_u32(attr.st_atime_ns)
writer.write_u64(int(attr.st_mtime))
writer.write_u32(attr.st_mtime_ns)
writer.write_u64(int(attr.st_ctime))
writer.write_u32(attr.st_ctime_ns)
def readlink(self, reader, writer):
path = reader.read_path(self.root)
try:
link = os.readlink(path)
except OSError as ex:
writer.write_result(-ex.errno)
return
writer.write_result(0)
writer.write_str(link)
def symlink(self, reader, writer):
source = reader.read_str()
target = reader.read_path(self.root)
result = 0
try:
os.symlink(source, target)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def link(self, reader, writer):
source = reader.read_path(self.root)
target = reader.read_path(self.root)
result = 0
try:
os.link(source, target)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def rename(self, reader, writer):
source = reader.read_path(self.root)
target = reader.read_path(self.root)
flags = reader.read_rename_flags()
result = 0
try:
if RENAME_EXCHANGE == (flags & RENAME_EXCHANGE):
# exchange is not supported
result = -errno.EINVAL
elif RENAME_NOREPLACE == (flags & RENAME_NOREPLACE):
os.rename(source, target)
else:
os.replace(source, target)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def chmod(self, reader, writer):
path = reader.read_path(self.root)
mode = reader.read_mode()
result = 0
try:
os.chmod(path, mode)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def chown(self, reader, writer):
path = reader.read_path(self.root)
uid = reader.read_u32()
gid = reader.read_u32()
result = 0
try:
os.chown(path, uid, gid)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def truncate(self, reader, writer):
path = reader.read_path(self.root)
size = reader.read_u64()
fd = reader.read_u64()
result = 0
try:
if fd != INVALID_FD:
os.ftruncate(fd, size)
else:
os.truncate(path, size)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def fsync(self, reader, writer):
_ = reader.read_path(self.root)
_ = reader.read_bool()
fd = reader.read_u64()
result = 0
try:
os.fsync(fd)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def utimens(self, reader, writer):
path = reader.read_path(self.root)
atime = reader.read_u64()
atime_ns = reader.read_u32()
mtime = reader.read_u64()
mtime_ns = reader.read_u32()
result = 0
try:
os.utime(path, (atime, mtime), ns = (atime_ns, mtime_ns))
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def open(self, reader, writer):
path = reader.read_path(self.root)
flags = reader.read_openflags()
try:
fd = os.open(path, flags)
except OSError as ex:
writer.write_result(-ex.errno)
return
writer.write_result(0)
writer.write_u64(fd)
def mknod(self, reader, writer):
path = reader.read_path(self.root)
mode = reader.read_mode()
rdev = reader.read_u64()
result = 0
try:
os.mknod(path, mode, rdev)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def create(self, reader, writer):
path = reader.read_path(self.root)
mode = reader.read_mode()
try:
flags = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
fd = os.open(path, flags, mode)
except OSError as ex:
writer.write_result(-ex.errno)
return
writer.write_result(0)
writer.write_u64(fd)
def release(self, reader, writer):
_ = reader.read_path(self.root)
fd = reader.read_u64()
result = 0
try:
os.close(fd)
except OSError as ex:
writer.write_result(-ex.errno)
return
writer.write_result(result)
def unlink(self, reader, writer):
path = reader.read_path(self.root)
result = 0
try:
os.unlink(path)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def read(self, reader, writer):
path = reader.read_path(self.root)
size = reader.read_u32()
offset = reader.read_u64()
fd = reader.read_u64()
try:
if fd != INVALID_FD:
buffer = os.pread(fd, size, offset)
else:
with os.open(path, os.O_RDONLY) as f:
buffer = os.pread(f, size, offset)
writer.write_result(len(buffer))
writer.write_bytes(buffer)
except OSError as ex:
writer.write_result(-ex.errno)
def write(self, reader, writer):
path = reader.read_path(self.root)
data = reader.read_bytes()
offset = reader.read_u64()
fd = reader.read_u64()
result = 0
try:
if fd != INVALID_FD:
result = os.pwrite(fd, data, offset)
else:
with os.open(path, os.O_WRONLY) as f:
result = os.pwrite(f, data, offset)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def mkdir(self, reader, writer):
path = reader.read_path(self.root)
mode = reader.read_u32()
result = 0
try:
os.mkdir(path, mode)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def readdir(self, reader, writer):
path = reader.read_path(self.root)
names = []
try:
with os.scandir(path) as entries:
for entry in entries:
names.append(entry.name)
except OSError as ex:
writer.write_result(-ex.errno)
return
writer.write_result(0)
writer.write_strings(names)
def rmdir(self, reader, writer):
path = reader.read_path(self.root)
result = 0
try:
os.rmdir(path)
except OSError as ex:
result = -ex.errno
writer.write_result(result)
def statfs(self, reader, writer):
path = reader.read_path(self.root)
try:
buffer = os.statvfs(path)
except OSError as ex:
writer.write_result(-ex.errno)
return
writer.write_result(0)
writer.write_u64(buffer.f_bsize)
writer.write_u64(buffer.f_frsize)
writer.write_u64(buffer.f_blocks)
writer.write_u64(buffer.f_bfree)
writer.write_u64(buffer.f_bavail)
writer.write_u64(buffer.f_files)
writer.write_u64(buffer.f_ffree)
writer.write_u64(buffer.f_namemax)
def getcreds(self, _, writer):
credentials = self.token if self.token != None and self.token != "" else getpass.getpass(prompt="credentials: ")
writer.write_str(credentials)
def main():
parser = argparse.ArgumentParser(prog='webfuse_provider')
parser.add_argument('-p', '--path', type=str, required=False, default='.',
help='path to provide (default: \".\")')
parser.add_argument('-u', '--url', type=str, required=True,
help='URL of webfuse server, e.g. \"ws://localhost:8081\"')
parser.add_argument('-t', '--token', type=str, required=False, default='',
help='authentication token (default: contents of environment variable WEBFUSE_TOKEN)')
args = parser.parse_args()
token = args.token if args.token != "" else os.environ.get('WEBFUSE_TOKEN')
provider = FilesystemProvider(args.path, args.url, token)
try:
asyncio.run(provider.run())
except KeyboardInterrupt as _:
pass
if __name__ == '__main__':
main()

@ -0,0 +1,7 @@
#!/bin/bash
openssl req -x509 -newkey rsa:4096 \
-keyout server-key.pem \
-out server-cert.pem \
-days 365 -nodes -batch \
-subj /CN=localhost

@ -1,6 +1,7 @@
#include <iostream>
#include "webfuse/webfuse.hpp"
int main(int argc, char* argv[])
{
return EXIT_SUCCESS;
}
webfuse::app app;
return app.run(argc, argv);
}

@ -0,0 +1,511 @@
#include "webfuse/provider.hpp"
#include "webfuse/version.hpp"
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <getopt.h>
#include <csignal>
#include <cstdlib>
#include <iostream>
namespace
{
enum class command
{
run,
show_help,
show_version
};
class context
{
public:
context(int argc, char* argv[])
: base_path(".")
, url("")
, cmd(command::run)
, exit_code()
{
char const * const webfuse_token = getenv("WEBFUSE_TOKEN");
if (nullptr != webfuse_token)
{
token = webfuse_token;
}
struct option const long_options[] =
{
{"path" , required_argument, nullptr, 'p'},
{"url" , required_argument, nullptr, 'u'},
{"ca-path", required_argument, nullptr, 'a'},
{"token" , required_argument, nullptr, 't'},
{"version", no_argument , nullptr, 'v'},
{"help" , no_argument , nullptr, 'h'},
{nullptr , 0 , nullptr, 0 }
};
optind = 0;
opterr = 0;
bool finished = false;
while (!finished)
{
int option_index = 0;
const int c = getopt_long(argc, argv, "p:u:a:t:vh", long_options, &option_index);
switch (c)
{
case -1:
finished = true;
break;
case 'p':
base_path = optarg;
break;
case 'u':
url = optarg;
break;
case 'a':
ca_path = optarg;
break;
case 't':
token = optarg;
break;
case 'h':
cmd = command::show_help;
break;
case 'v':
cmd = command::show_version;
break;
default:
std::cerr << "error: unknown option" << std::endl;
cmd = command::show_help;
exit_code = EXIT_FAILURE;
finished = true;
break;
}
}
if ((cmd == command::run) && (url.empty()))
{
std::cerr << "error: missing url" << std::endl;
cmd = command::show_help;
exit_code = EXIT_FAILURE;
}
}
std::string base_path;
std::string url;
std::string ca_path;
std::string token;
command cmd;
int exit_code;
};
void print_usage()
{
std::cout << R"(webfuse2 provider, (c) 2022 by Falk Werner
expose a local directory via webfuse2
Usage:
webfuse_provider -u <url> [-p <path>] [-a <ca_path>]
Options:
--url, -u set url of webfuse2 service
--path, -p set path of directory to expose (default: .)
--ca-path, -a set path of ca file (default: not set)
--token, -t set authentication token (default: see below)
--version, -v print version and quit
--help, -h print this message and quit
Authentication Token:
When requested by webfuse server, the authentication token
is determined as follows:
- if present, use contents of environment variable WEBFUSE_TOKEN
- else if specified, use the contents fo the -t option
- else query user for authentication token
Examples:
webfuse-provider -u ws://localhost:8080/
webfuse-provider -u ws://localhost:8080/ -p /some/directory
)";
}
void print_version()
{
std::cout << webfuse::get_version() << std::endl;
}
static bool shutdown_requested = false;
void on_signal(int _)
{
(void) _;
shutdown_requested = true;
}
class filesystem: public webfuse::filesystem_i
{
public:
filesystem(std::string const & base_path, std::string const & token)
: token_(token)
{
char buffer[PATH_MAX];
char * resolved_path = ::realpath(base_path.c_str(), buffer);
if (nullptr == resolved_path)
{
throw std::runtime_error("failed to resolve path");
}
struct stat info;
int const rc = stat(resolved_path, &info);
if (!S_ISDIR(info.st_mode))
{
throw std::runtime_error("path is not a directory");
}
base_path_ = resolved_path;
}
~filesystem() override
{
}
int access(std::string const & path, int mode) override
{
auto const full_path = get_full_path(path);
auto const result = ::access(full_path.c_str(), mode);
return (result == 0) ? 0 : -errno;
}
int getattr(std::string const & path, struct stat * attr) override
{
auto const full_path = get_full_path(path);
auto const result = lstat(full_path.c_str(), attr);
return (result == 0) ? 0 : -errno;
}
int readlink(std::string const & path, std::string & out) override
{
auto const full_path = get_full_path(path);
char buffer[PATH_MAX];
int const result = ::readlink(full_path.c_str(), buffer, PATH_MAX);
if ((0 <= result) && (result < PATH_MAX))
{
buffer[result] = '\0';
out = buffer;
}
return (result >= 0) ? 0 : -errno;
}
int symlink(std::string const & from, std::string const & to) override
{
auto const full_from = ('/' == from.at(0)) ? get_full_path(from) : from;
auto const full_to = get_full_path(to);
int const result = ::symlink(full_from.c_str(), full_to.c_str());
return (result == 0) ? 0 : -errno;
}
int link(std::string const & old_path, std::string const & new_path) override
{
auto const from = get_full_path(old_path);
auto const to = get_full_path(new_path);
int const result = ::link(from.c_str(), to.c_str());
return (result == 0) ? 0 : -errno;
}
int rename(std::string const & old_path, std::string const & new_path, int flags) override
{
auto const full_old = get_full_path(old_path);
auto const full_new = get_full_path(new_path);
int const result = ::renameat2(-1, full_old.c_str(), -1, full_new.c_str(), flags);
return (result == 0) ? 0 : -errno;
}
int chmod(std::string const & path, mode_t mode) override
{
auto const full_path = get_full_path(path);
int const result = ::chmod(full_path.c_str(), mode);
return (result == 0) ? 0 : -errno;
}
int chown(std::string const & path, uid_t uid, gid_t gid) override
{
auto const full_path = get_full_path(path);
int const result = ::chown(full_path.c_str(), uid, gid);
return (result == 0) ? 0 : -errno;
}
int truncate(std::string const & path, uint64_t size, uint64_t handle) override
{
auto const full_path = get_full_path(path);
int result = 0;
if (handle == webfuse::invalid_handle)
{
result = ::truncate(full_path.c_str(), size);
}
else
{
result = ::ftruncate(static_cast<int>(handle), size);
}
return (result == 0) ? 0 : -errno;
}
int fsync(std::string const & path, bool is_datasync, uint64_t handle) override
{
int result = 0;
if (handle != webfuse::invalid_handle)
{
if (!is_datasync)
{
result = ::fsync(static_cast<int>(handle));
}
else
{
result = ::fdatasync(static_cast<int>(handle));
}
}
// we do not sync files, which are not open
return (result == 0) ? 0 : -errno;
}
int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) override
{
int result = 0;
if (handle == webfuse::invalid_handle)
{
auto const full_path = get_full_path(path);
result = ::utimensat(-1, full_path.c_str(), tv, 0);
}
else
{
result = ::futimens(static_cast<int>(handle), tv);
}
return (result == 0) ? 0 : -errno;
}
int open(std::string const & path, int flags, uint64_t & handle) override
{
auto const full_path = get_full_path(path);
int const fd = ::open(full_path.c_str(), flags);
if (0 <= fd)
{
handle = static_cast<int>(fd);
}
return (0 <= fd) ? 0 : -errno;
}
int mknod(std::string const & path, mode_t mode, dev_t rdev) override
{
auto const full_path = get_full_path(path);
int const result = ::mknod(full_path.c_str(), mode, rdev);
return (result == 0) ? 0 : -errno;
}
int create(std::string const & path, mode_t mode, uint64_t & handle) override
{
auto const full_path = get_full_path(path);
int const fd = ::creat(full_path.c_str(), mode);
if (0 <= fd)
{
handle = static_cast<int>(fd);
}
return (0 <= fd) ? 0 : -errno;
}
int release(std::string const & path, uint64_t handle) override
{
int result = 0;
if (handle != webfuse::invalid_handle)
{
result = ::close(static_cast<int>(handle));
}
return (result == 0) ? 0 : -errno;
}
int unlink(std::string const & path) override
{
auto const full_path = get_full_path(path);
int const result = ::unlink(full_path.c_str());
return (result == 0) ? 0 : -errno;
}
int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override
{
int result = -1;
if (handle != webfuse::invalid_handle)
{
auto const full_path = get_full_path(path);
int fd = ::open(full_path.c_str(), O_RDONLY);
if (0 <= fd)
{
result = ::pread(fd, buffer, buffer_size, offset);
::close(fd);
}
}
else
{
result = ::pread(static_cast<int>(handle), buffer, buffer_size, offset);
}
return (result >= 0) ? result : -errno;
}
int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override
{
int result = -1;
if (handle == webfuse::invalid_handle)
{
auto const full_path = get_full_path(path);
int fd = ::open(full_path.c_str(), O_WRONLY);
if (0 <= fd)
{
result = ::pwrite(fd, buffer, buffer_size, offset);
::close(fd);
}
}
else
{
result = ::pwrite(static_cast<int>(handle), buffer, buffer_size, offset);
}
return (result >= 0) ? result : -errno;
}
int mkdir(std::string const & path, mode_t mode) override
{
auto const full_path = get_full_path(path);
int const result = ::mkdir(full_path.c_str(), mode);
return (result == 0) ? 0 : -errno;
}
int readdir(std::string const & path, std::vector<std::string> & entries) override
{
auto const full_path = get_full_path(path);
int result = 0;
DIR * directory = opendir(full_path.c_str());
if (NULL != directory)
{
dirent * entry = ::readdir(directory);
while (entry != nullptr)
{
entries.push_back(std::string(entry->d_name));
entry = ::readdir(directory);
}
closedir(directory);
}
else
{
result = -errno;
}
return result;
}
int rmdir(std::string const & path) override
{
auto const full_path = get_full_path(path);
int const result = ::rmdir(full_path.c_str());
return (result == 0) ? 0 : -errno;
}
int statfs(std::string const & path, struct statvfs * statistics) override
{
auto const full_path = get_full_path(path);
int const result = ::statvfs(full_path.c_str(), statistics);
return (result == 0) ? 0 : -errno;
}
std::string get_credentials() override
{
return (!token_.empty()) ? token_ : getpass("credentials: ");
}
private:
std::string get_full_path(std::string const & path)
{
return base_path_ + path;
}
std::string base_path_;
std::string token_;
};
}
int main(int argc, char* argv[])
{
context ctx(argc, argv);
switch (ctx.cmd)
{
case command::run:
try
{
signal(SIGINT, &on_signal);
signal(SIGTERM, &on_signal);
filesystem fs(ctx.base_path, ctx.token);
webfuse::provider provider(fs, ctx.ca_path);
provider.set_connection_listener([](bool connected) {
if (!connected)
{
shutdown_requested = true;
}
});
provider.connect(ctx.url);
while (!shutdown_requested)
{
provider.service();
}
}
catch (std::exception const & ex)
{
std::cerr << "error: " << ex.what() << std::endl;
ctx.exit_code = EXIT_FAILURE;
}
catch (...)
{
std::cerr << "error: unspecified error" << std::endl;
ctx.exit_code = EXIT_FAILURE;
}
break;
case command::show_version:
print_version();
break;
case command::show_help:
// fall-through
default:
print_usage();
break;
}
return ctx.exit_code;
}

@ -0,0 +1,429 @@
#include "webfuse/filesystem.hpp"
#include <errno.h>
#include <cstring>
#include <stdexcept>
namespace webfuse
{
filesystem::filesystem(ws_server& server)
: proxy(server)
{
}
filesystem::~filesystem()
{
}
int filesystem::access(std::string const & path, int mode)
{
try
{
messagewriter req(request_type::access);
req.write_str(path);
req.write_access_mode(mode);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.access(path, mode);
}
}
int filesystem::getattr(std::string const & path, struct stat * attr)
{
try
{
messagewriter req(request_type::getattr);
req.write_str(path);
auto reader = proxy.perform(std::move(req));
int const result = reader.read_result();
if (0 == result)
{
reader.read_attr(attr);
}
return result;
}
catch(...)
{
return fallback.getattr(path, attr);
}
}
int filesystem::readlink(std::string const & path, std::string & out)
{
try
{
messagewriter req(request_type::readlink);
req.write_str(path);
auto reader = proxy.perform(std::move(req));
int const result = reader.read_result();
if (0 == result)
{
out = reader.read_str();
}
return result;
}
catch(...)
{
return fallback.readlink(path, out);
}
}
int filesystem::symlink(std::string const & target, std::string const & linkpath)
{
try
{
messagewriter req(request_type::symlink);
req.write_str(target);
req.write_str(linkpath);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.symlink(target, linkpath);
}
}
int filesystem::link(std::string const & old_path, std::string const & new_path)
{
try
{
messagewriter req(request_type::link);
req.write_str(old_path);
req.write_str(new_path);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.link(old_path, new_path);
}
}
int filesystem::rename(std::string const & old_path, std::string const & new_path, int flags)
{
try
{
messagewriter req(request_type::rename);
req.write_str(old_path);
req.write_str(new_path);
req.write_rename_flags(flags);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.rename(old_path, new_path, flags);
}
}
int filesystem::chmod(std::string const & path, mode_t mode)
{
try
{
messagewriter req(request_type::chmod);
req.write_str(path);
req.write_mode(mode);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.chmod(path, mode);
}
}
int filesystem::chown(std::string const & path, uid_t uid, gid_t gid)
{
try
{
messagewriter req(request_type::chown);
req.write_str(path);
req.write_uid(uid);
req.write_gid(gid);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.chown(path, uid, gid);
}
}
int filesystem::truncate(std::string const & path, uint64_t size, uint64_t handle)
{
try
{
messagewriter req(request_type::truncate);
req.write_str(path);
req.write_u64(size);
req.write_u64(handle);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.truncate(path, size, handle);
}
}
int filesystem::fsync(std::string const & path, bool is_datasync, uint64_t handle)
{
try
{
messagewriter req(request_type::fsync);
req.write_str(path);
req.write_bool(is_datasync);
req.write_u64(handle);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.fsync(path, is_datasync, handle);
}
}
int filesystem::utimens(std::string const &path, struct timespec const tv[2], uint64_t handle)
{
try
{
messagewriter req(request_type::utimens);
req.write_str(path);
req.write_time(tv[0]);
req.write_time(tv[1]);
req.write_u64(handle);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.utimens(path, tv, handle);
}
}
int filesystem::open(std::string const & path, int flags, uint64_t & handle)
{
try
{
messagewriter req(request_type::open);
req.write_str(path);
req.write_openflags(flags);
auto reader = proxy.perform(std::move(req));
int result = reader.read_result();
if (0 == result)
{
handle = reader.read_u64();
}
return result;
}
catch(...)
{
return fallback.open(path, flags, handle);
}
}
int filesystem::mknod(std::string const & path, mode_t mode, dev_t rdev)
{
try
{
messagewriter req(request_type::mknod);
req.write_str(path);
req.write_mode(mode);
req.write_u64(rdev);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.mknod(path, mode, rdev);
}
}
int filesystem::create(std::string const & path, mode_t mode, uint64_t & handle)
{
try
{
messagewriter req(request_type::create);
req.write_str(path);
req.write_mode(mode);
auto reader = proxy.perform(std::move(req));
int result = reader.read_result();
if (0 == result)
{
handle = reader.read_u64();
}
return result;
}
catch(...)
{
return fallback.create(path, mode, handle);
}
}
int filesystem::release(std::string const & path, uint64_t handle)
{
try
{
messagewriter req(request_type::release);
req.write_str(path);
req.write_u64(handle);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.release(path, handle);
}
}
int filesystem::unlink(std::string const & path)
{
try
{
messagewriter req(request_type::unlink);
req.write_str(path);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.unlink(path);
}
}
int filesystem::read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle)
{
try
{
messagewriter req(request_type::read);
req.write_str(path);
req.write_u32(buffer_size);
req.write_u64(offset);
req.write_u64(handle);
auto reader = proxy.perform(std::move(req));
int result = reader.read_result();
if (result > 0)
{
std::string data = reader.read_bytes();
if (data.size() <= buffer_size)
{
memcpy(reinterpret_cast<void*>(buffer), reinterpret_cast<void const*>(data.data()), data.size());
}
else
{
throw std::runtime_error("invalid message");
}
}
return result;
}
catch(...)
{
return fallback.read(path, buffer, buffer_size, offset, handle);
}
}
int filesystem::write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle)
{
try
{
messagewriter req(request_type::write);
req.write_str(path);
req.write_data(buffer, buffer_size);
req.write_u64(offset);
req.write_u64(handle);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.write(path, buffer, buffer_size, offset, handle);
}
}
int filesystem::mkdir(std::string const & path, mode_t mode)
{
try
{
messagewriter req(request_type::mkdir);
req.write_str(path);
req.write_mode(mode);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.mkdir(path, mode);
}
}
int filesystem::readdir(std::string const & path, std::vector<std::string> & entries)
{
try
{
messagewriter req(request_type::readdir);
req.write_str(path);
auto resp = proxy.perform(std::move(req));
int result = resp.read_result();
if (0 == result)
{
resp.read_strings(entries);
}
return result;
}
catch(...)
{
return fallback.readdir(path, entries);
}
}
int filesystem::rmdir(std::string const & path)
{
try
{
messagewriter req(request_type::rmdir);
req.write_str(path);
auto reader = proxy.perform(std::move(req));
return reader.read_result();
}
catch(...)
{
return fallback.rmdir(path);
}
}
int filesystem::statfs(std::string const & path, struct statvfs * statistics)
{
try
{
messagewriter req(request_type::statfs);
req.write_str(path);
auto reader = proxy.perform(std::move(req));
int result = reader.read_result();
if (0 == result)
{
reader.read_statistics(statistics);
}
return result;
}
catch(...)
{
return fallback.statfs(path, statistics);
}
}
// get credentials is handled internally
std::string filesystem::get_credentials()
{
throw std::runtime_error("not implemented");
}
}

@ -0,0 +1,59 @@
#ifndef WEBFUSE_FILESYSTEM_HPP
#define WEBFUSE_FILESYSTEM_HPP
#include "webfuse/filesystem/filesystem_i.hpp"
#include "webfuse/filesystem/empty_filesystem.hpp"
#include "webfuse/ws/server.hpp"
namespace webfuse
{
class filesystem: public filesystem_i
{
filesystem(filesystem const &) = delete;
filesystem& operator=(filesystem const &) = delete;
filesystem(filesystem &&) = delete;
filesystem& operator=(filesystem &&) = delete;
public:
explicit filesystem(ws_server& server);
~filesystem() override;
int access(std::string const & path, int mode) override;
int getattr(std::string const & path, struct stat * attr) override;
int readlink(std::string const & path, std::string & out) override;
int symlink(std::string const & target, std::string const & linkpath) override;
int link(std::string const & old_path, std::string const & new_path) override;
int rename(std::string const & old_path, std::string const & new_path, int flags) override;
int chmod(std::string const & path, mode_t mode) override;
int chown(std::string const & path, uid_t uid, gid_t gid) override;
int truncate(std::string const & path, uint64_t size, uint64_t handle) override;
int fsync(std::string const & path, bool is_datasync, uint64_t handle) override;
int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) override;
int open(std::string const & path, int flags, uint64_t & handle) override;
int mknod(std::string const & path, mode_t mode, dev_t rdev) override;
int create(std::string const & path, mode_t mode, uint64_t & handle) override;
int release(std::string const & path, uint64_t handle) override;
int unlink(std::string const & path) override;
int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override;
int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override;
int mkdir(std::string const & path, mode_t mode) override;
int readdir(std::string const & path, std::vector<std::string> & entries) override;
int rmdir(std::string const & path) override;
int statfs(std::string const & path, struct statvfs * statistics) override;
std::string get_credentials() override;
private:
ws_server &proxy;
empty_filesystem fallback;
};
}
#endif

@ -0,0 +1,41 @@
#include "webfuse/filesystem/accessmode.hpp"
#include <unistd.h>
namespace webfuse
{
access_mode::access_mode(int8_t value)
: value_(value)
{
}
access_mode::operator int8_t() const
{
return value_;
}
access_mode access_mode::from_int(int value)
{
int8_t result = 0;
if (0 != (value & R_OK)) { result |= r_ok; }
if (0 != (value & W_OK)) { result |= w_ok; }
if (0 != (value & X_OK)) { result |= x_ok; }
return access_mode(result);
}
int access_mode::to_int() const
{
int result = 0;
if (0 != (value_ & r_ok)) { result |= R_OK; }
if (0 != (value_ & w_ok)) { result |= W_OK; }
if (0 != (value_ & x_ok)) { result |= X_OK; }
return result;
}
}

@ -0,0 +1,28 @@
#ifndef WEBFUSE_ACCESSMODE_HPP
#define WEBFUSE_ACCESSMODE_HPP
#include <cinttypes>
namespace webfuse
{
class access_mode
{
public:
static constexpr int8_t const f_ok = 0; // F_OK
static constexpr int8_t const r_ok = 4; // R_OK
static constexpr int8_t const w_ok = 2; // W_OK
static constexpr int8_t const x_ok = 1; // X_OK
access_mode(int8_t value = f_ok);
operator int8_t() const;
static access_mode from_int(int value);
int to_int() const;
private:
int8_t value_;
};
}
#endif

@ -0,0 +1,140 @@
#include "webfuse/filesystem/empty_filesystem.hpp"
#include <errno.h>
namespace webfuse
{
int empty_filesystem::access(std::string const & path, int mode)
{
if (path == "/")
{
return 0;
}
return -ENOENT;
}
int empty_filesystem::getattr(std::string const & path, struct stat * attr)
{
if (path == "/")
{
attr->st_ino = 1;
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0555; // NOLINT(readability-magic-numbers)
return 0;
}
return -ENOENT;
}
int empty_filesystem::readlink(std::string const & path, std::string & out)
{
return -ENOENT;
}
int empty_filesystem::symlink(std::string const & target, std::string const & linkpath)
{
return -ENOENT;
}
int empty_filesystem::link(std::string const & old_path, std::string const & new_path)
{
return -ENOENT;
}
int empty_filesystem::rename(std::string const & old_path, std::string const & new_path, int flags)
{
return -ENOENT;
}
int empty_filesystem::chmod(std::string const & path, mode_t mode)
{
return -EPERM;
}
int empty_filesystem::chown(std::string const & path, uid_t uid, gid_t gid)
{
return -EPERM;
}
int empty_filesystem::truncate(std::string const & path, uint64_t size, uint64_t handle)
{
return -EPERM;
}
int empty_filesystem::fsync(std::string const & path, bool is_datasync, uint64_t handle)
{
return 0;
}
int empty_filesystem::utimens(std::string const &path, struct timespec const tv[2], uint64_t handle)
{
return -ENOSYS;
}
int empty_filesystem::open(std::string const & path, int flags, uint64_t & handle)
{
return -ENOENT;
}
int empty_filesystem::mknod(std::string const & path, mode_t mode, dev_t rdev)
{
return -EPERM;
}
int empty_filesystem::create(std::string const & path, mode_t mode, uint64_t & handle)
{
return -EPERM;
}
int empty_filesystem::release(std::string const & path, uint64_t handle)
{
return 0;
}
int empty_filesystem::unlink(std::string const & path)
{
return -EPERM;
}
int empty_filesystem::read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle)
{
return -EBADF;
}
int empty_filesystem::write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle)
{
return -EBADF;
}
int empty_filesystem::mkdir(std::string const & path, mode_t mode)
{
return -EPERM;
}
int empty_filesystem::readdir(std::string const & path, std::vector<std::string> & entries)
{
if (path == "/")
{
return 0;
}
return -ENOENT;
}
int empty_filesystem::rmdir(std::string const & path)
{
return -EPERM;
}
int empty_filesystem::statfs(std::string const & path, struct statvfs * statistics)
{
return -ENOSYS;
}
std::string empty_filesystem::get_credentials()
{
return "";
}
}

@ -0,0 +1,48 @@
#ifndef WEBFUSE_EMPTYFILESYSTEM_HPP
#define WEBFUSE_EMPTYFILESYSTEM_HPP
#include "webfuse/filesystem/filesystem_i.hpp"
namespace webfuse
{
class empty_filesystem: public filesystem_i
{
public:
~empty_filesystem() override = default;
int access(std::string const & path, int mode) override;
int getattr(std::string const & path, struct stat * attr) override;
int readlink(std::string const & path, std::string & out) override;
int symlink(std::string const & target, std::string const & linkpath) override;
int link(std::string const & old_path, std::string const & new_path) override;
int rename(std::string const & old_path, std::string const & new_path, int flags) override;
int chmod(std::string const & path, mode_t mode) override;
int chown(std::string const & path, uid_t uid, gid_t gid) override;
int truncate(std::string const & path, uint64_t size, uint64_t handle) override;
int fsync(std::string const & path, bool is_datasync, uint64_t handle) override;
int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) override;
int open(std::string const & path, int flags, uint64_t & handle) override;
int mknod(std::string const & path, mode_t mode, dev_t rdev) override;
int create(std::string const & path, mode_t mode, uint64_t & handle) override;
int release(std::string const & path, uint64_t handle) override;
int unlink(std::string const & path) override;
int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override;
int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) override;
int mkdir(std::string const & path, mode_t mode) override;
int readdir(std::string const & path, std::vector<std::string> & entries) override;
int rmdir(std::string const & path) override;
int statfs(std::string const & path, struct statvfs * statistics) override;
std::string get_credentials() override;
};
}
#endif

@ -0,0 +1,50 @@
#include "webfuse/filesystem/filemode.hpp"
#include <sys/stat.h>
namespace webfuse
{
filemode::filemode(uint32_t value)
: value_(value)
{
}
filemode::operator uint32_t() const
{
return value_;
}
filemode filemode::from_mode(mode_t value)
{
uint32_t result = value & 07777; // NOLINT(readability-magic-numbers)
if (S_ISREG(value) ) { result |= filemode::reg; }
if (S_ISDIR(value) ) { result |= filemode::dir; }
if (S_ISCHR(value) ) { result |= filemode::chr; }
if (S_ISBLK(value) ) { result |= filemode::blk; }
if (S_ISFIFO(value)) { result |= filemode::fifo; }
if (S_ISLNK(value) ) { result |= filemode::lnk; }
if (S_ISSOCK(value)) { result |= filemode::sock; }
return filemode(result);
}
mode_t filemode::to_mode() const
{
mode_t result = value_ & 07777; // NOLINT(readability-magic-numbers)
if (is_reg() ) { result |= S_IFREG; }
if (is_dir() ) { result |= S_IFDIR; }
if (is_chr() ) { result |= S_IFCHR; }
if (is_blk() ) { result |= S_IFBLK; }
if (is_fifo()) { result |= S_IFIFO; }
if (is_lnk() ) { result |= S_IFLNK; }
if (is_sock()) { result |= S_IFSOCK; }
return result;
}
}

@ -0,0 +1,75 @@
#ifndef WEBFUSE_FILEMODE_HPP
#define WEBFUSE_FILEMODE_HPP
#include <fcntl.h>
#include <cinttypes>
namespace webfuse
{
class filemode
{
public:
static constexpr uint32_t const protection_mask = 0000777;
static constexpr uint32_t const sticky_mask = 0007000;
static constexpr uint32_t const filetype_mask = 0170000;
static constexpr uint32_t const suid = 04000; // S_ISUID
static constexpr uint32_t const sgid = 02000; // S_ISGID
static constexpr uint32_t const svtx = 01000; // S_ISVTX
static constexpr uint32_t const reg = 0100000; // S_IFREG
static constexpr uint32_t const dir = 0040000; // S_IFDIR
static constexpr uint32_t const chr = 0020000; // S_IFCHR
static constexpr uint32_t const blk = 0060000; // S_IFBLK
static constexpr uint32_t const fifo = 0010000; // S_IFIFO
static constexpr uint32_t const lnk = 0120000; // S_IFLNK
static constexpr uint32_t const sock = 0140000; // S_IFSOCK
explicit filemode(uint32_t value = 0);
operator uint32_t() const;
static filemode from_mode(mode_t value);
mode_t to_mode() const;
inline bool is_reg() const
{
return (filemode::reg == (value_ & filemode::filetype_mask));
}
inline bool is_dir() const
{
return (filemode::dir == (value_ & filemode::filetype_mask));
}
inline bool is_chr() const
{
return (filemode::chr == (value_ & filemode::filetype_mask));
}
inline bool is_blk() const
{
return (filemode::blk == (value_ & filemode::filetype_mask));
}
inline bool is_fifo() const
{
return (filemode::fifo == (value_ & filemode::filetype_mask));
}
inline bool is_lnk() const
{
return (filemode::lnk == (value_ & filemode::filetype_mask));
}
inline bool is_sock() const
{
return (filemode::sock == (value_ & filemode::filetype_mask));
}
private:
uint32_t value_;
};
}
#endif

@ -0,0 +1,57 @@
#ifndef WEBFUSE_FILESYSTEM_I_HPP
#define WEBFUSE_FILESYSTEM_I_HPP
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <unistd.h>
#include <cinttypes>
#include <string>
#include <vector>
namespace webfuse
{
constexpr uint64_t const invalid_handle = static_cast<uint64_t>(-1);
class filesystem_i
{
public:
virtual ~filesystem_i() = default;
virtual int access(std::string const & path, int mode) = 0;
virtual int getattr(std::string const & path, struct stat * attr) = 0;
virtual int readlink(std::string const & path, std::string & out) = 0;
virtual int symlink(std::string const & target, std::string const & linkpath) = 0;
virtual int link(std::string const & old_path, std::string const & new_path) = 0;
virtual int rename(std::string const & old_path, std::string const & new_path, int flags) = 0;
virtual int chmod(std::string const & path, mode_t mode) = 0;
virtual int chown(std::string const & path, uid_t uid, gid_t gid) = 0;
virtual int truncate(std::string const & path, uint64_t size, uint64_t handle) = 0;
virtual int fsync(std::string const & path, bool is_datasync, uint64_t handle) = 0;
virtual int utimens(std::string const &path, struct timespec const tv[2], uint64_t handle) = 0;
virtual int open(std::string const & path, int flags, uint64_t & handle) = 0;
virtual int mknod(std::string const & path, mode_t mode, dev_t rdev) = 0;
virtual int create(std::string const & path, mode_t mode, uint64_t & handle) = 0;
virtual int release(std::string const & path, uint64_t handle) = 0;
virtual int unlink(std::string const & path) = 0;
virtual int read(std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) = 0;
virtual int write(std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle) = 0;
virtual int mkdir(std::string const & path, mode_t mode) = 0;
virtual int readdir(std::string const & path, std::vector<std::string> & entries) = 0;
virtual int rmdir(std::string const & path) = 0;
virtual int statfs(std::string const & path, struct statvfs * statistics) = 0;
virtual std::string get_credentials() = 0;
};
}
#endif

@ -0,0 +1,43 @@
#include "webfuse/filesystem/filesystem_statistics.hpp"
namespace webfuse
{
filesystem_statistics::filesystem_statistics()
: bsize(0)
, frsize(0)
, blocks(0)
, bfree(0)
, bavail(0)
, files(0)
, ffree(0)
, f_namemax(0)
{
}
filesystem_statistics::filesystem_statistics(struct statvfs const & other)
{
bsize = other.f_bsize;
frsize = other.f_frsize;
blocks = other.f_blocks;
bfree = other.f_bfree;
bavail = other.f_bavail;
files = other.f_files;
ffree = other.f_ffree;
f_namemax = other.f_namemax;
}
void filesystem_statistics::copy_to(struct statvfs & other) const
{
other.f_bsize = bsize;
other.f_frsize = frsize;
other.f_blocks = blocks;
other.f_bfree = bfree;
other.f_bavail = bavail;
other.f_files = files;
other.f_ffree = ffree;
other.f_namemax = f_namemax;
}
}

@ -0,0 +1,31 @@
#ifndef WEBFUSE_FILESYSTEMSTATISTICS_HPP
#define WEBFUSE_FILESYSTEMSTATISTICS_HPP
#include <sys/statvfs.h>
#include <cinttypes>
namespace webfuse
{
class filesystem_statistics
{
public:
filesystem_statistics();
explicit filesystem_statistics(struct statvfs const & other);
void copy_to(struct statvfs & other) const;
uint64_t bsize;
uint64_t frsize;
uint64_t blocks;
uint64_t bfree;
uint64_t bavail;
uint64_t files;
uint64_t ffree;
uint64_t f_namemax;
};
}
#endif

@ -0,0 +1,79 @@
#include "webfuse/filesystem/openflags.hpp"
#include <fcntl.h>
namespace webfuse
{
openflags::openflags(int32_t value)
: value_(value)
{
}
openflags::operator int32_t() const
{
return value_;
}
openflags openflags::from_int(int value)
{
int32_t result = 0;
if (O_RDONLY == (value & O_RDONLY )) { result |= openflags::rdonly; }
if (O_WRONLY == (value & O_WRONLY )) { result |= openflags::wronly; }
if (O_RDWR == (value & O_RDWR )) { result |= openflags::rdwr; }
if (O_APPEND == (value & O_APPEND )) { result |= openflags::append; }
if (O_ASYNC == (value & O_ASYNC )) { result |= openflags::async; }
if (O_CLOEXEC == (value & O_CLOEXEC )) { result |= openflags::cloexec; }
if (O_CREAT == (value & O_CREAT )) { result |= openflags::creat; }
if (O_DIRECT == (value & O_DIRECT )) { result |= openflags::direct; }
if (O_DIRECTORY == (value & O_DIRECTORY)) { result |= openflags::directory; }
if (O_DSYNC == (value & O_DSYNC )) { result |= openflags::dsync; }
if (O_EXCL == (value & O_EXCL )) { result |= openflags::excl; }
if (O_LARGEFILE == (value & O_LARGEFILE)) { result |= openflags::largefile; }
if (O_NOATIME == (value & O_NOATIME )) { result |= openflags::noatime; }
if (O_NOCTTY == (value & O_NOCTTY )) { result |= openflags::noctty; }
if (O_NOFOLLOW == (value & O_NOFOLLOW )) { result |= openflags::nofollow; }
if (O_NONBLOCK == (value & O_NONBLOCK )) { result |= openflags::nonblock; }
if (O_NDELAY == (value & O_NDELAY )) { result |= openflags::ndelay; }
if (O_PATH == (value & O_PATH )) { result |= openflags::path; }
if (O_SYNC == (value & O_SYNC )) { result |= openflags::sync; }
if (O_TMPFILE == (value & O_TMPFILE )) { result |= openflags::tmpfile; }
if (O_TRUNC == (value & O_TRUNC )) { result |= openflags::trunc; }
return openflags(result);
}
int openflags::to_int() const
{
int result = 0;
if (openflags::rdonly == (value_ & openflags::rdonly )) { result |= O_RDONLY; }
if (openflags::wronly == (value_ & openflags::wronly )) { result |= O_WRONLY; }
if (openflags::rdwr == (value_ & openflags::rdwr )) { result |= O_RDWR; }
if (openflags::append == (value_ & openflags::append )) { result |= O_APPEND; }
if (openflags::async == (value_ & openflags::async )) { result |= O_ASYNC; }
if (openflags::cloexec == (value_ & openflags::cloexec )) { result |= O_CLOEXEC; }
if (openflags::creat == (value_ & openflags::creat )) { result |= O_CREAT; }
if (openflags::direct == (value_ & openflags::direct )) { result |= O_DIRECT; }
if (openflags::directory == (value_ & openflags::directory)) { result |= O_DIRECTORY; }
if (openflags::dsync == (value_ & openflags::dsync )) { result |= O_DSYNC; }
if (openflags::excl == (value_ & openflags::excl )) { result |= O_EXCL; }
if (openflags::largefile == (value_ & openflags::largefile)) { result |= O_LARGEFILE; }
if (openflags::noatime == (value_ & openflags::noatime )) { result |= O_NOATIME; }
if (openflags::noctty == (value_ & openflags::noctty )) { result |= O_NOCTTY; }
if (openflags::nofollow == (value_ & openflags::nofollow )) { result |= O_NOFOLLOW; }
if (openflags::nonblock == (value_ & openflags::nonblock )) { result |= O_NONBLOCK; }
if (openflags::ndelay == (value_ & openflags::ndelay )) { result |= O_NDELAY; }
if (openflags::path == (value_ & openflags::path )) { result |= O_PATH; }
if (openflags::sync == (value_ & openflags::sync )) { result |= O_SYNC; }
if (openflags::tmpfile == (value_ & openflags::tmpfile )) { result |= O_TMPFILE; }
if (openflags::trunc == (value_ & openflags::trunc )) { result |= O_TRUNC; }
return result;
}
}

@ -0,0 +1,47 @@
#ifndef WEBFUSE_OPENFLAGS_HPP
#define WEBFUSE_OPENFLAGS_HPP
#include <cinttypes>
namespace webfuse
{
class openflags
{
public:
static constexpr int32_t const accessmode_mask = 03; // O_ACCMODE
static constexpr int32_t const rdonly = 0; // O_RDONLY
static constexpr int32_t const wronly = 01; // O_WRONLY
static constexpr int32_t const rdwr = 02; // O_RDWR
static constexpr int32_t const append = 02000; // O_APPEND
static constexpr int32_t const async = 020000; // O_ASYNC
static constexpr int32_t const cloexec = 02000000; // O_CLOEXEC
static constexpr int32_t const creat = 0100; // O_CREAT
static constexpr int32_t const direct = 040000; // O_DIRECT
static constexpr int32_t const directory = 0200000; // O_DIRECTORY
static constexpr int32_t const dsync = 010000; // O_DSYNC
static constexpr int32_t const excl = 0200; // O_EXCL
static constexpr int32_t const largefile = 0100000; // O_LARGEFILE
static constexpr int32_t const noatime = 01000000; // O_NOATIME
static constexpr int32_t const noctty = 0400; // O_NOCTTY
static constexpr int32_t const nofollow = 0400000; // O_NOFOLLOW
static constexpr int32_t const nonblock = 04000; // O_NONBLOCK
static constexpr int32_t const ndelay = 04000; // O_NDELAY
static constexpr int32_t const path = 010000000; // O_PATH
static constexpr int32_t const sync = 04010000; // O_SYNC
static constexpr int32_t const tmpfile = 020200000; // O_TMPFILE
static constexpr int32_t const trunc = 01000; // O_TRUNC
explicit openflags(int32_t value = 0);
operator int32_t() const;
static openflags from_int(int value);
int to_int() const;
private:
int32_t value_;
};
}
#endif

@ -0,0 +1,122 @@
#include "webfuse/filesystem/status.hpp"
#include <errno.h>
namespace webfuse
{
status::status(int32_t value)
: value_(value)
{
}
status::operator int32_t() const
{
return value_;
}
status status::from_fusestatus(int value)
{
if (value >= 0)
{
return static_cast<int32_t>(value);
}
switch(value)
{
case -E2BIG: return status::bad_e2big;
case -EACCES: return status::bad_eacces;
case -EAGAIN: return status::bad_eagain;
case -EBADF: return status::bad_ebadf;
case -EBUSY: return status::bad_ebusy;
case -EDESTADDRREQ: return status::bad_edestaddrreq;
case -EDQUOT: return status::bad_edquot;
case -EEXIST: return status::bad_eexist;
case -EFAULT: return status::bad_efault;
case -EFBIG: return status::bad_efbig;
case -EINTR: return status::bad_eintr;
case -EINVAL: return status::bad_einval;
case -EIO: return status::bad_eio;
case -EISDIR: return status::bad_eisdir;
case -ELOOP: return status::bad_eloop;
case -EMFILE: return status::bad_emfile;
case -EMLINK: return status::bad_emlink;
case -ENAMETOOLONG: return status::bad_enametoolong;
case -ENFILE: return status::bad_enfile;
case -ENODATA: return status::bad_enodata;
case -ENODEV: return status::bad_enodev;
case -ENOENT: return status::bad_enoent;
case -ENOMEM: return status::bad_enomem;
case -ENOSPC: return status::bad_enospc;
case -ENOSYS: return status::bad_enosys;
case -ENOTDIR: return status::bad_enotdir;
case -ENOTEMPTY: return status::bad_enotempty;
case -ENOTSUP: return status::bad_enotsup;
case -ENXIO: return status::bad_enxio;
case -EOVERFLOW: return status::bad_eoverflow;
case -EPERM: return status ::bad_eperm;
case -EPIPE: return status::bad_epipe;
case -ERANGE: return status::bad_erange;
case -EROFS: return status::bad_erofs;
case -ETXTBSY: return status::bad_etxtbsy;
case -EXDEV: return status::bad_exdev;
default: return static_cast<int32_t>(value);
}
}
int status::to_fusestatus() const
{
if (value_ >= 0)
{
return static_cast<int>(value_);
}
switch(value_)
{
case status::bad_e2big: return -E2BIG;
case status::bad_eacces: return -EACCES;
case status::bad_eagain: return -EAGAIN;
case status::bad_ebadf: return -EBADF;
case status::bad_ebusy: return -EBUSY;
case status::bad_edestaddrreq: return -EDESTADDRREQ;
case status::bad_edquot: return -EDQUOT;
case status::bad_eexist: return -EEXIST;
case status::bad_efault: return -EFAULT;
case status::bad_efbig: return -EFBIG;
case status::bad_eintr: return -EINTR;
case status::bad_einval: return -EINVAL;
case status::bad_eio: return -EIO;
case status::bad_eisdir: return -EISDIR;
case status::bad_eloop: return -ELOOP;
case status::bad_emfile: return -EMFILE;
case status::bad_emlink: return -EMLINK;
case status::bad_enametoolong: return -ENAMETOOLONG;
case status::bad_enfile: return -ENFILE;
case status::bad_enodata: return -ENODATA;
case status::bad_enodev: return -ENODEV;
case status::bad_enoent: return -ENOENT;
case status::bad_enomem: return -ENOMEM;
case status::bad_enospc: return -ENOSPC;
case status::bad_enosys: return -ENOSYS;
case status::bad_enotdir: return -ENOTDIR;
case status::bad_enotempty: return -ENOTEMPTY;
case status::bad_enotsup: return -ENOTSUP;
case status::bad_enxio: return -ENXIO;
case status::bad_eoverflow: return -EOVERFLOW;
case status::bad_eperm: return -EPERM;
case status::bad_epipe: return -EPIPE;
case status::bad_erange: return -ERANGE;
case status::bad_erofs: return -EROFS;
case status::bad_etxtbsy: return -ETXTBSY;
case status::bad_exdev: return -EXDEV;
default: return static_cast<int32_t>(value_);
}
}
bool status::is_good() const
{
return (value_ == status::good);
}
}

@ -0,0 +1,64 @@
#ifndef WEBFUSE_STATUS_HPP
#define WEBFUSE_STATUS_HPP
#include <cinttypes>
namespace webfuse
{
class status
{
public:
static constexpr int32_t const good = 0;
static constexpr int32_t const bad_e2big = -7; // -E2BIG
static constexpr int32_t const bad_eacces = -13; // -EACCES
static constexpr int32_t const bad_eagain = -11; // -EAGAIN
static constexpr int32_t const bad_ebadf = -9; // -EBADF
static constexpr int32_t const bad_ebusy = -16; // -EBUSY
static constexpr int32_t const bad_edestaddrreq = -89; // -EDESTADDRREQ
static constexpr int32_t const bad_edquot = -122; // -EDQUOT
static constexpr int32_t const bad_eexist = -17; // -EEXIST
static constexpr int32_t const bad_efault = -14; // -EFAULT
static constexpr int32_t const bad_efbig = -27; // -EFBIG
static constexpr int32_t const bad_eintr = -4; // -EINTR
static constexpr int32_t const bad_einval = -22; // -EINVAL
static constexpr int32_t const bad_eio = -5; // -EIO
static constexpr int32_t const bad_eisdir = -21; // -EISDIR
static constexpr int32_t const bad_eloop = -40; // -ELOOP
static constexpr int32_t const bad_emfile = -24; // -EMFILE
static constexpr int32_t const bad_emlink = -31; // -EMLINK
static constexpr int32_t const bad_enametoolong = -36; // -ENAMETOOLONG
static constexpr int32_t const bad_enfile = -23; // -ENFILE
static constexpr int32_t const bad_enodata = -61; // -ENODATA
static constexpr int32_t const bad_enodev = -19; // -ENODEV
static constexpr int32_t const bad_enoent = -2; // -ENOENT
static constexpr int32_t const bad_enomem = -12; // -ENOMEM
static constexpr int32_t const bad_enospc = -28; // -ENOSPC
static constexpr int32_t const bad_enosys = -38; // -ENOSYS
static constexpr int32_t const bad_enotdir = -20; // -ENOTDIR
static constexpr int32_t const bad_enotempty = -39; // -ENOTEMPTY
static constexpr int32_t const bad_enotsup = -95; // -ENOTSUP
static constexpr int32_t const bad_enxio = -6; // -ENXIO
static constexpr int32_t const bad_eoverflow = -75; // -EOVERFLOW
static constexpr int32_t const bad_eperm = -1; // -EPERM
static constexpr int32_t const bad_epipe = -32; // -EPIPE
static constexpr int32_t const bad_erange = -34; // -ERANGE
static constexpr int32_t const bad_erofs = -30; // -EROFS
static constexpr int32_t const bad_etxtbsy = -26; // -ETXTBSY
static constexpr int32_t const bad_exdev = -18; // -EXDEV
static constexpr int32_t const bad_ewouldblock = -11; // -EWOULDBLOCK
status(int32_t value = status::good);
operator int32_t() const;
static status from_fusestatus(int value);
int to_fusestatus() const;
bool is_good() const;
private:
int32_t value_;
};
}
#endif

@ -0,0 +1,304 @@
#define FUSE_USE_VERSION 31
#include "webfuse/fuse.hpp"
#include <fuse.h>
#include <cstring>
#include <cstdio>
extern "C"
{
static webfuse::filesystem_i * fs_get_filesystem()
{
struct fuse_context * context = fuse_get_context();
void * private_data = context->private_data;
return reinterpret_cast<webfuse::filesystem_i*>(private_data);
}
static uint64_t fs_get_handle(fuse_file_info * info)
{
return (nullptr != info) ? info->fh : ((uint64_t) -1);
}
static void * fs_init(fuse_conn_info * connection, fuse_config * config)
{
(void) connection;
config->use_ino = 1;
config->entry_timeout = 0;
config->attr_timeout = 0;
config->negative_timeout = 0;
struct fuse_context * context = fuse_get_context();
return context->private_data;
}
static int fs_access(char const * path, int mode)
{
auto * const fs = fs_get_filesystem();
return fs->access(path, mode);
}
static int fs_getattr(char const * path, struct stat * buffer, fuse_file_info * info)
{
(void) info;
auto * const fs = fs_get_filesystem();
return fs->getattr(path, buffer);
}
static int fs_readlink(char const * path, char * buffer, size_t buffer_size)
{
auto * const fs = fs_get_filesystem();
std::string out;
auto result = fs->readlink(path, out);
if (0 == result)
{
snprintf(buffer, buffer_size, "%s", out.c_str());
}
return result;
}
static int fs_symlink(char const * target, char const * linkpath)
{
auto * const fs = fs_get_filesystem();
return fs->symlink(target, linkpath);
}
static int fs_link(char const * old_path, char const * new_path)
{
auto * const fs = fs_get_filesystem();
return fs->link(old_path, new_path);
}
static int fs_rename(char const * from, char const * to, unsigned int flags)
{
auto * const fs = fs_get_filesystem();
return fs->rename(from, to, flags);
}
static int fs_chmod(char const * path, mode_t mode, fuse_file_info * info)
{
(void) info;
auto * const fs = fs_get_filesystem();
return fs->chmod(path, mode);
}
static int fs_chown(char const * path, uid_t uid, gid_t gid, fuse_file_info * info)
{
(void) info;
auto * const fs = fs_get_filesystem();
return fs->chown(path, uid, gid);
}
static int fs_truncate(char const * path, off_t size, fuse_file_info * info)
{
auto * const fs = fs_get_filesystem();
auto const handle = fs_get_handle(info);
return fs->truncate(path, size, handle);
}
static int fs_fsync(char const * path, int isdatasync, fuse_file_info * info)
{
auto * const fs = fs_get_filesystem();
bool const is_datasync = (isdatasync != 0);
auto const handle = fs_get_handle(info);
return fs->fsync(path, is_datasync, handle);
}
static int fs_open(char const * path, fuse_file_info * info)
{
auto * const fs = fs_get_filesystem();
auto const flags = info->flags;
return fs->open(path, flags, info->fh);
}
static int fs_mknod(char const * path, mode_t mode, dev_t raw_rdev)
{
auto * const fs = fs_get_filesystem();
auto const rdev = static_cast<uint64_t>(raw_rdev);
return fs->mknod(path, mode, rdev);
}
static int fs_create(char const * path, mode_t mode, fuse_file_info * info)
{
auto * const fs = fs_get_filesystem();
return fs->create(path, mode, info->fh);
}
static int fs_release(char const * path, fuse_file_info * info)
{
auto * const fs = fs_get_filesystem();
auto const handle = fs_get_handle(info);
return fs->release(path, handle);
}
static int fs_unlink(char const * path)
{
auto * const fs = fs_get_filesystem();
return fs->unlink(path);
}
static int fs_read(char const * path, char * buffer,
size_t buffer_size, off_t raw_offset,
fuse_file_info * info)
{
auto * const fs = fs_get_filesystem();
auto const offset = static_cast<uint64_t>(raw_offset);
auto const handle = fs_get_handle(info);
return fs->read(path, buffer, buffer_size, offset, handle);
}
static int fs_write(char const * path, char const * buffer,
size_t buffer_size, off_t raw_offset,
fuse_file_info * info)
{
auto * const fs = fs_get_filesystem();
auto const offset = static_cast<uint64_t>(raw_offset);
auto const handle = fs_get_handle(info);
return fs->write(path, buffer, buffer_size, offset, handle);
}
static int fs_mkdir(char const * path, mode_t mode)
{
auto * const fs = fs_get_filesystem();
return fs->mkdir(path, mode);
}
static int fs_readdir(char const * path, void * buffer,
fuse_fill_dir_t filler, off_t offset, fuse_file_info * info,
fuse_readdir_flags flags)
{
auto * const fs = fs_get_filesystem();
std::vector<std::string> names;
auto const result = fs->readdir(path, names);
if (0 == result)
{
filler(buffer, ".", nullptr, 0, static_cast<fuse_fill_dir_flags>(0));
filler(buffer, "..", nullptr, 0, static_cast<fuse_fill_dir_flags>(0));
for (auto const & name: names)
{
filler(buffer, name.c_str(), nullptr, 0, static_cast<fuse_fill_dir_flags>(0));
}
}
return result;
}
static int fs_rmdir(char const * path)
{
auto * const fs = fs_get_filesystem();
return fs->rmdir(path);
}
static int fs_statfs(char const * path, struct statvfs * buffer)
{
auto * const fs = fs_get_filesystem();
return fs->statfs(path, buffer);
}
static int fs_utimes(const char * path, const struct timespec tv[2], struct fuse_file_info * info)
{
auto * const fs = fs_get_filesystem();
auto handle = fs_get_handle(info);
return fs->utimens(path, tv, handle);
}
}
namespace webfuse
{
class fuse::detail
{
public:
filesystem_i & filesystem;
};
fuse::fuse(filesystem_i & filesystem)
: d(new detail{filesystem})
{
}
fuse::~fuse()
{
delete d;
}
fuse::fuse(fuse && other)
{
this->d = other.d;
other.d = nullptr;
}
fuse& fuse::operator=(fuse && other)
{
if (this != &other)
{
delete d;
this->d = other.d;
other.d = nullptr;
}
return *this;
}
int fuse::run(int argc, char * argv[])
{
void * context = reinterpret_cast<void*>(&d->filesystem);
struct fuse_operations operations;
memset(reinterpret_cast<void*>(&operations), 0, sizeof(operations));
operations.init = fs_init;
operations.access = fs_access;
operations.getattr = fs_getattr;
operations.readlink = fs_readlink;
operations.symlink = fs_symlink;
operations.link = fs_link;
operations.rename = fs_rename;
operations.chmod = fs_chmod;
operations.chown = fs_chown;
operations.truncate = fs_truncate;
operations.fsync = fs_fsync;
operations.open = fs_open;
operations.mknod = fs_mknod;
operations.create = fs_create;
operations.release = fs_release;
operations.unlink = fs_unlink;
operations.read = fs_read;
operations.write = fs_write;
operations.mkdir = fs_mkdir;
operations.readdir = fs_readdir;
operations.rmdir = fs_rmdir;
operations.statfs = fs_statfs;
operations.utimens = fs_utimes;
return fuse_main(argc, argv, &operations, context);
}
void fuse::print_usage()
{
struct fuse_operations operations;
memset(reinterpret_cast<void*>(&operations), 0, sizeof(operations));
int const argc = 2;
char progname[] = "webfuse";
char show_help[] = "--help";
char * argv[] = { progname, show_help, nullptr};
fuse_main(argc, argv, &operations, nullptr);
}
}

@ -0,0 +1,27 @@
#ifndef WEBFUSE_FUSE_HPP
#define WEBFUSE_FUSE_HPP
#include "webfuse/filesystem/filesystem_i.hpp"
namespace webfuse
{
class fuse
{
fuse (fuse const &) = delete;
fuse& operator=(fuse const &) = delete;
public:
explicit fuse(filesystem_i & filesystem);
~fuse();
fuse (fuse && other);
fuse& operator=(fuse && other);
int run(int argc, char * argv[]);
static void print_usage();
private:
class detail;
detail * d;
};
}
#endif

@ -0,0 +1,437 @@
#include "webfuse/provider.hpp"
#include "webfuse/ws/client.hpp"
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <iostream>
namespace webfuse
{
class provider::detail
{
public:
detail(filesystem_i & fs, std::string const & ca_path)
: fs_(fs)
, client(ca_path, [this](auto& reader) { return this->on_message(reader); })
{
}
~detail()
{
}
void connect(std::string const & url)
{
client.connect(url);
}
void service()
{
client.service();
}
void interrupt()
{
client.interrupt();
}
void set_connection_listener(std::function<void(bool)> listener)
{
client.set_connection_listener(listener);
}
messagewriter on_message(messagereader & reader)
{
auto const message_id = reader.read_u32();
auto const req_type = get_request_type(reader.read_u8());
auto const resp_type = get_response_type(req_type);
messagewriter writer(resp_type);
writer.set_id(message_id);
switch (req_type)
{
case request_type::access:
fs_access(reader, writer);
break;
case request_type::getattr:
fs_getattr(reader, writer);
break;
case request_type::readlink:
fs_readlink(reader, writer);
break;
case request_type::symlink:
fs_symlink(reader, writer);
break;
case request_type::link:
fs_link(reader, writer);
break;
case request_type::rename:
fs_rename(reader, writer);
break;
case request_type::chmod:
fs_chmod(reader, writer);
break;
case request_type::chown:
fs_chown(reader, writer);
break;
case request_type::truncate:
fs_truncate(reader, writer);
break;
case request_type::fsync:
fs_fsync(reader, writer);
break;
case request_type::utimens:
fs_utimens(reader, writer);
break;
case request_type::open:
fs_open(reader, writer);
break;
case request_type::mknod:
fs_mknod(reader, writer);
break;
case request_type::create:
fs_create(reader, writer);
break;
case request_type::release:
fs_release(reader, writer);
break;
case request_type::unlink:
fs_unlink(reader, writer);
break;
case request_type::read:
fs_read(reader, writer);
break;
case request_type::write:
fs_write(reader, writer);
break;
case request_type::mkdir:
fs_mkdir(reader, writer);
break;
case request_type::readdir:
fs_readdir(reader, writer);
break;
case request_type::rmdir:
fs_rmdir(reader, writer);
break;
case request_type::statfs:
fs_statfs(reader, writer);
break;
case request_type::getcreds:
fs_get_credentials(reader, writer);
break;
default:
std::cout << "unknown request: " << ((int) req_type) << std::endl;
break;
}
return std::move(writer);
}
private:
void fs_access(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const mode = reader.read_access_mode();
auto const result = fs_.access(path, mode);
writer.write_i32(result);
}
void fs_getattr(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
struct stat buffer;
auto const result = fs_.getattr(path, &buffer);
writer.write_i32(result);
if (0 == result)
{
writer.write_attr(&buffer);
}
}
void fs_readlink(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
std::string out;
auto const result = fs_.readlink(path, out);
writer.write_i32(result);
if (0 == result)
{
writer.write_str(out);
}
}
void fs_symlink(messagereader & reader, messagewriter & writer)
{
auto const from = reader.read_str();
auto const to = reader.read_str();
auto const result = fs_.symlink(from, to);
writer.write_i32(result);
}
void fs_link(messagereader & reader, messagewriter & writer)
{
auto const from = reader.read_str();
auto const to = reader.read_str();
auto const result = fs_.link(from, to);
writer.write_i32(result);
}
void fs_rename(messagereader & reader, messagewriter & writer)
{
auto const from = reader.read_str();
auto const to = reader.read_str();
auto const flags = reader.read_u8();
auto const result = fs_.rename(from, to, flags);
writer.write_i32(result);
}
void fs_chmod(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const mode = reader.read_mode();
auto const result = fs_.chmod(path, mode);
writer.write_i32(result);
}
void fs_chown(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const uid = static_cast<uid_t>(reader.read_u32());
auto const gid = static_cast<gid_t>(reader.read_u32());
auto const result = fs_.chown(path, uid, gid);
writer.write_i32(result);
}
void fs_truncate(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const size = reader.read_u64();
auto const handle = reader.read_u64();
auto const result = fs_.truncate(path, size, handle);
writer.write_i32(result);
}
void fs_fsync(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const is_datasync = reader.read_bool();
auto const handle = reader.read_u64();
auto const result = fs_.fsync(path, is_datasync, handle);
writer.write_i32(result);
}
void fs_utimens(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
struct timespec times[2];
reader.read_time(times[0]);
reader.read_time(times[1]);
auto const handle = reader.read_u64();
auto const result = fs_.utimens(path, times, handle);
writer.write_i32(result);
}
void fs_open(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const flags = reader.read_openflags();
uint64_t handle = static_cast<uint64_t>(-1);
auto const result = fs_.open(path, flags, handle);
writer.write_i32(result);
if (result == 0)
{
writer.write_u64(handle);
}
}
void fs_mknod(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const mode = reader.read_mode();
auto const dev = reader.read_u32();
auto const result = fs_.mknod(path, mode, dev);
writer.write_i32(result);
}
void fs_create(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const mode = reader.read_mode();
uint64_t handle = static_cast<uint64_t>(-1);
auto const result = fs_.create(path, mode, handle);
writer.write_i32(result);
if (result == 0)
{
writer.write_u64(handle);
}
}
void fs_release(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const handle = reader.read_u64();
auto const result = fs_.release(path, handle);
writer.write_i32(result);
}
void fs_unlink(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const result = fs_.unlink(path);
writer.write_i32(result);
}
void fs_read(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const size = reader.read_u32();
auto const offset = reader.read_u64();
auto const handle = reader.read_u64();
std::vector<char> buffer(size);
auto const result = fs_.read(path, buffer.data(), size, offset, handle);
writer.write_i32(result);
if (0 < result)
{
writer.write_data(buffer.data(), result);
}
}
void fs_write(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const buffer = reader.read_bytes();
auto const offset = reader.read_u64();
auto const handle = reader.read_u64();
auto const result = fs_.write(path, buffer.c_str(), buffer.size(), offset, handle);
writer.write_i32(result);
}
void fs_mkdir(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const mode = reader.read_mode();
auto const result = fs_.mkdir(path, mode);
writer.write_i32(result);
}
void fs_rmdir(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
auto const result = fs_.rmdir(path);
writer.write_i32(result);
}
void fs_readdir(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
std::vector<std::string> entries;
auto const result = fs_.readdir(path, entries);
writer.write_i32(result);
if (0 == result)
{
writer.write_strings(entries);
}
}
void fs_statfs(messagereader & reader, messagewriter & writer)
{
auto const path = reader.read_str();
struct statvfs statistics;
memset(reinterpret_cast<void*>(&statistics), 0, sizeof(statistics));
auto const result = fs_.statfs(path, &statistics);
writer.write_i32(result);
if (0 == result)
{
writer.write_statistics(&statistics);
}
}
void fs_get_credentials(messagereader & reader, messagewriter & writer)
{
std::string credentials = fs_.get_credentials();
writer.write_str(credentials);
}
filesystem_i & fs_;
ws_client client;
};
provider::provider(filesystem_i & fs, std::string const & ca_path)
: d(new detail(fs, ca_path))
{
}
provider::~provider()
{
delete d;
}
provider::provider(provider && other)
{
this->d = other.d;
other.d = nullptr;
}
provider& provider::operator=(provider && other)
{
if (this != &other)
{
delete this->d;
this->d = other.d;
other.d = nullptr;
}
return *this;
}
void provider::connect(std::string const & url)
{
d->connect(url);
}
void provider::service()
{
d->service();
}
void provider::interrupt()
{
d->interrupt();
}
void provider::set_connection_listener(std::function<void(bool)> listener)
{
d->set_connection_listener(listener);
}
}

@ -0,0 +1,31 @@
#ifndef WEBFUSE_PROVIDER_I_HPP
#define WEBFUSE_PROVIDER_I_HPP
#include "webfuse/filesystem/filesystem_i.hpp"
#include <functional>
#include <string>
namespace webfuse
{
class provider
{
provider(provider const &) = delete;
provider& operator=(provider const &) = delete;
public:
provider(filesystem_i & fs, std::string const & ca_path);
~provider();
provider(provider && other);
provider& operator=(provider && other);
void set_connection_listener(std::function<void(bool)> listener);
void connect(std::string const & url);
void service();
void interrupt();
private:
class detail;
detail * d;
};
}
#endif

@ -0,0 +1,38 @@
#include "webfuse/request_type.hpp"
namespace webfuse
{
request_type get_request_type(uint8_t value)
{
switch (value)
{
case static_cast<uint8_t>(request_type::access): return request_type::access;
case static_cast<uint8_t>(request_type::getattr): return request_type::getattr;
case static_cast<uint8_t>(request_type::readlink): return request_type::readlink;
case static_cast<uint8_t>(request_type::symlink): return request_type::symlink;
case static_cast<uint8_t>(request_type::link): return request_type::link;
case static_cast<uint8_t>(request_type::rename): return request_type::rename;
case static_cast<uint8_t>(request_type::chmod): return request_type::chmod;
case static_cast<uint8_t>(request_type::chown): return request_type::chown;
case static_cast<uint8_t>(request_type::truncate): return request_type::truncate;
case static_cast<uint8_t>(request_type::fsync): return request_type::fsync;
case static_cast<uint8_t>(request_type::open): return request_type::open;
case static_cast<uint8_t>(request_type::mknod): return request_type::mknod;
case static_cast<uint8_t>(request_type::create): return request_type::create;
case static_cast<uint8_t>(request_type::release): return request_type::release;
case static_cast<uint8_t>(request_type::unlink): return request_type::unlink;
case static_cast<uint8_t>(request_type::read): return request_type::read;
case static_cast<uint8_t>(request_type::write): return request_type::write;
case static_cast<uint8_t>(request_type::mkdir): return request_type::mkdir;
case static_cast<uint8_t>(request_type::readdir): return request_type::readdir;
case static_cast<uint8_t>(request_type::rmdir): return request_type::rmdir;
case static_cast<uint8_t>(request_type::statfs): return request_type::statfs;
case static_cast<uint8_t>(request_type::utimens): return request_type::utimens;
case static_cast<uint8_t>(request_type::getcreds): return request_type::getcreds;
default:
return request_type::unknown;
}
}
}

@ -0,0 +1,41 @@
#ifndef WEBFUSE_REQUEST_TYPE
#define WEBFUSE_REQUEST_TYPE
#include <cinttypes>
namespace webfuse
{
enum class request_type: uint8_t
{
unknown = 0x00,
access = 0x01,
getattr = 0x02,
readlink = 0x03,
symlink = 0x04,
link = 0x05,
rename = 0x06,
chmod = 0x07,
chown = 0x08,
truncate = 0x09,
fsync = 0x0a,
open = 0x0b,
mknod = 0x0c,
create = 0x0d,
release = 0x0e,
unlink = 0x0f,
read = 0x10,
write = 0x11,
mkdir = 0x12,
readdir = 0x13,
rmdir = 0x14,
statfs = 0x15,
utimens = 0x16,
getcreds = 0x17
};
request_type get_request_type(uint8_t value);
}
#endif

@ -0,0 +1,39 @@
#include "webfuse/response_type.hpp"
namespace webfuse
{
response_type get_response_type(request_type value)
{
switch (value)
{
case request_type::access: return response_type::access;
case request_type::getattr: return response_type::getattr;
case request_type::readlink: return response_type::readlink;
case request_type::symlink: return response_type::symlink;
case request_type::link: return response_type::link;
case request_type::rename: return response_type::rename;
case request_type::chmod: return response_type::chmod;
case request_type::chown: return response_type::chown;
case request_type::truncate: return response_type::truncate;
case request_type::fsync: return response_type::fsync;
case request_type::open: return response_type::open;
case request_type::mknod: return response_type::mknod;
case request_type::create: return response_type::create;
case request_type::release: return response_type::release;
case request_type::unlink: return response_type::unlink;
case request_type::read: return response_type::read;
case request_type::write: return response_type::write;
case request_type::mkdir: return response_type::mkdir;
case request_type::readdir: return response_type::readdir;
case request_type::rmdir: return response_type::rmdir;
case request_type::statfs: return response_type::statfs;
case request_type::utimens: return response_type::utimens;
case request_type::getcreds: return response_type::getcreds;
default:
return response_type::unknown;
}
}
}

@ -0,0 +1,42 @@
#ifndef WEBFUSE_RESPONSE_TYPE
#define WEBFUSE_RESPONSE_TYPE
#include "request_type.hpp"
#include <cinttypes>
namespace webfuse
{
enum class response_type: uint8_t
{
unknown = 0x80,
access = 0x81,
getattr = 0x82,
readlink = 0x83,
symlink = 0x84,
link = 0x85,
rename = 0x86,
chmod = 0x87,
chown = 0x88,
truncate = 0x89,
fsync = 0x8a,
open = 0x8b,
mknod = 0x8c,
create = 0x8d,
release = 0x8e,
unlink = 0x8f,
read = 0x90,
write = 0x91,
mkdir = 0x92,
readdir = 0x93,
rmdir = 0x94,
statfs = 0x95,
utimens = 0x96,
getcreds = 0x97
};
response_type get_response_type(request_type value);
}
#endif

@ -0,0 +1,67 @@
#include "webfuse/util/authenticator.hpp"
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
namespace webfuse
{
authenticator::authenticator(std::string const & app)
: app_(app)
{
}
bool authenticator::authenticate(std::string const & token)
{
bool result = false;
int fds[2];
int const rc = pipe(fds);
if (0 != rc)
{
return false;
}
pid_t const pid = fork();
if (pid == 0)
{
// child
close(STDIN_FILENO);
dup2(fds[0], STDIN_FILENO);
// prepare file descriptors
closefrom(1);
open("/dev/null", O_WRONLY);
dup2(STDOUT_FILENO, STDERR_FILENO);
execl(app_.c_str(), app_.c_str(), nullptr);
exit(EXIT_FAILURE);
}
else if (pid > 0)
{
write(fds[1], reinterpret_cast<void const*>(token.c_str()), token.size());
close(fds[1]);
// parent
int exit_status = EXIT_FAILURE;
int status = 0;
int const rc = waitpid(pid, &status, 0);
if (rc == pid)
{
exit_status = WEXITSTATUS(status);
}
close(fds[0]);
result = (exit_status == EXIT_SUCCESS);
}
return result;
}
}

@ -0,0 +1,23 @@
#ifndef WEBFUSE_AUTHENTICATOR_HPP
#define WEBFUSE_AUTHENTICATOR_HPP
#include <string>
namespace webfuse
{
class authenticator
{
public:
explicit authenticator(std::string const & app);
~authenticator() = default;
bool authenticate(std::string const & token);
private:
std::string app_;
};
}
#endif

@ -0,0 +1,56 @@
#include "webfuse/util/commandline_args.hpp"
#include <cstring>
#include <stdexcept>
namespace webfuse
{
commandline_args::commandline_args(char const * prog_name, int capacity)
: capacity_(capacity)
, argc(0)
{
if (capacity < 1)
{
throw std::runtime_error("too few commandline args");
}
argv = new char*[capacity_ + 1];
argv[0] = nullptr;
push(prog_name);
}
commandline_args::~commandline_args()
{
for(int i = 0; i < argc; i++)
{
free(argv[i]);
}
delete[] argv;
}
void commandline_args::push(char const * arg)
{
if (argc < capacity_)
{
argv[argc] = strdup(arg);
argv[argc + 1] = nullptr;
argc++;
}
else
{
throw std::runtime_error("capacity exceeded");
}
}
int commandline_args::get_argc() const
{
return argc;
}
char ** commandline_args::get_argv()
{
return argv;
}
}

@ -0,0 +1,29 @@
#ifndef WEBFUSE_COMMANDLINE_ARGS_HPP
#define WEBFUSE_COMMANDLINE_ARGS_HPP
namespace webfuse
{
class commandline_args
{
commandline_args (commandline_args const &) = delete;
commandline_args& operator=(commandline_args const &) = delete;
commandline_args (commandline_args &&) = delete;
commandline_args& operator=(commandline_args &&) = delete;
public:
commandline_args(char const * prog_name, int capacity);
~commandline_args();
void push(char const * arg);
int get_argc() const;
char ** get_argv();
private:
int capacity_;
int argc;
char ** argv;
};
}
#endif

@ -0,0 +1,30 @@
#include "webfuse/util/commandline_reader.hpp"
namespace webfuse
{
commandline_reader::commandline_reader(int argc, char * argv[])
: current_(0)
, argc_(argc)
, argv_(argv)
{
}
bool commandline_reader::next()
{
if (current_ < argc_)
{
current_++;
}
bool const has_next = (current_ < argc_);
return has_next;
}
char const * commandline_reader::current() const
{
return argv_[current_];
}
}

@ -0,0 +1,22 @@
#ifndef WEBFUSE_COMMANDLINE_READER_HPP
#define WEBFUSE_COMMANDLINE_READER_HPP
namespace webfuse
{
class commandline_reader
{
public:
commandline_reader(int argc, char * argv[]);
~commandline_reader() = default;
bool next();
char const * current() const;
private:
int current_;
int argc_;
char ** argv_;
};
}
#endif

@ -0,0 +1,11 @@
#include "webfuse/version.hpp"
namespace webfuse
{
char const * get_version()
{
return "@webfuse_VERSION@";
}
}

@ -0,0 +1,12 @@
#ifndef WEBFUSE_VERSION_HPP
#define WEBFUSE_VERSION_HPP
namespace webfuse
{
char const * get_version();
}
#endif

@ -0,0 +1,54 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/fuse.hpp"
#include "webfuse/filesystem.hpp"
#include "webfuse/ws/server.hpp"
#include "webfuse/version.hpp"
#include <iostream>
namespace webfuse
{
int app::run(int argc, char * argv[]) // NOLINT(readability-convert-member-functions-to-static)
{
ws_config config(argc, argv);
switch (config.cmd)
{
case command::run:
{
ws_server server(config);
filesystem filesystem(server);
fuse fuse_fs(filesystem);
config.exit_code = fuse_fs.run(config.args.get_argc(), config.args.get_argv());
}
break;
case command::print_version:
std::cout << webfuse::get_version() << std::endl;
break;
case command::show_help:
// fall-through
default:
{
fuse::print_usage();
std::cout << R"(
WEBFUSE options:
--wf-port PORT port number of websocket server (default: 8081)
--wf-vhost VHOST name of the virtual host (default: localhost)
--wf-cert PATH path of the server's public certificate (optional)
--wf-key PATH path of the server's private key (optional)
--wf-authenticator PATH path of authenticatior app (optional)
--wf-auth-header NAME name of the authentication header (optional)
--wf-docroot PATH enables HTTP server with given document root (optional)
--wf-timeout TIMEOUT communication timeout in seconds (default: 10)
--wf-version print version and exit
)";
}
break;
}
return config.exit_code;
}
}

@ -0,0 +1,15 @@
#ifndef WEBFUSE_HPP
#define WEBFUSE_HPP
namespace webfuse
{
class app
{
public:
int run(int argc, char * argv[]);
};
}
#endif

@ -0,0 +1,240 @@
#include "webfuse/ws/client.hpp"
#include "webfuse/ws/url.hpp"
#include <libwebsockets.h>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
namespace
{
struct user_data
{
webfuse::ws_client_handler handler;
std::function<void(bool)> connection_listener;
struct lws * connection;
std::string current_message;
std::queue<webfuse::messagewriter> requests;
};
extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, void* user, void * in, size_t length)
{
int result = 0;
lws_protocols const * protocol = lws_get_protocol(wsi);
user_data * context = (nullptr != protocol) ? reinterpret_cast<user_data*>(protocol->user) : nullptr;
if (nullptr != context)
{
switch(reason)
{
case LWS_CALLBACK_CLIENT_ESTABLISHED:
context->connection_listener(true);
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
context->connection = nullptr;
context->requests = std::move(std::queue<webfuse::messagewriter>());
context->current_message.clear();
context->connection_listener(false);
break;
case LWS_CALLBACK_CLIENT_CLOSED:
context->connection = nullptr;
context->requests = std::move(std::queue<webfuse::messagewriter>());
context->current_message.clear();
context->connection_listener(false);
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
{
auto * fragment = reinterpret_cast<char*>(in);
context->current_message.append(fragment, length);
if (0 != lws_is_final_fragment(wsi))
{
try
{
webfuse::messagereader reader(context->current_message);
auto writer = context->handler(reader);
context->requests.emplace(std::move(writer));
lws_callback_on_writable(wsi);
}
catch(...)
{
// ToDo: log
std::cerr << "error: failed to create response" << std::endl;
}
}
}
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
// fall-through
case LWS_CALLBACK_CLIENT_WRITEABLE:
{
if (!context->requests.empty())
{
auto writer = std::move(context->requests.front());
context->requests.pop();
size_t size;
auto * data = writer.get_data(size);
lws_write(wsi, data, size, LWS_WRITE_BINARY);
}
if (!context->requests.empty())
{
lws_callback_on_writable(wsi);
}
}
break;
default:
break;
}
}
return result;
}
}
namespace webfuse
{
class ws_client::detail
{
detail(detail const &) = delete;
detail& operator=(detail const &) = delete;
detail(detail &&) = delete;
detail& operator=(detail &&) = delete;
public:
detail(std::string const & ca_path, ws_client_handler handler)
{
lws_set_log_level(0, nullptr);
memset(reinterpret_cast<void*>(protocols), 0, sizeof(lws_protocols) * 2);
protocols[0].callback = &webfuse_client_callback;
protocols[0].name = "webfuse2-client";
protocols[0].per_session_data_size = 0;
protocols[0].user = reinterpret_cast<void*>(&data);
memset(reinterpret_cast<void*>(&info), 0, sizeof(lws_context_creation_info));
info.port = CONTEXT_PORT_NO_LISTEN;
info.protocols = protocols;
info.uid = -1;
info.gid = -1;
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
data.handler = handler;
data.connection_listener = [](bool){ };
data.connection = nullptr;
context = lws_create_context(&info);
struct lws_vhost * vhost = lws_create_vhost(context, &info);
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
if (!ca_path.empty())
{
info.client_ssl_ca_filepath = ca_path.c_str();
}
lws_init_vhost_client_ssl(&info, vhost);
}
~detail()
{
lws_context_destroy(context);
}
void connect(std::string const & url)
{
ws_url parsed_url(url);
lws_client_connect_info info;
memset(reinterpret_cast<void*>(&info), 0, sizeof(lws_client_connect_info));
info.context = context;
info.port = parsed_url.port;
info.address = parsed_url.hostname.c_str();
info.host = info.address;
info.path = parsed_url.path.c_str();
info.origin = info.address;
info.ssl_connection = (parsed_url.use_tls) ? LCCSCF_USE_SSL : 0;
info.protocol = "webfuse2";
info.local_protocol_name = "webfuse2-client";
info.pwsi = &data.connection;
lws_client_connect_via_info(&info);
}
void service()
{
lws_service(context, 0);
}
void interrupt()
{
lws_cancel_service(context);
}
void set_connection_listener(std::function<void(bool)> listener)
{
data.connection_listener = listener;
}
private:
lws_context_creation_info info;
lws_protocols protocols[2];
lws_context * context;
user_data data;
};
ws_client::ws_client(std::string const & ca_path, ws_client_handler handler)
: d(new detail(ca_path, handler))
{
}
ws_client::~ws_client()
{
delete d;
}
ws_client::ws_client(ws_client && other)
{
this->d = other.d;
other.d = nullptr;
}
ws_client& ws_client::operator=(ws_client && other)
{
if (this != &other)
{
delete this->d;
this->d = other.d;
other.d = nullptr;
}
return *this;
}
void ws_client::connect(std::string url)
{
d->connect(url);
}
void ws_client::service()
{
d->service();
}
void ws_client::interrupt()
{
d->interrupt();
}
void ws_client::set_connection_listener(std::function<void(bool)> listener)
{
d->set_connection_listener(listener);
}
}

@ -0,0 +1,35 @@
#ifndef WEBFUSE_WSCLIENT_HPP
#define WEBFUSE_WSCLIENT_HPP
#include "webfuse/ws/messagewriter.hpp"
#include "webfuse/ws/messagereader.hpp"
#include <functional>
namespace webfuse
{
using ws_client_handler = std::function<messagewriter (messagereader & reader)>;
class ws_client
{
ws_client(ws_client const &) = delete;
ws_client& operator=(ws_client const &) = delete;
public:
ws_client(std::string const & ca_path, ws_client_handler handler);
~ws_client();
ws_client(ws_client && other);
ws_client& operator=(ws_client && other);
void set_connection_listener(std::function<void(bool)> listener);
void connect(std::string url);
void service();
void interrupt();
private:
class detail;
detail * d;
};
}
#endif

@ -0,0 +1,133 @@
#include "webfuse/ws/config.hpp"
#include "webfuse/util/commandline_reader.hpp"
#include <cctype>
#include <algorithm>
#include <iostream>
namespace
{
constexpr int const default_port = 8081;
constexpr char const default_vhost_name[] = "localhost";
constexpr uint64_t const default_timeout_secs = 10;
void verify(webfuse::ws_config & config)
{
if (config.cmd == webfuse::command::run)
{
if ((config.use_tls) && ((config.key_path.empty()) || (config.cert_path.empty())))
{
std::cerr << "error: use of TLS requires both, key ander certificate path" << std::endl;
config.cmd = webfuse::command::show_help;
config.exit_code = EXIT_FAILURE;
}
}
}
bool get_arg(webfuse::ws_config & config, webfuse::commandline_reader& reader, std::string & value, std::string const error_message)
{
const bool has_next = reader.next();
if (has_next)
{
value = reader.current();
}
else
{
std::cerr << "error: " << error_message << std::endl;
config.cmd = webfuse::command::show_help;
config.exit_code = EXIT_FAILURE;
}
return has_next;
}
}
namespace webfuse
{
// NOLINTNEXTLINE
ws_config::ws_config(int argc, char * argv[])
: exit_code(EXIT_SUCCESS)
, args(argv[0], argc)
, cmd(command::run)
, port(default_port)
, vhost_name(default_vhost_name)
, use_tls(false)
, timeout_secs(default_timeout_secs)
{
commandline_reader reader(argc, argv);
while ((exit_code == EXIT_SUCCESS) && (reader.next()))
{
std::string const arg = reader.current();
if ((arg == "-h") || (arg == "--help"))
{
cmd = command::show_help;
}
else if (arg == "--wf-port")
{
std::string value;
if (get_arg(*this, reader, value, "missing PORT"))
{
port = static_cast<uint16_t>(std::stoi(value));
}
}
else if (arg == "--wf-vhost")
{
get_arg(*this, reader, vhost_name, "missing VHOST");
}
else if (arg == "--wf-key")
{
if (get_arg(*this, reader, key_path, "missing KEY_PATH"))
{
use_tls = true;
}
}
else if (arg == "--wf-cert")
{
if (get_arg(*this, reader, cert_path, "missing CERT_PATH"))
{
use_tls = true;
}
}
else if (arg == "--wf-authenticator")
{
get_arg(*this, reader, authenticator, "missing AUTHENTICATOR");
}
else if (arg == "--wf-auth-header")
{
if (get_arg(*this, reader, auth_header, "missing AUTH_HEADER"))
{
std::transform(auth_header.begin(), auth_header.end(), auth_header.begin(),
[](auto c) {return std::tolower(c); });
}
}
else if (arg == "--wf-timeout")
{
std::string timeout_str;
if (get_arg(*this, reader, timeout_str, "missing TIMEOUT"))
{
timeout_secs = static_cast<uint64_t>(std::stoi(timeout_str));
}
}
else if (arg == "--wf-docroot")
{
get_arg(*this, reader, docroot, "missing DOCROOT");
}
else if (arg == "--wf-version")
{
cmd = command::print_version;
}
else
{
args.push(arg.c_str());
}
}
verify(*this);
}
}

@ -0,0 +1,44 @@
#ifndef WEBFUSE_WS_CONFIG_HPP
#define WEBFUSE_WS_CONFIG_HPP
#include <webfuse/util/commandline_args.hpp>
#include <cinttypes>
#include <string>
namespace webfuse
{
enum class command
{
run,
print_version,
show_help
};
class ws_config
{
public:
ws_config(int argc, char * argv[]);
int exit_code;
commandline_args args;
command cmd;
uint16_t port;
std::string vhost_name;
std::string docroot;
bool use_tls;
std::string cert_path;
std::string key_path;
std::string authenticator;
std::string auth_header;
uint64_t timeout_secs;
};
}
#endif

@ -0,0 +1,195 @@
#include "webfuse/ws/messagereader.hpp"
#include "webfuse/filesystem/status.hpp"
#include "webfuse/filesystem/filemode.hpp"
#include "webfuse/filesystem/accessmode.hpp"
#include "webfuse/filesystem/openflags.hpp"
#include <stdexcept>
namespace webfuse
{
messagereader::messagereader(std::string & value)
: data(std::move(value))
, pos(0)
{
}
messagereader::messagereader(messagereader && other)
{
this->data = std::move(other.data);
this->pos = other.pos;
}
messagereader& messagereader::operator=(messagereader && other)
{
if (this != &other)
{
this->data = std::move(other.data);
this->pos = other.pos;
}
return *this;
}
int messagereader::read_result()
{
status value(read_i32());
return value.to_fusestatus();
}
int messagereader::read_access_mode()
{
auto const value = read_u8();
access_mode mode(static_cast<int8_t>(value));
return mode.to_int();
}
void messagereader::read_attr(struct stat * attr)
{
attr->st_ino = static_cast<ino_t>(read_u64());
attr->st_nlink = static_cast<nlink_t>(read_u64());
attr->st_mode = read_mode();
attr->st_uid = static_cast<uid_t>(read_u32());
attr->st_gid = static_cast<gid_t>(read_u32());
attr->st_rdev = static_cast<dev_t>(read_u64());
attr->st_size = static_cast<off_t>(read_u64());
attr->st_blocks = static_cast<blkcnt64_t>(read_u64());
attr->st_atim.tv_sec = static_cast<time_t>(read_u64());
attr->st_atim.tv_nsec = static_cast<long>(read_u32());
attr->st_mtim.tv_sec = static_cast<time_t>(read_u64());
attr->st_mtim.tv_nsec = static_cast<long>(read_u32());
attr->st_ctim.tv_sec = static_cast<time_t>(read_u64());
attr->st_ctim.tv_nsec = static_cast<long>(read_u32());
}
void messagereader::read_statistics(struct statvfs * statistics)
{
statistics->f_bsize = read_u64();
statistics->f_frsize = read_u64();
statistics->f_blocks = read_u64();
statistics->f_bfree = read_u64();
statistics->f_bavail = read_u64();
statistics->f_files = read_u64();
statistics->f_ffree = read_u64();
statistics->f_namemax = read_u64();
}
mode_t messagereader::read_mode()
{
filemode mode(read_u32());
return mode.to_mode();
}
bool messagereader::read_bool()
{
return (1 == read_u8());
}
uint8_t messagereader::read_u8()
{
if (pos < data.size())
{
uint8_t value = static_cast<uint8_t>(data[pos]);
pos++;
return value;
}
throw std::runtime_error("out of bounds");
}
uint32_t messagereader::read_u32()
{
// NOLINTBEGIN(readability-magic-numbers)
if ((pos + 3) < data.size())
{
uint32_t value =
((static_cast<uint32_t>(data[pos ]) & 0xff) << 24) |
((static_cast<uint32_t>(data[pos + 1]) & 0xff) << 16) |
((static_cast<uint32_t>(data[pos + 2]) & 0xff) << 8) |
((static_cast<uint32_t>(data[pos + 3]) & 0xff) );
pos += 4;
return value;
}
// NOLINTEND(readability-magic-numbers)
throw std::runtime_error("out of bounds");
}
uint64_t messagereader::read_u64()
{
// NOLINTBEGIN(readability-magic-numbers)
if ((pos + 7) < data.size())
{
uint32_t value =
(static_cast<uint64_t>(data[pos ] & 0xff) << 56) |
(static_cast<uint64_t>(data[pos + 1] & 0xff) << 48) |
(static_cast<uint64_t>(data[pos + 2] & 0xff) << 40) |
(static_cast<uint64_t>(data[pos + 3] & 0xff) << 32) |
(static_cast<uint64_t>(data[pos + 4] & 0xff) << 24) |
(static_cast<uint64_t>(data[pos + 5] & 0xff) << 16) |
(static_cast<uint64_t>(data[pos + 6] & 0xff) << 8) |
(static_cast<uint64_t>(data[pos + 7] & 0xff) );
pos += 8;
return value;
}
// NOLINTEND(readability-magic-numbers)
throw std::runtime_error("out of bounds");
}
int32_t messagereader::read_i32()
{
uint32_t value = read_u32();
return static_cast<int32_t>(value);
}
std::string messagereader::read_str()
{
return std::move(read_bytes());
}
std::string messagereader::read_bytes()
{
uint32_t size = read_u32();
if ((pos + size) <= data.size())
{
std::string const value(&data[pos], size);
pos += size;
return std::move(value);
}
throw std::runtime_error("out of bounds");
}
void messagereader::read_strings(std::vector<std::string> &entries)
{
uint32_t const count = read_u32();
for(uint32_t i = 0; i < count; i++)
{
std::string entry = read_str();
entries.push_back(entry);
}
}
void messagereader::read_time(struct timespec &time)
{
time.tv_sec = static_cast<time_t>(read_u64());
time.tv_nsec = static_cast<long>(read_u32());
}
int messagereader::read_openflags()
{
auto const value = read_i32();
openflags flags(value);
return flags.to_int();
}
}

@ -0,0 +1,53 @@
#ifndef WEBFUSE_MESSAGEREADER_HPP
#define WEBFUSE_MESSAGEREADER_HPP
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <unistd.h>
#include <cinttypes>
#include <string>
#include <vector>
namespace webfuse
{
class messagereader
{
messagereader(messagereader const &) = delete;
messagereader& operator=(messagereader const &) = delete;
public:
explicit messagereader(std::string & value);
~messagereader() = default;
messagereader(messagereader && other);
messagereader& operator=(messagereader && other);
int read_result();
void read_attr(struct stat * attr);
void read_statistics(struct statvfs * statistics);
int read_access_mode();
mode_t read_mode();
bool read_bool();
uint8_t read_u8();
uint32_t read_u32();
uint64_t read_u64();
int32_t read_i32();
std::string read_str();
std::string read_bytes();
void read_strings(std::vector<std::string> &entries);
void read_time(struct timespec &time);
int read_openflags();
private:
std::string data;
size_t pos;
};
}
#endif

@ -0,0 +1,223 @@
#include "webfuse/ws/messagewriter.hpp"
#include "webfuse/filesystem/accessmode.hpp"
#include "webfuse/filesystem/filemode.hpp"
#include "webfuse/filesystem/openflags.hpp"
#include <libwebsockets.h>
namespace webfuse
{
constexpr uint8_t const rename_noreplace = 0x01;
constexpr uint8_t const rename_exchange = 0x02;
messagewriter::messagewriter(request_type req_type)
: id(0)
, data(LWS_PRE)
{
write_u32(0);
write_u8(static_cast<uint8_t>(req_type));
}
messagewriter::messagewriter(response_type res_type)
: id(0)
, data(LWS_PRE)
{
write_u32(0);
write_u8(static_cast<uint8_t>(res_type));
}
messagewriter::messagewriter(messagewriter && other)
{
this->id = other.id;
this->data = std::move(other.data);
}
messagewriter& messagewriter::operator=(messagewriter && other)
{
if (this != &other)
{
this->id = other.id;
this->data = std::move(other.data);
}
return *this;
}
void messagewriter::set_id(uint32_t value)
{
id = value;
// NOLINTBEGIN(readability-magic-numbers)
data[LWS_PRE ] = (id >> 24) & 0xff;
data[LWS_PRE + 1] = (id >> 16) & 0xff;
data[LWS_PRE + 2] = (id >> 8) & 0xff;
data[LWS_PRE + 3] = id & 0xff;
// NOLINTEND(readability-magic-numbers)
}
uint32_t messagewriter::get_id() const
{
return id;
}
void messagewriter::write_bool(bool value)
{
data.push_back(value ? 0x01 : 0x00);
}
void messagewriter::write_u8(uint8_t value)
{
data.push_back(value);
}
void messagewriter::write_i8(int8_t value)
{
data.push_back(static_cast<uint8_t>(value));
}
void messagewriter::write_i32(int32_t value)
{
write_u32((static_cast<uint32_t>(value)));
}
void messagewriter::write_u32(uint32_t value)
{
auto const offset = data.size();
// NOLINTBEGIN(readability-magic-numbers)
data.resize(offset + 4);
data[offset ] = (value >> 24) & 0xff;
data[offset + 1] = (value >> 16) & 0xff;
data[offset + 2] = (value >> 8) & 0xff;
data[offset + 3] = value & 0xff;
// NOLINTEND(readability-magic-numbers)
}
void messagewriter::write_u64(uint64_t value)
{
auto const offset = data.size();
// NOLINTBEGIN(readability-magic-numbers)
data.resize(offset + 8);
data[offset ] = (value >> 56) & 0xff;
data[offset + 1] = (value >> 48) & 0xff;
data[offset + 2] = (value >> 40) & 0xff;
data[offset + 3] = (value >> 32) & 0xff;
data[offset + 4] = (value >> 24) & 0xff;
data[offset + 5] = (value >> 16) & 0xff;
data[offset + 6] = (value >> 8) & 0xff;
data[offset + 7] = value & 0xff;
// NOLINTEND(readability-magic-numbers)
}
void messagewriter::write_str(std::string const &value)
{
write_data(value.data(), value.size());
}
void messagewriter::write_data(char const * buffer, size_t size)
{
uint32_t const effective_size = size & 0xffffffff;
write_u32(effective_size);
if (size > 0)
{
auto const offset = data.size();
data.resize(offset + effective_size);
void * to = reinterpret_cast<void*>(&data[offset]);
void const * from = reinterpret_cast<void const *>(buffer);
memcpy(to, from, effective_size);
}
}
void messagewriter::write_strings(std::vector<std::string> const & list)
{
uint32_t const count = list.size() & 0xffffffff;
write_u32(count);
for (auto const & item: list)
{
write_str(item);
}
}
void messagewriter::write_attr(struct stat const * attr)
{
write_u64(static_cast<uint64_t>(attr->st_ino));
write_u64(static_cast<uint64_t>(attr->st_nlink));
write_mode(filemode::from_mode(attr->st_mode));
write_u32(static_cast<uint32_t>(attr->st_uid));
write_u32(static_cast<uint32_t>(attr->st_gid));
write_u64(static_cast<uint64_t>(attr->st_rdev));
write_u64(static_cast<uint64_t>(attr->st_size));
write_u64(static_cast<uint64_t>(attr->st_blocks));
write_time(attr->st_atim);
write_time(attr->st_mtim);
write_time(attr->st_ctim);
}
void messagewriter::write_access_mode(int value)
{
access_mode mode = access_mode::from_int(value);
write_i8(mode);
}
void messagewriter::write_rename_flags(unsigned int value)
{
uint8_t flags = 0;
if (RENAME_NOREPLACE == (value & RENAME_NOREPLACE)) { flags |= rename_noreplace; }
if (RENAME_EXCHANGE == (value & RENAME_EXCHANGE )) { flags |= rename_exchange; }
write_u8(flags);
}
void messagewriter::write_mode(mode_t value)
{
filemode mode = filemode::from_mode(value);
write_u32(mode);
}
void messagewriter::write_uid(uid_t value)
{
write_u32(static_cast<uint32_t>(value));
}
void messagewriter::write_gid(gid_t value)
{
write_u32(static_cast<uint32_t>(value));
}
void messagewriter::write_openflags(int value)
{
openflags flags = openflags::from_int(value);
write_i32(flags);
}
void messagewriter::write_time(timespec const & value)
{
write_u64(static_cast<uint64_t>(value.tv_sec));
write_u32(static_cast<uint32_t>(value.tv_nsec));
}
void messagewriter::write_statistics(struct statvfs const * statistics)
{
write_u64(statistics->f_bsize);
write_u64(statistics->f_frsize);
write_u64(statistics->f_blocks);
write_u64(statistics->f_bfree);
write_u64(statistics->f_bavail);
write_u64(statistics->f_files);
write_u64(statistics->f_ffree);
write_u64(statistics->f_namemax);
}
unsigned char * messagewriter::get_data(size_t &size)
{
size = data.size() - LWS_PRE;
void * result = reinterpret_cast<void *>(&data[LWS_PRE]);
return reinterpret_cast<unsigned char *>(result);
}
}

@ -0,0 +1,60 @@
#ifndef WEBFUSE_MESSAGEWRITER_HPP
#define WEBFUSE_MESSAGEWRITER_HPP
#include "webfuse/request_type.hpp"
#include "webfuse/response_type.hpp"
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <cinttypes>
#include <string>
#include <vector>
namespace webfuse
{
class messagewriter
{
messagewriter(messagewriter const &) = delete;
messagewriter& operator=(messagewriter const &) = delete;
public:
explicit messagewriter(request_type req_type);
explicit messagewriter(response_type res_type);
~messagewriter() = default;
messagewriter(messagewriter && other);
messagewriter& operator=(messagewriter && other);
void set_id(uint32_t value);
uint32_t get_id() const;
void write_bool(bool value);
void write_u8(uint8_t value);
void write_i8(int8_t value);
void write_i32(int32_t value);
void write_u32(uint32_t value);
void write_u64(uint64_t value);
void write_str(std::string const &value);
void write_data(char const * buffer, size_t size);
void write_strings(std::vector<std::string> const & list);
void write_attr(struct stat const * attr);
void write_access_mode(int value);
void write_rename_flags(unsigned int value);
void write_mode(mode_t value);
void write_uid(uid_t value);
void write_gid(gid_t value);
void write_openflags(int value);
void write_time(timespec const & value);
void write_statistics(struct statvfs const * statistics);
unsigned char * get_data(size_t &size);
private:
uint32_t id;
std::vector<uint8_t> data;
};
}
#endif

@ -0,0 +1,189 @@
#include "webfuse/ws/server.hpp"
#include "webfuse/ws/server_handler.hpp"
#include <libwebsockets.h>
#include <cinttypes>
#include <cstring>
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <future>
#include <chrono>
#include <stdexcept>
#include <queue>
#include <string>
#include <unordered_map>
extern "C"
{
static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
auto const * protocol = lws_get_protocol(wsi);
if (nullptr == protocol) { return 0; }
if (&ws_server_callback != protocol->callback) { return 0; }
auto * handler = reinterpret_cast<webfuse::server_handler*>(protocol->user);
int result = 0;
switch(reason)
{
case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
result = handler->filter_connection(wsi);
break;
case LWS_CALLBACK_ESTABLISHED:
result = handler->on_established(wsi);
break;
case LWS_CALLBACK_CLOSED:
handler->on_closed(wsi);
break;
case LWS_CALLBACK_RECEIVE:
handler->on_receive(wsi, in, len);
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
result = handler->on_writable();
break;
default:
break;
}
return result;
}
}
namespace webfuse
{
class ws_server::detail
{
detail(detail const &) = delete;
detail& operator=(detail const &) = delete;
detail(detail &&) = delete;
detail& operator=(detail &&) = delete;
public:
detail(ws_config const & config)
: shutdown_requested(false)
, docroot(config.docroot)
, data(config.authenticator, config.auth_header)
, timeout_secs(config.timeout_secs)
{
lws_set_log_level(0, nullptr);
memset(reinterpret_cast<void*>(&http_mount), 0, sizeof(http_mount));
http_mount.mount_next = nullptr;
http_mount.mountpoint = "/";
http_mount.origin = docroot.c_str();
http_mount.def = "index.html";
http_mount.origin_protocol = LWSMPRO_FILE;
http_mount.mountpoint_len = 1;
memset(reinterpret_cast<void*>(protocols), 0, sizeof(protocols));
protocols[0].name = "http";
protocols[0].callback = lws_callback_http_dummy;
protocols[1].name = "webfuse2";
protocols[1].callback = &ws_server_callback;
protocols[1].per_session_data_size = 0;
protocols[1].user = reinterpret_cast<void*>(&data);
memset(reinterpret_cast<void*>(&info), 0, sizeof(info));
info.port = config.port;
info.mounts = &http_mount;
info.protocols = (docroot.empty()) ? &protocols[1] : protocols;
info.vhost_name = config.vhost_name.c_str();
info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE | LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
if (config.use_tls)
{
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.ssl_cert_filepath = config.cert_path.c_str();
info.ssl_private_key_filepath = config.key_path.c_str();
}
context = lws_create_context(&info);
lws_create_vhost(context, &info);
// lws_vhost * const vhost = lws_create_vhost(context, &info);
// int port = lws_get_vhost_port(vhost);
thread = std::thread([this]() {
while (!shutdown_requested)
{
data.poll();
lws_service(context, 0);
}
});
}
~detail()
{
shutdown_requested = true;
lws_cancel_service(context);
thread.join();
lws_context_destroy(context);
}
std::thread thread;
std::atomic<bool> shutdown_requested;
lws_protocols protocols[3];
lws_http_mount http_mount;
lws_context_creation_info info;
lws_context * context;
std::string docroot;
server_handler data;
uint64_t timeout_secs;
};
ws_server::ws_server(ws_config const & config)
: d(new detail(config))
{
}
ws_server::~ws_server()
{
delete d;
}
ws_server::ws_server(ws_server && other)
{
this->d = other.d;
other.d = nullptr;
}
ws_server& ws_server::operator=(ws_server && other)
{
if (this != &other)
{
delete this->d;
this->d = other.d;
other.d = nullptr;
}
return *this;
}
messagereader ws_server::perform(messagewriter writer)
{
auto result = d->data.perform(std::move(writer));
lws_cancel_service(d->context);
if(std::future_status::timeout == result.wait_for(std::chrono::seconds(d->timeout_secs)))
{
throw std::runtime_error("timeout");
}
return std::move(result.get());
}
}

@ -0,0 +1,33 @@
#ifndef WEBFUSE_WSSERVER_HPP
#define WEBFUSE_WSSERVER_HPP
#include "webfuse/ws/config.hpp"
#include "webfuse/ws/messagewriter.hpp"
#include "webfuse/ws/messagereader.hpp"
#include <vector>
#include <string>
#include <memory>
namespace webfuse
{
class ws_server
{
ws_server(ws_server const &) = delete;
ws_server& operator=(ws_server const &) = delete;
public:
ws_server(ws_config const & config);
~ws_server();
ws_server(ws_server && other);
ws_server& operator=(ws_server && other);
messagereader perform(messagewriter writer);
private:
class detail;
detail * d;
};
}
#endif

@ -0,0 +1,309 @@
#include "webfuse/ws/server_handler.hpp"
#include "webfuse/util/authenticator.hpp"
#include <exception>
#include <stdexcept>
#include <iostream>
namespace
{
std::string get_auth_token_of_known_header(lws * wsi, lws_token_indexes header)
{
std::string token;
int const length = lws_hdr_total_length(wsi, header);
if (length > 0)
{
std::vector<char> data(length + 1);
int const actual_length = lws_hdr_copy(wsi, data.data(), length + 1, header);
if (actual_length > 0)
{
token = data.data();
}
}
return token;
}
std::string get_auth_token_from_custom_header(lws * wsi, std::string const & auth_header)
{
std::string token;
int const length = lws_hdr_custom_length(wsi, auth_header.c_str(), auth_header.size());
if (length > 0)
{
std::vector<char> data(length + 1);
int const actual_length = lws_hdr_custom_copy(wsi, data.data(), length + 1,
auth_header.c_str(), auth_header.size());
if (actual_length > 0)
{
token = data.data();
}
}
return token;
}
}
namespace webfuse
{
server_handler::server_handler(std::string const & auth_app, std::string const & auth_hdr)
: connection(nullptr)
, id(0)
, shutdown_requested(false)
, is_authenticated(false)
, authenticator(auth_app)
, auth_header(auth_hdr)
{
}
int server_handler::filter_connection(lws * wsi)
{
return authenticate_via_header(wsi);
}
int server_handler::on_established(lws * wsi)
{
if (nullptr == connection)
{
connection = wsi;
id = 0;
if ((!is_authenticated) && (!authenticator.empty()))
{
{
messagewriter writer(request_type::getcreds);
std::lock_guard<std::mutex> lock(mut);
uint32_t id = next_id();
writer.set_id(id);
requests.emplace(std::move(writer));
}
lws_callback_on_writable(wsi);
}
return 0;
}
// already connected: refuse
return -1;
}
void server_handler::on_receive(lws * wsi, void* in, int len)
{
auto * fragment = reinterpret_cast<char*>(in);
current_message.append(fragment, len);
if (0 != lws_is_final_fragment(wsi))
{
try
{
webfuse::messagereader reader(current_message);
uint32_t id = reader.read_u32();
auto const message_type = reader.read_u8(); // read message type: ToDo: use it
if (static_cast<response_type>(message_type) == response_type::getcreds)
{
finish_authentication(wsi, std::move(reader));
}
else
{
std::lock_guard<std::mutex> lock(mut);
auto it = pending_responses.find(id);
if (it != pending_responses.end())
{
it->second.set_value(std::move(reader));
pending_responses.erase(it);
}
else
{
// ToDo: log request not found
std::cout << "warning: request not found: id=" << id << std::endl;
for(auto const & entry: pending_responses)
{
std::cout << "\t" << entry.first << std::endl;
}
}
}
}
catch(...)
{
// ToDo: log invalid message
std::cout << "warning: invalid message" << std::endl;
}
}
}
int server_handler::on_writable()
{
if (shutdown_requested) { return -1; }
webfuse::messagewriter writer(webfuse::request_type::unknown);
bool has_msg = false;
bool has_more = false;
{
std::lock_guard<std::mutex> lock(mut);
has_msg = !(requests.empty());
if (has_msg)
{
has_msg = true;
writer = std::move(requests.front());
requests.pop();
has_more = !(requests.empty());
}
}
if (has_msg)
{
size_t size;
unsigned char * raw_data = writer.get_data(size);
lws_write(connection, raw_data, size, LWS_WRITE_BINARY);
}
if (has_more)
{
lws_callback_on_writable(connection);
}
return 0;
}
void server_handler::on_closed(lws * wsi)
{
if (wsi == connection)
{
connection = nullptr;
is_authenticated = false;
shutdown_requested = false;
}
}
void server_handler::poll()
{
std::lock_guard<std::mutex> lock(mut);
if (!requests.empty())
{
if (nullptr != connection)
{
lws_callback_on_writable(connection);
}
else
{
requests = std::move(std::queue<webfuse::messagewriter>());
pending_responses.clear();
}
}
}
std::future<messagereader> server_handler::perform(messagewriter writer)
{
std::promise<messagereader> p;
std::future<messagereader> result = p.get_future();
if (is_authenticated)
{
std::lock_guard<std::mutex> lock(mut);
uint32_t id = next_id();
writer.set_id(id);
requests.emplace(std::move(writer));
pending_responses.emplace(id, std::move(p));
}
else
{
try
{
throw std::runtime_error("unauthenticated");
}
catch(std::exception const &ex)
{
p.set_exception(std::current_exception());
}
}
return result;
}
int server_handler::authenticate_via_header(lws * wsi)
{
// authentication is disabled
if (authenticator.empty())
{
is_authenticated = true;
return 0;
}
// authentication is enabled, but not via HTTP header
if (auth_header.empty())
{
is_authenticated = false;
return 0;
}
// delay authentication if HTTP header is not provided
std::string token = get_auth_token(wsi);
if (token.empty())
{
is_authenticated = false;
return 0;
}
// close connection, when authentication fails
webfuse::authenticator auth(authenticator);
int const result = auth.authenticate(token) ? 0 : -1;
is_authenticated = (result == 0);
return result;
}
std::string server_handler::get_auth_token(lws * wsi) const
{
if (auth_header == "authorization")
{
return get_auth_token_of_known_header(wsi, WSI_TOKEN_HTTP_AUTHORIZATION);
}
if (auth_header == "x-auth-token")
{
return get_auth_token_of_known_header(wsi, WSI_TOKEN_X_AUTH_TOKEN);
}
return get_auth_token_from_custom_header(wsi, auth_header);
}
uint32_t server_handler::next_id()
{
id++;
if (0 == id)
{
id = 1;
}
return id;
}
void server_handler::finish_authentication(lws * wsi, messagereader reader)
{
auto const credentials = reader.read_str();
webfuse::authenticator auth(authenticator);
auto const result = auth.authenticate(credentials);
if (result)
{
is_authenticated = true;
}
else
{
shutdown_requested = true;
lws_callback_on_writable(wsi);
}
}
}

@ -0,0 +1,61 @@
#ifndef WEBFUSE_SERVER_HANDLER_HPP
#define WEBFUSE_SERVER_HANDLER_HPP
#include "webfuse/ws/messagereader.hpp"
#include "webfuse/ws/messagewriter.hpp"
#include <libwebsockets.h>
#include <string>
#include <queue>
#include <unordered_map>
#include <mutex>
#include <future>
#include <atomic>
namespace webfuse
{
class server_handler
{
public:
server_handler(std::string const & auth_app, std::string const & auth_hdr);
int filter_connection(lws * wsi);
int on_established(lws* wsi);
void on_receive(lws * wsi, void* in, int len);
int on_writable();
void on_closed(lws * wsi);
std::future<messagereader> perform(messagewriter writer);
void poll();
private:
int authenticate_via_header(lws * wsi);
std::string get_auth_token(lws * wsi) const;
uint32_t next_id();
void finish_authentication(lws * wsi, messagereader reader);
struct lws * connection;
uint32_t id;
bool shutdown_requested;
std::atomic<bool> is_authenticated;
std::string authenticator;
std::string auth_header;
std::string current_message;
std::mutex mut;
std::queue<webfuse::messagewriter> requests;
std::unordered_map<uint32_t, std::promise<webfuse::messagereader>> pending_responses;
};
}
#endif

@ -0,0 +1,70 @@
#include "webfuse/ws/url.hpp"
#include <cstring>
#include <stdexcept>
namespace
{
bool starts_with(std::string const & value, std::string const & prefix)
{
return (0 == value.find(prefix));
}
std::string parse_protocol(std::string const &url, bool &use_tls)
{
if (starts_with(url, "ws://"))
{
use_tls = false;
return url.substr(strlen("ws://"));
}
if (starts_with(url, "wss://"))
{
use_tls = true;
return url.substr(strlen("wss://"));
}
throw std::runtime_error("unknown protocol");
}
}
namespace webfuse
{
constexpr uint16_t const ws_port = 80;
constexpr uint16_t const wss_port = 443;
ws_url::ws_url(std::string const & url)
{
auto remainder = parse_protocol(url, use_tls);
auto const path_start = remainder.find('/');
if (path_start != std::string::npos)
{
path = remainder.substr(path_start);
remainder = remainder.substr(0, path_start);
}
else
{
path = "/";
}
auto const port_start = remainder.find(':');
if (port_start != std::string::npos)
{
auto const port_str = remainder.substr(port_start + 1);
port = static_cast<uint16_t>(std::stoi(port_str));
hostname = remainder.substr(0, port_start);
}
else
{
port = (use_tls) ? wss_port : ws_port;
hostname = remainder;
}
}
}

@ -0,0 +1,24 @@
#ifndef WEBFUSE_URL_HPP
#define WEBFUSE_URL_HPP
#include <cinttypes>
#include <string>
namespace webfuse
{
class ws_url
{
public:
ws_url(std::string const & url);
~ws_url() = default;
bool use_tls;
std::string hostname;
uint16_t port;
std::string path;
};
}
#endif

@ -0,0 +1,71 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include "webfuse/test/daemon.hpp"
#include <gtest/gtest.h>
#include <unistd.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
namespace
{
int fs_getattr (std::string const & path, struct stat * attr)
{
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 0;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else if (path == "/foo")
{
attr->st_nlink = 0;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}
}
TEST(readdir, existing_file)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, access("/foo", F_OK)).WillOnce(Return(0));
EXPECT_CALL(fs, access("/foo", R_OK)).WillOnce(Return(0));
EXPECT_CALL(fs, access("/foo", W_OK)).WillOnce(Return(0));
EXPECT_CALL(fs, access("/foo", X_OK)).WillOnce(Return(-EACCES));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/foo";
ASSERT_EQ(0, ::access(path.c_str(), F_OK));
ASSERT_EQ(0, ::access(path.c_str(), R_OK));
ASSERT_EQ(0, ::access(path.c_str(), W_OK));
ASSERT_EQ(-1, ::access(path.c_str(), X_OK));
ASSERT_EQ(EACCES, errno);
}
TEST(access, non_existing_file)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, access("/foo", F_OK)).WillOnce(Return(-ENOENT));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/foo";
ASSERT_EQ(-1, ::access(path.c_str(), F_OK));
ASSERT_EQ(ENOENT, errno);
}

@ -0,0 +1,79 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
TEST(chmod, success)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if (path == "/some_file")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, chmod("/some_file", S_IFREG | 0644)).WillOnce(Return(0));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_file";
ASSERT_EQ(0, ::chmod(path.c_str(), 0644));
}
TEST(chmod, fail)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if (path == "/some_file")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, chmod("/some_file", S_IFREG | 0644)).WillOnce(Return(-EACCES));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_file";
ASSERT_NE(0, ::chmod(path.c_str(), 0644));
ASSERT_EQ(EACCES, errno);
}

@ -0,0 +1,79 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
TEST(chown, success)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if (path == "/some_file")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, chown("/some_file", 42, 42)).WillOnce(Return(0));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_file";
ASSERT_EQ(0, ::chown(path.c_str(), 42, 42));
}
TEST(chown, fail)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if (path == "/some_file")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, chown("/some_file", 42, 42)).WillOnce(Return(-EACCES));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_file";
ASSERT_NE(0, ::chown(path.c_str(), 42, 42));
ASSERT_EQ(EACCES, errno);
}

@ -0,0 +1,56 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
#include <fcntl.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
using testing::AtMost;
TEST(fsync, success)
{
bool file_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&file_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if ((path == "/some_file") && (file_created))
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0644;
attr->st_size = 0;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, create("/some_file", _, _)).WillOnce(Invoke([&file_created](auto const &, auto const, auto &){
file_created = true;
return 0;
}));
EXPECT_CALL(fs, fsync("/some_file", _, _)).WillOnce(Return(0));
EXPECT_CALL(fs, release("/some_file", _)).Times(AtMost(1)).WillOnce(Return(0));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_file";
int fd = creat(path.c_str(), 0644);
ASSERT_LT(0, fd);
ASSERT_EQ(0, ::fsync(fd));
close(fd);
}

@ -0,0 +1,88 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
TEST(link, sucessfully_create_link)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else if (path == "/link-target")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else if ((path == "/some_link") && (link_created))
{
attr->st_nlink = 2;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, link("/link-target", "/some_link")).WillOnce(Invoke([&link_created](auto const &, auto const &){
link_created = true;
return 0;
}));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_link";
auto const from = fixture.get_path() + "/link-target";
ASSERT_EQ(0, ::link(from.c_str(), path.c_str()));
}
TEST(link, failed_to_create_link)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else if (path == "/link-target")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, link("/link-target", "/some_link")).WillOnce(Return(-EDQUOT));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_link";
auto const from = fixture.get_path() + "/link-target";
ASSERT_NE(0, ::link(from.c_str(), path.c_str()));
ASSERT_EQ(EDQUOT, errno);
}

@ -0,0 +1,51 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
#include <fstream>
#include <sstream>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
using testing::AtMost;
TEST(mkdir, success)
{
bool created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if ((path == "/some_dir") && (created))
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0644;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, mkdir("/some_dir", _)).WillOnce(Invoke([&created](auto const &, auto) {
created = true;
return 0;
}));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_dir";
ASSERT_EQ(0, ::mkdir(path.c_str(), 0644));
}

@ -0,0 +1,52 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
#include <fcntl.h>
#include <sys/sysmacros.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
using testing::AtMost;
TEST(mknod, success)
{
bool file_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&file_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else if ((path == "/some_dev") && (file_created))
{
attr->st_nlink = 1;
attr->st_mode = S_IFIFO | 0644;
attr->st_size = 0;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, mknod("/some_dev", _, _)).Times(AtMost(1)).WillOnce(Invoke([&file_created](auto const &, auto, auto ){
file_created = true;
return 0;
}));
EXPECT_CALL(fs, release("/some_dev", _)).Times(AtMost(1)).WillOnce(Return(0));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_dev";
ASSERT_EQ(0, ::mkfifo(path.c_str(), 0644));
}

@ -0,0 +1,49 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
#include <fcntl.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
using testing::AtMost;
TEST(open, success)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if (path == "/some_file")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, open("/some_file", O_RDONLY, _)).WillOnce(Return(0));
EXPECT_CALL(fs, release("/some_file", _)).Times(AtMost(1)).WillOnce(Return(0));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_file";
int fd = ::open(path.c_str(), O_RDONLY);
ASSERT_LT(0, fd);
ASSERT_EQ(0, ::close(fd));
}

@ -0,0 +1,70 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
#include <fstream>
#include <sstream>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
using testing::AtMost;
TEST(read, success)
{
std::string expected = "Hello, World!";
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&expected](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if (path == "/some_file")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0644;
attr->st_size = expected.size();
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, open("/some_file", _, _)).WillOnce(Return(0));
EXPECT_CALL(fs, read("/some_file", _, _, _, _)).Times(AnyNumber()).WillRepeatedly(Invoke([&expected](auto const &, char * buffer, size_t buffer_size, uint64_t offset, auto){
if (offset < expected.size())
{
auto const count = std::min(buffer_size, expected.size() - offset);
memcpy(reinterpret_cast<void*>(buffer), reinterpret_cast<void const *>(&expected[offset]), count);
return (int) count;
}
else
{
return 0;
}
}));
EXPECT_CALL(fs, release("/some_file", _)).Times(AtMost(1)).WillOnce(Return(0));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_file";
std::string contents;
{
std::ifstream in(path);
std::stringstream buffer;
buffer << in.rdbuf();
contents = buffer.str();
}
ASSERT_EQ(expected, contents);
}

@ -0,0 +1,97 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include "webfuse/test/daemon.hpp"
#include <gtest/gtest.h>
#include <unistd.h>
#include <dirent.h>
#include <string>
#include <unordered_map>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
TEST(readdir, existing_dir)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](auto const & path, auto * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if ((path == "/") or (path == "/some_dir"))
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, readdir("/some_dir",_)).WillOnce(Invoke([](auto const & path, auto & entries) {
entries.push_back("foo");
return 0;
}));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_dir";
std::unordered_map<std::string, bool> expected_entries = {
{".", false},
{"..", false},
{"foo", false},
};
DIR * dir = opendir(path.c_str());
ASSERT_NE(nullptr, dir);
dirent * entry = readdir(dir);
int count = 0;
while (nullptr != entry)
{
count++;
auto it = expected_entries.find(entry->d_name);
ASSERT_NE(expected_entries.end(), it);
ASSERT_FALSE(it->second);
it->second = true;
entry = readdir(dir);
}
closedir(dir);
ASSERT_EQ(3, count);
}
TEST(readdir, non_existing_dir)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](auto const & path, auto * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, readdir("/some_dir",_)).Times(0);
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_dir";
DIR * dir = opendir(path.c_str());
ASSERT_EQ(nullptr, dir);
ASSERT_EQ(ENOENT, errno);
}

@ -0,0 +1,74 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
namespace
{
int fs_getattr(std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else if (path == "/some_link")
{
attr->st_nlink = 1;
attr->st_mode = S_IFLNK | 0755;
return 0;
}
else
{
return -ENOENT;
}
}
}
TEST(readlink, existing_link)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr));
EXPECT_CALL(fs, readlink("/some_link",_)).WillOnce(Invoke([](auto const & path, auto & out) {
out = "link-target";
return 0;
}));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_link";
char buffer[100];
auto const length = ::readlink(path.c_str(), buffer, 99);
ASSERT_LT(0, length);
buffer[length] = '\0';
ASSERT_STREQ("link-target", buffer);
}
TEST(readlink, non_existing_link)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr));
EXPECT_CALL(fs, readlink("/some_link",_)).WillOnce(Invoke([](auto const & path, auto & out) {
return -ENOENT;
}));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_link";
char buffer[100];
ASSERT_GT(0, ::readlink(path.c_str(), buffer, 100));
ASSERT_EQ(ENOENT, errno);
}

@ -0,0 +1,81 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
TEST(rename, success)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if (path == "/some_file")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, rename("/some_file", "/another_file", _)).WillOnce(Return(0));
webfuse::fixture fixture(fs);
auto const old_name = fixture.get_path() + "/some_file";
auto const new_name = fixture.get_path() + "/another_file";
ASSERT_EQ(0, ::rename(old_name.c_str(), new_name.c_str()));
}
TEST(rename, fail)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if (path == "/some_file")
{
attr->st_nlink = 1;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, rename("/some_file", "/another_file", _)).WillOnce(Return(-EDQUOT));
webfuse::fixture fixture(fs);
auto const old_name = fixture.get_path() + "/some_file";
auto const new_name = fixture.get_path() + "/another_file";
ASSERT_NE(0, ::rename(old_name.c_str(), new_name.c_str()));
ASSERT_EQ(EDQUOT, errno);
}

@ -0,0 +1,51 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
#include <fstream>
#include <sstream>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
using testing::AtMost;
TEST(rmdir, success)
{
bool removed = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&removed](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
if ((path == "/some_dir") && (!removed))
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0644;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, rmdir("/some_dir")).WillOnce(Invoke([&removed](auto const &) {
removed = true;
return 0;
}));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_dir";
ASSERT_EQ(0, ::rmdir(path.c_str()));
}

@ -0,0 +1,46 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
#include <sys/statvfs.h>
#include <fstream>
#include <sstream>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
using testing::AtMost;
TEST(statfs, success)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, statfs("/", _)).Times(AnyNumber()).WillRepeatedly(Invoke([](auto const &, struct statvfs * statistics){
return 0;
}));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/";
struct statvfs statistics;
ASSERT_EQ(0, ::statvfs(path.c_str(), &statistics));
}

@ -0,0 +1,74 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include <gtest/gtest.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
TEST(symlink, sucessfully_create_symlink)
{
bool link_created = false;
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([&link_created](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else if ((path == "/some_link") && (link_created))
{
attr->st_nlink = 1;
attr->st_mode = S_IFLNK | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, symlink("link-target", "/some_link")).WillOnce(Invoke([&link_created](auto const &, auto const &){
link_created = true;
return 0;
}));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_link";
ASSERT_EQ(0, ::symlink("link-target", path.c_str()));
}
TEST(symlink, failed_to_create_symlink)
{
webfuse::filesystem_mock fs;
EXPECT_CALL(fs, access("/",_)).Times(AnyNumber()).WillRepeatedly(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke([](std::string const & path, struct stat * attr){
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/")
{
attr->st_nlink = 1;
attr->st_mode = S_IFDIR | 0755;
return 0;
}
else
{
return -ENOENT;
}
}));
EXPECT_CALL(fs, symlink("link-target", "/some_link")).WillOnce(Return(-EDQUOT));
webfuse::fixture fixture(fs);
auto const path = fixture.get_path() + "/some_link";
ASSERT_NE(0, ::symlink("link-target", path.c_str()));
ASSERT_EQ(EDQUOT, errno);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save