Files
wip-rs/crates/wip-fs-server/README.md

146 lines
6.0 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-http` 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 HTTP listener is loopback-only. TLS termination, certificates,
authentication, an externally visible URL scheme, and proxy routing are
deployment concerns rather than separate WIP transport profiles.
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
Run the loopback listener in read-only mode:
```console
cargo run -p wip-fs-server -- \
--root /srv/public-files \
--listen 127.0.0.1:8080 \
--base-path /wip
```
Run 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 \
--read-write
```
The server reports the effective root, `ReadOnly`/`ReadWrite` mode, endpoint,
and actual listen address. It does not enumerate the root.
## Standard interactions
The following examples use the built-in HTTP listener. The JSON follows the
standard WIP over HTTP binding; validators and operation `Bytes` are base64 body
fields, never `ETag` or `If-Match` headers.
Observe only the root Object:
```console
curl -sS -X POST http://127.0.0.1:8080/wip/v1/observe \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
--data '{"path":"/","depth":0}'
```
Observe the root and two levels of indexable children:
```console
curl -sS -X POST http://127.0.0.1:8080/wip/v1/observe \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
--data '{"path":"/","depth":2}'
```
A regular-file observation 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-observation>"}'
```
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=="}
}'
```
`observe` 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. Object observation recursively opens only the
requested depth. A successful response is complete through that depth; if the
configured projected-node budget would be exceeded, the interaction fails with
`ResourceLimitExceeded` instead of returning a partial observation. I/O failure
and cancellation remain errors.
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.