diff --git a/include/nwsalvage.h b/include/nwsalvage.h index 6167e33..a50d2b1 100644 --- a/include/nwsalvage.h +++ b/include/nwsalvage.h @@ -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); diff --git a/src/nwsalvage.c b/src/nwsalvage.c index 4f88e93..67bc179 100644 --- a/src/nwsalvage.c +++ b/src/nwsalvage.c @@ -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) { diff --git a/tests/salvage/README.md b/tests/salvage/README.md index 8c1b4b9..e770225 100644 --- a/tests/salvage/README.md +++ b/tests/salvage/README.md @@ -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 diff --git a/tests/salvage/salvage_config_smoke.c b/tests/salvage/salvage_config_smoke.c index 59094c6..077f930 100644 --- a/tests/salvage/salvage_config_smoke.c +++ b/tests/salvage/salvage_config_smoke.c @@ -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