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 - uses: actions/checkout@v3
- name: Install APT dependencies - 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 - name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 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) cmake_minimum_required(VERSION 3.10)
project(webfuse VERSION 2.0.0) project(webfuse VERSION 2.0.0)
add_executable(webfuse option(WITHOUT_PROVIDER "Disabled build of webfuse provider" OFF)
src/main.cpp) 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.
```` ## Motivation
cmake -B build
cmake --build build 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[]) 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