From 23be03808795786b81e5f22e786449c68e9ced2f Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sun, 31 May 2026 19:39:29 +0200 Subject: [PATCH] NCP 87/17 Recover Salvageable File --- include/namspace.h | 2 + include/nwsalvage.h | 2 + src/namspace.c | 53 +++ src/nwconn.c | 5 + src/nwsalvage.c | 422 ++++++++++++++++++++++ tests/salvage/CMakeLists.txt | 4 + tests/salvage/ncp_salvage_recover_smoke.c | 132 +++++++ tests/salvage/salvage_smoke_suite.sh | 60 +++ 8 files changed, 680 insertions(+) create mode 100644 tests/salvage/ncp_salvage_recover_smoke.c diff --git a/include/namspace.h b/include/namspace.h index 0b60a37..bff60b1 100644 --- a/include/namspace.h +++ b/include/namspace.h @@ -108,6 +108,8 @@ typedef struct { extern int handle_func_0x57(uint8 *p, uint8 *responsedata, int task); extern int handle_func_0x57_salvage_scan(uint8 *p, int request_len, uint8 *responsedata, int task); +extern int handle_func_0x57_salvage_recover(uint8 *p, int request_len, + uint8 *responsedata, int task); extern int handle_func_0x56(uint8 *p, uint8 *responsedata, int task); extern int fill_namespace_buffer(int volume, uint8 *rdata); diff --git a/include/nwsalvage.h b/include/nwsalvage.h index 7d632b6..ce0f671 100644 --- a/include/nwsalvage.h +++ b/include/nwsalvage.h @@ -200,6 +200,8 @@ int nwsalvage_read_metadata(const char *metadata_path, int nwsalvage_scan_directory(int volume, const char *unix_directory, unsigned long scan_sequence, struct nwsalvage_scan_result *result); +int nwsalvage_recover_scan_sequence(int volume, unsigned long scan_sequence, + unsigned long directory_base, int task); /* * Capture a server-side delete before nw_unlink_node() would remove it. * Returns 0 when the file was moved to the recycle repository and metadata diff --git a/src/namspace.c b/src/namspace.c index 5ce7203..63b0def 100644 --- a/src/namspace.c +++ b/src/namspace.c @@ -2839,6 +2839,59 @@ int handle_func_0x57_salvage_scan(uint8 *q, int request_len, return(result); } + +int handle_func_0x57_salvage_recover(uint8 *q, int request_len, + uint8 *responsedata, int task) +{ + int namespace; + uint32 scan_sequence; + uint32 volume; + uint32 directory_base; + int result; + + (void)namespace; + (void)responsedata; + + /* + * ncpfs ncp_ns_salvage_file() packs 87/17 as: + * byte subfunction 0x11 + * byte namespace + * byte reserved + * dword scan sequence / deleted file id + * dword volume id + * dword directory/base id + * pstr new filename + * mars_nwe recovers to the original_path from the trusted JSON sidecar; the + * client supplied new filename is intentionally not used for the first pass. + */ + if (!q || request_len < 16 || q[0] != 0x11) + return(-0xfb); + + namespace = (int)q[1]; + scan_sequence = GET_32(q + 3); + volume = GET_32(q + 7); + directory_base = GET_32(q + 11); + + if (volume >= (uint32)used_nw_volumes) + return(-0x98); + + result = nwsalvage_recover_scan_sequence((int)volume, scan_sequence, + directory_base, task); + if (result < 0) { + if (errno == EEXIST) + return(-0x92); + return(-0x98); + } + if (result == 0) + return(-0xff); + + XDPRINTF((3, 0, + "NCP 87/17 salvage recover ns=%d seq=0x%lx vol=%lu base=0x%lx result=%d", + namespace, (unsigned long)scan_sequence, (unsigned long)volume, + (unsigned long)directory_base, result)); + return(0); +} + int handle_func_0x57(uint8 *p, uint8 *responsedata, int task) { int result = -0xfb; /* unknown request */ diff --git a/src/nwconn.c b/src/nwconn.c index ea2872c..12fad41 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -5088,6 +5088,11 @@ static int handle_ncp_serv(void) break; case 0x11: /* 87/17 Recover Salvageable File */ + result = handle_func_0x57_salvage_recover(requestdata, requestlen, + responsedata, + ncprequest->task); + break; + case 0x12: /* 87/18 Purge Salvageable File */ result = -0xfb; break; diff --git a/src/nwsalvage.c b/src/nwsalvage.c index c40dfa5..1d1fa4c 100644 --- a/src/nwsalvage.c +++ b/src/nwsalvage.c @@ -6,6 +6,7 @@ #include "nwarchive.h" #include "nwatalk.h" #include "nwattrib.h" +#include "nwfile.h" #include "trustee.h" #include "tools.h" #include "connect.h" @@ -1994,6 +1995,427 @@ int nwsalvage_scan_directory(int volume, const char *unix_directory, return(found); } + +static int nwsalvage_original_path_to_unix(int volume, + const char *original_path, + char *unixname, + size_t unixname_len) +{ + char volume_root[NWSALVAGE_PATH_MAX]; + char volume_name[NWSALVAGE_REPOSITORY_NAME_MAX]; + const char *colon; + const char *rel; + size_t volume_name_len; + + if (!original_path || !*original_path || !unixname || !unixname_len) { + errno = EINVAL; + return(-1); + } + + if (nw_get_volume_name(volume, (uint8 *)volume_name, + sizeof(volume_name)) < 1 || + nwsalvage_copy_volume_root(volume, volume_root, sizeof(volume_root)) < 0) + return(-1); + + colon = strchr(original_path, ':'); + if (!colon) { + errno = EINVAL; + return(-1); + } + + volume_name_len = strlen(volume_name); + if ((size_t)(colon - original_path) != volume_name_len || + strncasecmp(original_path, volume_name, volume_name_len) != 0) { + errno = EINVAL; + return(-1); + } + + rel = colon + 1; + while (*rel == '/' || *rel == '\\') + rel++; + + if (!*rel || !nwsalvage_relative_path_valid(rel)) { + errno = EINVAL; + return(-1); + } + + return(nwsalvage_path_join(unixname, unixname_len, volume_root, rel)); +} + +static int nwsalvage_hex_nibble(int c) +{ + if (c >= '0' && c <= '9') return(c - '0'); + if (c >= 'a' && c <= 'f') return(c - 'a' + 10); + if (c >= 'A' && c <= 'F') return(c - 'A' + 10); + return(-1); +} + +static int nwsalvage_hex_to_bytes(const char *hex, uint8 *out, size_t out_len) +{ + size_t i; + + if (!hex || !out || strlen(hex) != out_len * 2) { + errno = EINVAL; + return(-1); + } + + for (i = 0; i < out_len; i++) { + int hi = nwsalvage_hex_nibble((unsigned char)hex[i * 2]); + int lo = nwsalvage_hex_nibble((unsigned char)hex[i * 2 + 1]); + if (hi < 0 || lo < 0) { + errno = EINVAL; + return(-1); + } + out[i] = (uint8)((hi << 4) | lo); + } + return(0); +} + +static int nwsalvage_finder_info_is_zero(const uint8 *finder_info, size_t len) +{ + size_t i; + for (i = 0; i < len; i++) + if (finder_info[i]) + return(0); + return(1); +} + +static int nwsalvage_restore_metadata(int volume, const char *unixname, + const struct nwsalvage_metadata_entry *entry) +{ + struct stat stb; + struct utimbuf times; + int i; + + if (!unixname || !entry) { + errno = EINVAL; + return(-1); + } + + if (stat(unixname, &stb) < 0) + return(-1); + + (void)set_nw_attrib_dword(volume, (char *)unixname, &stb, + (uint32)entry->attributes); + + (void)mars_nwe_set_archive_info((char *)unixname, + !!(entry->netware_archive_flags & MARS_NWE_ARCHIVE_HAS_DATE), + (uint16)entry->netware_archive_date, + !!(entry->netware_archive_flags & MARS_NWE_ARCHIVE_HAS_TIME), + (uint16)entry->netware_archive_time, + !!(entry->netware_archive_flags & MARS_NWE_ARCHIVE_HAS_ARCHIVER), + (uint32)entry->netware_archiver_id); + + (void)mars_nwe_set_file_info((char *)unixname, + !!(entry->netware_fileinfo_flags & MARS_NWE_FILEINFO_HAS_CREATE_DATE), + (uint16)entry->netware_create_date, + !!(entry->netware_fileinfo_flags & MARS_NWE_FILEINFO_HAS_CREATE_TIME), + (uint16)entry->netware_create_time, + !!(entry->netware_fileinfo_flags & MARS_NWE_FILEINFO_HAS_CREATOR), + (uint32)entry->netware_creator_id); + + (void)mars_nwe_set_file_modifier_info((char *)unixname, + !!(entry->netware_fileinfo_flags & MARS_NWE_FILEINFO_HAS_MODIFIER), + (uint32)entry->netware_modifier_id); + + if (entry->finder_info_hex[0]) { + uint8 finder_info[NWATALK_FINDER_INFO_LEN]; + if (nwsalvage_hex_to_bytes(entry->finder_info_hex, finder_info, + sizeof(finder_info)) == 0 && + !nwsalvage_finder_info_is_zero(finder_info, sizeof(finder_info))) + (void)nwatalk_set_finder_info(unixname, finder_info, sizeof(finder_info)); + } + + if (entry->afp_attributes) + (void)nwatalk_set_afp_attributes(unixname, (uint16)entry->afp_attributes); + + if (entry->afp_entry_id[0]) { + char *end = NULL; + unsigned long entry_id = strtoul(entry->afp_entry_id, &end, 0); + if (end && *end == '\0') + (void)nwatalk_set_entry_id(unixname, (uint32)entry_id); + } + + (void)tru_set_inherited_mask(volume, (uint8 *)unixname, &stb, + (int)entry->inherited_rights_mask); + + if (entry->trustee_count) { + NW_OIC nwoic[NWSALVAGE_TRUSTEE_MAX]; + int count = (int)entry->trustee_count; + if (count > NWSALVAGE_TRUSTEE_MAX) + count = NWSALVAGE_TRUSTEE_MAX; + memset(nwoic, 0, sizeof(nwoic)); + for (i = 0; i < count; i++) { + nwoic[i].id = (uint32)entry->trustees[i].object_id; + nwoic[i].trustee = (int)entry->trustees[i].rights; + } + (void)tru_add_trustee_set(volume, (uint8 *)unixname, &stb, count, nwoic); + } + + if (entry->mode) { + if (seteuid(0)) {} + (void)chmod(unixname, (mode_t)(entry->mode & 07777)); + (void)reseteuid(); + } + + times.actime = (time_t)entry->atime; + times.modtime = (time_t)entry->mtime; + if (times.actime || times.modtime) + (void)utime(unixname, ×); + + return(0); +} + +static int nwsalvage_copy_payload_with_novell_io(int volume, + const char *source_unixname, + const char *dest_unixname, + const struct stat *source_stb, + int task) +{ + struct stat src_open_stb; + struct stat dst_open_stb; + int source_handle = -1; + int dest_handle = -1; + unsigned long long remaining; + uint32 offset = 0; + int result = 0; + + if (!source_unixname || !dest_unixname || !source_stb) { + errno = EINVAL; + return(-1); + } + + source_handle = file_creat_open(volume, (uint8 *)source_unixname, + &src_open_stb, 0, 1, 8, task); + if (source_handle < 0) + return(source_handle); + + dest_handle = file_creat_open(volume, (uint8 *)dest_unixname, + &dst_open_stb, FILE_ATTR_NORMAL, 3, 2 | 8, + task); + if (dest_handle < 0) { + (void)nw_close_file(source_handle, 0, task); + return(dest_handle); + } + + remaining = (unsigned long long)source_stb->st_size; + while (remaining) { + uint32 chunk; + int copied; + + if (offset == 0xffffffffUL) { + result = -0xfe; + break; + } + + chunk = (remaining > 0x7fffffffUL) ? 0x7fffffffUL : (uint32)remaining; + copied = nw_server_copy(source_handle, offset, dest_handle, offset, chunk); + if (copied < 0) { + result = copied; + break; + } + if ((uint32)copied != chunk) { + result = -0xff; + break; + } + remaining -= chunk; + offset += chunk; + } + + if (!result) + result = nw_commit_file(dest_handle); + + (void)nw_close_file(dest_handle, 0, task); + (void)nw_close_file(source_handle, 0, task); + return(result); +} + +static int nwsalvage_metadata_volume_matches(const struct nwsalvage_metadata_entry *entry, + const char *volume_name) +{ + const char *colon; + + if (!entry || !volume_name) + return(0); + + colon = strchr(entry->original_path, ':'); + if (!colon) + return(0); + + return((size_t)(colon - entry->original_path) == strlen(volume_name) && + strncasecmp(entry->original_path, volume_name, + (size_t)(colon - entry->original_path)) == 0); +} + +static int nwsalvage_find_by_base_in_tree(const char *metadata_dir, + const char *volume_root, + const char *volume_name, + unsigned long directory_base, + unsigned long scan_sequence, + unsigned long *match_index, + struct nwsalvage_scan_result *result) +{ + DIR *dir; + struct dirent *de; + int found = 0; + + dir = opendir(metadata_dir); + if (!dir) + return(errno == ENOENT ? 0 : -1); + + while (!found && (de = readdir(dir)) != NULL) { + char child[NWSALVAGE_PATH_MAX]; + struct stat st; + size_t len; + + if (de->d_name[0] == '.') + continue; + if (nwsalvage_path_join(child, sizeof(child), metadata_dir, de->d_name) < 0) + continue; + if (stat(child, &st) < 0) + continue; + + if (S_ISDIR(st.st_mode)) { + found = nwsalvage_find_by_base_in_tree(child, volume_root, volume_name, + directory_base, scan_sequence, + match_index, result); + if (found < 0) + break; + continue; + } + + if (!S_ISREG(st.st_mode)) + continue; + + len = strlen(de->d_name); + if (len < 6 || strcmp(de->d_name + len - 5, ".json") != 0) + continue; + + memset(&result->metadata, 0, sizeof(result->metadata)); + if (nwsalvage_read_metadata(child, &result->metadata) < 0) + continue; + if (!nwsalvage_metadata_volume_matches(&result->metadata, volume_name)) + continue; + if (result->metadata.original_parent_entry_id != directory_base) + continue; + + if (*match_index < scan_sequence) { + (*match_index)++; + continue; + } + + if (*match_index != scan_sequence) + continue; + + if (nwsalvage_path_join(result->recycle_path, sizeof(result->recycle_path), + volume_root, result->metadata.recycle_relative_path) < 0) + continue; + + result->scan_sequence = *match_index; + result->scan_volume = (unsigned long)atoi(volume_name); /* overwritten below */ + result->scan_directory_base = directory_base; + strmaxcpy(result->metadata_path, child, sizeof(result->metadata_path) - 1); + found = 1; + } + + closedir(dir); + return(found); +} + +static int nwsalvage_find_by_base(int volume, unsigned long scan_sequence, + unsigned long directory_base, + struct nwsalvage_scan_result *result) +{ + struct nwsalvage_config config; + char volume_root[NWSALVAGE_PATH_MAX]; + char metadata_root[NWSALVAGE_PATH_MAX]; + char volume_name[NWSALVAGE_REPOSITORY_NAME_MAX]; + unsigned long match_index = 0; + int found; + + if (!result) { + errno = EINVAL; + return(-1); + } + + if (nwsalvage_config_load_from_ini(&config, nwsalvage_ini_get, NULL) < 0) + return(-1); + if (!config.enabled) + return(0); + + if (nwsalvage_copy_volume_root(volume, volume_root, sizeof(volume_root)) < 0) + return(-1); + if (nw_get_volume_name(volume, (uint8 *)volume_name, + sizeof(volume_name)) < 1) + return(-1); + if (nwsalvage_path_join(metadata_root, sizeof(metadata_root), volume_root, + config.metadata_repository) < 0) + return(-1); + + memset(result, 0, sizeof(*result)); + found = nwsalvage_find_by_base_in_tree(metadata_root, volume_root, volume_name, + directory_base, scan_sequence, + &match_index, result); + if (found > 0) + result->scan_volume = (unsigned long)volume; + return(found); +} + +int nwsalvage_recover_scan_sequence(int volume, unsigned long scan_sequence, + unsigned long directory_base, int task) +{ + struct nwsalvage_scan_result scan; + char dest_unixname[NWSALVAGE_PATH_MAX]; + struct stat source_stb; + int result; + + memset(&scan, 0, sizeof(scan)); + result = nwsalvage_find_by_base(volume, scan_sequence, directory_base, &scan); + if (result <= 0) + return(result); + + if (nwsalvage_original_path_to_unix(volume, scan.metadata.original_path, + dest_unixname, sizeof(dest_unixname)) < 0) + return(-1); + + if (access(dest_unixname, F_OK) == 0) { + errno = EEXIST; + return(-1); + } + if (errno != ENOENT) + return(-1); + + if (stat(scan.recycle_path, &source_stb) < 0) + return(-1); + if (!S_ISREG(source_stb.st_mode)) { + errno = EINVAL; + return(-1); + } + + if (make_parent_dirs(dest_unixname) < 0) + return(-1); + + result = nwsalvage_copy_payload_with_novell_io(volume, scan.recycle_path, + dest_unixname, &source_stb, + task); + if (result < 0) { + unlink(dest_unixname); + errno = EIO; + return(-1); + } + + if (nwsalvage_restore_metadata(volume, dest_unixname, &scan.metadata) < 0) { + /* The payload is already safely restored through mars_nwe I/O. Keep it. */ + } + + if (unlink(scan.recycle_path) < 0) + return(-1); + if (unlink(scan.metadata_path) < 0) + return(-1); + + return(1); +} + static void nwsalvage_fill_trustees(int volume, const char *unixname, const struct stat *stb, struct nwsalvage_deleted_entry *entry) diff --git a/tests/salvage/CMakeLists.txt b/tests/salvage/CMakeLists.txt index 85e3852..772a4b2 100644 --- a/tests/salvage/CMakeLists.txt +++ b/tests/salvage/CMakeLists.txt @@ -66,6 +66,10 @@ if(SALVAGE_NCPFS_INCLUDE_DIR AND 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}) + + add_executable(ncp_salvage_recover_smoke ncp_salvage_recover_smoke.c) + target_include_directories(ncp_salvage_recover_smoke PRIVATE ${SALVAGE_NCPFS_INCLUDE_DIR}) + target_link_libraries(ncp_salvage_recover_smoke ${SALVAGE_NCPFS_LIBRARY}) else() message(STATUS "Skipping salvage NCP smoke helpers: ncpfs/libncp headers or library not found" diff --git a/tests/salvage/ncp_salvage_recover_smoke.c b/tests/salvage/ncp_salvage_recover_smoke.c new file mode 100644 index 0000000..e6c4833 --- /dev/null +++ b/tests/salvage/ncp_salvage_recover_smoke.c @@ -0,0 +1,132 @@ +/* + * Linux smoke helper for NCP 87/17 Recover Salvageable File. + * + * The helper scans a directory with the official ncplib salvage scan API, then + * recovers the Nth matching deleted original name using ncp_ns_salvage_file(). + * It intentionally selects by the returned scan tuple (seq/vol/base), not by + * backend .recycle version names such as "Copy #1 of ...". + */ + +#include +#include +#include +#include + +#include +#include + +#ifndef NCP_PATH_STD +#define NCP_PATH_STD 0 +#endif + +/* Older distro headers may miss the prototype although patched libncp has it. */ +long ncp_ns_salvage_file(NWCONN_HANDLE conn, u_int8_t src_ns, + const struct ncp_deleted_file *finfo, const char *newfname); + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [ncpfs options] DIRECTORY ORIGINAL_NAME [MATCH_INDEX]\n" + "\n" + "ncpfs options are parsed by ncp_initialize(), for example:\n" + " -S SERVER -U USER -P PASSWORD -n\n" + "\n" + "Example:\n" + " %s -S MARS -U SUPERVISOR -P secret SYS:PUBLIC SLVGCHK.TXT 0\n", + prog, prog); +} + +int main(int argc, char **argv) +{ + NWCONN_HANDLE conn; + long init_err = 0; + const char *path = NULL; + const char *wanted_name = NULL; + int wanted_index = 0; + int matched_index = 0; + struct ncp_deleted_file info; + struct ncp_deleted_file selected; + char name[512]; + char selected_name[512]; + long err; + int selected_valid = 0; + int i; + + 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], "-h") || !strcmp(argv[i], "--help")) { + usage(argv[0]); + ncp_close(conn); + return 0; + } else if (!path) { + path = argv[i]; + } else if (!wanted_name) { + wanted_name = argv[i]; + } else { + wanted_index = atoi(argv[i]); + } + } + + if (!path || !wanted_name) { + usage(argv[0]); + ncp_close(conn); + return 2; + } + + memset(&info, 0, sizeof(info)); + memset(&selected, 0, sizeof(selected)); + selected_name[0] = '\0'; + info.seq = -1; + + while ((err = ncp_ns_scan_salvageable_file(conn, NW_NS_DOS, + NCP_DIRSTYLE_NOHANDLE, 0, 0, (const unsigned char *)path, + NCP_PATH_STD, &info, name, sizeof(name))) == 0) { + printf("NCP salvage recover candidate seq=%d vol=%u base=%u name=%s\n", + (int)info.seq, (unsigned int)info.vol, + (unsigned int)info.base, name); + if (!strcmp(name, wanted_name)) { + if (matched_index == wanted_index) { + selected = info; + snprintf(selected_name, sizeof(selected_name), "%s", name); + selected_valid = 1; + break; + } + matched_index++; + } + } + + if (!selected_valid) { + fprintf(stderr, + "no matching salvage entry: path=%s name=%s index=%d last_error=0x%04x\n", + path, wanted_name, wanted_index, (unsigned int)err); + ncp_close(conn); + return 1; + } + + err = ncp_ns_salvage_file(conn, NW_NS_DOS, &selected, selected_name); + if (err) { + fprintf(stderr, + "NCP salvage recover failed: seq=%d vol=%u base=%u name=%s error=0x%04x\n", + (int)selected.seq, (unsigned int)selected.vol, + (unsigned int)selected.base, selected_name, (unsigned int)err); + ncp_close(conn); + return 1; + } + + printf("NCP salvage recover ok seq=%d vol=%u base=%u name=%s\n", + (int)selected.seq, (unsigned int)selected.vol, + (unsigned int)selected.base, selected_name); + ncp_close(conn); + return 0; +} diff --git a/tests/salvage/salvage_smoke_suite.sh b/tests/salvage/salvage_smoke_suite.sh index 699bcfd..330e43e 100755 --- a/tests/salvage/salvage_smoke_suite.sh +++ b/tests/salvage/salvage_smoke_suite.sh @@ -265,6 +265,65 @@ cat_metadata() { grep -q '"trustees"' "$path" || fail_check "metadata missing trustees" } + +run_ncp_salvage_recover() { + local status before_scan_tmp after_scan_tmp + before_scan_tmp=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-recover-before.XXXXXX") || { + fail_check "could not allocate recover pre-scan temp file" + return 1 + } + after_scan_tmp=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-recover-after.XXXXXX") || { + rm -f "$before_scan_tmp" + fail_check "could not allocate recover post-scan temp file" + return 1 + } + + section "NCP salvage recover 87/17" + emit "recover_path=$SCAN_PATH" + emit "recover_name=$BASENAME" + emit "\$ ./ncp_salvage_recover_smoke -S '$SERVER' -U '$USER_NAME' -P ****** '$SCAN_PATH' '$BASENAME' 0" + "$SCRIPT_DIR/ncp_salvage_recover_smoke" -S "$SERVER" -U "$USER_NAME" \ + -P "$PASSWORD" "$SCAN_PATH" "$BASENAME" 0 2>&1 | tee "$before_scan_tmp" | tee -a "$REPORT_TMP" + status=${PIPESTATUS[0]} + emit "[exit=$status]" + if [ "$status" -ne 0 ]; then + fail_check "NCP salvage recover failed for $SCAN_PATH/$BASENAME" + rm -f "$before_scan_tmp" "$after_scan_tmp" + return 1 + fi + + check_file "recover smoke: restored live payload" "$UNIX_PATH" + if [ -e "$FIRST_RECYCLE" ]; then + fail_check "recover smoke: first recycle payload still exists: $FIRST_RECYCLE" + else + emit "recover smoke: first recycle payload consumed: $FIRST_RECYCLE" + fi + if [ -e "$FIRST_META" ]; then + fail_check "recover smoke: first metadata sidecar still exists: $FIRST_META" + else + emit "recover smoke: first metadata sidecar consumed: $FIRST_META" + fi + + section "NCP salvage scan after recover" + "$SCRIPT_DIR/ncp_salvage_scan_smoke" -S "$SERVER" -U "$USER_NAME" \ + -P "$PASSWORD" "$SCAN_PATH" 2>&1 | tee "$after_scan_tmp" | tee -a "$REPORT_TMP" + status=${PIPESTATUS[0]} + emit "[exit=$status]" + if [ "$status" -ne 0 ]; then + fail_check "NCP salvage scan after recover failed for $SCAN_PATH" + else + local basename_count + basename_count=$(grep -F "name=$BASENAME" "$after_scan_tmp" | wc -l) + if [ "$basename_count" -lt 1 ]; then + fail_check "NCP salvage scan after recover expected remaining history entry named $BASENAME" + else + emit "NCP salvage scan after recover remaining $BASENAME entries=$basename_count" + fi + fi + + rm -f "$before_scan_tmp" "$after_scan_tmp" +} + run_ncp_salvage_scan() { local status scan_tmp scan_tmp=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-scan.XXXXXX") || { @@ -337,6 +396,7 @@ if [ -f "$SECOND_META" ]; then fi run_ncp_salvage_scan +run_ncp_salvage_recover finish_report [ "$FAILURES" -eq 0 ]