From 3368c8561cb06406ebefd978b46f332f40a1ead5 Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 30 May 2026 19:54:46 +0000 Subject: [PATCH] tests: add AFP delete smoke cleanup --- tests/linux/CMakeLists.txt | 4 + tests/linux/afp_delete_smoke.c | 296 +++++++++++++++++++++++++++++++++ tests/linux/afp_smoke_suite.sh | 48 +++--- 3 files changed, 324 insertions(+), 24 deletions(-) create mode 100644 tests/linux/afp_delete_smoke.c diff --git a/tests/linux/CMakeLists.txt b/tests/linux/CMakeLists.txt index 0197eb3..48ea5e0 100644 --- a/tests/linux/CMakeLists.txt +++ b/tests/linux/CMakeLists.txt @@ -71,6 +71,10 @@ add_executable(afp_create_file_smoke afp_create_file_smoke.c) target_include_directories(afp_create_file_smoke PRIVATE ${NCPFS_INCLUDE_DIR}) target_link_libraries(afp_create_file_smoke ${NCPFS_LIBRARY}) +add_executable(afp_delete_smoke afp_delete_smoke.c) +target_include_directories(afp_delete_smoke PRIVATE ${NCPFS_INCLUDE_DIR}) +target_link_libraries(afp_delete_smoke ${NCPFS_LIBRARY}) + add_executable(afp_temp_dir_handle_smoke afp_temp_dir_handle_smoke.c) target_include_directories(afp_temp_dir_handle_smoke PRIVATE ${NCPFS_INCLUDE_DIR}) target_link_libraries(afp_temp_dir_handle_smoke ${NCPFS_LIBRARY}) diff --git a/tests/linux/afp_delete_smoke.c b/tests/linux/afp_delete_smoke.c new file mode 100644 index 0000000..a97b504 --- /dev/null +++ b/tests/linux/afp_delete_smoke.c @@ -0,0 +1,296 @@ +/* + * Linux smoke test for NetWare AFP Delete. + * + * The helper sends the WebSDK/nwafp.h AFP Delete request through ncpfs/libncp + * and, on success, verifies that AFP Entry ID From Path Name no longer finds + * the deleted object. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifndef NCPC_SUBFUNCTION +#define NCPC_SUBFUNCTION 0x10000 +#endif +#ifndef NCPC_SFN +#define NCPC_SFN(FN, SFN) ((FN) | ((SFN) << 8) | NCPC_SUBFUNCTION) +#endif + +#define AFP_DELETE 0x03 +#define AFP_GET_ENTRY_ID_FROM_PATH 0x0c +#define NWE_INVALID_PATH 0x9c +#ifndef NWE_INVALID_NCP_PACKET_LENGTH +#define NWE_INVALID_NCP_PACKET_LENGTH 0x7e +#endif + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [--expect-completion CODE] [--volume N] [--entry-id ID] " + "[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 -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/AFPFILE\n", + prog, prog); +} + +static int parse_u32(const char *text, uint32_t *value) +{ + char *end = NULL; + unsigned long v; + + errno = 0; + v = strtoul(text, &end, 0); + if (errno || !end || *end || v > 0xffffffffUL) + return -1; + *value = (uint32_t)v; + return 0; +} + +static void cpu_to_be32(uint32_t v, uint8_t p[4]) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static uint32_t be32_to_cpu(const uint8_t p[4]) +{ + return ((uint32_t)p[0] << 24) | + ((uint32_t)p[1] << 16) | + ((uint32_t)p[2] << 8) | + p[3]; +} + +static int split_parent_leaf(const char *path, char *parent, size_t parent_size, + char *leaf, size_t leaf_size) +{ + const char *slash = strrchr(path, '/'); + const char *backslash = strrchr(path, '\\'); + const char *sep = slash; + size_t parent_len; + size_t leaf_len; + + if (backslash && (!sep || backslash > sep)) + sep = backslash; + + if (!sep) { + const char *colon = strchr(path, ':'); + if (!colon || colon[1] == '\0') + return -1; + parent_len = (size_t)(colon - path + 1); + leaf_len = strlen(colon + 1); + if (parent_len >= parent_size || leaf_len >= leaf_size || !leaf_len) + return -1; + memcpy(parent, path, parent_len); + parent[parent_len] = '\0'; + memcpy(leaf, colon + 1, leaf_len + 1); + return 0; + } + + parent_len = (size_t)(sep - path); + leaf_len = strlen(sep + 1); + if (!parent_len || !leaf_len || parent_len >= parent_size || + leaf_len >= leaf_size) + return -1; + + memcpy(parent, path, parent_len); + parent[parent_len] = '\0'; + memcpy(leaf, sep + 1, leaf_len + 1); + return 0; +} + +static NWCCODE afp_get_entry_id_from_path(NWCONN_HANDLE conn, + const char *path, + uint32_t *entry_id) +{ + size_t path_len = strlen(path); + uint8_t request[1 + 1 + 255]; + uint8_t reply_buf[4]; + NW_FRAGMENT reply; + NWCCODE err; + + if (path_len > 255) + return NWE_INVALID_PATH; + + request[0] = 0; /* directory handle 0, raw VOL:path */ + request[1] = (uint8_t)path_len; + memcpy(request + 2, path, path_len); + + memset(reply_buf, 0, sizeof(reply_buf)); + reply.fragAddr.rw = reply_buf; + reply.fragSize = sizeof(reply_buf); + + err = NWRequestSimple(conn, + NCPC_SFN(0x23, AFP_GET_ENTRY_ID_FROM_PATH), + request, + 2 + path_len, + &reply); + if (err) + return err; + if (reply.fragSize < 4) + return NWE_INVALID_NCP_PACKET_LENGTH; + + *entry_id = be32_to_cpu(reply_buf); + return 0; +} + +int main(int argc, char **argv) +{ + NWCONN_HANDLE conn; + NW_FRAGMENT reply; + long init_err = 0; + const char *path = NULL; + uint32_t volume_number = 0; + uint32_t base_entry_id = 0; + uint32_t expect_completion = 0xffffffffU; + int i; + char parent[256]; + char leaf[256]; + size_t leaf_len; + uint8_t request[1 + 4 + 1 + 255]; + uint8_t reply_buf[4]; + NWCCODE err; + uint32_t ignored_entry_id = 0; + + 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], "--volume")) { + if (++i >= argc || parse_u32(argv[i], &volume_number) || + volume_number > 255) { + fprintf(stderr, "invalid --volume value\n"); + ncp_close(conn); + return 2; + } + } else if (!strcmp(argv[i], "--entry-id")) { + if (++i >= argc || parse_u32(argv[i], &base_entry_id)) { + fprintf(stderr, "invalid --entry-id value\n"); + ncp_close(conn); + return 2; + } + } else if (!strcmp(argv[i], "--expect-completion")) { + if (++i >= argc || parse_u32(argv[i], &expect_completion) || + expect_completion > 255) { + fprintf(stderr, "invalid --expect-completion value\n"); + ncp_close(conn); + return 2; + } + } 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; + } + + if (split_parent_leaf(path, parent, sizeof(parent), leaf, sizeof(leaf))) { + fprintf(stderr, "could not split parent/leaf from path: %s\n", path); + ncp_close(conn); + return 2; + } + + if (!base_entry_id) { + err = afp_get_entry_id_from_path(conn, parent, &base_entry_id); + if (err) { + fprintf(stderr, "AFP parent Entry ID lookup failed: parent=%s error=0x%04x\n", + parent, (unsigned int)err); + ncp_close(conn); + return 1; + } + } + + leaf_len = strlen(leaf); + if (leaf_len > 255) { + fprintf(stderr, "leaf name too long for AFP path-modifying string\n"); + ncp_close(conn); + return 2; + } + + memset(request, 0, sizeof(request)); + request[0] = (uint8_t)volume_number; + cpu_to_be32(base_entry_id, request + 1); + request[5] = (uint8_t)leaf_len; + memcpy(request + 6, leaf, leaf_len); + + memset(reply_buf, 0, sizeof(reply_buf)); + reply.fragAddr.rw = reply_buf; + reply.fragSize = sizeof(reply_buf); + + err = NWRequestSimple(conn, + NCPC_SFN(0x23, AFP_DELETE), + request, + 6 + leaf_len, + &reply); + + if (expect_completion != 0xffffffffU) { + if (err != (NWCCODE)expect_completion) { + fprintf(stderr, + "AFP Delete completion mismatch: path=%s got=0x%04x expected=0x%02x\n", + path, (unsigned int)err, (unsigned int)expect_completion); + ncp_close(conn); + return 1; + } + printf("AFP Delete returned expected completion 0x%02x: path=%s parent=%s leaf=%s parent_entry_id=0x%08x\n", + (unsigned int)expect_completion, path, parent, leaf, + (unsigned int)base_entry_id); + ncp_close(conn); + return 0; + } + + if (err) { + fprintf(stderr, + "AFP Delete failed: path=%s parent=%s leaf=%s parent_entry_id=0x%08x error=0x%04x\n", + path, parent, leaf, (unsigned int)base_entry_id, + (unsigned int)err); + ncp_close(conn); + return 1; + } + + err = afp_get_entry_id_from_path(conn, path, &ignored_entry_id); + if (!err) { + fprintf(stderr, + "AFP Delete verify failed: path=%s still resolves to entry_id=0x%08x\n", + path, (unsigned int)ignored_entry_id); + ncp_close(conn); + return 1; + } + + printf("AFP Delete path=%s parent=%s leaf=%s parent_entry_id=0x%08x verified\n", + path, parent, leaf, (unsigned int)base_entry_id); + ncp_close(conn); + return 0; +} diff --git a/tests/linux/afp_smoke_suite.sh b/tests/linux/afp_smoke_suite.sh index a800720..f6c154d 100755 --- a/tests/linux/afp_smoke_suite.sh +++ b/tests/linux/afp_smoke_suite.sh @@ -433,14 +433,14 @@ run_cmd \ if [ -d "$UNIX_DIR_PATH" ] || [ -d "$UNIX_DIR_PATH_DOS" ]; then run_optional_cmd \ "Prepare AFP Create Directory cleanup" \ - "rmdir '$UNIX_DIR_PATH' or '$UNIX_DIR_PATH_DOS'" \ - cleanup_created_dir "$UNIX_DIR_PATH" "$UNIX_DIR_PATH_DOS" || true + "./afp_delete_smoke $COMMON_PRINT '$CREATE_DIR_PATH'" \ + "$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_DIR_PATH" || true fi if [ -d "$UNIX_DIR20_PATH" ] || [ -d "$UNIX_DIR20_PATH_DOS" ]; then run_optional_cmd \ "Prepare AFP 2.0 Create Directory cleanup" \ - "rmdir '$UNIX_DIR20_PATH' or '$UNIX_DIR20_PATH_DOS'" \ - cleanup_created_dir "$UNIX_DIR20_PATH" "$UNIX_DIR20_PATH_DOS" || true + "./afp_delete_smoke $COMMON_PRINT '$CREATE_DIR20_PATH'" \ + "$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_DIR20_PATH" || true fi run_cmd \ @@ -454,27 +454,27 @@ run_cmd \ "$SCRIPT_DIR/afp_create_directory_smoke" --afp20 \ -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_DIR20_PATH" -run_optional_cmd \ - "Cleanup AFP Create Directory" \ - "rmdir '$UNIX_DIR_PATH' or '$UNIX_DIR_PATH_DOS'" \ - cleanup_created_dir "$UNIX_DIR_PATH" "$UNIX_DIR_PATH_DOS" || true +run_cmd \ + "AFP Delete Created Directory" \ + "./afp_delete_smoke $COMMON_PRINT '$CREATE_DIR_PATH'" \ + "$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_DIR_PATH" -run_optional_cmd \ - "Cleanup AFP 2.0 Create Directory" \ - "rmdir '$UNIX_DIR20_PATH' or '$UNIX_DIR20_PATH_DOS'" \ - cleanup_created_dir "$UNIX_DIR20_PATH" "$UNIX_DIR20_PATH_DOS" || true +run_cmd \ + "AFP Delete Created 2.0 Directory" \ + "./afp_delete_smoke $COMMON_PRINT '$CREATE_DIR20_PATH'" \ + "$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_DIR20_PATH" if [ -f "$UNIX_FILE_PATH" ] || [ -f "$UNIX_FILE_PATH_DOS" ]; then run_optional_cmd \ "Prepare AFP Create File cleanup" \ - "rm '$UNIX_FILE_PATH' or '$UNIX_FILE_PATH_DOS'" \ - cleanup_created_file "$UNIX_FILE_PATH" "$UNIX_FILE_PATH_DOS" || true + "./afp_delete_smoke $COMMON_PRINT '$CREATE_FILE_PATH'" \ + "$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE_PATH" || true fi if [ -f "$UNIX_FILE20_PATH" ] || [ -f "$UNIX_FILE20_PATH_DOS" ]; then run_optional_cmd \ "Prepare AFP 2.0 Create File cleanup" \ - "rm '$UNIX_FILE20_PATH' or '$UNIX_FILE20_PATH_DOS'" \ - cleanup_created_file "$UNIX_FILE20_PATH" "$UNIX_FILE20_PATH_DOS" || true + "./afp_delete_smoke $COMMON_PRINT '$CREATE_FILE20_PATH'" \ + "$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE20_PATH" || true fi run_cmd \ @@ -488,15 +488,15 @@ run_cmd \ "$SCRIPT_DIR/afp_create_file_smoke" --afp20 \ -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE20_PATH" -run_optional_cmd \ - "Cleanup AFP Create File" \ - "rm '$UNIX_FILE_PATH' or '$UNIX_FILE_PATH_DOS'" \ - cleanup_created_file "$UNIX_FILE_PATH" "$UNIX_FILE_PATH_DOS" || true +run_cmd \ + "AFP Delete Created File" \ + "./afp_delete_smoke $COMMON_PRINT '$CREATE_FILE_PATH'" \ + "$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE_PATH" -run_optional_cmd \ - "Cleanup AFP 2.0 Create File" \ - "rm '$UNIX_FILE20_PATH' or '$UNIX_FILE20_PATH_DOS'" \ - cleanup_created_file "$UNIX_FILE20_PATH" "$UNIX_FILE20_PATH_DOS" || true +run_cmd \ + "AFP Delete Created 2.0 File" \ + "./afp_delete_smoke $COMMON_PRINT '$CREATE_FILE20_PATH'" \ + "$SCRIPT_DIR/afp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE20_PATH" run_cmd \ "AFP Open File Fork" \