tests: verify salvage payloads through NCP reads

This commit is contained in:
bot
2026-06-01 07:12:54 +00:00
committed by Mario Fetka
parent a57a87bf9b
commit 33236d6a4e
3 changed files with 233 additions and 68 deletions

View File

@@ -63,6 +63,10 @@ if(SALVAGE_NCPFS_INCLUDE_DIR AND SALVAGE_NCPFS_LIBRARY)
target_include_directories(ncp_delete_smoke PRIVATE ${SALVAGE_NCPFS_INCLUDE_DIR})
target_link_libraries(ncp_delete_smoke ${SALVAGE_NCPFS_LIBRARY})
add_executable(ncp_read_smoke ncp_read_smoke.c)
target_include_directories(ncp_read_smoke PRIVATE ${SALVAGE_NCPFS_INCLUDE_DIR})
target_link_libraries(ncp_read_smoke ${SALVAGE_NCPFS_LIBRARY})
add_executable(ncp_salvage_scan_smoke ncp_salvage_scan_smoke.c)
target_include_directories(ncp_salvage_scan_smoke PRIVATE ${SALVAGE_NCPFS_INCLUDE_DIR})
target_link_libraries(ncp_salvage_scan_smoke ${SALVAGE_NCPFS_LIBRARY})

View File

