From 33236d6a4e20890eebe96858897646bd7e5a7f4d Mon Sep 17 00:00:00 2001 From: bot Date: Mon, 1 Jun 2026 07:12:54 +0000 Subject: [PATCH] tests: verify salvage payloads through NCP reads --- tests/salvage/CMakeLists.txt | 4 + tests/salvage/ncp_read_smoke.c | 147 ++++++++++++++++++++++++++ tests/salvage/salvage_smoke_suite.sh | 150 +++++++++++++++------------ 3 files changed, 233 insertions(+), 68 deletions(-) create mode 100644 tests/salvage/ncp_read_smoke.c diff --git a/tests/salvage/CMakeLists.txt b/tests/salvage/CMakeLists.txt index 3713378..05f5f4c 100644 --- a/tests/salvage/CMakeLists.txt +++ b/tests/salvage/CMakeLists.txt @@ -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}) diff --git a/tests/salvage/ncp_read_smoke.c b/tests/salvage/ncp_read_smoke.c new file mode 100644 index 0000000..d677f02 --- /dev/null +++ b/tests/salvage/ncp_read_smoke.c @@ -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 +#include +#include +#include +#include + +#include +#include +#include + +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; +} diff --git a/tests/salvage/salvage_smoke_suite.sh b/tests/salvage/salvage_smoke_suite.sh index 0f8c27b..bc1c53d 100755 --- a/tests/salvage/salvage_smoke_suite.sh +++ b/tests/salvage/salvage_smoke_suite.sh @@ -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() {