149 lines
6.1 KiB
Markdown
149 lines
6.1 KiB
Markdown
# wip-fs-server
|
|
|
|
`wip-fs-server` projects one configured Linux filesystem directory onto WIP
|
|
Worldspace `/` at request time. It does not register the filesystem contents or
|
|
keep one object instance per entry. It uses `wip-host` for publication and typed
|
|
operation dispatch and uses the standard `wip-https` routes, JSON codecs,
|
|
envelopes, batching, and HTTP status mapping.
|
|
|
|
## Security model
|
|
|
|
- The effective root is opened once as a directory capability. Root path
|
|
components must be real directories (symlink components are rejected), and the
|
|
displayed effective root is read back from that same pinned descriptor. Every
|
|
request is resolved segment-by-segment with directory-relative, `O_NOFOLLOW`
|
|
handles.
|
|
- Symlinks are projected as links and are never followed. Their stored target is
|
|
available only through the `SymlinkInspect.read_link` application operation.
|
|
- Regular files, directories, symlinks, and unsupported entry types have distinct
|
|
public descriptions and capability sets.
|
|
- WIP names are Unicode. Linux names that are not valid UTF-8 are deliberately
|
|
not published; valid UTF-8 names map byte-for-byte and therefore cannot collide.
|
|
Unicode paths containing backend-invalid NUL bytes are likewise treated as
|
|
unpublished rather than as backend failures.
|
|
- No object `ref` is issued. This avoids claiming representation identity for
|
|
hard links or path-specific names. Regular files and symlinks receive an opaque
|
|
validator containing filesystem identity plus protected metadata; replacing a
|
|
path therefore does not preserve its validator.
|
|
- Protocol metadata and errors never contain the backend absolute path. Startup
|
|
prints only the explicitly configured effective root and security-sensitive
|
|
modes.
|
|
- The built-in listener is cleartext and loopback-only. Production deployments
|
|
must pass `--behind-tls-proxy` and terminate HTTPS at a trusted reverse proxy.
|
|
Plain development HTTP requires the explicit `--development-http` flag.
|
|
|
|
The implementation currently targets Linux because it uses `O_PATH`,
|
|
directory-relative descriptor traversal, and `/proc/self/fd` to reopen the same
|
|
pinned backend object without resolving the untrusted Worldspace path twice.
|
|
|
|
## Run
|
|
|
|
Read-only behind a TLS-terminating reverse proxy:
|
|
|
|
```console
|
|
cargo run -p wip-fs-server -- \
|
|
--root /srv/public-files \
|
|
--listen 127.0.0.1:8443 \
|
|
--base-path /wip \
|
|
--behind-tls-proxy
|
|
```
|
|
|
|
Local development with the write capability deliberately enabled:
|
|
|
|
```console
|
|
cargo run -p wip-fs-server -- \
|
|
--root ./example-files \
|
|
--listen 127.0.0.1:8080 \
|
|
--base-path /wip \
|
|
--development-http \
|
|
--read-write
|
|
```
|
|
|
|
The server reports the effective root, `ReadOnly`/`ReadWrite` mode, transport
|
|
mode, endpoint, and actual listen address. It does not enumerate the root.
|
|
|
|
## Standard interactions
|
|
|
|
The following examples use development HTTP for readability. The JSON is the
|
|
standard `wip-https` profile; validators and operation `Bytes` are base64 body
|
|
fields, never `ETag` or `If-Match` headers.
|
|
|
|
Fetch root:
|
|
|
|
```console
|
|
curl -sS -X POST http://127.0.0.1:8080/wip/v1/fetch \
|
|
-H 'Content-Type: application/json' -H 'Accept: application/json' \
|
|
--data '{"path":"/"}'
|
|
```
|
|
|
|
Fetch a bounded tree (the root counts toward `limit`):
|
|
|
|
```console
|
|
curl -sS -X POST http://127.0.0.1:8080/wip/v1/fetch_tree \
|
|
-H 'Content-Type: application/json' -H 'Accept: application/json' \
|
|
--data '{"path":"/","depth":2,"limit":100}'
|
|
```
|
|
|
|
A regular-file fetch returns the opaque read interface reference and an object
|
|
validator. Retrieve that descriptor with:
|
|
|
|
```console
|
|
curl -sS -X POST http://127.0.0.1:8080/wip/v1/fetch_interface \
|
|
-H 'Content-Type: application/json' -H 'Accept: application/json' \
|
|
--data '{"interface":"<read-interface-from-fetch>"}'
|
|
```
|
|
|
|
Then call a bounded range read:
|
|
|
|
```console
|
|
curl -sS -X POST http://127.0.0.1:8080/wip/v1/call_operation \
|
|
-H 'Content-Type: application/json' -H 'Accept: application/json' \
|
|
--data '{
|
|
"target":{"path":"/notes.txt","validator":"<base64-validator>"},
|
|
"interface":{"reference":"<read-interface>"},
|
|
"operation":"read",
|
|
"arguments":{"offset":0,"length":4096}
|
|
}'
|
|
```
|
|
|
|
In `--read-write` mode a second regular-file interface declares `replace`.
|
|
Writes require the current object validator and return the post-write validator:
|
|
|
|
```console
|
|
curl -sS -X POST http://127.0.0.1:8080/wip/v1/call_operation \
|
|
-H 'Content-Type: application/json' -H 'Accept: application/json' \
|
|
--data '{
|
|
"target":{"path":"/notes.txt","validator":"<base64-validator>"},
|
|
"interface":{"reference":"<write-interface>"},
|
|
"operation":"replace",
|
|
"arguments":{"contents":"dXBkYXRlZA=="}
|
|
}'
|
|
```
|
|
|
|
`fetch`, `fetch_tree`, and `fetch_interface` can also be combined through the
|
|
standard `POST /wip/v1/batch` route. `call_operation` is never batchable.
|
|
|
|
## Resource and failure behavior
|
|
|
|
`ResourceLimits` bounds request/response bytes, HTTP request-head bytes, JSON
|
|
nesting, batch items, file read/write bytes, tree depth/node count, pinned handles,
|
|
and active connections/requests. One absolute request deadline covers header and
|
|
body intake, dispatch, cooperative filesystem work, and response transmission;
|
|
stalled clients are closed and cannot occupy a worker beyond that bound. Workers
|
|
accept connections directly, so the listener never moves more than the configured
|
|
concurrency into active userspace service. Advisory file-lock acquisition uses a
|
|
nonblocking loop and checks the same deadline/cancellation rather than allowing a
|
|
contended lock to stall a worker. Tree traversal is breadth-first and opens only
|
|
requested entries. Once a limit or depth boundary is reached it reads only far
|
|
enough to
|
|
prove whether an additional visible child exists, so `truncated` is true only
|
|
when something was actually omitted. I/O failure and cancellation remain errors,
|
|
not clean exhaustion.
|
|
|
|
Whole-file replacement compares the request validator against the identity and
|
|
state of the same pinned file handle used for the write. Failures before mutation
|
|
carry a `NotCommitted` guarantee and map to `Internal` or
|
|
`ResourceLimitExceeded`; a failure after truncation/write begins maps to
|
|
`OperationOutcomeUnknown`. The server and Clients do not automatically retry
|
|
side-effecting operations.
|