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, 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_NOFOLLOWhandles. - Symlinks are projected as links and are never followed. Their stored target is
available only through the
SymlinkInspect.read_linkapplication 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
refis 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:
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:
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:
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:
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:
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:
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:
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=="}
}'
Resource and failure behavior
ResourceLimits bounds request/response bytes, HTTP request-head bytes, JSON
nesting, 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.