mirror of
https://github.com/falk-werner/webfuse
synced 2024-10-27 20:34:10 +00:00
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
This commit is contained in:
commit
e14f6e273c
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -18,10 +18,16 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install APT dependencies
|
||||
run: sudo apt install libfuse3-dev libwecksockets-dev
|
||||
run: sudo apt install libfuse3-dev libwebsockets-dev libgtest-dev libgmock-dev clang-tidy valgrind
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Unit Test
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target test
|
||||
|
||||
- name: Memcheck
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target memcheck
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,3 @@
|
||||
/build/
|
||||
/build/
|
||||
/.vscode/
|
||||
*.pem
|
||||
|
128
CMakeLists.txt
128
CMakeLists.txt
@ -1,5 +1,129 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(webfuse VERSION 2.0.0)
|
||||
|
||||
add_executable(webfuse
|
||||
src/main.cpp)
|
||||
option(WITHOUT_PROVIDER "Disabled build of webfuse provider" OFF)
|
||||
option(WITHOUT_TEST "Disables unit and integration tests" OFF)
|
||||
option(WITHOUT_CLANG_TIDY "Disables clang tidy" OFF)
|
||||
|
||||
set (CMAKE_CXX_STANDARD 17)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(FUSE REQUIRED IMPORTED_TARGET fuse3)
|
||||
pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets)
|
||||
|
||||
configure_file(src/webfuse/version.cpp.in version.cpp)
|
||||
|
||||
add_library(webfuse_static STATIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.cpp
|
||||
src/webfuse/webfuse.cpp
|
||||
src/webfuse/provider.cpp
|
||||
src/webfuse/fuse.cpp
|
||||
src/webfuse/request_type.cpp
|
||||
src/webfuse/response_type.cpp
|
||||
src/webfuse/util/commandline_args.cpp
|
||||
src/webfuse/util/commandline_reader.cpp
|
||||
src/webfuse/util/authenticator.cpp
|
||||
src/webfuse/filesystem.cpp
|
||||
src/webfuse/filesystem/status.cpp
|
||||
src/webfuse/filesystem/accessmode.cpp
|
||||
src/webfuse/filesystem/openflags.cpp
|
||||
src/webfuse/filesystem/filemode.cpp
|
||||
src/webfuse/filesystem/filesystem_statistics.cpp
|
||||
src/webfuse/filesystem/empty_filesystem.cpp
|
||||
src/webfuse/ws/config.cpp
|
||||
src/webfuse/ws/server.cpp
|
||||
src/webfuse/ws/server_handler.cpp
|
||||
src/webfuse/ws/client.cpp
|
||||
src/webfuse/ws/messagewriter.cpp
|
||||
src/webfuse/ws/messagereader.cpp
|
||||
src/webfuse/ws/url.cpp
|
||||
)
|
||||
|
||||
target_include_directories(webfuse_static PUBLIC src)
|
||||
target_link_libraries(webfuse_static PUBLIC PkgConfig::FUSE PkgConfig::LWS)
|
||||
|
||||
if(NOT(WITHOUT_CLANG_TIDY))
|
||||
set_property(
|
||||
TARGET webfuse_static
|
||||
PROPERTY CXX_CLANG_TIDY clang-tidy -checks=readability-*,-readability-identifier-length -warnings-as-errors=*)
|
||||
endif()
|
||||
|
||||
|
||||
add_executable(webfuse src/main.cpp)
|
||||
target_link_libraries(webfuse PRIVATE webfuse_static)
|
||||
install(TARGETS webfuse DESTINATION bin)
|
||||
|
||||
|
||||
if(NOT(WITHOUT_PROVIDER))
|
||||
|
||||
add_executable(webfuse_provider src/provider_main.cpp)
|
||||
target_link_libraries(webfuse_provider PRIVATE webfuse_static)
|
||||
install(TARGETS webfuse_provider DESTINATION bin)
|
||||
|
||||
endif()
|
||||
|
||||
if(NOT(WITHOUT_TEST))
|
||||
|
||||
pkg_check_modules(GTEST REQUIRED gtest_main)
|
||||
pkg_check_modules(GMOCK REQUIRED gmock)
|
||||
|
||||
add_executable(unit_tests
|
||||
test-src/unit/webfuse/test_app.cpp
|
||||
test-src/unit/webfuse/test_request_type.cpp
|
||||
test-src/unit/webfuse/test_response_type.cpp
|
||||
test-src/unit/webfuse/filesystem/test_status.cpp
|
||||
test-src/unit/webfuse/filesystem/test_accessmode.cpp
|
||||
test-src/unit/webfuse/filesystem/test_openflags.cpp
|
||||
test-src/unit/webfuse/filesystem/test_filemode.cpp
|
||||
)
|
||||
|
||||
target_include_directories(unit_tests PRIVATE test-src/unit ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS})
|
||||
target_compile_options(unit_tests PRIVATE
|
||||
${GTEST_CFLAGS} ${GTEST_CFLAGS_OTHER}
|
||||
${GMOCK_CFLAGS} ${GMOCK_CFLAGS_OTHER}
|
||||
)
|
||||
target_link_libraries(unit_tests PRIVATE webfuse_static ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
|
||||
|
||||
add_executable(integration_tests
|
||||
test-src/integration/webfuse/test/tempdir.cpp
|
||||
test-src/integration/webfuse/test/fixture.cpp
|
||||
test-src/integration/webfuse/test/process.cpp
|
||||
test-src/integration/webfuse/test/daemon.cpp
|
||||
test-src/integration/test_access.cpp
|
||||
test-src/integration/test_readdir.cpp
|
||||
test-src/integration/test_readlink.cpp
|
||||
test-src/integration/test_symlink.cpp
|
||||
test-src/integration/test_link.cpp
|
||||
test-src/integration/test_rename.cpp
|
||||
test-src/integration/test_chmod.cpp
|
||||
test-src/integration/test_chown.cpp
|
||||
test-src/integration/test_truncate.cpp
|
||||
test-src/integration/test_fsync.cpp
|
||||
test-src/integration/test_utimens.cpp
|
||||
test-src/integration/test_open.cpp
|
||||
test-src/integration/test_mknod.cpp
|
||||
test-src/integration/test_unlink.cpp
|
||||
test-src/integration/test_read.cpp
|
||||
test-src/integration/test_write.cpp
|
||||
test-src/integration/test_mkdir.cpp
|
||||
test-src/integration/test_rmdir.cpp
|
||||
test-src/integration/test_statfs.cpp
|
||||
)
|
||||
|
||||
target_include_directories(integration_tests PRIVATE test-src/integration ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS})
|
||||
target_compile_options(integration_tests PRIVATE
|
||||
${GTEST_CFLAGS} ${GTEST_CFLAGS_OTHER}
|
||||
${GMOCK_CFLAGS} ${GMOCK_CFLAGS_OTHER}
|
||||
)
|
||||
target_link_libraries(integration_tests PRIVATE webfuse_static ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME unit_tests COMMAND unit_tests)
|
||||
add_test(NAME integration_tests COMMAND integration_tests)
|
||||
|
||||
find_program(VALGRIND valgrind REQUIRED)
|
||||
if(VALGRIND)
|
||||
add_custom_target(memcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./unit_tests)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
79
README.md
79
README.md
@ -1,10 +1,75 @@
|
||||
# webfuse2
|
||||
[![build](https://github.com/falk-werner/webfuse/actions/workflows/build.yml/badge.svg)](https://github.com/falk-werner/webfuse/actions/workflows/build.yml)
|
||||
|
||||
Reimplementation of webfuse.
|
||||
# webfuse
|
||||
|
||||
## Build
|
||||
webfuse combines libwebsockets and libfuse. It allows to attach a remote filesystem via websockets.
|
||||
|
||||
````
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
````
|
||||
## Motivation
|
||||
|
||||
Many embedded devices, such as smart home or [IoT](https://en.wikipedia.org/wiki/Internet_of_things) devices are very limited regarding to their (non-volatile) memory resources. Such devices are typically comprised of an embedded linux and a small web server, providing an interface for maintenance purposes.
|
||||
|
||||
Some use cases, such as firmware update, require to transfer (larger) files to the device. The firmware file is often stored multiple times on the device:
|
||||
|
||||
1. cached by the web server, e.g. [lighttpd](https://redmine.lighttpd.net/boards/2/topics/3451)
|
||||
2. copied to locally, e.g. /tmp
|
||||
3. uncompressed, also to /tmp
|
||||
|
||||
Techniques like [SquashFS](https://en.wikipedia.org/wiki/SquashFS) help to avoid the third step, since the upgrade file can be mounted directly. [RAUC](https://rauc.io/) shows the use of SquashFS within an update facility.
|
||||
However at least one (unecessary) copy of the upload file is needed on the device.
|
||||
|
||||
To avoid Steps 1 and 2, it would be great to keep the update file entirely in web server, just like [NFS](https://en.wikipedia.org/wiki/Network_File_System) or [WebDAV](https://wiki.archlinux.org/index.php/WebDAV). Unfortunately, NFS is not based on any protocol, natively usable by a web application. WebDAV is based on HTTP, but it needs a server providing the update file.
|
||||
|
||||
webfuse solves this problem by using the [WebSocket](https://en.wikipedia.org/wiki/WebSocket) protocol. The emdedded device runs a service, known as webfuse adapter, awaiting incoming connections, e.g. from a web browser. The browser acts as a file system provider, providing the update file to the device.
|
||||
|
||||
## Concept
|
||||
|
||||
![concept](doc/concept.png)
|
||||
|
||||
With webfuse it is possible to implement remote filesystems based on websockets.
|
||||
Therefore, webfuse defined two roles participating in a webfuse connection:
|
||||
|
||||
- webfuse service
|
||||
- webfuse provider
|
||||
|
||||
### Webfuse Service
|
||||
|
||||
A `webfuse service` is both,
|
||||
- a [websocket](https://en.wikipedia.org/wiki/WebSocket) service providing the `webfuse` protocol
|
||||
- a [fuse](https://github.com/libfuse/libfuse) filesystem attached to a local mountpoint
|
||||
|
||||
The `webfuse service` awaits incoming connections from a `webfuse provider`. Once connected, it communicates all the filesystem requests originated by the `libfuse` to the connected `webfuse provider` using the `websocket`-based `webfuse protocol`.
|
||||
|
||||
By doing so, `webfuse` allows to inject a filesystem to a remote device.
|
||||
|
||||
### Webfuse Provider
|
||||
|
||||
A `webfuse provider` provides a filesystem to a remote device using the `websocket`-based `webfuse protocol`. Therefore, a `webfuse provider` implements a `websocket` client.
|
||||
|
||||
|
||||
## Similar Projects
|
||||
|
||||
### Davfs2
|
||||
|
||||
[davfs2](http://savannah.nongnu.org/projects/davfs2) is a Linux file system driver that allows to mount a [WebDAV](https://wiki.archlinux.org/index.php/WebDAV) resource. WebDAV is an extension to HTTP/1.1 that allows remote collaborative authoring of Web resources.
|
||||
|
||||
Unlike webfuse, davfs2 mounts a remote filesystem locally, that is provided by a WebDAV server. In contrast, webfuse starts a server awaiting client connections to attach the remote file system.
|
||||
|
||||
## Further Documentation
|
||||
|
||||
- [Build instructions](doc/build.md)
|
||||
- [Webfuse command line options](doc/webfuse.md)
|
||||
- [Webfuse provider command line options](doc/webfuse_provider.md)
|
||||
- [Webfuse Protocol](doc/protocol.md)
|
||||
- [Authentication](doc/authentication.md)
|
||||
|
||||
## webfuse legacy
|
||||
|
||||
`webfuse2` is a complete re-implementation of the original idea behind `webfuse`. In contrast to the original `webfuse` implementation, `webfuse2` provides also write access to the filesystem and allows to use all standard options of a fuse filesystem.
|
||||
|
||||
But `webfuse2` marks also some breaking changes:
|
||||
|
||||
- `webfuse2` uses a new, binary protocol which is not compatible to the JSON-based protocol of legacy `webfuse`
|
||||
- `webfuse` does not provide an API nor a library to program against
|
||||
_(if you are interested in an API or a library for webfuse2 feel free to create an issue)_
|
||||
|
||||
When you are interested in the original `webfuse` implementation take a look at this [branch](https://github.com/falk-werner/webfuse/tree/master).
|
||||
|
7
doc/README.md
Normal file
7
doc/README.md
Normal file
@ -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)
|
34
doc/authentication.md
Normal file
34
doc/authentication.md
Normal file
@ -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.
|
35
doc/build.md
Normal file
35
doc/build.md
Normal file
@ -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 |
|
BIN
doc/concept.png
Normal file
BIN
doc/concept.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
26
doc/concept.uml
Normal file
26
doc/concept.uml
Normal file
@ -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
|
916
doc/protocol.md
Normal file
916
doc/protocol.md
Normal file
@ -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
|
||||
````
|
79
doc/webfuse.md
Normal file
79
doc/webfuse.md
Normal file
@ -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`
|
39
doc/webfuse_provider.md
Normal file
39
doc/webfuse_provider.md
Normal file
@ -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`
|
1
example/authenticator/pam/.gitignore
vendored
Normal file
1
example/authenticator/pam/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build/
|
26
example/authenticator/pam/CMakeLists.txt
Normal file
26
example/authenticator/pam/CMakeLists.txt
Normal file
@ -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()
|
31
example/authenticator/pam/README.md
Normal file
31
example/authenticator/pam/README.md
Normal file
@ -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
|
7
example/authenticator/pam/etc/pam.d/webfuse
Normal file
7
example/authenticator/pam/etc/pam.d/webfuse
Normal file
@ -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
|
181
example/authenticator/pam/src/main.cpp
Normal file
181
example/authenticator/pam/src/main.cpp
Normal file
@ -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;
|
||||
}
|
47
example/authenticator/pam/src/token_decode.cpp
Normal file
47
example/authenticator/pam/src/token_decode.cpp
Normal file
@ -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;
|
||||
}
|
32
example/authenticator/pam/src/token_encode.cpp
Normal file
32
example/authenticator/pam/src/token_encode.cpp
Normal file
@ -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;
|
||||
}
|
13
example/authenticator/simple/authenticator.sh
Executable file
13
example/authenticator/simple/authenticator.sh
Executable file
@ -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
|
||||
|
||||
|
6
example/provider/javascript/README.md
Normal file
6
example/provider/javascript/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Webfuse JavaScript example
|
||||
|
||||
mkdir -p /tmp/test
|
||||
webfuse -f /tmp/test --wf-docroot .
|
||||
|
||||
Visit [http://localhost:8081/](http://localhost:8081/).
|
37
example/provider/javascript/index.html
Normal file
37
example/provider/javascript/index.html
Normal file
@ -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>
|
104
example/provider/javascript/js/filesystem.js
Normal file
104
example/provider/javascript/js/filesystem.js
Normal file
@ -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 }
|
47
example/provider/javascript/js/startup.js
Normal file
47
example/provider/javascript/js/startup.js
Normal file
@ -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);
|
9
example/provider/javascript/js/webfuse/accessmode.js
Normal file
9
example/provider/javascript/js/webfuse/accessmode.js
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
const AccessMode = {
|
||||
F_OK: 0,
|
||||
R_OK: 4,
|
||||
W_OK: 2,
|
||||
X_OK: 1
|
||||
};
|
||||
|
||||
export { AccessMode }
|
102
example/provider/javascript/js/webfuse/basefilesystem.js
Normal file
102
example/provider/javascript/js/webfuse/basefilesystem.js
Normal file
@ -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 }
|
40
example/provider/javascript/js/webfuse/errno.js
Normal file
40
example/provider/javascript/js/webfuse/errno.js
Normal file
@ -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 }
|
58
example/provider/javascript/js/webfuse/messagereader.js
Normal file
58
example/provider/javascript/js/webfuse/messagereader.js
Normal file
@ -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 }
|
75
example/provider/javascript/js/webfuse/messagewriter.js
Normal file
75
example/provider/javascript/js/webfuse/messagewriter.js
Normal file
@ -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 }
|
28
example/provider/javascript/js/webfuse/openflags.js
Normal file
28
example/provider/javascript/js/webfuse/openflags.js
Normal file
@ -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 }
|
309
example/provider/javascript/js/webfuse/webfuse.js
Normal file
309
example/provider/javascript/js/webfuse/webfuse.js
Normal file
@ -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 }
|
28
example/provider/javascript/style.css
Normal file
28
example/provider/javascript/style.css
Normal file
@ -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;
|
||||
}
|
17
example/provider/python/pylintrc
Normal file
17
example/provider/python/pylintrc
Normal file
@ -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
|
||||
|
1
example/provider/python/requirements.txt
Normal file
1
example/provider/python/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
websockets==10.4
|
600
example/provider/python/webfuse_provider.py
Executable file
600
example/provider/python/webfuse_provider.py
Executable file
@ -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()
|
7
script/create_cert.sh
Executable file
7
script/create_cert.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
openssl req -x509 -newkey rsa:4096 \
|
||||
-keyout server-key.pem \
|
||||
-out server-cert.pem \
|
||||
-days 365 -nodes -batch \
|
||||
-subj /CN=localhost
|
@ -1,6 +1,7 @@
|
||||
#include <iostream>
|
||||
#include "webfuse/webfuse.hpp"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
webfuse::app app;
|
||||
return app.run(argc, argv);
|
||||
}
|
||||
|
511
src/provider_main.cpp
Normal file
511
src/provider_main.cpp
Normal file
@ -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;
|
||||
}
|
429
src/webfuse/filesystem.cpp
Normal file
429
src/webfuse/filesystem.cpp
Normal file
@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
59
src/webfuse/filesystem.hpp
Normal file
59
src/webfuse/filesystem.hpp
Normal file
@ -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
|
41
src/webfuse/filesystem/accessmode.cpp
Normal file
41
src/webfuse/filesystem/accessmode.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
28
src/webfuse/filesystem/accessmode.hpp
Normal file
28
src/webfuse/filesystem/accessmode.hpp
Normal file
@ -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
|
140
src/webfuse/filesystem/empty_filesystem.cpp
Normal file
140
src/webfuse/filesystem/empty_filesystem.cpp
Normal file
@ -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 "";
|
||||
}
|
||||
|
||||
|
||||
}
|
48
src/webfuse/filesystem/empty_filesystem.hpp
Normal file
48
src/webfuse/filesystem/empty_filesystem.hpp
Normal file
@ -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
|
50
src/webfuse/filesystem/filemode.cpp
Normal file
50
src/webfuse/filesystem/filemode.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
75
src/webfuse/filesystem/filemode.hpp
Normal file
75
src/webfuse/filesystem/filemode.hpp
Normal file
@ -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
|
57
src/webfuse/filesystem/filesystem_i.hpp
Normal file
57
src/webfuse/filesystem/filesystem_i.hpp
Normal file
@ -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
|
43
src/webfuse/filesystem/filesystem_statistics.cpp
Normal file
43
src/webfuse/filesystem/filesystem_statistics.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
31
src/webfuse/filesystem/filesystem_statistics.hpp
Normal file
31
src/webfuse/filesystem/filesystem_statistics.hpp
Normal file
@ -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
|
79
src/webfuse/filesystem/openflags.cpp
Normal file
79
src/webfuse/filesystem/openflags.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
47
src/webfuse/filesystem/openflags.hpp
Normal file
47
src/webfuse/filesystem/openflags.hpp
Normal file
@ -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
|
122
src/webfuse/filesystem/status.cpp
Normal file
122
src/webfuse/filesystem/status.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
64
src/webfuse/filesystem/status.hpp
Normal file
64
src/webfuse/filesystem/status.hpp
Normal file
@ -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
|
304
src/webfuse/fuse.cpp
Normal file
304
src/webfuse/fuse.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
27
src/webfuse/fuse.hpp
Normal file
27
src/webfuse/fuse.hpp
Normal file
@ -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
|
437
src/webfuse/provider.cpp
Normal file
437
src/webfuse/provider.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
31
src/webfuse/provider.hpp
Normal file
31
src/webfuse/provider.hpp
Normal file
@ -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
|
38
src/webfuse/request_type.cpp
Normal file
38
src/webfuse/request_type.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
41
src/webfuse/request_type.hpp
Normal file
41
src/webfuse/request_type.hpp
Normal file
@ -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
|
39
src/webfuse/response_type.cpp
Normal file
39
src/webfuse/response_type.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
42
src/webfuse/response_type.hpp
Normal file
42
src/webfuse/response_type.hpp
Normal file
@ -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
|
67
src/webfuse/util/authenticator.cpp
Normal file
67
src/webfuse/util/authenticator.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
23
src/webfuse/util/authenticator.hpp
Normal file
23
src/webfuse/util/authenticator.hpp
Normal file
@ -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
|
56
src/webfuse/util/commandline_args.cpp
Normal file
56
src/webfuse/util/commandline_args.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
29
src/webfuse/util/commandline_args.hpp
Normal file
29
src/webfuse/util/commandline_args.hpp
Normal file
@ -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
|
30
src/webfuse/util/commandline_reader.cpp
Normal file
30
src/webfuse/util/commandline_reader.cpp
Normal file
@ -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_];
|
||||
}
|
||||
|
||||
|
||||
}
|
22
src/webfuse/util/commandline_reader.hpp
Normal file
22
src/webfuse/util/commandline_reader.hpp
Normal file
@ -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
|
11
src/webfuse/version.cpp.in
Normal file
11
src/webfuse/version.cpp.in
Normal file
@ -0,0 +1,11 @@
|
||||
#include "webfuse/version.hpp"
|
||||
|
||||
namespace webfuse
|
||||
{
|
||||
|
||||
char const * get_version()
|
||||
{
|
||||
return "@webfuse_VERSION@";
|
||||
}
|
||||
|
||||
}
|
12
src/webfuse/version.hpp
Normal file
12
src/webfuse/version.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef WEBFUSE_VERSION_HPP
|
||||
#define WEBFUSE_VERSION_HPP
|
||||
|
||||
namespace webfuse
|
||||
{
|
||||
|
||||
char const * get_version();
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
54
src/webfuse/webfuse.cpp
Normal file
54
src/webfuse/webfuse.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
15
src/webfuse/webfuse.hpp
Normal file
15
src/webfuse/webfuse.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef WEBFUSE_HPP
|
||||
#define WEBFUSE_HPP
|
||||
|
||||
namespace webfuse
|
||||
{
|
||||
|
||||
class app
|
||||
{
|
||||
public:
|
||||
int run(int argc, char * argv[]);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
240
src/webfuse/ws/client.cpp
Normal file
240
src/webfuse/ws/client.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
35
src/webfuse/ws/client.hpp
Normal file
35
src/webfuse/ws/client.hpp
Normal file
@ -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
|
133
src/webfuse/ws/config.cpp
Normal file
133
src/webfuse/ws/config.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
44
src/webfuse/ws/config.hpp
Normal file
44
src/webfuse/ws/config.hpp
Normal file
@ -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
|
195
src/webfuse/ws/messagereader.cpp
Normal file
195
src/webfuse/ws/messagereader.cpp
Normal file
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
53
src/webfuse/ws/messagereader.hpp
Normal file
53
src/webfuse/ws/messagereader.hpp
Normal file
@ -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
|
223
src/webfuse/ws/messagewriter.cpp
Normal file
223
src/webfuse/ws/messagewriter.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
60
src/webfuse/ws/messagewriter.hpp
Normal file
60
src/webfuse/ws/messagewriter.hpp
Normal file
@ -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
|
189
src/webfuse/ws/server.cpp
Normal file
189
src/webfuse/ws/server.cpp
Normal file
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
33
src/webfuse/ws/server.hpp
Normal file
33
src/webfuse/ws/server.hpp
Normal file
@ -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
|
309
src/webfuse/ws/server_handler.cpp
Normal file
309
src/webfuse/ws/server_handler.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
61
src/webfuse/ws/server_handler.hpp
Normal file
61
src/webfuse/ws/server_handler.hpp
Normal file
@ -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
|
70
src/webfuse/ws/url.cpp
Normal file
70
src/webfuse/ws/url.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
24
src/webfuse/ws/url.hpp
Normal file
24
src/webfuse/ws/url.hpp
Normal file
@ -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
|
71
test-src/integration/test_access.cpp
Normal file
71
test-src/integration/test_access.cpp
Normal file
@ -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);
|
||||
}
|
79
test-src/integration/test_chmod.cpp
Normal file
79
test-src/integration/test_chmod.cpp
Normal file
@ -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);
|
||||
}
|
79
test-src/integration/test_chown.cpp
Normal file
79
test-src/integration/test_chown.cpp
Normal file
@ -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);
|
||||
}
|
56
test-src/integration/test_fsync.cpp
Normal file
56
test-src/integration/test_fsync.cpp
Normal file
@ -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);
|
||||
}
|
88
test-src/integration/test_link.cpp
Normal file
88
test-src/integration/test_link.cpp
Normal file
@ -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);
|
||||
}
|
51
test-src/integration/test_mkdir.cpp
Normal file
51
test-src/integration/test_mkdir.cpp
Normal file
@ -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));
|
||||
}
|
52
test-src/integration/test_mknod.cpp
Normal file
52
test-src/integration/test_mknod.cpp
Normal file
@ -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));
|
||||
}
|
49
test-src/integration/test_open.cpp
Normal file
49
test-src/integration/test_open.cpp
Normal file
@ -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));
|
||||
}
|
70
test-src/integration/test_read.cpp
Normal file
70
test-src/integration/test_read.cpp
Normal file
@ -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);
|
||||
}
|
97
test-src/integration/test_readdir.cpp
Normal file
97
test-src/integration/test_readdir.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
74
test-src/integration/test_readlink.cpp
Normal file
74
test-src/integration/test_readlink.cpp
Normal file
@ -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);
|
||||
}
|
81
test-src/integration/test_rename.cpp
Normal file
81
test-src/integration/test_rename.cpp
Normal file
@ -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);
|
||||
}
|
51
test-src/integration/test_rmdir.cpp
Normal file
51
test-src/integration/test_rmdir.cpp
Normal file
@ -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()));
|
||||
}
|
46
test-src/integration/test_statfs.cpp
Normal file
46
test-src/integration/test_statfs.cpp
Normal file
@ -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));
|
||||
}
|
74
test-src/integration/test_symlink.cpp
Normal file
74
test-src/integration/test_symlink.cpp
Normal file
@ -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…
Reference in New Issue
Block a user