afp: persist ProDOS info metadata
All checks were successful
Source release / source-package (push) Successful in 52s
All checks were successful
Source release / source-package (push) Successful in 52s
This commit is contained in:
15
AI.md
15
AI.md
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
42
src/nwconn.c
42
src/nwconn.c
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'" \
|
||||
|
||||
Reference in New Issue
Block a user