From eb2983bc1af1acd0596a8f637553a4c2bd27fc27 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Fri, 29 May 2026 23:43:56 +0000 Subject: [PATCH] nwconn: implement AFP entry id path lookup Wire NCP 0x23/0x0c AFP Get Entry ID From Path Name to a real path lookup when the optional Netatalk/libatalk backend is available. The WebSDK documents NCP 0x2222/35/12 as converting a NetWare directory handle plus short-name path string into a unique 32-bit Macintosh file or directory Entry ID. The request carries the AFP subfunction, NetWare directory handle, path length, and path string; the reply carries the 4-byte AFP Entry ID. The SDK headers expose the same operation as AFPGetEntryIDFromPathName() and NWAFPGetEntryIDFromPathName(), and NWAFPSupported() uses this path-name probe to test AFP support. Resolve the NetWare directory handle and path through the existing mars_nwe path machinery, require the optional libatalk backend before returning AFP success, and then ask libatalk for an AppleDouble/CNID-style id when available. If libatalk is present but the file has no stored id yet, return a deterministic stat-derived local entry id as a temporary fallback so the path-name probe can succeed without inventing NetWare directory base numbers. Keep all other AFP subfunctions returning invalid namespace for now. They still need Finder Info, resource fork, AFP file information, fork open, and persistent CNID/directory-id support before they can safely report success. Add the SDK request/reply semantics to the inline endpoint comments and keep the remaining AFP work tracked in TODO.md. This implements only the AFP path-to-entry-id probe; it does not add general AFP file or resource-fork semantics yet. --- TODO.md | 9 ++-- include/nwatalk.h | 1 + src/nwatalk.c | 30 +++++++++++++ src/nwconn.c | 111 +++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 137 insertions(+), 14 deletions(-) diff --git a/TODO.md b/TODO.md index 3c65c2c..8f0e7fb 100644 --- a/TODO.md +++ b/TODO.md @@ -190,7 +190,8 @@ Follow-up: Current status: -- `NCP 0x23` AFP calls still return invalid namespace. +- `NCP 0x23` still returns invalid namespace for AFP calls that are not implemented yet. +- `AFP Get Entry ID From Path Name` is the first implemented AFP subfunction when the optional Netatalk/libatalk backend is available. - The AFP dispatcher now decodes the WebSDK/NWAFP subfunction number in diagnostics so real client probes can be mapped to the corresponding AFP call before implementation work starts. @@ -207,9 +208,11 @@ Follow-up: - Candidate libatalk pieces include the new AppleDouble/Finder Info/resource fork helper wrappers, plus future CNID/directory-id helpers, attribute mapping, and filename conversion. -- Keep returning invalid namespace until MARS-NWE has a real per-volume Mac - namespace/AFP metadata layer. Do not return success for AFP calls without +- Keep returning invalid namespace for AFP calls that still lack a real per-volume Mac + namespace/AFP metadata layer. Do not return success for additional AFP calls without data/resource fork and Finder Info semantics. +- Replace the temporary stat-derived AFP entry-id fallback with a persistent + CNID/directory-id mapping once the libatalk/CNID backend is integrated. ## Deferred / optional protocol work diff --git a/include/nwatalk.h b/include/nwatalk.h index dde080c..4285520 100644 --- a/include/nwatalk.h +++ b/include/nwatalk.h @@ -9,5 +9,6 @@ int nwatalk_backend_available(void); int nwatalk_get_finder_info(const char *path, uint8 *finder_info, int finder_info_len); int nwatalk_get_resource_fork_size(const char *path, uint32 *resource_size); +int nwatalk_get_entry_id(const char *path, uint32 *entry_id); #endif diff --git a/src/nwatalk.c b/src/nwatalk.c index 16536cc..3aeeeb9 100644 --- a/src/nwatalk.c +++ b/src/nwatalk.c @@ -100,3 +100,33 @@ int nwatalk_get_resource_fork_size(const char *path, uint32 *resource_size) return(-0xbf); /* invalid namespace / backend unavailable */ #endif } + + +int nwatalk_get_entry_id(const char *path, uint32 *entry_id) +{ +#if NETATALK_SUPPORT + struct adouble ad; + struct stat stbuff; + uint32_t id; + int result; + + if (!entry_id) return(-0x9c); + *entry_id = 0; + if (!path || !*path) return(-0x9c); + + if (stat(path, &stbuff)) return(-0x9c); + + result = nwatalk_open_adouble(path, &ad); + if (result < 0) return(result); + + id = ad_getid(&ad, stbuff.st_dev, stbuff.st_ino, 0, NULL); + if (id) *entry_id = (uint32)id; + + ad_close(&ad, 0); + return(id ? 0 : -0x9c); +#else + (void)path; + (void)entry_id; + return(-0xbf); /* invalid namespace / backend unavailable */ +#endif +} diff --git a/src/nwconn.c b/src/nwconn.c index 1ad0337..150af74 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -32,6 +32,8 @@ # define LOC_RW_BUFFERSIZE 512 #endif #include +#include +#include #if !CALL_NWCONN_OVER_SOCKET #include #include @@ -476,6 +478,85 @@ static const char *afp_call_name(int ufunc) } } +static int afp_request_offset(void) +/* + * AFP NCP 0x23 calls are documented with a two byte high-low request + * length followed by the AFP subfunction. Some old debug paths treated the + * first payload byte as the subfunction. Accept both layouts so diagnostics + * and the first implemented AFP call work with either requester encoding. + */ +{ + if (requestlen >= 3) { + int plen = GET_BE16(requestdata); + if (plen == requestlen - 2) + return(2); + } + return(0); +} + +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 + * no stored CNID/AppleDouble id yet. This is not a NetWare-internal directory + * base number; it is only the AFP id returned by the path-name probe. + */ +{ + uint32 hash = 2166136261U; + const uint8 *p; + size_t len; + +#define AFP_HASH_BYTES(ptr, size) do { \ + p = (const uint8 *)(ptr); \ + len = (size); \ + while (len--) { hash ^= *p++; hash *= 16777619U; } \ + } while (0) + + AFP_HASH_BYTES(&volume, sizeof(volume)); + AFP_HASH_BYTES(&stb->st_dev, sizeof(stb->st_dev)); + AFP_HASH_BYTES(&stb->st_ino, sizeof(stb->st_ino)); +#undef AFP_HASH_BYTES + + hash &= 0x7fffffffU; + if (!hash) hash = 1; + return(hash); +} + +static int afp_get_entry_id_from_path_name(uint8 *afp_req, int afp_len, + uint8 *response) +{ + uint8 dir_handle; + int path_len; + int volume; + char unixname[PATH_MAX]; + struct stat stbuff; + uint32 entry_id = 0; + int result; + + if (afp_len < 3) return(-0x7e); /* NCP Boundary Check Failed */ + + dir_handle = afp_req[1]; + path_len = (int)afp_req[2]; + if (path_len < 0 || afp_len < 3 + path_len) return(-0x7e); + + if (!nwatalk_backend_available()) return(-0xbf); /* invalid namespace */ + + volume = conn_get_kpl_unxname(unixname, sizeof(unixname), + (int)dir_handle, afp_req + 3, path_len); + if (volume < 0) return(volume); + + if (stat(unixname, &stbuff)) return(-0x9c); /* Invalid Path */ + + result = nwatalk_get_entry_id(unixname, &entry_id); + if (result < 0 || !entry_id) + entry_id = afp_fallback_entry_id(volume, &stbuff); + + U32_TO_BE32(entry_id, response); + XDPRINTF((3,0, "AFP Get Entry ID From Path Name: dh=%d path='%s' entry=0x%08x%s", + (int)dir_handle, visable_data(afp_req + 3, path_len), entry_id, + (result < 0) ? " fallback" : "")); + return(4); +} + static int handle_ncp_serv(void) { int function = (int)ncprequest->function; @@ -2404,7 +2485,10 @@ static int handle_ncp_serv(void) } break; case 0x23 : { /* div AFP Calls */ - int ufunc = (int) *requestdata; + int afp_off = afp_request_offset(); + uint8 *afp_req = requestdata + afp_off; + int afp_len = requestlen - afp_off; + int ufunc = (afp_len > 0) ? (int)*afp_req : -1; /* * NetWare AFP calls are server-side NCP entry points for * Mac namespace semantics: AFP entry IDs, Finder Info, @@ -2420,17 +2504,22 @@ static int handle_ncp_serv(void) * (0x0d/0x0e), Get/Set File Information (0x0f/0x10), and * Scan File Information (0x11). * - * Netatalk/libatalk can be enabled at build time as an - * optional local metadata backend for AppleDouble/Finder - * Info/resource-fork access, but mars_nwe must still - * decode and answer the NetWare NCP calls itself. Do not - * proxy these calls to afpd or report success until the - * required Mac namespace semantics exist. + * Implement the path-name entry-id probe first because the + * SDK helpers use it to test AFP support. It still requires + * the optional libatalk backend to be present; without a Mac + * namespace backend, keep returning invalid namespace. */ - XDPRINTF((3,0, "AFP call rejected: ufunc=0x%02x (%s), Mac namespace unavailable, libatalk backend=%s", - ufunc, afp_call_name(ufunc), - nwatalk_backend_available() ? "enabled" : "disabled")); - completition=0xbf; /* we say invalid namespace here */ + if (ufunc == 0x0c) { + int result = afp_get_entry_id_from_path_name(afp_req, + afp_len, responsedata); + if (result > -1) data_len = result; + else completition = (uint8)-result; + } else { + XDPRINTF((3,0, "AFP call rejected: ufunc=0x%02x (%s), Mac namespace unavailable, libatalk backend=%s", + ufunc, afp_call_name(ufunc), + nwatalk_backend_available() ? "enabled" : "disabled")); + completition=0xbf; /* we say invalid namespace here */ + } } break; case 0x3b : /* commit file to disk */