@@ -0,0 +1,147 @@
/*
* Linux smoke helper for reading a NetWare file through ncpfs/ncplib.
*
* The helper is intentionally small and uses the public ncplib open/read/close
* calls. The salvage smoke script uses it instead of local Unix cat so the
* content check goes through the same NCP path that created/recovered the file.
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncp/nwcalls.h>
#include <ncp/ncplib.h>
#include <ncp/kernel/ncp.h>
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [--expect TEXT] [ncpfs options] PATH\n"
"\n"
"ncpfs options are parsed by ncp_initialize(), for example:\n"
" -S SERVER -U USER -P PASSWORD -n\n"
"\n"
"Example:\n"
" %s --expect hello -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/SALVAGE.TXT\n",
prog, prog);
}
static int content_matches(const char *buf, size_t len, const char *expected)
{
size_t expected_len;
if (!expected)
return 1;
expected_len = strlen(expected);
if (len == expected_len && !memcmp(buf, expected, expected_len))
return 1;
if (len == expected_len + 1 && buf[expected_len] == '\n' &&
!memcmp(buf, expected, expected_len))
return 1;
return 0;
}
int main(int argc, char **argv)
{
NWCONN_HANDLE conn;
long init_err = 0;
const char *path = NULL;
const char *expected = NULL;
struct ncp_file_info file_info;
char *buf = NULL;
size_t alloc_len;
long err;
int i;
int rc = 1;
if (NWCallsInit(NULL, NULL)) {
fprintf(stderr, "NWCallsInit failed\n");
return 2;
}
conn = ncp_initialize(&argc, argv, 1, &init_err);
if (!conn) {
fprintf(stderr, "ncp_initialize/login failed: %ld\n", init_err);
usage(argv[0]);
return 2;
}
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--expect")) {
if (++i >= argc) {
fprintf(stderr, "missing --expect value\n");
ncp_close(conn);
return 2;
}
expected = argv[i];
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
usage(argv[0]);
ncp_close(conn);
return 0;
} else if (!path) {
path = argv[i];
} else {
fprintf(stderr, "unexpected argument: %s\n", argv[i]);
usage(argv[0]);
ncp_close(conn);
return 2;
}
}
if (!path) {
usage(argv[0]);
ncp_close(conn);
return 2;
}
memset(&file_info, 0, sizeof(file_info));
err = ncp_open_file(conn, 0, path, 0, AR_READ_ONLY, &file_info);
if (err) {
fprintf(stderr, "NCP open for read failed: path=%s error=0x%04x\n",
path, (unsigned int)err);
ncp_close(conn);
return 1;
}
alloc_len = file_info.file_length ? (size_t)file_info.file_length : 1;
buf = malloc(alloc_len + 1);
if (!buf) {
fprintf(stderr, "out of memory reading path=%s size=%lu\n",
path, (unsigned long)alloc_len);
ncp_close_file(conn, file_info.file_id);
ncp_close(conn);
return 1;
}
err = ncp_read(conn, file_info.file_id, 0, alloc_len, buf);
if (err < 0) {
fprintf(stderr, "NCP read failed: path=%s error=%ld\n", path, err);
goto out;
}
buf[err] = '\0';
if (err > 0 && fwrite(buf, 1, (size_t)err, stdout) != (size_t)err) {
fprintf(stderr, "stdout write failed while reading path=%s\n", path);
goto out;
}
if (!content_matches(buf, (size_t)err, expected)) {
fprintf(stderr,
"NCP read content mismatch: path=%s bytes=%ld expected='%s'\n",
path, err, expected ? expected : "");
goto out;
}
fprintf(stderr, "NCP read path=%s bytes=%ld verified\n", path, err);
rc = 0;
out:
free(buf);
ncp_close_file(conn, file_info.file_id);
ncp_close(conn);
return rc;
}

View File

@@ -2,9 +2,10 @@
# End-to-end mars_nwe salvage smoke suite.
#
# The suite purges stale salvage entries through NCP first, then creates up to
# three deleted versions through NCP writes/deletes. It cats the live payload,
# the .recycle payload for each version, and the restored live file after
# recover so content changes are visible in the report.
# three deleted versions through NCP writes/deletes. It reads the live payload,
# the .recycle payload for each version, and the restored live file back through
# NCP so content changes are visible in the report even when local Unix file
# permissions prevent cat.
set -u
@@ -43,11 +44,11 @@ Options:
-h, --help Show this help
The suite uses tests/salvage/ncp_delete_smoke to create, write and delete the
file through classic NCP requests, and tests/salvage/ncp_salvage_purge_smoke to
purge stale salvage entries through NCP before the content checks start. It does
not use local Unix rm/unlink/write for cleanup or payload changes; local
filesystem access is only used for cat/readback of live, .recycle and .salvage
files.
file through classic NCP requests, tests/salvage/ncp_read_smoke to read and
verify payloads through NCP, and tests/salvage/ncp_salvage_purge_smoke to purge
stale salvage entries through NCP before the content checks start. It does not
use local Unix rm/unlink/write/cat for cleanup, payload changes or content
verification.
USAGE
}
@@ -140,6 +141,19 @@ relative_from_netware_path() {
printf '%s\n' "$path"
}
netware_volume_name() {
local path=$1
path=${path//\\//}
case "$path" in *:*) printf '%s\n' "${path%%:*}" ;; *) return 1 ;; esac
}
netware_path_from_volume_rel() {
local rel=$1 volume
volume=$(netware_volume_name "$NETWARE_PATH") || return 1
rel=${rel#/}
printf '%s:%s\n' "$volume" "$rel"
}
netware_directory_path() {
local path=$1 volume rest
path=${path//\\//}
@@ -213,6 +227,13 @@ prepare_paths() {
FIRST_META=$(resolve_case_path "$VOLUME_ROOT" "$SALVAGE_REPOSITORY/$FIRST_REL.json")
SECOND_META=$(resolve_case_path "$VOLUME_ROOT" "$SALVAGE_REPOSITORY/$SECOND_REL.json")
THIRD_META=$(resolve_case_path "$VOLUME_ROOT" "$SALVAGE_REPOSITORY/$THIRD_REL.json")
FIRST_RECYCLE_NW=$(netware_path_from_volume_rel "$RECYCLE_REPOSITORY/$FIRST_REL") || return 1
SECOND_RECYCLE_NW=$(netware_path_from_volume_rel "$RECYCLE_REPOSITORY/$SECOND_REL") || return 1
THIRD_RECYCLE_NW=$(netware_path_from_volume_rel "$RECYCLE_REPOSITORY/$THIRD_REL") || return 1
FIRST_META_NW=$(netware_path_from_volume_rel "$SALVAGE_REPOSITORY/$FIRST_REL.json") || return 1
SECOND_META_NW=$(netware_path_from_volume_rel "$SALVAGE_REPOSITORY/$SECOND_REL.json") || return 1
THIRD_META_NW=$(netware_path_from_volume_rel "$SALVAGE_REPOSITORY/$THIRD_REL.json") || return 1
}
clean_stale_artifacts() {
@@ -229,73 +250,57 @@ check_file() {
return 0
}
cat_file_to_report() {
local path=$1 out_file=$2 status
ncp_read_path_to_report() {
local ncp_path=$1 expected=${2-} status
if [ -r "$path" ]; then
emit "\$ cat '$path'"
cat "$path" 2>&1 | tee "$out_file" | tee -a "$REPORT_TMP"
status=${PIPESTATUS[0]}
elif command -v sudo >/dev/null 2>&1; then
emit "\$ sudo -n cat '$path'"
sudo -n cat "$path" 2>&1 | tee "$out_file" | tee -a "$REPORT_TMP"
status=${PIPESTATUS[0]}
if [ -n "$expected" ]; then
emit "\$ ./ncp_read_smoke --expect '$expected' -S '$SERVER' -U '$USER_NAME' -P ****** '$ncp_path'"
"$SCRIPT_DIR/ncp_read_smoke" --expect "$expected" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$ncp_path" 2>&1 | tee -a "$REPORT_TMP"
else
emit "\$ cat '$path'"
cat "$path" 2>&1 | tee "$out_file" | tee -a "$REPORT_TMP"
status=${PIPESTATUS[0]}
emit "\$ ./ncp_read_smoke -S '$SERVER' -U '$USER_NAME' -P ****** '$ncp_path'"
"$SCRIPT_DIR/ncp_read_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$ncp_path" 2>&1 | tee -a "$REPORT_TMP"
fi
status=${PIPESTATUS[0]}
emit "[exit=$status]"
return "$status"
}
cat_metadata() {
local label=$1 path=$2 cat_tmp
cat_tmp=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-cat.XXXXXX") || {
fail_check "could not allocate cat temp file"
ncp_read_metadata() {
local label=$1 ncp_path=$2 read_tmp status
read_tmp=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-read.XXXXXX") || {
fail_check "could not allocate NCP read temp file"
return 1
}
section "$label"
if ! cat_file_to_report "$path" "$cat_tmp"; then
fail_check "could not cat metadata: $path"
rm -f "$cat_tmp"
emit "ncp_path=$ncp_path"
emit "\$ ./ncp_read_smoke -S '$SERVER' -U '$USER_NAME' -P ****** '$ncp_path'"
"$SCRIPT_DIR/ncp_read_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$ncp_path" 2>&1 | tee "$read_tmp" | tee -a "$REPORT_TMP"
status=${PIPESTATUS[0]}
emit "[exit=$status]"
if [ "$status" -ne 0 ]; then
fail_check "could not NCP-read metadata: $ncp_path"
rm -f "$read_tmp"
return 1
fi
grep -q '"source"[[:space:]]*:[[:space:]]*"mars_nwe"' "$cat_tmp" || fail_check "metadata missing source=mars_nwe"
grep -q '"original_path"' "$cat_tmp" || fail_check "metadata missing original_path"
grep -q '"recycle_relative_path"' "$cat_tmp" || fail_check "metadata missing recycle_relative_path"
grep -q '"salvage_relative_path"' "$cat_tmp" || fail_check "metadata missing salvage_relative_path"
grep -q '"trustees"' "$cat_tmp" || fail_check "metadata missing trustees"
rm -f "$cat_tmp"
grep -q '"source"[[:space:]]*:[[:space:]]*"mars_nwe"' "$read_tmp" || fail_check "metadata missing source=mars_nwe"
grep -q '"original_path"' "$read_tmp" || fail_check "metadata missing original_path"
grep -q '"recycle_relative_path"' "$read_tmp" || fail_check "metadata missing recycle_relative_path"
grep -q '"salvage_relative_path"' "$read_tmp" || fail_check "metadata missing salvage_relative_path"
grep -q '"trustees"' "$read_tmp" || fail_check "metadata missing trustees"
rm -f "$read_tmp"
}
cat_text_file() {
local label=$1 path=$2 expected=${3-} cat_tmp
cat_tmp=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-cat.XXXXXX") || {
fail_check "could not allocate cat temp file"
return 1
}
ncp_read_text_file() {
local label=$1 ncp_path=$2 expected=${3-}
section "$label"
emit "path=$path"
if ! cat_file_to_report "$path" "$cat_tmp"; then
fail_check "could not cat file: $path"
rm -f "$cat_tmp"
emit "ncp_path=$ncp_path"
if ! ncp_read_path_to_report "$ncp_path" "$expected"; then
fail_check "could not NCP-read file: $ncp_path"
return 1
fi
if [ -n "$expected" ]; then
if ! grep -Fxq -- "$expected" "$cat_tmp"; then
fail_check "file content mismatch: $path expected='$expected'"
rm -f "$cat_tmp"
return 1
fi
fi
rm -f "$cat_tmp"
return 0
}
@@ -313,7 +318,7 @@ run_ncp_create_with_payload() {
fail_check "NCP create/write failed for $NETWARE_PATH"
return 1
fi
cat_text_file "$label: cat live file after NCP write" "$UNIX_PATH" "$content"
ncp_read_text_file "$label: NCP read live file after NCP write" "$NETWARE_PATH" "$content"
return 0
}
@@ -390,15 +395,27 @@ run_ncp_salvage_purge_all() {
}
salvage_paths_for_index() {
local index=$1 recycle_var=$2 meta_var=$3 recycle_path= meta_path=
local index=$1 recycle_var=$2 meta_var=$3 recycle_nw_var=$4 meta_nw_var=$5
local recycle_path= meta_path= recycle_nw_path= meta_nw_path=
case "$index" in
0) recycle_path=$FIRST_RECYCLE; meta_path=$FIRST_META ;;
1) recycle_path=$SECOND_RECYCLE; meta_path=$SECOND_META ;;
2) recycle_path=$THIRD_RECYCLE; meta_path=$THIRD_META ;;
0)
recycle_path=$FIRST_RECYCLE; meta_path=$FIRST_META
recycle_nw_path=$FIRST_RECYCLE_NW; meta_nw_path=$FIRST_META_NW
;;
1)
recycle_path=$SECOND_RECYCLE; meta_path=$SECOND_META
recycle_nw_path=$SECOND_RECYCLE_NW; meta_nw_path=$SECOND_META_NW
;;
2)
recycle_path=$THIRD_RECYCLE; meta_path=$THIRD_META
recycle_nw_path=$THIRD_RECYCLE_NW; meta_nw_path=$THIRD_META_NW
;;
*) return 1 ;;
esac
printf -v "$recycle_var" '%s' "$recycle_path"
printf -v "$meta_var" '%s' "$meta_path"
printf -v "$recycle_nw_var" '%s' "$recycle_nw_path"
printf -v "$meta_nw_var" '%s' "$meta_nw_path"
}
recover_first_salvage_entry() {
@@ -423,8 +440,7 @@ recover_first_salvage_entry() {
return 1
fi
check_file "$label: restored live payload exists" "$UNIX_PATH" && \
cat_text_file "$label: cat restored live file" "$UNIX_PATH"
ncp_read_text_file "$label: NCP read restored live file" "$NETWARE_PATH"
count_salvage_entries "$label: NCP salvage scan after recover" after_count || return 1
if [ "$after_count" -ne $((before_count - 1)) ]; then
@@ -435,9 +451,9 @@ recover_first_salvage_entry() {
}
run_delete_capture_cycle() {
local index=$1 label=$2 content=$3 recycle_path meta_path
local index=$1 label=$2 content=$3 recycle_path meta_path recycle_nw_path meta_nw_path
salvage_paths_for_index "$index" recycle_path meta_path || {
salvage_paths_for_index "$index" recycle_path meta_path recycle_nw_path meta_nw_path || {
fail_check "$label: unsupported version index $index"
return 1
}
@@ -445,10 +461,8 @@ run_delete_capture_cycle() {
run_ncp_create_with_payload "$label: NCP create/write live file" "$content" || return 1
run_ncp_delete_only "$label: NCP delete live file" || return 1
check_file "$label: recycle payload before recover" "$recycle_path" && \
cat_text_file "$label: cat recycle payload before recover" "$recycle_path" "$content"
check_file "$label: salvage metadata before recover" "$meta_path" && \
cat_metadata "$label: cat salvage metadata before recover" "$meta_path"
ncp_read_text_file "$label: NCP read recycle payload before recover" "$recycle_nw_path" "$content"
ncp_read_metadata "$label: NCP read salvage metadata before recover" "$meta_nw_path"
}
run_version_capture_cycles() {