afp: persist ProDOS info metadata
All checks were successful
Source release / source-package (push) Successful in 52s

This commit is contained in:
ai
2026-06-01 09:44:56 +00:00
committed by Mario Fetka
parent 92b0c4a34a
commit 59bfdd65b2
12 changed files with 287 additions and 18 deletions

15
AI.md
View File

@@ -109,7 +109,7 @@ use, the current project status that the user pasted into the chat.
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
ProDOSInfo from the nwatalk xattr-backed JSON snapshot, 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
@@ -199,3 +199,16 @@ Normal NCP reads of `.recycle` or `.salvage` are expected to fail with invalid
path. Verify payload data through the visible live file after NCP write or
recover, using `ncp_read_smoke`. Treat the final summary (`failures=0`,
`ncp_warnings=0`) as the important signal.
### AFP ProDOSInfo storage
ProDOSInfo is AFP/NCP per-entry metadata. Store it in the existing nwatalk
AFP metadata layer, not in nwarchive/nwxattr directly and not in a parallel DB.
The xattr key is `user.org.mars-nwe.afp.prodos-info` via the mars_nwe xattr
wrapper name `org.mars-nwe.afp.prodos-info`; it is a raw 6-byte value, analogous
to FinderInfo's 32-byte `org.mars-nwe.afp.finder-info`.
Salvage captures this as `prodos_info_hex` (12 hex characters) beside
`finder_info_hex`. AFP 35/19 Get Macintosh Info On Deleted File returns
FinderInfo[32] followed by ProDOSInfo[6] from the Salvage snapshot.

View File

@@ -4,12 +4,17 @@
#include "net.h"
#define NWATALK_FINDER_INFO_LEN 32
#define NWATALK_PRODOS_INFO_LEN 6
int nwatalk_backend_available(void);
int nwatalk_get_finder_info(const char *path, uint8 *finder_info,
int finder_info_len);
int nwatalk_set_finder_info(const char *path, const uint8 *finder_info,
int finder_info_len);
int nwatalk_get_prodos_info(const char *path, uint8 *prodos_info,
int prodos_info_len);
int nwatalk_set_prodos_info(const char *path, const uint8 *prodos_info,
int prodos_info_len);
int nwatalk_get_afp_attributes(const char *path, uint16 *attributes);
int nwatalk_set_afp_attributes(const char *path, uint16 attributes);
int nwatalk_get_resource_fork_size(const char *path, uint32 *resource_size);

View File

