From a10f256b77f98884da02fa362f4b3aaa5cfa9b28 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sat, 30 May 2026 07:07:32 +0000 Subject: [PATCH] nwconn: implement AFP entry id from NetWare handle Wire NCP 0x23/0x06 AFP Get Entry ID From NetWare Handle to the existing AFP entry-id backend. The WebSDK documents NCP 0x2222/35/06 as taking a 6-byte NetWare file handle and returning the volume number, a 32-bit AFP Entry ID, and a fork indicator. The SDK headers expose the same operation as AFPGetEntryIDFromNetWareHandle() and NWAFPGetEntryIDFromNetWareHandle(). Use the connection-local mars_nwe file handle table to map the supplied NetWare file handle back to its Unix path, require the optional libatalk backend as for the other AFP calls, and then return a libatalk Entry ID when available or the existing stat-derived fallback ID otherwise. Report the data fork for now because mars_nwe does not yet expose AFP resource-fork open semantics. Extend the Linux AFP entry-id smoke test with --from-handle. The test opens the requested file through libncp in the same connection, sends the returned 6-byte NetWare file handle to the AFP call, and closes the file afterwards. This implements the read-only data-fork handle-to-entry-id path; persistent CNID mapping and resource-fork handle semantics remain future work. --- src/nwconn.c | 78 ++++++++++++++++++++++++++++++- tests/linux/afp_entry_id_smoke.c | 79 ++++++++++++++++++++++++++------ 2 files changed, 140 insertions(+), 17 deletions(-) diff --git a/src/nwconn.c b/src/nwconn.c index 3e3d966..2ac6e05 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -450,6 +450,20 @@ static int afp_request_offset(void) return(0); } + +static int afp_volume_from_unixname(const char *unixname) +{ + int k; + + if (!unixname) return(-1); + for (k = 0; k < used_nw_volumes; k++) { + int len = nw_volumes[k].unixnamlen; + if (len > 0 && !strncmp(unixname, (char *)nw_volumes[k].unixname, len)) + return(k); + } + return(-1); +} + static uint32 afp_fallback_entry_id(int volume, const struct stat *stb) /* * Build a stable local AFP entry id from Unix identity data when libatalk has @@ -544,6 +558,59 @@ static int afp_get_entry_id_from_name(uint8 *afp_req, int afp_len, return(4); } + +static int afp_get_entry_id_from_netware_handle(uint8 *afp_req, int afp_len, + uint8 *response) +{ + uint32 fhandle; + uint8 *unixname; + int volume; + struct stat stbuff; + uint32 entry_id = 0; + int result; + + if (afp_len < 7) { + XDPRINTF((2,0, "AFP Get Entry ID From NetWare Handle rejected: short request len=%d", + afp_len)); + return(-0x7e); /* NCP Boundary Check Failed */ + } + + fhandle = GET_32(afp_req + 3); /* bytes 1..2 are the extended handle */ + unixname = file_get_unix_name(fhandle); + if (!unixname) { + XDPRINTF((2,0, "AFP Get Entry ID From NetWare Handle rejected: bad handle=%u", + fhandle)); + return(-0x88); /* Invalid File Handle */ + } + + if (!nwatalk_backend_available()) { + XDPRINTF((3,0, "AFP Get Entry ID From NetWare Handle rejected: libatalk backend unavailable")); + return(-0xbf); /* invalid namespace */ + } + + if (stat((char *)unixname, &stbuff)) { + XDPRINTF((2,0, "AFP Get Entry ID From NetWare Handle stat failed: handle=%u unix='%s' errno=%d", + fhandle, unixname, errno)); + return(-0x88); /* Invalid File Handle */ + } + + volume = afp_volume_from_unixname((char *)unixname); + if (volume < 0) volume = 0; + + result = nwatalk_get_entry_id((char *)unixname, &entry_id); + if (result < 0 || !entry_id) + entry_id = afp_fallback_entry_id(volume, &stbuff); + + response[0] = (uint8)volume; + U32_TO_BE32(entry_id, response + 1); + response[5] = 0; /* data fork */ + + XDPRINTF((3,0, "AFP Get Entry ID From NetWare Handle: handle=%u volume=%d unix='%s' entry=0x%08x%s", + fhandle, volume, unixname, entry_id, + (result < 0) ? " fallback" : "")); + return(6); +} + static int afp_get_entry_id_from_path_name(uint8 *afp_req, int afp_len, uint8 *response) { @@ -2900,8 +2967,10 @@ static int handle_ncp_serv(void) * Get Entry ID From Name call accepts a volume/base AFP ID * plus a modifying path string; until persistent CNID/base * lookup exists, support the same path-backed SYS:-style - * smoke-test subset. Then expose the - * read-only AFP Get File Information query for the same + * smoke-test subset. Get Entry ID From NetWare Handle + * maps an already-open mars_nwe file handle back to its + * Unix path and returns the corresponding AFP ID. Then expose + * the read-only AFP Get File Information query for the same * SYS:-style path inputs. AFP 2.0 Get File Information * uses the same request/reply layout for this read-only * path-backed subset, so route it through the same helper @@ -2915,6 +2984,11 @@ static int handle_ncp_serv(void) afp_len, responsedata); if (result > -1) data_len = result; else completition = (uint8)-result; + } else if (ufunc == 0x06) { + int result = afp_get_entry_id_from_netware_handle(afp_req, + afp_len, responsedata); + if (result > -1) data_len = result; + else completition = (uint8)-result; } else if (ufunc == 0x0c) { int result = afp_get_entry_id_from_path_name(afp_req, afp_len, responsedata); diff --git a/tests/linux/afp_entry_id_smoke.c b/tests/linux/afp_entry_id_smoke.c index 95ef473..e527370 100644 --- a/tests/linux/afp_entry_id_smoke.c +++ b/tests/linux/afp_entry_id_smoke.c @@ -23,8 +23,9 @@ #define NCPC_SFN(FN, SFN) ((FN) | ((SFN) << 8) | NCPC_SUBFUNCTION) #endif -#define AFP_GET_ENTRY_ID_FROM_NAME 0x04 -#define AFP_GET_ENTRY_ID_FROM_PATH_NAME 0x0c +#define AFP_GET_ENTRY_ID_FROM_NAME 0x04 +#define AFP_GET_ENTRY_ID_FROM_NETWARE_HANDLE 0x06 +#define AFP_GET_ENTRY_ID_FROM_PATH_NAME 0x0c #define NWE_INVALID_NAMESPACE 0xbf #define NWE_INVALID_PATH 0x9c #define AFP_TEMP_DH_NONE 0xff @@ -33,7 +34,7 @@ static void usage(const char *prog) { fprintf(stderr, "Usage: %s [--allow-invalid-namespace] [--allow-invalid-path] " - "[--from-name] [--volume N] [--entry-id ID] " + "[--from-name] [--from-handle] [--volume N] [--entry-id ID] " "[--dir-handle N] [--alloc-handle] [--raw-path] [ncpfs options] PATH\n" "\n" "ncpfs options are parsed by ncp_initialize(), for example:\n" @@ -42,11 +43,12 @@ static void usage(const char *prog) "Examples:\n" " %s -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n" " %s --from-name -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n" + " %s --from-handle -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/pmdflts.ini\n" " %s --dir-handle 2 -S MARS -U SUPERVISOR -P secret PUBLIC\n" " %s --alloc-handle -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n" " %s --allow-invalid-namespace -S MARS SYS:PUBLIC\n" " %s --allow-invalid-path -S MARS SYS:NO_SUCH_PATH\n", - prog, prog, prog, prog, prog, prog, prog); + prog, prog, prog, prog, prog, prog, prog, prog); } static int parse_u8(const char *text, unsigned int *value) @@ -194,13 +196,16 @@ int main(int argc, char **argv) int raw_path = 1; int alloc_handle = 0; int from_name = 0; + int from_handle = 0; uint32_t volume_number = 0; uint32_t base_entry_id = 0; + struct ncp_file_info opened_file; + int opened_file_valid = 0; int i; size_t path_len; char volume[32]; uint8_t request[1 + 4 + 1 + 255]; - uint8_t reply_buf[4]; + uint8_t reply_buf[6]; NWCCODE err; if (NWCallsInit(NULL, NULL)) { @@ -230,6 +235,12 @@ int main(int argc, char **argv) allow_invalid_path = 1; } else if (!strcmp(argv[i], "--from-name")) { from_name = 1; + from_handle = 0; + raw_path = 1; + alloc_handle = 0; + } else if (!strcmp(argv[i], "--from-handle")) { + from_handle = 1; + from_name = 0; raw_path = 1; alloc_handle = 0; } else if (!strcmp(argv[i], "--volume")) { @@ -300,16 +311,34 @@ int main(int argc, char **argv) allocated_dir_handle = dir_handle; } + if (from_handle) { + memset(&opened_file, 0, sizeof(opened_file)); + err = ncp_open_file(conn, 0, request_path, 0, AR_READ_ONLY, &opened_file); + if (err) { + fprintf(stderr, + "Open file failed before AFP request: completion=0x%02x (%u) path=%s request_path=%s\n", + (unsigned int)err & 0xff, (unsigned int)err, path, request_path); + deallocate_dir_handle(conn, allocated_dir_handle); + ncp_close(conn); + return 1; + } + opened_file_valid = 1; + } + path_len = strlen(request_path); if (path_len > 255) { fprintf(stderr, "PATH is too long for AFP Get Entry ID From Path Name: %zu\n", path_len); + if (opened_file_valid) + (void)ncp_close_file(conn, opened_file.file_id); deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 2; } - if (from_name) { + if (from_handle) { + memcpy(request, opened_file.file_id, 6); + } else if (from_name) { request[0] = (uint8_t)volume_number; cpu_to_be32(base_entry_id, request + 1); request[5] = (uint8_t)path_len; @@ -325,17 +354,21 @@ int main(int argc, char **argv) reply.fragSize = sizeof(reply_buf); err = NWRequestSimple(conn, - NCPC_SFN(0x23, from_name - ? AFP_GET_ENTRY_ID_FROM_NAME - : AFP_GET_ENTRY_ID_FROM_PATH_NAME), + NCPC_SFN(0x23, from_handle + ? AFP_GET_ENTRY_ID_FROM_NETWARE_HANDLE + : (from_name + ? AFP_GET_ENTRY_ID_FROM_NAME + : AFP_GET_ENTRY_ID_FROM_PATH_NAME)), request, - (from_name ? 6 : 2) + path_len, + from_handle ? 6 : ((from_name ? 6 : 2) + path_len), &reply); if (((unsigned int)err & 0xff) == NWE_INVALID_NAMESPACE && allow_invalid_namespace) { printf("AFP Get Entry ID returned invalid namespace " "as expected: path=%s request_path=%s dir_handle=%u\n", path, request_path, dir_handle); + if (opened_file_valid) + (void)ncp_close_file(conn, opened_file.file_id); deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 0; @@ -345,6 +378,8 @@ int main(int argc, char **argv) printf("AFP Get Entry ID returned invalid path " "as expected: path=%s request_path=%s dir_handle=%u\n", path, request_path, dir_handle); + if (opened_file_valid) + (void)ncp_close_file(conn, opened_file.file_id); deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 0; @@ -355,23 +390,37 @@ int main(int argc, char **argv) "AFP Get Entry ID From Path Name failed: completion=0x%02x (%u) path=%s request_path=%s dir_handle=%u\n", (unsigned int)err & 0xff, (unsigned int)err, path, request_path, dir_handle); + if (opened_file_valid) + (void)ncp_close_file(conn, opened_file.file_id); deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 1; } - if (reply.fragSize < 4) { + if (reply.fragSize < (from_handle ? 6 : 4)) { fprintf(stderr, "short AFP reply: %zu bytes\n", reply.fragSize); + if (opened_file_valid) + (void)ncp_close_file(conn, opened_file.file_id); deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 1; } - printf("AFP Entry ID path=%s request_path=%s dir_handle=%u entry_id=0x%08x (%u)\n", - path, request_path, dir_handle, - (unsigned int)be32_to_cpu(reply_buf), - (unsigned int)be32_to_cpu(reply_buf)); + if (from_handle) { + printf("AFP Entry ID From NetWare Handle path=%s volume=%u entry_id=0x%08x (%u) fork=%u\n", + path, (unsigned int)reply_buf[0], + (unsigned int)be32_to_cpu(reply_buf + 1), + (unsigned int)be32_to_cpu(reply_buf + 1), + (unsigned int)reply_buf[5]); + } else { + printf("AFP Entry ID path=%s request_path=%s dir_handle=%u entry_id=0x%08x (%u)\n", + path, request_path, dir_handle, + (unsigned int)be32_to_cpu(reply_buf), + (unsigned int)be32_to_cpu(reply_buf)); + } + if (opened_file_valid) + (void)ncp_close_file(conn, opened_file.file_id); deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 0;