diff --git a/tests/salvage/SALVAGE_BACKEND_DESIGN.md b/tests/salvage/SALVAGE_BACKEND_DESIGN.md new file mode 100644 index 0000000..713d212 --- /dev/null +++ b/tests/salvage/SALVAGE_BACKEND_DESIGN.md @@ -0,0 +1,222 @@ +# mars_nwe salvage backend design + +This document describes the planned mars_nwe salvage backend for NetWare 3.x +compatibility. The backend should live in `src/nwsalvage.c` with public +interfaces in `include/nwsalvage.h`. + +The goal is to implement NetWare-style salvage without relying on filesystem +undelete features. mars_nwe sees the delete while the file still exists, so it +can move the file to a recycle repository and write the metadata required for +NetWare scan/recover/purge calls. + +## Directory layout + +The file payload and the mars_nwe metadata are deliberately separated. + +```text +/ + .recycle/ + / + / + + + .salvage/ + / + / + .json +``` + +Example: + +```text +SYS/.recycle/SUPERVISOR/PUBLIC/PMDFLTS.INI +SYS/.salvage/SUPERVISOR/PUBLIC/PMDFLTS.INI.json +``` + +When a name collides, mars_nwe should version the recycle name in a +Samba-like way and use the same versioned name for the sidecar JSON: + +```text +SYS/.recycle/SUPERVISOR/PUBLIC/PMDFLTS.~1~.INI +SYS/.salvage/SUPERVISOR/PUBLIC/PMDFLTS.~1~.INI.json +``` + +The `.recycle` tree contains the actual deleted file. The `.salvage` tree +contains only mars_nwe metadata and indexes. This keeps the recycle area usable +for administrators and compatible with Samba-style recycle setups, while +allowing mars_nwe to keep NetWare-specific salvage state. + +## Why not filesystem undelete + +Linux and FreeBSD do not provide a portable, online, filesystem-independent +undelete API that matches NetWare salvage semantics. ext-style recovery tools, +snapshot filesystems, and forensic recovery tools do not provide the stable +per-directory scan/recover/purge model required by the NetWare NCPs. + +mars_nwe should therefore implement salvage as controlled server-side +"trash-on-delete" behavior: + +1. Delete request reaches mars_nwe. +2. mars_nwe captures the required metadata. +3. mars_nwe moves the file to `.recycle`. +4. mars_nwe writes a JSON metadata record to `.salvage`. +5. NetWare salvage NCPs operate on those metadata records. + +## JSON metadata + +Use one JSON file per deleted object. Do not use a single large JSON file per +directory; per-object JSON is easier to inspect, easier to clean up, and safer +when one record is damaged. + +Minimal example: + +```json +{ + "version": 1, + "source": "mars_nwe", + "volume": "SYS", + "deleted_by": "SUPERVISOR", + "deleted_at": 1780215322, + + "original_path": "SYS:PUBLIC/PMDFLTS.INI", + "original_parent_entry_id": 4, + "original_name": "PMDFLTS.INI", + "dos_name": "PMDFLTS.INI", + "long_name": "pmdflts.ini", + + "recycle_relative_path": ".recycle/SUPERVISOR/PUBLIC/PMDFLTS.INI", + "salvage_relative_path": ".salvage/SUPERVISOR/PUBLIC/PMDFLTS.INI.json", + + "attributes": 8192, + "size": 8161, + "atime": 1700000000, + "mtime": 1700000000, + "ctime": 1700000000, + "backup_time": 1700000000, + + "finder_info_hex": "0000000000000000000000000000000000000000000000000000000000000000", + "afp_entry_id": "0x2cc1243d", + "resource_fork_size": 0 +} +``` + +The JSON should store paths relative to the volume root wherever possible. That +makes the records easier to move with the volume and easier to read manually. + +## Cleanup of stale metadata + +Samba or an administrator may delete files from `.recycle`. mars_nwe must not +leave stale salvage entries forever. + +The salvage backend should include an opportunistic cleanup pass: + +```text +scan .salvage/**/*.json + read recycle_relative_path + if the .recycle file no longer exists: + remove the JSON record + remove empty .salvage directories where safe +``` + +Cleanup should run at least: + +- on server startup, +- before `Scan Salvageable Files`, +- before AFP `0x13` deleted-file metadata lookup, +- optionally on a low-frequency timer later. + +The first implementation can keep this simple and synchronous. + +## Backend module + +Implement the backend in: + +```text +src/nwsalvage.c +include/nwsalvage.h +``` + +Planned responsibilities: + +- build `.recycle` and `.salvage` paths for a volume, +- generate versioned recycle names on collision, +- move a deleted file into `.recycle`, +- write/read JSON records, +- remove stale JSON when the recycle file is gone, +- scan records by original parent directory entry, +- recover a record by moving the recycle file back, +- purge a record by deleting both recycle file and JSON metadata. + +The AFP code must not implement salvage directly. AFP `0x13` should be only an +adapter on top of `nwsalvage`. + +## NetWare 3.x NCP scope + +mars_nwe targets NetWare 3.x-style compatibility for this slice. The salvage +backend should initially support the 3.x-era NCPs: + +```text +NCP 0x2222 / 22 / 27 Scan Salvageable Files (old) +NCP 0x2222 / 87 / 16 Scan Salvageable Files +NCP 0x2222 / 87 / 17 Recover Salvageable File +NCP 0x2222 / 87 / 18 Purge Salvageable File +``` + +Later NetWare 4.11+ list variants and UTF-8 extensions are out of scope for the +initial mars_nwe 3.x salvage slice: + +```text +NCP 0x2222 / 87 / 41 Scan Salvageable File List +NCP 0x2222 / 87 / 42 Purge Salvageable File List +NCP 0x2222 / 89 / 16 UTF-8 / extended Scan Salvageable Files +``` + +## AFP integration + +AFP `0x13 Get Macintosh Info On Deleted Files` remains unsupported until the +mars_nwe salvage backend exists. + +After the backend is available, AFP `0x13` should: + +1. validate the request volume and deleted DOS directory entry, +2. resolve the deleted entry through `nwsalvage`, +3. return FinderInfo from the JSON record when available, +4. return ProDOS information as zeroes unless mars_nwe later gains a ProDOS + metadata store, +5. return resource fork size as zero while resource forks remain unsupported, +6. return the deleted filename from the salvage record. + +AFP must not scan `.recycle` or `.salvage` itself. + +## Test plan + +Create a separate test group first: + +```text +tests/salvage/ +``` + +Initial tests should cover the backend without AFP: + +- create sample file, +- delete through the salvage helper path, +- verify the file is moved to `.recycle`, +- verify the JSON exists in `.salvage`, +- verify cleanup removes stale JSON when the recycle file is deleted, +- verify recover restores the file, +- verify purge removes both file and JSON. + +Only after those tests are stable should `tests/afp` gain an AFP `0x13` smoke. + +## Configuration direction + +The first implementation may use fixed per-volume defaults: + +```text +recycle repository: .recycle +salvage metadata: .salvage +``` + +Later configuration can expose those names if needed. The default should remain +compatible with a Samba-style recycle layout and should keep `.salvage` as +metadata-only state.