salvage: preserve xattr backed metadata

This commit is contained in:
Mario Fetka
2026-05-31 10:32:09 +00:00
parent 2c089f5fe3
commit ce34e2df65
4 changed files with 140 additions and 1 deletions

View File

@@ -48,7 +48,19 @@ struct nwsalvage_deleted_entry {
const char *finder_info_hex;
const char *afp_entry_id;
unsigned int afp_attributes;
unsigned long long resource_fork_size;
unsigned int netware_archive_flags;
unsigned int netware_archive_date;
unsigned int netware_archive_time;
unsigned long netware_archiver_id;
unsigned int netware_fileinfo_flags;
unsigned int netware_create_date;
unsigned int netware_create_time;
unsigned long netware_creator_id;
unsigned long netware_modifier_id;
};
struct nwsalvage_metadata_entry {
@@ -73,7 +85,19 @@ struct nwsalvage_metadata_entry {
char finder_info_hex[NWSALVAGE_FINDER_INFO_HEX_LEN + 1];
char afp_entry_id[NWSALVAGE_AFP_ENTRY_ID_MAX];
unsigned int afp_attributes;
unsigned long long resource_fork_size;
unsigned int netware_archive_flags;
unsigned int netware_archive_date;
unsigned int netware_archive_time;
unsigned long netware_archiver_id;
unsigned int netware_fileinfo_flags;
unsigned int netware_create_date;
unsigned int netware_create_time;
unsigned long netware_creator_id;
unsigned long netware_modifier_id;
};
int nwsalvage_config_defaults(struct nwsalvage_config *config);

View File

@@ -685,10 +685,42 @@ int nwsalvage_write_metadata(const char *metadata_path,
if (!failed && json_add_string(doc, object, "afp_entry_id",
entry->afp_entry_id ? entry->afp_entry_id : "") < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "afp_attributes",
entry->afp_attributes) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "resource_fork_size",
entry->resource_fork_size) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_archive_flags",
entry->netware_archive_flags) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_archive_date",
entry->netware_archive_date) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_archive_time",
entry->netware_archive_time) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_archiver_id",
entry->netware_archiver_id) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_fileinfo_flags",
entry->netware_fileinfo_flags) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_create_date",
entry->netware_create_date) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_create_time",
entry->netware_create_time) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_creator_id",
entry->netware_creator_id) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_modifier_id",
entry->netware_modifier_id) < 0)
failed = 1;
if (!failed && !yyjson_mut_write_file(metadata_path, doc,
YYJSON_WRITE_PRETTY_TWO_SPACES |
YYJSON_WRITE_NEWLINE_AT_END,
@@ -805,11 +837,53 @@ int nwsalvage_read_metadata(const char *metadata_path,
entry->afp_entry_id,
sizeof(entry->afp_entry_id)) < 0)
failed = 1;
if (!failed && json_get_uint64(object, "afp_attributes", &value) < 0)
failed = 1;
else if (!failed)
entry->afp_attributes = (unsigned int)value;
if (!failed && json_get_uint64(object, "resource_fork_size", &value) < 0)
failed = 1;
else if (!failed)
entry->resource_fork_size = value;
if (!failed && json_get_uint64(object, "netware_archive_flags", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_archive_flags = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_archive_date", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_archive_date = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_archive_time", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_archive_time = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_archiver_id", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_archiver_id = (unsigned long)value;
if (!failed && json_get_uint64(object, "netware_fileinfo_flags", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_fileinfo_flags = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_create_date", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_create_date = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_create_time", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_create_time = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_creator_id", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_creator_id = (unsigned long)value;
if (!failed && json_get_uint64(object, "netware_modifier_id", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_modifier_id = (unsigned long)value;
yyjson_doc_free(doc);
if (failed) {

View File

@@ -10,6 +10,8 @@ contract before `src/nwsalvage.c` exists:
- deleted file payloads live below the recycle repository,
- per-object JSON metadata lives below the salvage repository,
- JSON metadata preserves mars_nwe xattr-backed NetWare/AFP fields
from `nwarchive` and `nwatalk`,
- there is no large per-directory index file,
- stale JSON metadata is detectable when the matching recycle payload is gone.
@@ -20,7 +22,10 @@ Default repository names come from the `nwserv.conf`/`nw.ini` template options:
49 .recycle .salvage
```
Expected layout for a deleted `SYS:PUBLIC/PMDFLTS.INI` owned by `SUPERVISOR`:
Expected layout for a deleted `SYS:PUBLIC/PMDFLTS.INI` owned by `SUPERVISOR`.
The sidecar JSON must carry the server-only metadata needed for exact recover,
including NetWare archive/fileinfo xattrs and AFP FinderInfo/entry-id/attribute
xattrs:
```text
SYS/.recycle/SUPERVISOR/PUBLIC/PMDFLTS.INI

View File

@@ -232,7 +232,17 @@ int main(void)
deleted_entry.finder_info_hex =
"0000000000000000000000000000000000000000000000000000000000000000";
deleted_entry.afp_entry_id = "0x2cc1243d";
deleted_entry.afp_attributes = 0x0600;
deleted_entry.resource_fork_size = 0;
deleted_entry.netware_archive_flags = 0x07;
deleted_entry.netware_archive_date = 0x5821;
deleted_entry.netware_archive_time = 0x6c40;
deleted_entry.netware_archiver_id = 0x01020304;
deleted_entry.netware_fileinfo_flags = 0x0f;
deleted_entry.netware_create_date = 0x5820;
deleted_entry.netware_create_time = 0x6b20;
deleted_entry.netware_creator_id = 0x00000001;
deleted_entry.netware_modifier_id = 0x00000002;
failures += expect_true(nwsalvage_write_metadata(path, &deleted_entry) == 0,
"metadata writer creates parent dirs and JSON");
@@ -252,6 +262,12 @@ int main(void)
"metadata JSON contains Finder info");
failures += expect_true(file_contains(path, "\"afp_entry_id\": \"0x2cc1243d\""),
"metadata JSON contains AFP entry id");
failures += expect_true(file_contains(path, "\"afp_attributes\": 1536"),
"metadata JSON contains AFP xattr attributes");
failures += expect_true(file_contains(path, "\"netware_archive_flags\": 7"),
"metadata JSON contains NetWare archive xattr flags");
failures += expect_true(file_contains(path, "\"netware_fileinfo_flags\": 15"),
"metadata JSON contains NetWare fileinfo xattr flags");
failures += expect_true(file_contains(path, "\"attributes\": 8192"),
"metadata JSON contains attributes");
failures += expect_true(nwsalvage_read_metadata(path, &metadata_entry) == 0,
@@ -291,8 +307,28 @@ int main(void)
"metadata reader returns Finder info");
failures += expect_true(!strcmp(metadata_entry.afp_entry_id, "0x2cc1243d"),
"metadata reader returns AFP entry id");
failures += expect_true(metadata_entry.afp_attributes == 0x0600,
"metadata reader returns AFP attributes");
failures += expect_true(metadata_entry.resource_fork_size == 0,
"metadata reader returns resource fork size");
failures += expect_true(metadata_entry.netware_archive_flags == 0x07,
"metadata reader returns NetWare archive flags");
failures += expect_true(metadata_entry.netware_archive_date == 0x5821,
"metadata reader returns NetWare archive date");
failures += expect_true(metadata_entry.netware_archive_time == 0x6c40,
"metadata reader returns NetWare archive time");
failures += expect_true(metadata_entry.netware_archiver_id == 0x01020304,
"metadata reader returns NetWare archiver id");
failures += expect_true(metadata_entry.netware_fileinfo_flags == 0x0f,
"metadata reader returns NetWare fileinfo flags");
failures += expect_true(metadata_entry.netware_create_date == 0x5820,
"metadata reader returns NetWare create date");
failures += expect_true(metadata_entry.netware_create_time == 0x6b20,
"metadata reader returns NetWare create time");
failures += expect_true(metadata_entry.netware_creator_id == 0x00000001,
"metadata reader returns NetWare creator id");
failures += expect_true(metadata_entry.netware_modifier_id == 0x00000002,
"metadata reader returns NetWare modifier id");
}
remove_test_tree(root);
#else