Files
mars-nwe/tests/salvage/SALVAGE_BACKEND_DESIGN.md
2026-05-31 18:25:22 +02:00

7.5 KiB

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.

<SYS>/
  .recycle/
    <user>/
      <original tree>/
        <deleted file>

  .salvage/
    <user>/
      <original tree>/
        <deleted file>.json

Example:

SYS/.recycle/SUPERVISOR/PUBLIC/PMDFLTS.INI
SYS/.salvage/SUPERVISOR/PUBLIC/PMDFLTS.INI.json

When a name collides and the versions behaviour is enabled, mars_nwe uses Samba vfs_recycle compatible names and the same selected name for the sidecar JSON:

SYS/.recycle/SUPERVISOR/PUBLIC/PMDFLTS.INI
SYS/.recycle/SUPERVISOR/PUBLIC/Copy #1 of PMDFLTS.INI
SYS/.salvage/SUPERVISOR/PUBLIC/PMDFLTS.INI.json
SYS/.salvage/SUPERVISOR/PUBLIC/Copy #1 of PMDFLTS.INI.json

If versioning is disabled, or a file matches the noversions list, the previous recycled payload/metadata is replaced instead of creating a Copy #x of ... entry.

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:

{
  "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,

  "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. Optional AFP/Finder fields should only be present when the corresponding mars_nwe-xattr metadata exists; missing FinderInfo must not be serialized as synthetic all-zero data.

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:

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:

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:

NCP 0x2222 / 22 / 27  Scan Salvageable Files (old)
NCP 0x2222 / 87 / 16  Scan Salvageable Files        initial scanner implemented
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. The 87/16 scanner walks the existing .salvage sidecar files and does not create a second index or trustee store:

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; the JSON stores a fixed 32-byte FinderInfo block, using all zeroes when mars_nwe has no FinderInfo xattr,
  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:

tests/salvage/

Initial tests cover the backend without AFP:

  • local layout contract,
  • create/delete through the normal NCP server path,
  • verify the file is moved to .recycle,
  • verify the JSON exists in .salvage,
  • verify Samba-compatible Copy #x of NAME history naming.

Follow-up tests should cover:

  • cleanup removes stale JSON when the recycle file is deleted,
  • recover restores the file,
  • purge removes both file and JSON,
  • scan endpoints enumerate the JSON records in NetWare-compatible order.

Only after those tests are stable should tests/afp gain an AFP 0x13 smoke.

Configuration direction

The active low-numbered configuration block is:

51 1                  # enable salvage
52 .recycle .salvage  # payload repository, metadata repository
53 kv                 # k=keeptree, v=versions, t=touch, m=touch_mtime
54 0700 0700          # repository/subdirectory modes
55 0 0                # minsize maxsize; bytes or kb/mb/gb suffixes
56 -                  # exclude patterns
57 -                  # exclude_dir patterns
58 -                  # noversions patterns
59 -                  # cleanup/history policy, reserved

The defaults should remain compatible with a Samba-style recycle layout and should keep .salvage as metadata-only state.