tests: verify salvage payloads through NCP reads
This commit is contained in:
@@ -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})
|
||||
|
||||
147
tests/salvage/ncp_read_smoke.c
Normal file
147
tests/salvage/ncp_read_smoke.c
Normal 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;
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user