diff --git a/TODO.md b/TODO.md index 2348815..e9e20b0 100644 --- a/TODO.md +++ b/TODO.md @@ -199,8 +199,10 @@ Current status: - `AFP Get File Information` is implemented for read-only path-based requests. Linux smoke coverage exists in `tests/linux/afp_file_info_smoke` and has been verified against `SYS:`, `SYS:PUBLIC`, `SYS:SYSTEM`, and `SYS:BURST`. - The current reply fills stat/libatalk-derived fields and leaves persistent - CNID Parent ID / fuller Mac namespace metadata as future work. + The same test can exercise the AFP 2.0 Get File Information subfunction via + `--afp20`, using the same path-backed read-only reply for now. The current + reply fills stat/libatalk-derived fields and leaves persistent CNID Parent ID + / fuller Mac namespace metadata as future work. - 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. @@ -223,8 +225,8 @@ Follow-up: - Replace the temporary stat-derived AFP entry-id fallback with a persistent CNID/directory-id mapping once the libatalk/CNID backend is integrated. - Extend the Linux AFP smoke tests once additional AFP subfunctions are - implemented, especially AFP 2.0 file information, Scan File Information, - Finder Info updates, fork open/read/write paths, and resource-fork handling. + implemented, especially Scan File Information, Finder Info updates, fork + open/read/write paths, and resource-fork handling. ## Deferred / optional protocol work diff --git a/src/nwconn.c b/src/nwconn.c index 4de9542..f811cb4 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -622,7 +622,7 @@ static uint16 afp_count_offspring(const char *unixname, const struct stat *stb) } static int afp_get_file_information(uint8 *afp_req, int afp_len, - uint8 *response) + uint8 *response, const char *call_name) { uint8 volume_number; uint32 request_entry_id; @@ -639,8 +639,8 @@ static int afp_get_file_information(uint8 *afp_req, int afp_len, int result; if (afp_len < 9) { - XDPRINTF((2,0, "AFP Get File Information rejected: short request len=%d", - afp_len)); + XDPRINTF((2,0, "%s rejected: short request len=%d", + call_name, afp_len)); return(-0x7e); /* NCP Boundary Check Failed */ } @@ -649,19 +649,19 @@ static int afp_get_file_information(uint8 *afp_req, int afp_len, request_mask = GET_BE16(afp_req + 6); path_len = (int)afp_req[8]; if (path_len < 0 || afp_len < 9 + path_len) { - XDPRINTF((2,0, "AFP Get File Information rejected: boundary check len=%d path_len=%d", - afp_len, path_len)); + XDPRINTF((2,0, "%s rejected: boundary check len=%d path_len=%d", + call_name, afp_len, path_len)); return(-0x7e); } if (!nwatalk_backend_available()) { - XDPRINTF((3,0, "AFP Get File Information rejected: libatalk backend unavailable")); + XDPRINTF((3,0, "%s rejected: libatalk backend unavailable", call_name)); return(-0xbf); /* invalid namespace */ } if (!path_len) { - XDPRINTF((2,0, "AFP Get File Information rejected: entry-id-only lookup unsupported vol=%d entry=0x%08x mask=0x%04x", - (int)volume_number, request_entry_id, request_mask)); + XDPRINTF((2,0, "%s rejected: entry-id-only lookup unsupported vol=%d entry=0x%08x mask=0x%04x", + call_name, (int)volume_number, request_entry_id, request_mask)); return(-0x9c); /* Invalid Path until persistent entry-id lookup exists */ } @@ -669,15 +669,15 @@ static int afp_get_file_information(uint8 *afp_req, int afp_len, path_len ? afp_req + 9 : &empty_path, path_len); if (volume < 0) { - XDPRINTF((2,0, "AFP Get File Information path resolve failed: vol=%d entry=0x%08x path='%s' result=-0x%x", - (int)volume_number, request_entry_id, + XDPRINTF((2,0, "%s path resolve failed: vol=%d entry=0x%08x path='%s' result=-0x%x", + call_name, (int)volume_number, request_entry_id, visable_data(afp_req + 9, path_len), -volume)); return(volume); } if (stat(unixname, &stbuff)) { - XDPRINTF((2,0, "AFP Get File Information stat failed: vol=%d entry=0x%08x path='%s' unix='%s' errno=%d", - (int)volume_number, request_entry_id, + XDPRINTF((2,0, "%s stat failed: vol=%d entry=0x%08x path='%s' unix='%s' errno=%d", + call_name, (int)volume_number, request_entry_id, visable_data(afp_req + 9, path_len), unixname, errno)); return(-0x9c); /* Invalid Path */ } @@ -714,8 +714,8 @@ static int afp_get_file_information(uint8 *afp_req, int afp_len, U16_TO_BE16(afp_basic_access_privileges(&stbuff), response + 112); /* ProDOS info at offset 114 stays zero until a real Mac namespace maps it. */ - XDPRINTF((3,0, "AFP Get File Information: vol=%d entry=0x%08x mask=0x%04x path='%s' reply_entry=0x%08x%s", - (int)volume_number, request_entry_id, request_mask, + XDPRINTF((3,0, "%s: vol=%d entry=0x%08x mask=0x%04x path='%s' reply_entry=0x%08x%s", + call_name, (int)volume_number, request_entry_id, request_mask, visable_data(afp_req + 9, path_len), entry_id, (result < 0) ? " fallback" : "")); return(120); @@ -2672,7 +2672,11 @@ static int handle_ncp_serv(void) * Implement the path-name entry-id probe first because the * SDK helpers use it to test AFP support, then expose the * read-only AFP Get File Information query for the same - * SYS:-style path inputs. Both still require + * 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 + * until persistent entry-id lookup and richer AFP 2.0 + * metadata are implemented. These calls still require * the optional libatalk backend to be present; without a Mac * namespace backend, keep returning invalid namespace. */ @@ -2681,9 +2685,10 @@ static int handle_ncp_serv(void) afp_len, responsedata); if (result > -1) data_len = result; else completition = (uint8)-result; - } else if (ufunc == 0x05) { + } else if (ufunc == 0x05 || ufunc == 0x0f) { int result = afp_get_file_information(afp_req, - afp_len, responsedata); + afp_len, responsedata, + afp_call_name(ufunc)); if (result > -1) data_len = result; else completition = (uint8)-result; } else { diff --git a/tests/linux/README.md b/tests/linux/README.md index a7a398e..e3c572e 100644 --- a/tests/linux/README.md +++ b/tests/linux/README.md @@ -66,10 +66,12 @@ expected `0x9c` Invalid Path completion. ## AFP File Information smoke test -`afp_file_info_smoke` sends the WebSDK-documented NetWare AFP request: +`afp_file_info_smoke` sends the WebSDK-documented NetWare AFP file +information requests: ```text NCP 0x2222/35/05 AFP Get File Information +NCP 0x2222/35/15 AFP 2.0 Get File Information ``` It uses the same libncp `NWRequestSimple()` transport path as the Entry ID @@ -85,10 +87,15 @@ Useful smoke cases for a standard MARS-NWE `SYS` volume are: ./tests/linux/afp_file_info_smoke -S MARS -U SUPERVISOR -P secret SYS:PUBLIC ./tests/linux/afp_file_info_smoke -S MARS -U SUPERVISOR -P secret SYS:SYSTEM ./tests/linux/afp_file_info_smoke -S MARS -U SUPERVISOR -P secret SYS:BURST + +# AFP 2.0 variant using the same path-backed read-only reply +./tests/linux/afp_file_info_smoke --afp20 -S MARS -U SUPERVISOR -P secret SYS:PUBLIC ``` -The current implementation fills fields that can be derived from Unix `stat(2)` -and the optional libatalk helper wrappers. Server-side diagnostics mark +The AFP 2.0 mode is selected with `--afp20` and currently exercises the same +path-backed read-only reply as the older call. The current implementation +fills fields that can be derived from Unix `stat(2)` and the optional libatalk +helper wrappers. Server-side diagnostics mark stat-derived temporary Entry IDs with `fallback`; Parent ID, persistent CNID/AppleDouble IDs, and fuller Finder Info/resource-fork semantics remain future Mac-namespace work. diff --git a/tests/linux/afp_file_info_smoke.c b/tests/linux/afp_file_info_smoke.c index 806a094..d276b92 100644 --- a/tests/linux/afp_file_info_smoke.c +++ b/tests/linux/afp_file_info_smoke.c @@ -19,7 +19,8 @@ #define NCPC_SFN(FN, SFN) ((FN) | ((SFN) << 8) | NCPC_SUBFUNCTION) #endif -#define AFP_GET_FILE_INFORMATION 0x05 +#define AFP_GET_FILE_INFORMATION 0x05 +#define AFP20_GET_FILE_INFORMATION 0x0f #define NWE_INVALID_NAMESPACE 0xbf #define NWE_INVALID_PATH 0x9c #define AFP_REPLY_LEN 120 @@ -28,7 +29,7 @@ static void usage(const char *prog) { fprintf(stderr, - "Usage: %s [--allow-invalid-namespace] [--allow-invalid-path] " + "Usage: %s [--afp20] [--allow-invalid-namespace] [--allow-invalid-path] " "[--volume N] [--entry-id ID] [--request-mask MASK] [ncpfs options] PATH\n" "\n" "ncpfs options are parsed by ncp_initialize(), for example:\n" @@ -36,9 +37,10 @@ static void usage(const char *prog) "\n" "Examples:\n" " %s -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n" + " %s --afp20 -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); } static int parse_u32(const char *text, uint32_t *value) @@ -104,6 +106,7 @@ int main(int argc, char **argv) uint32_t volume_number = 0; uint32_t entry_id = 0; uint32_t request_mask = AFP_GET_ALL; + uint32_t afp_subfunction = AFP_GET_FILE_INFORMATION; int i; size_t path_len; uint8_t request[1 + 4 + 2 + 1 + 255]; @@ -125,7 +128,9 @@ int main(int argc, char **argv) } for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--allow-invalid-namespace")) { + if (!strcmp(argv[i], "--afp20")) { + afp_subfunction = AFP20_GET_FILE_INFORMATION; + } else if (!strcmp(argv[i], "--allow-invalid-namespace")) { allow_invalid_namespace = 1; } else if (!strcmp(argv[i], "--allow-invalid-path")) { allow_invalid_path = 1; @@ -187,23 +192,26 @@ int main(int argc, char **argv) reply.fragSize = sizeof(reply_buf); err = NWRequestSimple(conn, - NCPC_SFN(0x23, AFP_GET_FILE_INFORMATION), + NCPC_SFN(0x23, afp_subfunction), request, 8 + path_len, &reply); if (err == NWE_INVALID_NAMESPACE && allow_invalid_namespace) { - printf("AFP Get File Information returned invalid namespace as expected for path=%s\n", path); + printf("AFP Get File Information subfunction=0x%02x returned invalid namespace as expected for path=%s\n", + (unsigned int)afp_subfunction, path); ncp_close(conn); return 0; } if (err == NWE_INVALID_PATH && allow_invalid_path) { - printf("AFP Get File Information returned invalid path as expected for path=%s\n", path); + printf("AFP Get File Information subfunction=0x%02x returned invalid path as expected for path=%s\n", + (unsigned int)afp_subfunction, path); ncp_close(conn); return 0; } if (err) { fprintf(stderr, - "AFP Get File Information failed: completion=0x%02x (%u) path=%s\n", + "AFP Get File Information subfunction=0x%02x failed: completion=0x%02x (%u) path=%s\n", + (unsigned int)afp_subfunction, (unsigned int)err & 0xff, (unsigned int)err, path); ncp_close(conn); return 1; @@ -218,8 +226,9 @@ int main(int argc, char **argv) copy_fixed_string(long_name, sizeof(long_name), reply_buf + 64, 32); copy_fixed_string(short_name, sizeof(short_name), reply_buf + 100, 12); - printf("AFP File Info path=%s entry_id=0x%08x parent_id=0x%08x attrs=0x%04x " + printf("AFP File Info subfunction=0x%02x path=%s entry_id=0x%08x parent_id=0x%08x attrs=0x%04x " "data_len=%u resource_len=%u offspring=%u long_name=%s short_name=%s rights=0x%04x\n", + (unsigned int)afp_subfunction, path, be32_to_cpu(reply_buf + 0), be32_to_cpu(reply_buf + 4),