From 5fc5a5218fe0dadd7e64669f117ac509d9da3326 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sun, 31 May 2026 10:36:10 +0000 Subject: [PATCH] salvage: preserve trustee metadata --- include/nwsalvage.h | 14 ++++ src/nwsalvage.c | 105 +++++++++++++++++++++++++++ tests/salvage/README.md | 9 +++ tests/salvage/salvage_config_smoke.c | 24 ++++++ 4 files changed, 152 insertions(+) diff --git a/include/nwsalvage.h b/include/nwsalvage.h index a50d2b1..c7ffac3 100644 --- a/include/nwsalvage.h +++ b/include/nwsalvage.h @@ -19,6 +19,7 @@ typedef int (*nwsalvage_ini_getter)(int entry, char *str, #define NWSALVAGE_USER_NAME_MAX 128 #define NWSALVAGE_FINDER_INFO_HEX_LEN 64 #define NWSALVAGE_AFP_ENTRY_ID_MAX 32 +#define NWSALVAGE_TRUSTEE_MAX 100 struct nwsalvage_config { int enabled; @@ -26,6 +27,11 @@ struct nwsalvage_config { char metadata_repository[NWSALVAGE_REPOSITORY_NAME_MAX]; }; +struct nwsalvage_trustee_entry { + unsigned long object_id; + unsigned int rights; +}; + struct nwsalvage_deleted_entry { const char *source; const char *volume_name; @@ -61,6 +67,10 @@ struct nwsalvage_deleted_entry { unsigned int netware_create_time; unsigned long netware_creator_id; unsigned long netware_modifier_id; + + unsigned int inherited_rights_mask; + unsigned int trustee_count; + struct nwsalvage_trustee_entry trustees[NWSALVAGE_TRUSTEE_MAX]; }; struct nwsalvage_metadata_entry { @@ -98,6 +108,10 @@ struct nwsalvage_metadata_entry { unsigned int netware_create_time; unsigned long netware_creator_id; unsigned long netware_modifier_id; + + unsigned int inherited_rights_mask; + unsigned int trustee_count; + struct nwsalvage_trustee_entry trustees[NWSALVAGE_TRUSTEE_MAX]; }; int nwsalvage_config_defaults(struct nwsalvage_config *config); diff --git a/src/nwsalvage.c b/src/nwsalvage.c index 67bc179..89de18f 100644 --- a/src/nwsalvage.c +++ b/src/nwsalvage.c @@ -599,6 +599,98 @@ static int json_get_long(yyjson_val *object, const char *name, long *out) return(0); } + +static int json_add_trustees(yyjson_mut_doc *doc, + yyjson_mut_val *object, + const struct nwsalvage_deleted_entry *entry) +{ + yyjson_mut_val *array; + unsigned int i; + + if (!doc || !object || !entry) { + errno = EINVAL; + return(-1); + } + + if (entry->trustee_count > NWSALVAGE_TRUSTEE_MAX) { + errno = EINVAL; + return(-1); + } + + array = yyjson_mut_arr(doc); + if (!array) { + errno = ENOMEM; + return(-1); + } + + for (i = 0; i < entry->trustee_count; i++) { + yyjson_mut_val *trustee = yyjson_mut_obj(doc); + if (!trustee) { + errno = ENOMEM; + return(-1); + } + if (json_add_uint64(doc, trustee, "object_id", + entry->trustees[i].object_id) < 0 || + json_add_uint64(doc, trustee, "rights", + entry->trustees[i].rights) < 0 || + !yyjson_mut_arr_append(array, trustee)) { + if (!errno) errno = ENOMEM; + return(-1); + } + } + + if (!yyjson_mut_obj_add_val(doc, object, "trustees", array)) { + errno = ENOMEM; + return(-1); + } + + return(0); +} + +static int json_get_trustees(yyjson_val *object, + struct nwsalvage_metadata_entry *entry) +{ + yyjson_val *array; + yyjson_val *item; + size_t idx; + size_t max; + + if (!object || !entry) { + errno = EINVAL; + return(-1); + } + + array = yyjson_obj_get(object, "trustees"); + if (!yyjson_is_arr(array)) { + errno = EINVAL; + return(-1); + } + + max = yyjson_arr_size(array); + if (max > NWSALVAGE_TRUSTEE_MAX) { + errno = EINVAL; + return(-1); + } + + entry->trustee_count = (unsigned int)max; + yyjson_arr_foreach(array, idx, max, item) { + unsigned long long value; + + if (!yyjson_is_obj(item)) { + errno = EINVAL; + return(-1); + } + if (json_get_uint64(item, "object_id", &value) < 0) + return(-1); + entry->trustees[idx].object_id = (unsigned long)value; + if (json_get_uint64(item, "rights", &value) < 0) + return(-1); + entry->trustees[idx].rights = (unsigned int)value; + } + + return(0); +} + int nwsalvage_write_metadata(const char *metadata_path, const struct nwsalvage_deleted_entry *entry) { @@ -721,6 +813,12 @@ int nwsalvage_write_metadata(const char *metadata_path, entry->netware_modifier_id) < 0) failed = 1; + if (!failed && json_add_uint64(doc, object, "inherited_rights_mask", + entry->inherited_rights_mask) < 0) + failed = 1; + if (!failed && json_add_trustees(doc, object, entry) < 0) + failed = 1; + if (!failed && !yyjson_mut_write_file(metadata_path, doc, YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, @@ -884,6 +982,13 @@ int nwsalvage_read_metadata(const char *metadata_path, else if (!failed) entry->netware_modifier_id = (unsigned long)value; + if (!failed && json_get_uint64(object, "inherited_rights_mask", &value) < 0) + failed = 1; + else if (!failed) + entry->inherited_rights_mask = (unsigned int)value; + if (!failed && json_get_trustees(object, entry) < 0) + failed = 1; + yyjson_doc_free(doc); if (failed) { diff --git a/tests/salvage/README.md b/tests/salvage/README.md index e770225..474bb14 100644 --- a/tests/salvage/README.md +++ b/tests/salvage/README.md @@ -39,3 +39,12 @@ Future runtime tests should add direct coverage for: - `NCP 0x2222 / 87 / 17` Recover Salvageable File, - `NCP 0x2222 / 87 / 18` Purge Salvageable File, - AFP `0x13` as a thin adapter over the shared salvage backend. + +Trustee metadata +---------------- + +NetWare trustee rights are stored by mars_nwe in the trustee store, keyed by +volume/dev/inode, rather than in the file payload itself. Salvage metadata +therefore needs to preserve the inherited rights mask and the explicit trustee +object/right pairs so recovery can recreate equivalent trustee records for the +restored object. diff --git a/tests/salvage/salvage_config_smoke.c b/tests/salvage/salvage_config_smoke.c index 077f930..7d9503a 100644 --- a/tests/salvage/salvage_config_smoke.c +++ b/tests/salvage/salvage_config_smoke.c @@ -243,6 +243,12 @@ int main(void) deleted_entry.netware_create_time = 0x6b20; deleted_entry.netware_creator_id = 0x00000001; deleted_entry.netware_modifier_id = 0x00000002; + deleted_entry.inherited_rights_mask = 0x01ff; + deleted_entry.trustee_count = 2; + deleted_entry.trustees[0].object_id = 0x00000001; + deleted_entry.trustees[0].rights = 0x01ff; + deleted_entry.trustees[1].object_id = 0x01020304; + deleted_entry.trustees[1].rights = 0x0041; failures += expect_true(nwsalvage_write_metadata(path, &deleted_entry) == 0, "metadata writer creates parent dirs and JSON"); @@ -268,6 +274,12 @@ int main(void) "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, "\"inherited_rights_mask\": 511"), + "metadata JSON contains trustee inherited rights mask"); + failures += expect_true(file_contains(path, "\"trustees\":"), + "metadata JSON contains trustee rights array"); + failures += expect_true(file_contains(path, "\"object_id\": 16909060"), + "metadata JSON contains trustee object id"); failures += expect_true(file_contains(path, "\"attributes\": 8192"), "metadata JSON contains attributes"); failures += expect_true(nwsalvage_read_metadata(path, &metadata_entry) == 0, @@ -329,6 +341,18 @@ int main(void) "metadata reader returns NetWare creator id"); failures += expect_true(metadata_entry.netware_modifier_id == 0x00000002, "metadata reader returns NetWare modifier id"); + failures += expect_true(metadata_entry.inherited_rights_mask == 0x01ff, + "metadata reader returns trustee inherited rights mask"); + failures += expect_true(metadata_entry.trustee_count == 2, + "metadata reader returns trustee count"); + failures += expect_true(metadata_entry.trustees[0].object_id == 0x00000001, + "metadata reader returns first trustee object id"); + failures += expect_true(metadata_entry.trustees[0].rights == 0x01ff, + "metadata reader returns first trustee rights"); + failures += expect_true(metadata_entry.trustees[1].object_id == 0x01020304, + "metadata reader returns second trustee object id"); + failures += expect_true(metadata_entry.trustees[1].rights == 0x0041, + "metadata reader returns second trustee rights"); } remove_test_tree(root); #else