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

5.9 KiB

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_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:

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.