salvage: preserve trustee metadata
All checks were successful
Source release / source-package (push) Successful in 52s

This commit is contained in:
Mario Fetka
2026-05-31 10:36:10 +00:00
parent ce34e2df65
commit 5fc5a5218f
4 changed files with 152 additions and 0 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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