afp: add deleted file Macintosh info endpoint
All checks were successful
Source release / source-package (push) Successful in 55s

This commit is contained in:
ai
2026-06-01 08:57:51 +00:00
committed by Mario Fetka
parent 59e8694d7d
commit 92b0c4a34a
15 changed files with 560 additions and 50 deletions

22
AI.md
View File

@@ -99,17 +99,25 @@ use, the current project status that the user pasted into the chat.
than creating unrelated top-level scripts, unless a helper binary is needed
and then started by the suite.
## AFP 0x13 next task notes
## AFP 0x13 deleted-file info notes
- After the NCP salvage endpoint work is complete, the next intended task is
AFP `0x13 Get Macintosh Info On Deleted Files`.
- Implement AFP `0x13` as an adapter over the shared mars_nwe salvage/deleted
entry record. Do not scan `.recycle` or `.salvage` directly from AFP code.
- AFP `0x13 Get Macintosh Info On Deleted File` is NCP `0x2222 / 35 / 19`
(wire subfunction byte `0x13`). The Micro Focus / Novell WebSDK request is
`VolumeNumber` plus `DOSDirectoryNumber`; the reply is FinderInfo[32],
ProDOSInfo[6], ResourceForkSize, FileNameLen, FileName.
- Implement it only as an adapter over the shared mars_nwe salvage/deleted-entry
record. Do not expose or normally open `.recycle` or `.salvage` through AFP
code; those remain hidden backend repositories.
- The first implementation returns FinderInfo from the salvage JSON snapshot,
ProDOSInfo as zeroes, resource fork size from the JSON snapshot, and the
deleted original name.
- The AFP smoke suite has a dedicated `afp_deleted_info_smoke` helper. It
pre-cleans salvage entries in the tested directory through NCP purge, creates
a temporary AFP file, writes FinderInfo, deletes it, verifies AFP `0x13`, and
purges the tested deleted entry afterwards.
- Reuse existing AFP/nwatalk metadata mechanisms for FinderInfo, AFP
attributes, entry ids, resource fork state, and related restore/lookup
behavior. Do not add a parallel AFP metadata database.
- Check `tests/afp/` first for the endpoint inventory, WebSDK notes, and current
smoke coverage before writing code.
## Logging rules

12
TODO.md
View File

@@ -246,15 +246,15 @@ Current status:
versioned scan/recover/purge and stale-sidecar cleanup. It should be the
backing data source for AFP deleted-file compatibility work.
Next task:
Current AFP focus:
- Implement AFP `0x13 Get Macintosh Info On Deleted Files` as an adapter over
the mars_nwe salvage/deleted-entry record.
- AFP `0x13` must not scan `.recycle` or `.salvage` directly; those remain
hidden backend repositories.
- AFP `0x13 Get Macintosh Info On Deleted File` is now implemented as a
salvage/deleted-entry adapter and covered by the AFP smoke suite.
- Keep future AFP deleted-file work on the shared salvage backend; do not expose
`.recycle` or `.salvage` through normal AFP/NCP path opens.
- Keep AFP metadata restore/lookup paths tied to the existing mars_nwe AFP and
nwatalk mechanisms, not a new side database.
- Keep the detailed AFP TODO, inventory, and audit notes in `tests/afp/`.
- Keep the detailed AFP inventory and audit notes in `tests/afp/`.
## Deferred / optional protocol work

View File