@@ -31,6 +31,7 @@ typedef int (*nwsalvage_ini_getter)(int entry, char *str,
#define NWSALVAGE_NAME_MAX 256
#define NWSALVAGE_USER_NAME_MAX 128
#define NWSALVAGE_FINDER_INFO_HEX_LEN 64
#define NWSALVAGE_PRODOS_INFO_HEX_LEN 12
#define NWSALVAGE_AFP_ENTRY_ID_MAX 32
#define NWSALVAGE_TRUSTEE_MAX 100
#define NWSALVAGE_PATTERN_MAX 32
@@ -77,6 +78,7 @@ struct nwsalvage_deleted_entry {
long ctime;
const char *finder_info_hex;
const char *prodos_info_hex;
const char *afp_entry_id;
unsigned int afp_attributes;
unsigned long long resource_fork_size;
@@ -120,6 +122,7 @@ struct nwsalvage_metadata_entry {
long ctime;
char finder_info_hex[NWSALVAGE_FINDER_INFO_HEX_LEN + 1];
char prodos_info_hex[NWSALVAGE_PRODOS_INFO_HEX_LEN + 1];
char afp_entry_id[NWSALVAGE_AFP_ENTRY_ID_MAX];
unsigned int afp_attributes;
unsigned long long resource_fork_size;
@@ -193,6 +196,8 @@ 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);
int nwsalvage_metadata_prodos_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

View File

@@ -13,6 +13,7 @@
#define MARS_NWE_AFP_ENTRY_ID_XATTR "org.mars-nwe.afp.entry-id"
#define MARS_NWE_AFP_FINDER_INFO_XATTR "org.mars-nwe.afp.finder-info"
#define MARS_NWE_AFP_PRODOS_INFO_XATTR "org.mars-nwe.afp.prodos-info"
#define MARS_NWE_AFP_ATTRIBUTES_XATTR "org.mars-nwe.afp.attributes"
#define MARS_NWE_AFP_ENTRY_ID_VERSION 1
#define MARS_NWE_AFP_ATTRIBUTES_VERSION 1
@@ -128,6 +129,59 @@ int nwatalk_set_finder_info(const char *path, const uint8 *finder_info,
#endif
}
int nwatalk_get_prodos_info(const char *path, uint8 *prodos_info,
int prodos_info_len)
{
#if XATTR_SUPPORT
ssize_t len;
#endif
if (!prodos_info || prodos_info_len < NWATALK_PRODOS_INFO_LEN) {
return(-0x9c);
}
memset(prodos_info, 0, prodos_info_len);
#if XATTR_SUPPORT
if (path && *path) {
len = mars_nwe_getxattr(path, MARS_NWE_AFP_PRODOS_INFO_XATTR,
prodos_info, NWATALK_PRODOS_INFO_LEN);
if (len == NWATALK_PRODOS_INFO_LEN)
return(0);
memset(prodos_info, 0, prodos_info_len);
}
#else
(void)path;
#endif
/* Missing ProDOSInfo is a valid AFP state; absent metadata reads as zeroes. */
return(0);
}
int nwatalk_set_prodos_info(const char *path, const uint8 *prodos_info,
int prodos_info_len)
{
#if XATTR_SUPPORT
if (!path || !*path || !prodos_info || prodos_info_len < NWATALK_PRODOS_INFO_LEN)
return(-0x9c);
if (mars_nwe_setxattr(path, MARS_NWE_AFP_PRODOS_INFO_XATTR,
prodos_info, NWATALK_PRODOS_INFO_LEN, 0)) {
int err = errno;
XDPRINTF((3,0,"AFP ProDOSInfo xattr write failed for %s errno=%d", path, err));
return(-0x8c);
}
return(0);
#else
(void)path;
(void)prodos_info;
(void)prodos_info_len;
return(-0xbf);
#endif
}
int nwatalk_get_afp_attributes(const char *path, uint16 *attributes)
{
#if XATTR_SUPPORT

View File

@@ -1924,6 +1924,16 @@ static uint16 afp_count_offspring(const char *unixname, const struct stat *stb)
return((uint16)count);
}
static int afp_info_bytes_nonzero(const uint8 *data, size_t len)
{
size_t i;
if (!data) return(0);
for (i = 0; i < len; i++)
if (data[i]) return(1);
return(0);
}
static int afp_fill_file_info_response(const char *unixname,
const uint8 *display_path,
int display_path_len,
@@ -1938,6 +1948,7 @@ static int afp_fill_file_info_response(const char *unixname,
uint32 parent_id = 0;
uint32 resource_size = 0;
uint8 finder_info[NWATALK_FINDER_INFO_LEN];
uint8 prodos_info[NWATALK_PRODOS_INFO_LEN];
if (stat(unixname, &stbuff))
return(-0x9c); /* Invalid Path */
@@ -1996,7 +2007,11 @@ static int afp_fill_file_info_response(const char *unixname,
U32_TO_BE32(get_file_owner(&stbuff), response + 96);
afp_leaf_name_from_path(response + 100, 12, display_path, display_path_len);
U16_TO_BE16(afp_access_privileges(volume, unixname, &stbuff), response + 112);
/* ProDOS info at offset 114 stays zero until a real Mac namespace maps it. */
if (include_prodos_info) {
memset(prodos_info, 0, sizeof(prodos_info));
(void)nwatalk_get_prodos_info(unixname, prodos_info, sizeof(prodos_info));
memcpy(response + 114, prodos_info, sizeof(prodos_info));
}
if (entry_id_out) *entry_id_out = entry_id;
return(include_prodos_info ? 120 : 114);
@@ -2010,6 +2025,7 @@ static int afp_get_macintosh_info_on_deleted_file(uint8 *afp_req, int afp_len,
uint32 dos_directory_number;
struct nwsalvage_scan_result scan;
uint8 finder_info[NWATALK_FINDER_INFO_LEN];
uint8 prodos_info[NWATALK_PRODOS_INFO_LEN];
const char *name;
int name_len;
int result;
@@ -2048,7 +2064,10 @@ static int afp_get_macintosh_info_on_deleted_file(uint8 *afp_req, int afp_len,
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. */
memset(prodos_info, 0, sizeof(prodos_info));
if (nwsalvage_metadata_prodos_info(&scan.metadata, prodos_info,
sizeof(prodos_info)) == 0)
memcpy(response + 32, prodos_info, sizeof(prodos_info));
U32_TO_BE32((uint32)scan.metadata.resource_fork_size, response + 38);
name = scan.metadata.original_name[0] ? scan.metadata.original_name : "";
@@ -2057,9 +2076,11 @@ static int afp_get_macintosh_info_on_deleted_file(uint8 *afp_req, int afp_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",
"INFO AFP 35/19 DONE fn=0x23 sub=0x13 vol=0x%02x dosdir=0x%08x name=\"%s\" resource=0x%08lx prodos=%02x%02x%02x%02x%02x%02x",
(unsigned int)volume_number, (unsigned int)dos_directory_number,
name, (unsigned long)scan.metadata.resource_fork_size));
name, (unsigned long)scan.metadata.resource_fork_size,
prodos_info[0], prodos_info[1], prodos_info[2],
prodos_info[3], prodos_info[4], prodos_info[5]));
return(43 + name_len);
}
@@ -2241,6 +2262,7 @@ static int afp_set_file_information(uint8 *afp_req, int afp_len,
uint8 *modify_data = NULL;
uint8 *backup_data = NULL;
uint8 *finder_data = NULL;
uint8 *prodos_data = NULL;
if (afp_len < 9) {
XDPRINTF((2,0, "%s rejected: short request len=%d",
@@ -2282,6 +2304,7 @@ static int afp_set_file_information(uint8 *afp_req, int afp_len,
modify_data = afp_req + 14;
backup_data = afp_req + 18;
finder_data = afp_req + 22;
prodos_data = is_afp20 ? afp_req + 54 : NULL;
}
path_data = afp_req + path_off;
@@ -2338,6 +2361,8 @@ static int afp_set_file_information(uint8 *afp_req, int afp_len,
needs_afp_metadata_modify = 1;
if (request_mask & AFP_FILE_BITMAP_FINDER_INFO)
needs_afp_metadata_modify = 1;
if (is_afp20 && afp_info_bytes_nonzero(prodos_data, NWATALK_PRODOS_INFO_LEN))
needs_afp_metadata_modify = 1;
if (!resolved_by_entry_id) {
path_volume = afp_resolve_path_volume(path_data, path_len,
@@ -2436,8 +2461,14 @@ static int afp_set_file_information(uint8 *afp_req, int afp_len,
if (result < 0)
return(result);
}
if (is_afp20 && afp_info_bytes_nonzero(prodos_data, NWATALK_PRODOS_INFO_LEN)) {
result = nwatalk_set_prodos_info(unixname, prodos_data,
NWATALK_PRODOS_INFO_LEN);
if (result < 0)
return(result);
}
XDPRINTF((3,0, "%s: vol=%d request_vol=%d entry=0x%08x mask=0x%04x path='%s'%s%s%s%s%s%s attrs=0x%04x atime=%ld mtime=%ld",
XDPRINTF((3,0, "%s: vol=%d request_vol=%d entry=0x%08x mask=0x%04x path='%s'%s%s%s%s%s%s%s attrs=0x%04x atime=%ld mtime=%ld",
call_name, path_volume, (int)volume_number, request_entry_id,
request_mask, visable_data(path_data, path_len),
(request_mask & AFP_FILE_BITMAP_ATTRIBUTES) ? " attributes" : "",
@@ -2446,6 +2477,7 @@ static int afp_set_file_information(uint8 *afp_req, int afp_len,
(request_mask & AFP_FILE_BITMAP_MODIFY_DATE) ? " modify_time" : "",
(request_mask & AFP_FILE_BITMAP_BACKUP_DATE) ? " backup_time" : "",
(request_mask & AFP_FILE_BITMAP_FINDER_INFO) ? " finder_info" : "",
(is_afp20 && afp_info_bytes_nonzero(prodos_data, NWATALK_PRODOS_INFO_LEN)) ? " prodos_info" : "",
log_attrs, (long)log_atime, (long)log_mtime));
return(0);
}

View File

@@ -711,6 +711,13 @@ static const char *metadata_finder_info_hex(
entry->finder_info_hex : NULL);
}
static const char *metadata_prodos_info_hex(
const struct nwsalvage_deleted_entry *entry)
{
return(entry->prodos_info_hex && *entry->prodos_info_hex ?
entry->prodos_info_hex : NULL);
}
static int validate_hex_string(const char *value, size_t expected_len)
{
size_t i;
@@ -958,6 +965,7 @@ int nwsalvage_write_metadata(const char *metadata_path,
yyjson_mut_val *object;
yyjson_write_err err;
const char *finder_info_hex;
const char *prodos_info_hex;
int failed = 0;
if (!metadata_path || !*metadata_path || !entry ||
@@ -972,6 +980,10 @@ int nwsalvage_write_metadata(const char *metadata_path,
if (finder_info_hex &&
validate_hex_string(finder_info_hex, NWSALVAGE_FINDER_INFO_HEX_LEN) < 0)
return(-1);
prodos_info_hex = metadata_prodos_info_hex(entry);
if (prodos_info_hex &&
validate_hex_string(prodos_info_hex, NWSALVAGE_PRODOS_INFO_HEX_LEN) < 0)
return(-1);
if (!metadata_time_valid(entry->deleted_at) ||
!metadata_time_valid(entry->atime) ||
!metadata_time_valid(entry->mtime) ||
@@ -1036,6 +1048,9 @@ int nwsalvage_write_metadata(const char *metadata_path,
if (!failed && finder_info_hex &&
json_add_string(doc, object, "finder_info_hex", finder_info_hex) < 0)
failed = 1;
if (!failed && prodos_info_hex &&
json_add_string(doc, object, "prodos_info_hex", prodos_info_hex) < 0)
failed = 1;
if (!failed && json_add_string(doc, object, "afp_entry_id",
entry->afp_entry_id ? entry->afp_entry_id : "") < 0)
failed = 1;
@@ -1198,6 +1213,18 @@ int nwsalvage_read_metadata(const char *metadata_path,
failed = 1;
}
}
{
yyjson_val *prodos = yyjson_obj_get(object, "prodos_info_hex");
if (prodos) {
if (!yyjson_is_str(prodos) ||
strmaxcpy((uint8 *)entry->prodos_info_hex,
yyjson_get_str(prodos),
sizeof(entry->prodos_info_hex) - 1) < 0 ||
validate_hex_string(entry->prodos_info_hex,
NWSALVAGE_PRODOS_INFO_HEX_LEN) < 0)
failed = 1;
}
}
if (!failed && json_get_string_copy(object, "afp_entry_id",
entry->afp_entry_id,
sizeof(entry->afp_entry_id)) < 0)
@@ -1740,10 +1767,13 @@ static void nwsalvage_fill_afp_metadata(const char *unixname,
struct nwsalvage_deleted_entry *entry,
char *finder_info_hex,
size_t finder_info_hex_len,
char *prodos_info_hex,
size_t prodos_info_hex_len,
char *afp_entry_id,
size_t afp_entry_id_len)
{
uint8 finder_info[NWATALK_FINDER_INFO_LEN];
uint8 prodos_info[NWATALK_PRODOS_INFO_LEN];
uint16 afp_attributes = 0;
uint32 entry_id = 0;
uint32 resource_size = 0;
@@ -1755,6 +1785,13 @@ static void nwsalvage_fill_afp_metadata(const char *unixname,
entry->finder_info_hex = finder_info_hex;
}
memset(prodos_info, 0, sizeof(prodos_info));
if (nwatalk_get_prodos_info(unixname, prodos_info, sizeof(prodos_info)) == 0) {
nwsalvage_format_hex(prodos_info_hex, prodos_info_hex_len,
prodos_info, sizeof(prodos_info));
entry->prodos_info_hex = prodos_info_hex;
}
if (nwatalk_get_entry_id(unixname, &entry_id) == 0)
slprintf(afp_entry_id, (int)afp_entry_id_len - 1, "0x%08x", entry_id);
else
@@ -2143,6 +2180,19 @@ int nwsalvage_metadata_finder_info(const struct nwsalvage_metadata_entry *entry,
return(nwsalvage_hex_to_bytes(entry->finder_info_hex, out, out_len));
}
int nwsalvage_metadata_prodos_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->prodos_info_hex[0])
return(0);
return(nwsalvage_hex_to_bytes(entry->prodos_info_hex, out, out_len));
}
static int nwsalvage_finder_info_is_zero(const uint8 *finder_info, size_t len)
{
size_t i;
@@ -2198,6 +2248,13 @@ static int nwsalvage_restore_metadata(int volume, const char *unixname,
(void)nwatalk_set_finder_info(unixname, finder_info, sizeof(finder_info));
}
if (entry->prodos_info_hex[0]) {
uint8 prodos_info[NWATALK_PRODOS_INFO_LEN];
if (nwsalvage_hex_to_bytes(entry->prodos_info_hex, prodos_info,
sizeof(prodos_info)) == 0)
(void)nwatalk_set_prodos_info(unixname, prodos_info, sizeof(prodos_info));
}
if (entry->afp_attributes)
(void)nwatalk_set_afp_attributes(unixname, (uint16)entry->afp_attributes);
@@ -2412,6 +2469,7 @@ int nwsalvage_capture_node_delete(int volume, const char *unixname,
char volume_name[NWSALVAGE_REPOSITORY_NAME_MAX];
char deleted_by[NWSALVAGE_USER_NAME_MAX];
char finder_info_hex[NWSALVAGE_FINDER_INFO_HEX_LEN + 1];
char prodos_info_hex[NWSALVAGE_PRODOS_INFO_HEX_LEN + 1];
char afp_entry_id[NWSALVAGE_AFP_ENTRY_ID_MAX];
if (!unixname || !*unixname || !stb) {
@@ -2470,6 +2528,7 @@ int nwsalvage_capture_node_delete(int volume, const char *unixname,
memset(&entry, 0, sizeof(entry));
finder_info_hex[0] = '\0';
prodos_info_hex[0] = '\0';
afp_entry_id[0] = '\0';
entry.source = "mars_nwe";
@@ -2490,7 +2549,8 @@ int nwsalvage_capture_node_delete(int volume, const char *unixname,
entry.ctime = (long)stb->st_ctime;
nwsalvage_fill_afp_metadata(unixname, &entry, finder_info_hex,
sizeof(finder_info_hex), afp_entry_id,
sizeof(finder_info_hex), prodos_info_hex,
sizeof(prodos_info_hex), afp_entry_id,
sizeof(afp_entry_id));
nwsalvage_fill_netware_xattrs(unixname, &entry);
nwsalvage_fill_trustees(volume, unixname, stb, &entry);

View File

@@ -37,7 +37,7 @@ 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.
3. Return FinderInfo from the AFP metadata store when available, otherwise zeroes.
4. Return ProDOS information as zeroes unless mars_nwe gains a real ProDOS store.
4. Return ProDOS information from the Salvage JSON snapshot (`prodos_info_hex`), which is captured from the nwatalk ProDOSInfo xattr backend.
5. Return resource fork size as zero while resource forks remain unsupported.
6. Return the deleted file name from the salvage entry, not from a live path scan.

View File

@@ -1397,7 +1397,7 @@ 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
backend and returns the FinderInfo snapshot, ProDOSInfo snapshot, the stored
resource fork size, and the deleted original filename. It does not open or
expose `.recycle` or `.salvage` as AFP-visible paths.

View File

@@ -19,7 +19,7 @@ Current AFP mapping:
1. Resolve the deleted DOS directory entry through the mars_nwe salvage backend.
2. Return FinderInfo from the salvage JSON snapshot.
3. Return ProDOS information as zeroes unless mars_nwe gains a real ProDOS store.
3. Return ProDOS information from the nwatalk xattr-backed Salvage JSON snapshot (`prodos_info_hex`).
4. Return resource fork size from the salvage JSON snapshot.
5. Return the deleted filename from the salvage/deleted-entry record.

View File

@@ -26,7 +26,7 @@
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"
"Usage: %s [--expect-name NAME] [--expect-type FOURCC] [--expect-creator FOURCC] [--expect-prodos-hex 12HEX] [--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"
@@ -53,6 +53,30 @@ static uint32_t be32_to_cpu(const uint8_t p[4])
p[3];
}
static int hex_nibble(int c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
static int parse_hex_bytes(const char *text, uint8_t *out, size_t out_len)
{
size_t i;
if (!text || strlen(text) != out_len * 2)
return -1;
for (i = 0; i < out_len; i++) {
int hi = hex_nibble((unsigned char)text[i * 2]);
int lo = hex_nibble((unsigned char)text[i * 2 + 1]);
if (hi < 0 || lo < 0)
return -1;
out[i] = (uint8_t)((hi << 4) | lo);
}
return 0;
}
static int find_deleted(NWCONN_HANDLE conn, const char *dir,
const char *expect_name,
struct ncp_deleted_file *info,
@@ -117,6 +141,8 @@ int main(int argc, char **argv)
const char *expect_name = NULL;
const char *expect_type = NULL;
const char *expect_creator = NULL;
uint8_t expect_prodos[6];
int have_expect_prodos = 0;
int purge_after = 0;
int do_purge_all = 0;
struct ncp_deleted_file info;
@@ -128,6 +154,8 @@ int main(int argc, char **argv)
NWCCODE err;
int i;
memset(expect_prodos, 0, sizeof(expect_prodos));
if (NWCallsInit(NULL, NULL)) {
fprintf(stderr, "NWCallsInit failed\n");
return 2;
@@ -156,6 +184,12 @@ int main(int argc, char **argv)
ncp_close(conn); return 2;
}
expect_creator = argv[i];
} else if (!strcmp(argv[i], "--expect-prodos-hex")) {
if (++i >= argc || parse_hex_bytes(argv[i], expect_prodos, sizeof(expect_prodos))) {
fprintf(stderr, "--expect-prodos-hex must be 12 hex characters\n");
ncp_close(conn); return 2;
}
have_expect_prodos = 1;
} else if (!strcmp(argv[i], "--purge-after")) {
purge_after = 1;
} else if (!strcmp(argv[i], "--purge-all")) {
@@ -228,6 +262,9 @@ int main(int argc, char **argv)
(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);
printf("AFP deleted prodos=%02x%02x%02x%02x%02x%02x\n",
reply_buf[32], reply_buf[33], reply_buf[34],
reply_buf[35], reply_buf[36], reply_buf[37]);
if (expect_name && (name_len != (int)strlen(expect_name) ||
memcmp(reply_buf + 43, expect_name, name_len))) {
@@ -248,6 +285,13 @@ int main(int argc, char **argv)
ncp_close(conn);
return 1;
}
if (have_expect_prodos && memcmp(reply_buf + 32, expect_prodos, sizeof(expect_prodos))) {
fprintf(stderr, "AFP deleted info ProDOSInfo mismatch: got=%02x%02x%02x%02x%02x%02x\n",
reply_buf[32], reply_buf[33], reply_buf[34],
reply_buf[35], reply_buf[36], reply_buf[37]);
ncp_close(conn);
return 1;
}
if (purge_after) {
err = ncp_ns_purge_file(conn, &info);

View File

@@ -42,7 +42,7 @@ static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [--afp09|--afp20] [--expect-completion CODE] [--allow-invalid-namespace] [--allow-invalid-path] "
"[--volume N] [--entry-id ID] [--entry-id-only] [--type FOUR] [--creator FOUR] "
"[--volume N] [--entry-id ID] [--entry-id-only] [--type FOUR] [--creator FOUR] [--prodos-hex 12HEX] "
"[--hidden|--clear-hidden|--invisible|--clear-invisible|--system|--clear-system|--archive|--clear-archive] "
"[--access-time-epoch SECONDS] [--create-time-epoch SECONDS] [--mtime-epoch SECONDS] [--backup-time-epoch SECONDS] [--finder-info-only|--attributes-only|--access-time-only|--create-time-only|--timestamp-only|--backup-time-only] "
"[ncpfs options] PATH\n"
@@ -113,6 +113,30 @@ static int copy_fourcc(const char *text, uint8_t out[4])
return 0;
}
static int hex_nibble(int c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
static int parse_hex_bytes(const char *text, uint8_t *out, size_t out_len)
{
size_t i;
if (!text || strlen(text) != out_len * 2)
return -1;
for (i = 0; i < out_len; i++) {
int hi = hex_nibble((unsigned char)text[i * 2]);
int lo = hex_nibble((unsigned char)text[i * 2 + 1]);
if (hi < 0 || lo < 0)
return -1;
out[i] = (uint8_t)((hi << 4) | lo);
}
return 0;
}
static int epoch_to_nw_time(uint32_t epoch, uint8_t out[4])
{
time_t t = (time_t)epoch;
@@ -236,6 +260,8 @@ int main(int argc, char **argv)
uint32_t entry_id = 0;
int entry_id_only = 0;
uint8_t finder_info[32];
uint8_t prodos_info[6];
int have_prodos_info = 0;
uint8_t set_subfunction = AFP20_SET_FILE_INFORMATION;
uint16_t request_mask = AFP_FINDER_INFO_MASK;
uint16_t attr_request = 0;
@@ -257,6 +283,7 @@ int main(int argc, char **argv)
NWCCODE err;
memset(finder_info, 0, sizeof(finder_info));
memset(prodos_info, 0, sizeof(prodos_info));
memset(access_time, 0, sizeof(access_time));
memset(create_time, 0, sizeof(create_time));
memset(modify_time, 0, sizeof(modify_time));
@@ -401,6 +428,13 @@ int main(int argc, char **argv)
ncp_close(conn);
return 2;
}
} else if (!strcmp(argv[i], "--prodos-hex")) {
if (++i >= argc || parse_hex_bytes(argv[i], prodos_info, sizeof(prodos_info))) {
fprintf(stderr, "invalid --prodos-hex value, expected 12 hex characters\n");
ncp_close(conn);
return 2;
}
have_prodos_info = 1;
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
usage(argv[0]);
ncp_close(conn);
@@ -497,6 +531,8 @@ int main(int argc, char **argv)
if (have_backup_time)
memcpy(request + 17, backup_time, sizeof(backup_time));
memcpy(request + 21, finder_info, sizeof(finder_info));
if (set_subfunction == AFP20_SET_FILE_INFORMATION && have_prodos_info)
memcpy(request + 53, prodos_info, sizeof(prodos_info));
request[path_len_off] = (uint8_t)path_len;
memcpy(request + path_off, path, path_len);
data_off = path_off + path_len;
@@ -642,12 +678,22 @@ int main(int argc, char **argv)
return 1;
}
printf("AFP Set File Info subfunction=0x%02x path=%s bitmap=0x%04x attrs=0x%04x create=0x%04x access=0x%04x modify=0x%04x%04x backup=0x%04x%04x finder_type=%.4s finder_creator=%.4s entry_id=0x%08x verified%s\n",
if (have_prodos_info && memcmp(verify_buf + 114, prodos_info, sizeof(prodos_info))) {
fprintf(stderr,
"AFP Set File Information ProDOSInfo verify mismatch: path=%s got=%02x%02x%02x%02x%02x%02x\n",
path, verify_buf[114], verify_buf[115], verify_buf[116],
verify_buf[117], verify_buf[118], verify_buf[119]);
ncp_close(conn);
return 1;
}
printf("AFP Set File Info subfunction=0x%02x path=%s bitmap=0x%04x attrs=0x%04x create=0x%04x access=0x%04x modify=0x%04x%04x backup=0x%04x%04x finder_type=%.4s finder_creator=%.4s entry_id=0x%08x%s verified%s\n",
set_subfunction, path, request_mask, be16_to_cpu(verify_buf + 8),
be16_to_cpu(verify_buf + 20), be16_to_cpu(verify_buf + 22),
be16_to_cpu(verify_buf + 24), be16_to_cpu(verify_buf + 26),
be16_to_cpu(verify_buf + 28), be16_to_cpu(verify_buf + 30),
finder_info + 0, finder_info + 4, be32_to_cpu(verify_buf + 0),
have_prodos_info ? " prodos_info" : "",
entry_id_only ? " entry-id-only" : "");
ncp_close(conn);

View File

@@ -17,6 +17,7 @@ LOG_FILE="/var/log/mars_nwe/nw.log"
OUT_FILE=""
FINDER_TYPE="TEXT"
FINDER_CREATOR="MARS"
PRODOS_INFO_HEX="010203040506"
TIMESTAMP_EPOCH="1700000000"
CREATE_DIR_NAME=""
CREATE_FILE_NAME=""
@@ -42,6 +43,7 @@ Options:
--out FILE Write the complete report to FILE as well as stdout
--type FOURCC FinderInfo type written by Set File Info (default: $FINDER_TYPE)
--creator FOURCC FinderInfo creator written by Set File Info (default: $FINDER_CREATOR)
--prodos-hex 12HEX ProDOSInfo written by AFP 2.0 Set File Info (default: $PRODOS_INFO_HEX)
--mtime-epoch SECONDS AFP modify/backup timestamp to write (default: $TIMESTAMP_EPOCH)
--create-dir-name NAME Temporary directory name for AFP Create Directory (default: generated aHHMMSS)
--create-file-name NAME Temporary file name for AFP Create File (default: generated fHHMMR)
@@ -83,6 +85,8 @@ while [ $# -gt 0 ]; do
FINDER_TYPE=$2; shift 2 ;;
--creator)
FINDER_CREATOR=$2; shift 2 ;;
--prodos-hex)
PRODOS_INFO_HEX=$2; shift 2 ;;
--mtime-epoch)
TIMESTAMP_EPOCH=$2; shift 2 ;;
--create-dir-name)
@@ -370,6 +374,7 @@ emit "log=$LOG_FILE"
emit "log_window_seconds=$LOG_WINDOW_SECONDS"
emit "finder_type=$FINDER_TYPE"
emit "finder_creator=$FINDER_CREATOR"
emit "prodos_info_hex=$PRODOS_INFO_HEX"
emit "mtime_epoch=$TIMESTAMP_EPOCH"
emit "create_dir_path=$CREATE_DIR_PATH"
emit "create_dir20_path=$CREATE_DIR20_PATH"
@@ -724,18 +729,18 @@ run_cmd \
"./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'" \
"AFP Deleted Info set FinderInfo and ProDOSInfo" \
"./afp_set_file_info_smoke $COMMON_PRINT --finder-info-only --type '$FINDER_TYPE' --creator '$FINDER_CREATOR' --prodos-hex '$PRODOS_INFO_HEX' '$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"
--finder-info-only --type "$FINDER_TYPE" --creator "$FINDER_CREATOR" --prodos-hex "$PRODOS_INFO_HEX" "$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 \
"./afp_deleted_info_smoke --expect-type '$FINDER_TYPE' --expect-creator '$FINDER_CREATOR' --expect-prodos-hex '$PRODOS_INFO_HEX' --purge-after $COMMON_PRINT '$DIR_PATH'" \
"$SCRIPT_DIR/afp_deleted_info_smoke" --expect-type "$FINDER_TYPE" --expect-creator "$FINDER_CREATOR" --expect-prodos-hex "$PRODOS_INFO_HEX" --purge-after \
-S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$DIR_PATH"
run_cmd \
@@ -878,6 +883,11 @@ if command -v getfattr >/dev/null 2>&1; then
"getfattr -n user.org.mars-nwe.afp.finder-info -e hex '$UNIX_PATH'" \
getfattr -n user.org.mars-nwe.afp.finder-info -e hex "$UNIX_PATH"
run_optional_cmd \
"Linux xattr: AFP ProDOSInfo" \
"getfattr -n user.org.mars-nwe.afp.prodos-info -e hex '$UNIX_PATH'" \
getfattr -n user.org.mars-nwe.afp.prodos-info -e hex "$UNIX_PATH" || true
run_optional_cmd \
"Linux xattr: AFP Attributes (optional AFP-only bits)" \
"getfattr -n user.org.mars-nwe.afp.attributes -e hex '$UNIX_PATH'" \