From b3a5468441eca6d48842082b28841c71e6c07ce6 Mon Sep 17 00:00:00 2001 From: a Date: Sat, 30 May 2026 21:28:24 +0000 Subject: [PATCH] tests: add AFP open file fork entry-id smoke --- tests/linux/README.md | 27 +++--- tests/linux/afp_open_file_fork_smoke.c | 112 +++++++++++++++++++------ tests/linux/afp_smoke_suite.sh | 10 +++ 3 files changed, 115 insertions(+), 34 deletions(-) diff --git a/tests/linux/README.md b/tests/linux/README.md index 41d68f8..fb495d7 100644 --- a/tests/linux/README.md +++ b/tests/linux/README.md @@ -502,20 +502,25 @@ NCP 0x2222/35/08 AFP Open File Fork ``` mars_nwe supports raw `VOL:`-style path requests such as `SYS:` or -`HOME:` with base Entry ID zero and opens only the AFP data fork. Read and -write data-fork opens are routed through the existing NetWare file open/share -path (`nw_creat_open_file()`/`file_creat_open()`), so trustee rights, read-only +`HOME:` with base Entry ID zero and opens only the AFP data fork. It also +accepts entry-id-only data-fork opens for files whose AFP entry id is already +persisted in the `org.mars-nwe.afp.entry-id` nwatalk metadata. Read and write +data-fork opens are routed through the existing NetWare file open/share path +(`nw_creat_open_file()`/`file_creat_open()`), so trustee rights, read-only attributes, and share-deny checks stay shared with regular NCP file opens. For path-backed requests, mars_nwe resolves the effective NetWare volume from the -raw path prefix instead of assuming volume 0. On success the server returns the -normal six-byte NetWare file handle shape used by AFP handle APIs plus the -current data-fork length. The smoke helper immediately closes the returned -NetWare file handle in the same connection. +raw path prefix instead of assuming volume 0. Entry-id-only file opens reverse +resolve through the AFP/nwatalk metadata cache, not through DOS namespace +directory numbers. On success the server returns the normal six-byte NetWare +file handle shape used by AFP handle APIs plus the current data-fork length. +The smoke helper immediately closes the returned NetWare file handle in the +same connection. Useful smoke cases for a standard MARS-NWE `SYS` volume are: ```sh ./tests/linux/afp_open_file_fork_smoke -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/pmdflts.ini +./tests/linux/afp_open_file_fork_smoke --entry-id-only -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/pmdflts.ini ./tests/linux/afp_open_file_fork_smoke -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/ohlogscr.bat ``` @@ -529,6 +534,7 @@ against the standard DOS utility files produced: ```text AFP Open File Fork path=SYS:PUBLIC/pmdflts.ini handle=1 fork=0 access=0x01 fork_len=8161 +AFP Open File Fork path=SYS:PUBLIC/pmdflts.ini entry_id=0x32d8e6b2 handle=1 fork=0 access=0x01 fork_len=8161 entry-id-only AFP Open File Fork path=SYS:PUBLIC/ohlogscr.bat handle=1 fork=0 access=0x01 fork_len=1296 ``` @@ -548,12 +554,13 @@ then removed with AFP Delete: ```sh ./tests/linux/afp_create_file_smoke -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/afpwrit ./tests/linux/afp_open_file_fork_smoke --access 0x02 -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/afpwrit +./tests/linux/afp_open_file_fork_smoke --entry-id-only --access 0x02 -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/afpwrit ./tests/linux/afp_delete_smoke -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/afpwrit ``` -Resource-fork opens (`--fork 1`) and Entry-ID-only open remain unsupported until -AppleDouble/resource-fork support and persistent CNID/base-ID lookup exist. The -smoke helper can assert the resource-fork rejection explicitly: +Resource-fork opens (`--fork 1`) remain unsupported until AppleDouble/resource +fork support exists. The smoke helper can assert the resource-fork rejection +explicitly: ```sh ./tests/linux/afp_open_file_fork_smoke --expect-completion 0x9c --fork 1 -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/pmdflts.ini diff --git a/tests/linux/afp_open_file_fork_smoke.c b/tests/linux/afp_open_file_fork_smoke.c index 481faa5..732fb37 100644 --- a/tests/linux/afp_open_file_fork_smoke.c +++ b/tests/linux/afp_open_file_fork_smoke.c @@ -2,9 +2,9 @@ * Linux smoke test for NetWare AFP Open File Fork. * * This uses ncpfs/libncp so the request travels through the same NCP path as a - * normal Linux requester. mars_nwe routes path-backed AFP data-fork read and - * write opens through the normal NetWare open/share path and returns a normal - * NetWare file handle, which this helper closes before exiting. + * normal Linux requester. mars_nwe routes path-backed and entry-id-backed AFP + * data-fork read/write opens through the normal NetWare open/share path and + * returns a normal NetWare file handle, which this helper closes before exiting. */ #include @@ -36,8 +36,8 @@ static void usage(const char *prog) fprintf(stderr, "Usage: %s [--allow-invalid-namespace] [--allow-invalid-path] " "[--expect-completion CODE] [--volume N] [--entry-id ID] " - "[--fork N] [--access N] " - "[ncpfs options] PATH\n" + "[--entry-id-only] [--fork N] [--access N] " + "[ncpfs options] [PATH]\n" "\n" "ncpfs options are parsed by ncp_initialize(), for example:\n" " -S SERVER -U USER -P PASSWORD -n\n" @@ -47,8 +47,9 @@ static void usage(const char *prog) " %s --allow-invalid-namespace -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/pmdflts.ini\n" " %s --allow-invalid-path -S MARS -U SUPERVISOR -P secret SYS:NO_SUCH_FILE\n" " %s --access 0x02 -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/testfile.dat\n" + " %s --entry-id-only -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/pmdflts.ini\n" " %s --expect-completion 0x9c --fork 1 -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/pmdflts.ini\n", - prog, prog, prog, prog, prog, prog); + prog, prog, prog, prog, prog, prog, prog); } static int parse_u32(const char *text, uint32_t *value) @@ -96,6 +97,40 @@ static void cpu_to_le32(uint32_t v, uint8_t p[4]) p[3] = (uint8_t)(v >> 24); } + +static NWCCODE afp_get_entry_id_from_path(NWCONN_HANDLE conn, const char *path, + uint32_t volume_number, + uint32_t *entry_id) +{ + NW_FRAGMENT reply; + uint8_t request[1 + 4 + 1 + 255]; + uint8_t reply_buf[4]; + size_t path_len = strlen(path); + NWCCODE err; + + if (path_len > 255) + return NWE_INVALID_PATH; + + request[0] = (uint8_t)volume_number; + cpu_to_be32(0, request + 1); + request[5] = (uint8_t)path_len; + memcpy(request + 6, 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, 0x0c), + request, 6 + path_len, &reply); + if (err) + return err; + if (reply.fragSize < 4) + return NWE_INVALID_PATH; + + *entry_id = be32_to_cpu(reply_buf); + return 0; +} + static void close_file_handle(NWCONN_HANDLE conn, uint32_t fhandle) { uint8_t rq[7]; @@ -114,6 +149,7 @@ int main(int argc, char **argv) int allow_invalid_namespace = 0; int allow_invalid_path = 0; int expected_completion = -1; + int entry_id_only = 0; uint32_t volume_number = 0; uint32_t base_entry_id = 0; uint32_t fork_indicator = AFP_DATA_FORK; @@ -164,6 +200,9 @@ int main(int argc, char **argv) ncp_close(conn); return 2; } + entry_id_only = 1; + } else if (!strcmp(argv[i], "--entry-id-only")) { + entry_id_only = 1; } else if (!strcmp(argv[i], "--fork")) { if (++i >= argc || parse_u32(argv[i], &fork_indicator) || fork_indicator > 255) { @@ -192,14 +231,31 @@ int main(int argc, char **argv) } } - if (!path) { - fprintf(stderr, "missing PATH\n"); + if (!path && !base_entry_id) { + fprintf(stderr, "missing PATH or --entry-id\n"); usage(argv[0]); ncp_close(conn); return 2; } - path_len = strlen(path); + if (entry_id_only && !base_entry_id) { + err = afp_get_entry_id_from_path(conn, path, volume_number, + &base_entry_id); + if (err) { + fprintf(stderr, + "AFP Get Entry ID From Path Name failed before entry-id-only open: completion=0x%02x (%u) path=%s\n", + (unsigned int)err & 0xff, (unsigned int)err, path); + ncp_close(conn); + return 1; + } + if (!base_entry_id) { + fprintf(stderr, "AFP Get Entry ID From Path Name returned zero for %s\n", path); + ncp_close(conn); + return 1; + } + } + + path_len = (path && !entry_id_only) ? strlen(path) : 0; if (path_len > 255) { fprintf(stderr, "PATH is too long for AFP Open File Fork: %zu\n", path_len); ncp_close(conn); @@ -211,7 +267,8 @@ int main(int argc, char **argv) request[5] = (uint8_t)fork_indicator; request[6] = (uint8_t)access_mode; request[7] = (uint8_t)path_len; - memcpy(request + 8, path, path_len); + if (path_len) + memcpy(request + 8, path, path_len); memset(reply_buf, 0, sizeof(reply_buf)); reply.fragAddr.rw = reply_buf; @@ -225,16 +282,18 @@ int main(int argc, char **argv) if (expected_completion >= 0) { if (((unsigned int)err & 0xff) == (unsigned int)expected_completion) { - printf("AFP Open File Fork returned expected completion 0x%02x: path=%s fork=%u access=0x%02x\n", - expected_completion, path, (unsigned int)fork_indicator, + printf("AFP Open File Fork returned expected completion 0x%02x: path=%s entry_id=0x%08x fork=%u access=0x%02x\n", + expected_completion, path ? path : "(entry-id-only)", + (unsigned int)base_entry_id, (unsigned int)fork_indicator, (unsigned int)access_mode); ncp_close(conn); return 0; } if (!err) { fprintf(stderr, - "AFP Open File Fork unexpectedly succeeded: expected=0x%02x path=%s fork=%u access=0x%02x\n", - expected_completion, path, (unsigned int)fork_indicator, + "AFP Open File Fork unexpectedly succeeded: expected=0x%02x path=%s entry_id=0x%08x fork=%u access=0x%02x\n", + expected_completion, path ? path : "(entry-id-only)", + (unsigned int)base_entry_id, (unsigned int)fork_indicator, (unsigned int)access_mode); if (reply.fragSize >= 6) close_file_handle(conn, le32_to_cpu(reply_buf + 2)); @@ -242,8 +301,9 @@ int main(int argc, char **argv) return 1; } fprintf(stderr, - "AFP Open File Fork returned completion 0x%02x, expected 0x%02x: path=%s fork=%u access=0x%02x\n", - (unsigned int)err & 0xff, expected_completion, path, + "AFP Open File Fork returned completion 0x%02x, expected 0x%02x: path=%s entry_id=0x%08x fork=%u access=0x%02x\n", + (unsigned int)err & 0xff, expected_completion, + path ? path : "(entry-id-only)", (unsigned int)base_entry_id, (unsigned int)fork_indicator, (unsigned int)access_mode); ncp_close(conn); return 1; @@ -251,22 +311,24 @@ int main(int argc, char **argv) if (((unsigned int)err & 0xff) == NWE_INVALID_NAMESPACE && allow_invalid_namespace) { - printf("AFP Open File Fork returned invalid namespace as expected: path=%s\n", - path); + printf("AFP Open File Fork returned invalid namespace as expected: path=%s entry_id=0x%08x\n", + path ? path : "(entry-id-only)", (unsigned int)base_entry_id); ncp_close(conn); return 0; } if (((unsigned int)err & 0xff) == NWE_INVALID_PATH && allow_invalid_path) { - printf("AFP Open File Fork returned invalid path as expected: path=%s\n", path); + printf("AFP Open File Fork returned invalid path as expected: path=%s entry_id=0x%08x\n", + path ? path : "(entry-id-only)", (unsigned int)base_entry_id); ncp_close(conn); return 0; } if (err) { fprintf(stderr, - "AFP Open File Fork failed: completion=0x%02x (%u) path=%s\n", - (unsigned int)err & 0xff, (unsigned int)err, path); + "AFP Open File Fork failed: completion=0x%02x (%u) path=%s entry_id=0x%08x\n", + (unsigned int)err & 0xff, (unsigned int)err, + path ? path : "(entry-id-only)", (unsigned int)base_entry_id); ncp_close(conn); return 1; } @@ -279,9 +341,11 @@ int main(int argc, char **argv) fhandle = le32_to_cpu(reply_buf + 2); fork_len = be32_to_cpu(reply_buf + 6); - printf("AFP Open File Fork path=%s handle=%u fork=%u access=0x%02x fork_len=%u\n", - path, fhandle, (unsigned int)fork_indicator, - (unsigned int)access_mode, fork_len); + printf("AFP Open File Fork path=%s entry_id=0x%08x handle=%u fork=%u access=0x%02x fork_len=%u%s\n", + path ? path : "(entry-id-only)", (unsigned int)base_entry_id, + fhandle, (unsigned int)fork_indicator, + (unsigned int)access_mode, fork_len, + entry_id_only ? " entry-id-only" : ""); close_file_handle(conn, fhandle); ncp_close(conn); diff --git a/tests/linux/afp_smoke_suite.sh b/tests/linux/afp_smoke_suite.sh index c75a0ef..27f74fc 100755 --- a/tests/linux/afp_smoke_suite.sh +++ b/tests/linux/afp_smoke_suite.sh @@ -576,6 +576,11 @@ run_cmd \ "AFP Open File Fork" \ "./afp_open_file_fork_smoke $COMMON_PRINT '$NETWARE_PATH'" \ "$SCRIPT_DIR/afp_open_file_fork_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$NETWARE_PATH" +run_cmd \ + "AFP Open File Fork by Entry ID" \ + "./afp_open_file_fork_smoke --entry-id-only $COMMON_PRINT '$NETWARE_PATH'" \ + "$SCRIPT_DIR/afp_open_file_fork_smoke" --entry-id-only \ + -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$NETWARE_PATH" run_optional_cmd \ "Prepare AFP Open File Fork write cleanup" \ @@ -590,6 +595,11 @@ run_cmd \ "./afp_open_file_fork_smoke --access 0x02 $COMMON_PRINT '$CREATE_FILE_PATH'" \ "$SCRIPT_DIR/afp_open_file_fork_smoke" --access 0x02 \ -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE_PATH" +run_cmd \ + "AFP Open File Fork write access by Entry ID" \ + "./afp_open_file_fork_smoke --entry-id-only --access 0x02 $COMMON_PRINT '$CREATE_FILE_PATH'" \ + "$SCRIPT_DIR/afp_open_file_fork_smoke" --entry-id-only --access 0x02 \ + -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$CREATE_FILE_PATH" run_cmd \ "AFP Open File Fork write cleanup" \ "./afp_delete_smoke $COMMON_PRINT '$CREATE_FILE_PATH'" \