@@ -191,6 +191,8 @@ int nwsalvage_write_metadata(const char *metadata_path,
const struct nwsalvage_deleted_entry *entry);
int nwsalvage_read_metadata(const char *metadata_path,
struct nwsalvage_metadata_entry *entry);
int nwsalvage_metadata_finder_info(const struct nwsalvage_metadata_entry *entry,
unsigned char *out, size_t out_len);
/*
* Scan one salvageable entry in a directory. scan_sequence must be
@@ -201,6 +203,9 @@ int nwsalvage_scan_directory(int volume, const char *relative_dir,
unsigned long directory_base,
unsigned long scan_sequence,
struct nwsalvage_scan_result *result);
int nwsalvage_find_by_deleted_directory_number(int volume,
unsigned long dos_directory_number,
struct nwsalvage_scan_result *result);
int nwsalvage_recover_scan_result(int volume,
const struct nwsalvage_scan_result *scan,
const char *dest_unixname, int task);

View File

@@ -53,6 +53,7 @@
#include "nwattrib.h"
#include "nwarchive.h"
#include "namedos.h"
#include "nwsalvage.h"
int act_pid = 0;
@@ -2001,6 +2002,67 @@ static int afp_fill_file_info_response(const char *unixname,
return(include_prodos_info ? 120 : 114);
}
static int afp_get_macintosh_info_on_deleted_file(uint8 *afp_req, int afp_len,
uint8 *response)
{
uint8 volume_number;
uint32 dos_directory_number;
struct nwsalvage_scan_result scan;
uint8 finder_info[NWATALK_FINDER_INFO_LEN];
const char *name;
int name_len;
int result;
if (afp_len < 6) {
XDPRINTF((2,0, "AFP Get Macintosh Info On Deleted File rejected: short request len=%d",
afp_len));
return(-0x7e); /* NCP Boundary Check Failed */
}
volume_number = afp_req[1];
dos_directory_number = GET_BE32(afp_req + 2);
if (!nwatalk_backend_available()) {
XDPRINTF((3,0, "AFP Get Macintosh Info On Deleted File rejected: AFP xattr metadata backend unavailable"));
return(-0xbf); /* invalid namespace */
}
memset(&scan, 0, sizeof(scan));
result = nwsalvage_find_by_deleted_directory_number((int)volume_number,
(unsigned long)dos_directory_number,
&scan);
if (result < 0) {
XDPRINTF((2,0, "AFP Get Macintosh Info On Deleted File scan failed: vol=%d dosdir=0x%08x errno=%d",
(int)volume_number, (unsigned int)dos_directory_number, errno));
return(-0x9c); /* Invalid Path */
}
if (result == 0) {
XDPRINTF((2,0, "AFP Get Macintosh Info On Deleted File not found: vol=%d dosdir=0x%08x",
(int)volume_number, (unsigned int)dos_directory_number));
return(-0x9c); /* Invalid Path */
}
memset(response, 0, 51 + NWSALVAGE_NAME_MAX);
memset(finder_info, 0, sizeof(finder_info));
if (nwsalvage_metadata_finder_info(&scan.metadata, finder_info,
sizeof(finder_info)) == 0)
memcpy(response + 0, finder_info, sizeof(finder_info));
/* ProDOSInfo at +32 stays zero until mars_nwe stores ProDOS metadata. */
U32_TO_BE32((uint32)scan.metadata.resource_fork_size, response + 38);
name = scan.metadata.original_name[0] ? scan.metadata.original_name : "";
name_len = min((int)strlen(name), NWSALVAGE_NAME_MAX - 1);
response[42] = (uint8)name_len;
memcpy(response + 43, name, name_len);
XDPRINTF((3, 0,
"INFO AFP 35/19 DONE fn=0x23 sub=0x13 vol=0x%02x dosdir=0x%08x name=\"%s\" resource=0x%08lx",
(unsigned int)volume_number, (unsigned int)dos_directory_number,
name, (unsigned long)scan.metadata.resource_fork_size));
return(43 + name_len);
}
static int afp_get_file_information(uint8 *afp_req, int afp_len,
uint8 *response, const char *call_name)
{
@@ -4629,6 +4691,11 @@ static int handle_ncp_serv(void)
afp_len, responsedata);
if (result > -1) data_len = result;
else completition = (uint8)-result;
} else if (ufunc == 0x13) {
int result = afp_get_macintosh_info_on_deleted_file(afp_req,
afp_len, responsedata);
if (result > -1) data_len = result;
else completition = (uint8)-result;
} else if (ufunc == 0x05 || ufunc == 0x0f) {
int result = afp_get_file_information(afp_req,
afp_len, responsedata,

View File

@@ -1915,6 +1915,115 @@ static int nwsalvage_scan_metadata_dir(const char *metadata_dir,
return(found);
}
static int nwsalvage_scan_metadata_tree_by_directory_number(
const char *metadata_dir, const struct nwsalvage_config *config,
const char *volume_root, const char *volume_name,
unsigned long dos_directory_number,
struct nwsalvage_scan_result *result)
{
DIR *dir;
struct dirent *de;
unsigned long match_index;
int found;
if (!metadata_dir || !config || !volume_root || !volume_name || !result) {
errno = EINVAL;
return(-1);
}
match_index = 0;
found = nwsalvage_scan_metadata_dir(metadata_dir, config, volume_root,
volume_name, dos_directory_number,
0, &match_index, result);
if (found != 0)
return(found);
dir = opendir(metadata_dir);
if (!dir)
return(errno == ENOENT ? 0 : -1);
while ((de = readdir(dir)) != NULL) {
char child[NWSALVAGE_PATH_MAX];
struct stat st;
if (de->d_name[0] == '.')
continue;
if (nwsalvage_path_join(child, sizeof(child), metadata_dir, de->d_name) < 0)
continue;
if (stat(child, &st) < 0 || !S_ISDIR(st.st_mode))
continue;
found = nwsalvage_scan_metadata_tree_by_directory_number(
child, config, volume_root, volume_name, dos_directory_number, result);
if (found != 0)
break;
}
closedir(dir);
return(found);
}
int nwsalvage_find_by_deleted_directory_number(int volume,
unsigned long dos_directory_number,
struct nwsalvage_scan_result *result)
{
struct nwsalvage_config config;
char volume_root[NWSALVAGE_PATH_MAX];
char metadata_root[NWSALVAGE_PATH_MAX];
char volume_name[NWSALVAGE_REPOSITORY_NAME_MAX];
DIR *root;
struct dirent *de;
int found = 0;
if (!result) {
errno = EINVAL;
return(-1);
}
if (nwsalvage_config_load_from_ini(&config, nwsalvage_ini_get, NULL) < 0)
return(-1);
if (!config.enabled)
return(0);
if (nwsalvage_copy_volume_root(volume, volume_root, sizeof(volume_root)) < 0)
return(-1);
if (nw_get_volume_name(volume, (uint8 *)volume_name,
sizeof(volume_name)) < 1)
return(-1);
if (nwsalvage_path_join(metadata_root, sizeof(metadata_root), volume_root,
config.metadata_repository) < 0)
return(-1);
root = opendir(metadata_root);
if (!root)
return(errno == ENOENT ? 0 : -1);
while ((de = readdir(root)) != NULL) {
char user_dir[NWSALVAGE_PATH_MAX];
struct stat st;
if (de->d_name[0] == '.')
continue;
if (nwsalvage_path_join(user_dir, sizeof(user_dir),
metadata_root, de->d_name) < 0)
continue;
if (stat(user_dir, &st) < 0 || !S_ISDIR(st.st_mode))
continue;
found = nwsalvage_scan_metadata_tree_by_directory_number(
user_dir, &config, volume_root, volume_name,
dos_directory_number, result);
if (found != 0)
break;
}
closedir(root);
if (found > 0)
result->scan_volume = (unsigned long)volume;
return(found);
}
int nwsalvage_scan_directory(int volume, const char *relative_dir,
unsigned long directory_base,
unsigned long scan_sequence,
@@ -2021,6 +2130,19 @@ static int nwsalvage_hex_to_bytes(const char *hex, uint8 *out, size_t out_len)
return(0);
}
int nwsalvage_metadata_finder_info(const struct nwsalvage_metadata_entry *entry,
unsigned char *out, size_t out_len)
{
if (!entry || !out) {
errno = EINVAL;
return(-1);
}
memset(out, 0, out_len);
if (!entry->finder_info_hex[0])
return(0);
return(nwsalvage_hex_to_bytes(entry->finder_info_hex, out, out_len));
}
static int nwsalvage_finder_info_is_zero(const uint8 *finder_info, size_t len)
{
size_t i;

View File

@@ -26,13 +26,13 @@ must exist and be verified. The relevant non-AFP NCP family is:
- `NCP 0x2222 / 87 / 18` - Purge Salvageable File
- optional legacy `NCP 0x2222 / 22 / 27` - Scan Salvageable File (old)
Once that backend is present, AFP `0x13` should only translate the WebSDK/NWAFP
AFP `0x13` translates the WebSDK/NWAFP
wire request to the mars_nwe salvage entry and then append AFP-specific deleted
Macintosh metadata.
## Intended future mapping
When the salvage backend exists, AFP `0x13` should behave like this:
AFP `0x13` behaves like this:
1. Validate the request volume and DOS directory entry.
2. Look up the deleted entry through the mars_nwe salvage/deleted-entry backend.

View File

@@ -30,7 +30,7 @@ tests/afp/afp_endpoint_inventory.py
| `0x10` | AFP 2.0 Set File Information | implemented | same backend discipline as `0x09` |
| `0x11` | AFP 2.0 Scan File Information | implemented for path and directory entry-id starts | same backend discipline as `0x0a` |
| `0x12` | AFP Get DOS Name From Entry ID | implemented | mars_nwe path/namespace and AFP entry-id reverse lookup |
| `0x13` | AFP Get Macintosh Info On Deleted Files | unsupported / backend-dependent | requires mars_nwe salvage/deleted-entry backend first |
| `0x13` | AFP Get Macintosh Info On Deleted Files | implemented / salvage-backed | covered by afp_deleted_info_smoke |
## Backend rules for the final audit
@@ -51,18 +51,15 @@ must verify that each endpoint continues to use the mars_nwe core backend:
- `user.org.mars-nwe.afp.attributes` only for future AFP-only bits
- Resource forks remain unsupported and should return the documented completion
code instead of inventing a parallel storage backend.
- Deleted-file Macintosh metadata (`0x13`) must wait for the mars_nwe salvage
backend and must not perform an AFP-local deleted-file scan.
- Deleted-file Macintosh metadata (`0x13`) is implemented on the mars_nwe
salvage backend and must not perform an AFP-local deleted-file scan.
## Known final-audit items
## Resolved final-audit items
One unsupported item should stay visible until the WebSDK / Novell header
comparison is completed:
Resolved WebSDK compatibility items:
1. `0x13` is intentionally unsupported because the salvage/deleted-entry backend
is not part of the current AFP slice.
Resolved WebSDK compatibility item:
1. `0x13` is implemented as a salvage/deleted-entry backend adapter and covered
by `afp_deleted_info_smoke`.
- `0x0a` / `0x11` entry-id-only scan requests are supported when the base entry
ID resolves to a directory through mars_nwe namespace/basehandle logic.

View File

@@ -79,7 +79,7 @@ mars_nwe helper family used by the endpoint, not just the AFP handler.
| `0x10` | AFP 2.0 Set File Information | inline AFP 2.0 set file information case | TODO | TODO | TODO | TODO | AFP 2.0 variant |
| `0x11` | AFP 2.0 Scan File Information | `afp_scan_file_information` | TODO | TODO | TODO | TODO | entry-id-only directory scan supported |
| `0x12` | AFP Get DOS Name From Entry ID | `afp_get_dos_name_from_entry_id` | TODO | TODO | TODO | TODO | |
| `0x13` | AFP Get Macintosh Info On Deleted Files | unsupported | TODO | TODO | TODO | TODO | requires salvage/deleted-entry backend |
| `0x13` | AFP Get Macintosh Info On Deleted Files | implemented | server | helper | suite | docs | salvage-backed |
## Backend discipline checklist
@@ -106,15 +106,15 @@ are not valid scan bases.
### Deleted-file Macintosh metadata
AFP `0x13` is intentionally unsupported for now. It should remain unsupported
until the normal NetWare salvage family is present and verified:
AFP `0x13` is now implemented as a thin adapter over the normal NetWare
salvage family, which is present and verified:
- `NCP 0x2222 / 87 / 16` - Scan Salvageable Files
- `NCP 0x2222 / 87 / 17` - Recover Salvageable File
- `NCP 0x2222 / 87 / 18` - Purge Salvageable File
- optional legacy `NCP 0x2222 / 22 / 27` - Scan Salvageable File (old)
Do not implement AFP `0x13` as an AFP-local deleted-file scan.
Do not extend AFP `0x13` as an AFP-local deleted-file scan; keep it on the shared salvage backend.
## Completion criteria

View File

@@ -66,7 +66,7 @@ map:
| `0x10` | `0002R004.htm` | AFP 2.0 Set File Information | implemented through shared semantics |
| `0x11` | `0002R003.htm` | AFP 2.0 Scan File Information | implemented for path and directory entry-id starts |
| `0x12` | `0002R009.htm` | AFP Get DOS Name From Entry ID | implemented |
| `0x13` | `0002R014.htm` | AFP Get Macintosh Info On Deleted Files | unsupported / salvage-backend dependent |
| `0x13` | `0002R014.htm` | AFP Get Macintosh Info On Deleted Files | implemented / salvage-backend adapter |
No extra AFP subfunction beyond `0x13` appeared in the WebSDK AFP reference TOC.
@@ -115,7 +115,7 @@ The implementation remains intentionally narrow:
2. The existing mars_nwe directory scan path is reused after resolution.
3. File AFP xattr entry IDs are not valid scan bases.
### AFP 0x13 should remain unsupported for this slice
### AFP 0x13 is salvage-backed in this slice
The WebSDK `0x13` page makes this call dependent on deleted-file state: the
request is keyed by volume and DOS directory entry, and the reply describes a
@@ -127,15 +127,15 @@ contains the normal salvage family:
- Purge Salvageable File
- Scan Salvageable File (old)
Therefore `0x13` should stay unsupported until mars_nwe has a verified
salvage/deleted-entry backend. It must not be implemented as an AFP-local scan
of live paths or filesystem trash.
Therefore `0x13` is implemented only after the verified mars_nwe
salvage/deleted-entry backend. It must remain a backend adapter, not an
AFP-local scan of live paths or filesystem trash.
## Result
The WebSDK pass does not require renaming the AFP endpoint map again. The
remaining concrete work before closing the current AFP slice is:
1. Keep `0x13` documented unsupported until salvage exists.
1. Keep `0x13` documented as salvage-backed; do not add a second AFP deleted-file store.
2. Re-run `tests/afp/afp_endpoint_inventory.py` and the AFP smoke suite.
3. Fill the final audit table with these WebSDK source paths and results.

View File

@@ -79,6 +79,10 @@ add_executable(afp_delete_smoke afp_delete_smoke.c)
target_include_directories(afp_delete_smoke PRIVATE ${NCPFS_INCLUDE_DIR})
target_link_libraries(afp_delete_smoke ${NCPFS_LIBRARY})
add_executable(afp_deleted_info_smoke afp_deleted_info_smoke.c)
target_include_directories(afp_deleted_info_smoke PRIVATE ${NCPFS_INCLUDE_DIR})
target_link_libraries(afp_deleted_info_smoke ${NCPFS_LIBRARY})
add_executable(afp_rename_smoke afp_rename_smoke.c)
target_include_directories(afp_rename_smoke PRIVATE ${NCPFS_INCLUDE_DIR})
target_link_libraries(afp_rename_smoke ${NCPFS_LIBRARY})

View File

@@ -1386,3 +1386,22 @@ AFP 2.0 variants, bitmaps, record lengths, and unsupported completion codes for
the complete smoke-covered endpoint set. It should also audit every AFP handler
implementation and confirm that NetWare semantics still go through existing
mars_nwe functions/wrappers rather than AFP-local shortcuts.
## AFP Get Macintosh Info On Deleted File smoke test
`afp_deleted_info_smoke` covers the WebSDK / Novell AFP deleted-file call:
```text
NCP 0x2222/35/19 AFP Get Macintosh Info On Deleted File
```
The request carries `VolumeNumber` and `DOSDirectoryNumber`. The server maps
that deleted directory number through the shared mars_nwe salvage/deleted-entry
backend and returns the FinderInfo snapshot, zero ProDOS info, the stored
resource fork size, and the deleted original filename. It does not open or
expose `.recycle` or `.salvage` as AFP-visible paths.
The full AFP smoke suite pre-cleans deleted entries in the tested directory,
creates a temporary AFP file, writes FinderInfo through AFP Set File
Information, deletes the file through AFP Delete, verifies `0x13`, and purges
the tested salvage entry afterwards.

View File

@@ -6,27 +6,21 @@ neighboring AFP documentation files, not in the root `TODO.md`.
## Remaining AFP work
### `0x13 AFP Get Macintosh Info On Deleted Files`
### `0x13 AFP Get Macintosh Info On Deleted File`
Current status:
- Unsupported in the current AFP compatibility slice.
- Documented as salvage/deleted-entry-backend dependent.
- Must not be implemented as an AFP-local deleted-file scan.
- Implemented as a conservative adapter over the mars_nwe salvage/deleted-entry
backend.
- Covered by `afp_deleted_info_smoke` and the full `afp_smoke_suite.sh`.
- Does not scan or expose `.recycle` / `.salvage` as AFP-visible paths.
Required backend first:
- `NCP 0x2222 / 87 / 16` - Scan Salvageable Files
- `NCP 0x2222 / 87 / 17` - Recover Salvageable File
- `NCP 0x2222 / 87 / 18` - Purge Salvageable File
- Optional legacy `NCP 0x2222 / 22 / 27` - Scan Salvageable File (old)
Future AFP mapping:
Current AFP mapping:
1. Resolve the deleted DOS directory entry through the mars_nwe salvage backend.
2. Return FinderInfo from AFP metadata when available, otherwise zeroes.
2. Return FinderInfo from the salvage JSON snapshot.
3. Return ProDOS information as zeroes unless mars_nwe gains a real ProDOS store.
4. Return resource fork size as zero while resource forks remain unsupported.
4. Return resource fork size from the salvage JSON snapshot.
5. Return the deleted filename from the salvage/deleted-entry record.
References:

View File

@@ -0,0 +1,266 @@
/*
* Linux smoke test for AFP 0x13 Get Macintosh Info On Deleted File.
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncp/nwcalls.h>
#include <ncp/ncplib.h>
#include <ncp/kernel/ncp.h>
#ifndef NCPC_SUBFUNCTION
#define NCPC_SUBFUNCTION 0x10000
#endif
#ifndef NCPC_SFN
#define NCPC_SFN(FN, SFN) ((FN) | ((SFN) << 8) | NCPC_SUBFUNCTION)
#endif
#define AFP_GET_MAC_INFO_DELETED 0x13
#define NWE_NO_MORE_FILES 0x89ff
#define AFP_DELETED_REPLY_MAX 512
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [--expect-name NAME] [--expect-type FOURCC] [--expect-creator FOURCC] [--purge-after] [--purge-all] [ncpfs options] DIRECTORY\n"
"\n"
"ncpfs options are parsed by ncp_initialize(), for example:\n"
" -S SERVER -U USER -P PASSWORD -n\n"
"\n"
"Examples:\n"
" %s -S MARS -U SUPERVISOR -P secret --expect-name TEST SYS:PUBLIC\n"
" %s --purge-all -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n",
prog, prog, prog);
}
static void cpu_to_be32(uint32_t v, uint8_t p[4])
{
p[0] = (uint8_t)(v >> 24);
p[1] = (uint8_t)(v >> 16);
p[2] = (uint8_t)(v >> 8);
p[3] = (uint8_t)v;
}
static uint32_t be32_to_cpu(const uint8_t p[4])
{
return ((uint32_t)p[0] << 24) |
((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) |
p[3];
}
static int find_deleted(NWCONN_HANDLE conn, const char *dir,
const char *expect_name,
struct ncp_deleted_file *info,
char *name, size_t name_len)
{
long err;
memset(info, 0, sizeof(*info));
info->seq = -1;
while ((err = ncp_ns_scan_salvageable_file(conn, NW_NS_DOS,
NCP_DIRSTYLE_NOHANDLE, 0, 0, (const unsigned char *)dir,
NCP_PATH_STD, info, name, (int)name_len)) == 0) {
printf("NCP salvage scan candidate seq=%d vol=%u base=%u name=%s\n",
(int)info->seq, (unsigned int)info->vol,
(unsigned int)info->base, name);
if (!expect_name || !strcmp(name, expect_name))
return 0;
}
fprintf(stderr, "No matching deleted AFP entry found: dir=%s expect=%s last_error=0x%04x\n",
dir, expect_name ? expect_name : "<any>", (unsigned int)err);
return 1;
}
static int purge_all(NWCONN_HANDLE conn, const char *dir)
{
struct ncp_deleted_file info;
char name[512];
long err;
int count = 0;
memset(&info, 0, sizeof(info));
info.seq = -1;
while ((err = ncp_ns_scan_salvageable_file(conn, NW_NS_DOS,
NCP_DIRSTYLE_NOHANDLE, 0, 0, (const unsigned char *)dir,
NCP_PATH_STD, &info, name, sizeof(name))) == 0) {
printf("NCP salvage purge candidate seq=%d vol=%u base=%u name=%s\n",
(int)info.seq, (unsigned int)info.vol,
(unsigned int)info.base, name);
err = ncp_ns_purge_file(conn, &info);
if (err) {
fprintf(stderr, "NCP purge failed: dir=%s name=%s err=0x%04x\n",
dir, name, (unsigned int)err);
return 1;
}
count++;
memset(&info, 0, sizeof(info));
info.seq = -1;
}
printf("NCP purge all done dir=%s purged=%d last_error=0x%04x\n",
dir, count, (unsigned int)err);
return 0;
}
int main(int argc, char **argv)
{
NWCONN_HANDLE conn;
NW_FRAGMENT reply;
long init_err = 0;
const char *dir = NULL;
const char *expect_name = NULL;
const char *expect_type = NULL;
const char *expect_creator = NULL;
int purge_after = 0;
int do_purge_all = 0;
struct ncp_deleted_file info;
char name[512];
uint8_t request[5];
uint8_t reply_buf[AFP_DELETED_REPLY_MAX];
uint32_t resource_size;
int name_len;
NWCCODE err;
int i;
if (NWCallsInit(NULL, NULL)) {
fprintf(stderr, "NWCallsInit failed\n");
return 2;
}
conn = ncp_initialize(&argc, argv, 1, &init_err);
if (!conn) {
fprintf(stderr, "ncp_initialize/login failed: %ld\n", init_err);
usage(argv[0]);
return 2;
}
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--expect-name")) {
if (++i >= argc) { usage(argv[0]); ncp_close(conn); return 2; }
expect_name = argv[i];
} else if (!strcmp(argv[i], "--expect-type")) {
if (++i >= argc || strlen(argv[i]) != 4) {
fprintf(stderr, "--expect-type must be four characters\n");
ncp_close(conn); return 2;
}
expect_type = argv[i];
} else if (!strcmp(argv[i], "--expect-creator")) {
if (++i >= argc || strlen(argv[i]) != 4) {
fprintf(stderr, "--expect-creator must be four characters\n");
ncp_close(conn); return 2;
}
expect_creator = argv[i];
} else if (!strcmp(argv[i], "--purge-after")) {
purge_after = 1;
} else if (!strcmp(argv[i], "--purge-all")) {
do_purge_all = 1;
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
usage(argv[0]);
ncp_close(conn);
return 0;
} else if (!dir) {
dir = argv[i];
} else {
fprintf(stderr, "unexpected argument: %s\n", argv[i]);
usage(argv[0]);
ncp_close(conn);
return 2;
}
}
if (!dir) {
usage(argv[0]);
ncp_close(conn);
return 2;
}
if (do_purge_all) {
int rc = purge_all(conn, dir);
ncp_close(conn);
return rc;
}
if (find_deleted(conn, dir, expect_name, &info, name, sizeof(name))) {
ncp_close(conn);
return 1;
}
request[0] = (uint8_t)info.vol;
cpu_to_be32(info.base, request + 1);
memset(reply_buf, 0, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, NCPC_SFN(0x23, AFP_GET_MAC_INFO_DELETED),
request, sizeof(request), &reply);
if (err) {
fprintf(stderr,
"AFP Get Macintosh Info On Deleted File failed: completion=0x%02x (%u) vol=%u dosdir=0x%08x\n",
(unsigned int)err & 0xff, (unsigned int)err,
(unsigned int)info.vol, (unsigned int)info.base);
ncp_close(conn);
return 1;
}
if (reply.fragSize < 43) {
fprintf(stderr, "short AFP deleted-info reply: %zu bytes\n", reply.fragSize);
ncp_close(conn);
return 1;
}
resource_size = be32_to_cpu(reply_buf + 38);
name_len = reply_buf[42];
if (name_len < 0 || 43 + name_len > (int)reply.fragSize) {
fprintf(stderr, "bad AFP deleted-info name length: %d reply=%zu\n",
name_len, reply.fragSize);
ncp_close(conn);
return 1;
}
printf("AFP deleted info seq=%d vol=%u dosdir=%u resource=%u name=%.*s\n",
(int)info.seq, (unsigned int)info.vol, (unsigned int)info.base,
(unsigned int)resource_size, name_len, reply_buf + 43);
printf("AFP deleted finder type=%.4s creator=%.4s\n", reply_buf, reply_buf + 4);
if (expect_name && (name_len != (int)strlen(expect_name) ||
memcmp(reply_buf + 43, expect_name, name_len))) {
fprintf(stderr, "AFP deleted info name mismatch: got=%.*s expected=%s\n",
name_len, reply_buf + 43, expect_name);
ncp_close(conn);
return 1;
}
if (expect_type && memcmp(reply_buf, expect_type, 4)) {
fprintf(stderr, "AFP deleted info Finder type mismatch: got=%.4s expected=%s\n",
reply_buf, expect_type);
ncp_close(conn);
return 1;
}
if (expect_creator && memcmp(reply_buf + 4, expect_creator, 4)) {
fprintf(stderr, "AFP deleted info Finder creator mismatch: got=%.4s expected=%s\n",
reply_buf + 4, expect_creator);
ncp_close(conn);
return 1;
}
if (purge_after) {
err = ncp_ns_purge_file(conn, &info);
if (err) {
fprintf(stderr, "NCP purge after AFP deleted-info failed: err=0x%04x\n",
(unsigned int)err);
ncp_close(conn);
return 1;
}
printf("NCP purge after AFP deleted-info ok seq=%d name=%s\n",
(int)info.seq, name);
}
ncp_close(conn);
return 0;
}

View File

@@ -70,7 +70,7 @@ INLINE_ENDPOINTS: Dict[int, Tuple[str, str, Tuple[str, ...]]] = {
0x0D: ("inline AFP 2.0 create directory case", "implemented", ("mars_nwe path/namespace", "mars_nwe object lifecycle")),
0x0E: ("inline AFP 2.0 create file case", "implemented", ("mars_nwe path/namespace", "mars_nwe object lifecycle", "AFP-only xattrs via nwatalk")),
0x10: ("inline AFP 2.0 set file information case", "implemented", ("mars_nwe path/namespace", "mars_nwe attributes/archive/fileinfo", "mars_nwe trustee/rights", "AFP-only xattrs via nwatalk")),
0x13: ("manual review", "unsupported / final-audit item", ("needs manual review",)),
0x13: ("inline AFP deleted-info case", "implemented", ("mars_nwe salvage/deleted-entry backend", "AFP FinderInfo snapshot")),
}

View File

@@ -710,6 +710,34 @@ run_cmd \
"$SCRIPT_DIR/afp_set_file_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
--afp09 --finder-info-only --type "$FINDER_TYPE" --creator "$FINDER_CREATOR" "$NETWARE_PATH"
run_optional_cmd \
"Prepare AFP Deleted Info purge" \
"./afp_deleted_info_smoke --purge-all $COMMON_PRINT '$DIR_PATH'" \
"$SCRIPT_DIR/afp_deleted_info_smoke" --purge-all \
-S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$DIR_PATH" || true
run_optional_cmd \
"Prepare AFP Deleted Info file cleanup" \
"./afp_delete_smoke $COMMON_PRINT '$CREATE_FILE_PATH'" \
"$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE_PATH" || true
run_cmd \
"AFP Deleted Info create file" \
"./afp_create_file_smoke $COMMON_PRINT '$CREATE_FILE_PATH'" \
"$SCRIPT_DIR/afp_create_file_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE_PATH"
run_cmd \
"AFP Deleted Info set FinderInfo" \
"./afp_set_file_info_smoke $COMMON_PRINT --finder-info-only --type '$FINDER_TYPE' --creator '$FINDER_CREATOR' '$CREATE_FILE_PATH'" \
"$SCRIPT_DIR/afp_set_file_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
--finder-info-only --type "$FINDER_TYPE" --creator "$FINDER_CREATOR" "$CREATE_FILE_PATH"
run_cmd \
"AFP Deleted Info delete file" \
"./afp_delete_smoke $COMMON_PRINT '$CREATE_FILE_PATH'" \
"$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE_PATH"
run_cmd \
"AFP Get Macintosh Info On Deleted File" \
"./afp_deleted_info_smoke --expect-type '$FINDER_TYPE' --expect-creator '$FINDER_CREATOR' --purge-after $COMMON_PRINT '$DIR_PATH'" \
"$SCRIPT_DIR/afp_deleted_info_smoke" --expect-type "$FINDER_TYPE" --expect-creator "$FINDER_CREATOR" --purge-after \
-S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$DIR_PATH"
run_cmd \
"AFP Set File Information Hidden" \
"./afp_set_file_info_smoke $COMMON_PRINT --attributes-only --hidden '$NETWARE_PATH'" \