diff --git a/src/nwconn.c b/src/nwconn.c index 83761ec..bcc7c7c 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -2413,6 +2413,9 @@ static int afp_scan_file_information(uint8 *afp_req, int afp_len, int volume; char unixname[PATH_MAX]; char childname[PATH_MAX]; + uint8 resolved_path[255]; + uint8 *scan_path_data; + int scan_path_len; uint8 display_path[255]; int display_path_len; DIR *dir; @@ -2455,19 +2458,35 @@ static int afp_scan_file_information(uint8 *afp_req, int afp_len, return(-0xbf); /* invalid namespace */ } - if (!path_len) { - XDPRINTF((2,0, "%s rejected: entry-id-only scan unsupported vol=%d entry=0x%08x last=0x%08x mask=0x%04x req=0x%04x", - call_name, (int)volume_number, request_entry_id, last_seen_id, + scan_path_data = afp_req + path_off; + scan_path_len = path_len; + if (request_entry_id) { + result = afp_build_base_relative_path((int)volume_number, + request_entry_id, + afp_req + path_off, path_len, + resolved_path, + sizeof(resolved_path)); + if (result < 0) { + XDPRINTF((2,0, "%s entry-id path build failed: vol=%d entry=0x%08x last=0x%08x path='%s' result=-0x%x", + call_name, (int)volume_number, request_entry_id, last_seen_id, + visable_data(afp_req + path_off, path_len), -result)); + return(result); + } + scan_path_data = resolved_path; + scan_path_len = result; + } else if (!path_len) { + XDPRINTF((2,0, "%s rejected: empty path without entry-id vol=%d last=0x%08x mask=0x%04x req=0x%04x", + call_name, (int)volume_number, last_seen_id, search_mask, request_mask)); - return(-0x9c); /* Invalid Path until persistent entry-id lookup exists */ + return(-0x9c); /* Invalid Path */ } volume = conn_get_kpl_unxname(unixname, sizeof(unixname), 0, - afp_req + path_off, path_len); + scan_path_data, scan_path_len); if (volume < 0) { XDPRINTF((2,0, "%s path resolve failed: vol=%d entry=0x%08x last=0x%08x path='%s' result=-0x%x", call_name, (int)volume_number, request_entry_id, last_seen_id, - visable_data(afp_req + path_off, path_len), -volume)); + visable_data(scan_path_data, scan_path_len), -volume)); return(volume); } @@ -2475,7 +2494,7 @@ static int afp_scan_file_information(uint8 *afp_req, int afp_len, if (!dir) { XDPRINTF((2,0, "%s opendir failed: vol=%d entry=0x%08x last=0x%08x path='%s' unix='%s' errno=%d", call_name, (int)volume_number, request_entry_id, last_seen_id, - visable_data(afp_req + path_off, path_len), unixname, errno)); + visable_data(scan_path_data, scan_path_len), unixname, errno)); return(-0x9c); /* Invalid Path */ } @@ -2501,7 +2520,7 @@ static int afp_scan_file_information(uint8 *afp_req, int afp_len, } display_path_len = snprintf((char *)display_path, sizeof(display_path), - "%s%c%s", visable_data(afp_req + path_off, path_len), + "%s%c%s", visable_data(scan_path_data, scan_path_len), ':', de->d_name); if (display_path_len < 0 || display_path_len >= (int)sizeof(display_path)) { display_path_len = strlen(de->d_name); @@ -2521,7 +2540,7 @@ static int afp_scan_file_information(uint8 *afp_req, int afp_len, XDPRINTF((3,0, "%s: vol=%d entry=0x%08x last=0x%08x desired=%u mask=0x%04x req=0x%04x path='%s' count=1 reply_entry=0x%08x%s", call_name, (int)volume_number, request_entry_id, last_seen_id, (unsigned)desired_count, search_mask, request_mask, - visable_data(afp_req + path_off, path_len), + visable_data(scan_path_data, scan_path_len), entry_id ? entry_id : child_entry_id, (fallback || child_fallback) ? " fallback" : "")); return(2 + result); @@ -2531,7 +2550,7 @@ static int afp_scan_file_information(uint8 *afp_req, int afp_len, XDPRINTF((3,0, "%s completed: vol=%d entry=0x%08x last=0x%08x desired=%u path='%s' no more entries", call_name, (int)volume_number, request_entry_id, last_seen_id, (unsigned)desired_count, - visable_data(afp_req + path_off, path_len))); + visable_data(scan_path_data, scan_path_len))); return(-0xff); /* No files found / scan complete */ } diff --git a/tests/afp/AFP_ENDPOINT_INVENTORY.md b/tests/afp/AFP_ENDPOINT_INVENTORY.md index 4450708..d837f0f 100644 --- a/tests/afp/AFP_ENDPOINT_INVENTORY.md +++ b/tests/afp/AFP_ENDPOINT_INVENTORY.md @@ -21,14 +21,14 @@ tests/afp/afp_endpoint_inventory.py | `0x07` | AFP Rename | implemented | mars_nwe path/namespace and object lifecycle; AFP entry-id metadata follows renamed files | | `0x08` | AFP Open File Fork | implemented for data fork | mars_nwe file handles; resource fork remains unsupported | | `0x09` | AFP Set File Information | implemented | mars_nwe attributes/archive/fileinfo/trustee; AFP xattrs only for FinderInfo / AFP-only bits | -| `0x0a` | AFP Scan File Information | implemented for path/directory start | mars_nwe path/namespace; entry-id-only scan remains a final-audit item | +| `0x0a` | AFP Scan File Information | implemented for path and directory entry-id starts | mars_nwe path/namespace; entry IDs must resolve to directories | | `0x0b` | AFP Alloc Temporary Dir Handle | implemented | mars_nwe path/namespace and directory handles | | `0x0c` | AFP Get Entry ID From Path Name | implemented / validating | mars_nwe path/namespace; AFP entry-id metadata for files | | `0x0d` | AFP 2.0 Create Directory | implemented | mars_nwe path/namespace and object lifecycle | | `0x0e` | AFP 2.0 Create File | implemented | mars_nwe path/namespace and object lifecycle; AFP xattr only for file entry-id metadata | | `0x0f` | AFP 2.0 Get File Information | implemented | same backend discipline as `0x05` | | `0x10` | AFP 2.0 Set File Information | implemented | same backend discipline as `0x09` | -| `0x11` | AFP 2.0 Scan File Information | implemented for path/directory start | same backend discipline as `0x0a`; entry-id-only scan remains a final-audit item | +| `0x11` | AFP 2.0 Scan File Information | implemented for path and directory entry-id starts | same backend discipline as `0x0a` | | `0x12` | AFP Get DOS Name From Entry ID | implemented | mars_nwe path/namespace and AFP entry-id reverse lookup | | `0x13` | AFP Get Macintosh Info On Deleted Files | unsupported / backend-dependent | requires mars_nwe salvage/deleted-entry backend first | @@ -56,15 +56,17 @@ must verify that each endpoint continues to use the mars_nwe core backend: ## Known final-audit items -Two known items should stay visible until the WebSDK / Novell header comparison -is completed: +One unsupported item should stay visible until the WebSDK / Novell header +comparison is completed: -1. `0x0a` / `0x11` entry-id-only scan requests are still explicitly unsupported. - The final audit must decide whether that mode is required for the supported - compatibility slice. -2. `0x13` is intentionally unsupported because the salvage/deleted-entry backend +1. `0x13` is intentionally unsupported because the salvage/deleted-entry backend is not part of the current AFP slice. +Resolved WebSDK compatibility item: + +- `0x0a` / `0x11` entry-id-only scan requests are supported when the base entry + ID resolves to a directory through mars_nwe namespace/basehandle logic. + ## Final comparison checklist When doing the final WebSDK / Novell NWAFP header pass, compare: diff --git a/tests/afp/AFP_FINAL_AUDIT.md b/tests/afp/AFP_FINAL_AUDIT.md index 8d59a85..edbb01c 100644 --- a/tests/afp/AFP_FINAL_AUDIT.md +++ b/tests/afp/AFP_FINAL_AUDIT.md @@ -70,14 +70,14 @@ mars_nwe helper family used by the endpoint, not just the AFP handler. | `0x07` | AFP Rename | inline AFP rename case | TODO | TODO | TODO | TODO | | | `0x08` | AFP Open File Fork | inline AFP open file fork case | TODO | TODO | TODO | TODO | data fork only; resource fork unsupported | | `0x09` | AFP Set File Information | inline AFP set file information case | TODO | TODO | TODO | TODO | legacy variant | -| `0x0a` | AFP Scan File Information | `afp_scan_file_information` | TODO | TODO | TODO | TODO | entry-id-only scan is open review item | +| `0x0a` | AFP Scan File Information | `afp_scan_file_information` | TODO | TODO | TODO | TODO | entry-id-only directory scan supported | | `0x0b` | AFP Alloc Temporary Dir Handle | inline AFP alloc temporary dir handle case | TODO | TODO | TODO | TODO | | | `0x0c` | AFP Get Entry ID From Path Name | `afp_get_entry_id_from_path_name` | TODO | TODO | TODO | TODO | | | `0x0d` | AFP 2.0 Create Directory | inline AFP 2.0 create directory case | TODO | TODO | TODO | TODO | | | `0x0e` | AFP 2.0 Create File | inline AFP 2.0 create file case | TODO | TODO | TODO | TODO | | | `0x0f` | AFP 2.0 Get File Information | `afp_get_file_information` | TODO | TODO | TODO | TODO | AFP 2.0 variant | | `0x10` | AFP 2.0 Set File Information | inline AFP 2.0 set file information case | TODO | TODO | TODO | TODO | AFP 2.0 variant | -| `0x11` | AFP 2.0 Scan File Information | `afp_scan_file_information` | TODO | TODO | TODO | TODO | entry-id-only scan is open review item | +| `0x11` | AFP 2.0 Scan File Information | `afp_scan_file_information` | TODO | TODO | TODO | TODO | entry-id-only directory scan supported | | `0x12` | AFP Get DOS Name From Entry ID | `afp_get_dos_name_from_entry_id` | TODO | TODO | TODO | TODO | | | `0x13` | AFP Get Macintosh Info On Deleted Files | unsupported | TODO | TODO | TODO | TODO | requires salvage/deleted-entry backend | @@ -100,16 +100,9 @@ For every implemented endpoint, check these rules explicitly: ### Entry-id-only scan -The inventory currently reports: - -```text -entry-id-only scan unsupported -``` - -This affects AFP `0x0a` and AFP 2.0 `0x11`. The final audit must decide whether -WebSDK / Novell require scan to accept a directory entry ID with no path for the -current compatibility slice. If required, implement it by resolving directory -entry IDs through mars_nwe namespace/basehandle logic only. +AFP `0x0a` and AFP 2.0 `0x11` support directory entry-id starts. The entry ID +must resolve through mars_nwe namespace/basehandle logic; file AFP entry-id xattrs +are not valid scan bases. ### Deleted-file Macintosh metadata diff --git a/tests/afp/AFP_WEBSK_AUDIT_FINDINGS.md b/tests/afp/AFP_WEBSK_AUDIT_FINDINGS.md index 54f3d35..bae9a26 100644 --- a/tests/afp/AFP_WEBSK_AUDIT_FINDINGS.md +++ b/tests/afp/AFP_WEBSK_AUDIT_FINDINGS.md @@ -57,14 +57,14 @@ map: | `0x07` | `0002R016.htm` | AFP Rename | implemented | | `0x08` | `0002R015.htm` | AFP Open File Fork | implemented for data fork | | `0x09` | `0002R018.htm` | AFP Set File Information | implemented | -| `0x0a` | `0002R017.htm` | AFP Scan File Information | implemented for path/directory start; entry-id-only open item | +| `0x0a` | `0002R017.htm` | AFP Scan File Information | implemented for path and directory entry-id starts | | `0x0b` | `0002R005.htm` | AFP Alloc Temporary Directory Handle | implemented | | `0x0c` | `0002R012.htm` | AFP Get Entry ID From Path Name | implemented | | `0x0d` | `0002R001.htm` | AFP 2.0 Create Directory | implemented | | `0x0e` | `0002R002.htm` | AFP 2.0 Create File | implemented | | `0x0f` | `0002R013.htm` | AFP 2.0 Get File Information | implemented through shared handler | | `0x10` | `0002R004.htm` | AFP 2.0 Set File Information | implemented through shared semantics | -| `0x11` | `0002R003.htm` | AFP 2.0 Scan File Information | implemented for path/directory start; entry-id-only open item | +| `0x11` | `0002R003.htm` | AFP 2.0 Scan File Information | implemented for path and directory entry-id starts | | `0x12` | `0002R009.htm` | AFP Get DOS Name From Entry ID | implemented | | `0x13` | `0002R014.htm` | AFP Get Macintosh Info On Deleted Files | unsupported / salvage-backend dependent | @@ -102,24 +102,18 @@ implemented compatibility slice: ## Important implementation decisions -### Entry-id-only Scan File Information should be implemented next +### Entry-id-only Scan File Information The WebSDK Scan File Information pages describe the path string as relative to -`AFP Entry ID`. That means a request with a directory AFP entry ID and an empty -path is a natural representation of “scan this directory”. The current -inventory warning is therefore a real compatibility gap, not just harmless -parser noise: +`AFP Entry ID`. mars_nwe supports the compatible directory case: a request with +a directory AFP entry ID and an empty path scans that directory. -```text -entry-id-only scan unsupported -``` +The implementation remains intentionally narrow: -The correct implementation path is still narrow: - -1. Accept entry-id-only scan only when the entry ID resolves to a directory. -2. Resolve that directory through mars_nwe namespace/basehandle logic. -3. Continue using the existing mars_nwe directory scan path. -4. Do not resolve file AFP xattr entry IDs as scan bases. +1. Entry-id scan bases must resolve to directories through mars_nwe + namespace/basehandle logic. +2. The existing mars_nwe directory scan path is reused after resolution. +3. File AFP xattr entry IDs are not valid scan bases. ### AFP 0x13 should remain unsupported for this slice @@ -142,8 +136,6 @@ of live paths or filesystem trash. The WebSDK pass does not require renaming the AFP endpoint map again. The remaining concrete work before closing the current AFP slice is: -1. Implement and smoke-test entry-id-only `0x0a` / `0x11` Scan File Information - for directory entry IDs. -2. Keep `0x13` documented unsupported until salvage exists. -3. Re-run `tests/afp/afp_endpoint_inventory.py` and the AFP smoke suite. -4. Fill the final audit table with these WebSDK source paths and results. +1. Keep `0x13` documented unsupported until salvage exists. +2. Re-run `tests/afp/afp_endpoint_inventory.py` and the AFP smoke suite. +3. Fill the final audit table with these WebSDK source paths and results. diff --git a/tests/afp/README.md b/tests/afp/README.md index 3835f23..600f472 100644 --- a/tests/afp/README.md +++ b/tests/afp/README.md @@ -701,17 +701,19 @@ NCP 0x2222/35/17 AFP 2.0 Scan File Information The helper defaults to the AFP 2.0 subfunction (`0x11`) and uses `--afp10` to exercise the older `0x0a` endpoint. Both variants include the documented -DesiredResponseCount word; mars_nwe currently returns one path-backed read-only -entry per request, using the same AFP file information record as -`afp_file_info_smoke`. The test sends raw `SYS:`-style path requests with -directory handle 0 and uses the returned `next_last_seen` AFP Entry ID as the -continuation token for the next call. +DesiredResponseCount word; mars_nwe currently returns one read-only entry per +request, using the same AFP file information record as `afp_file_info_smoke`. +The test sends raw `SYS:`-style path requests with directory handle 0, and +`--entry-id-only` first resolves the directory path to an AFP directory entry ID +and then sends the scan request with `path_len=0`. Both forms use the returned +`next_last_seen` AFP Entry ID as the continuation token for the next call. Useful smoke sequence for a standard MARS-NWE `SYS:PUBLIC` directory: ```sh ./tests/afp/afp_scan_info_smoke -S MARS -U SUPERVISOR -P secret SYS:PUBLIC ./tests/afp/afp_scan_info_smoke --afp10 -S MARS -U SUPERVISOR -P secret SYS:PUBLIC +./tests/afp/afp_scan_info_smoke --entry-id-only -S MARS -U SUPERVISOR -P secret SYS:PUBLIC ./tests/afp/afp_scan_info_smoke -S MARS -U SUPERVISOR -P secret --last-seen 0x260437f6 SYS:PUBLIC ./tests/afp/afp_scan_info_smoke -S MARS -U SUPERVISOR -P secret --last-seen 0x6686342b SYS:PUBLIC ``` @@ -721,6 +723,7 @@ moved onto the existing mars_nwe namespace/basehandle path: ```text AFP Scan File Info subfunction=0x11 path=SYS:PUBLIC last_seen=0x00000000 desired=1 next_last_seen=0x00000004 entry_id=0x00000004 parent_id=0x00000000 attrs=0x2000 data_len=44424 resource_len=0 offspring=0 long_name=debug.exe short_name=debug.exe rights=0x9f00 +AFP Scan File Info subfunction=0x11 path=SYS:PUBLIC last_seen=0x00000000 desired=1 next_last_seen=0x00000004 entry_id=0x00000004 parent_id=0x00000000 attrs=0x2000 data_len=44424 resource_len=0 offspring=0 long_name=debug.exe short_name=debug.exe rights=0x9f00 entry-id-only ``` The AFP entry-id xattr remains a compatibility/cache location rather than the diff --git a/tests/afp/afp_scan_info_smoke.c b/tests/afp/afp_scan_info_smoke.c index 7026851..f199703 100644 --- a/tests/afp/afp_scan_info_smoke.c +++ b/tests/afp/afp_scan_info_smoke.c @@ -21,6 +21,7 @@ #define AFP_SCAN_FILE_INFORMATION 0x0a #define AFP20_SCAN_FILE_INFORMATION 0x11 +#define AFP_GET_ENTRY_ID_FROM_PATH_NAME 0x0c #define NWE_INVALID_NAMESPACE 0xbf #define NWE_INVALID_PATH 0x9c #define NWE_NO_FILES_FOUND 0xff @@ -33,9 +34,9 @@ static void usage(const char *prog) { fprintf(stderr, - "Usage: %s [--afp10|--afp20] [--allow-invalid-namespace] " - "[--allow-invalid-path] [--allow-empty] [--volume N] " - "[--entry-id ID] [--last-seen ID] [--desired-count N] " + "Usage: %s [--afp10|--afp20] [--entry-id-only] " + "[--allow-invalid-namespace] [--allow-invalid-path] [--allow-empty] " + "[--volume N] [--entry-id ID] [--last-seen ID] [--desired-count N] " "[--search-mask MASK] [--request-mask MASK] [ncpfs options] PATH\n" "\n" "ncpfs options are parsed by ncp_initialize(), for example:\n" @@ -109,6 +110,7 @@ int main(int argc, char **argv) int allow_invalid_namespace = 0; int allow_invalid_path = 0; int allow_empty = 0; + int entry_id_only = 0; uint32_t volume_number = 0; uint32_t entry_id = 0; uint32_t last_seen_id = 0; @@ -147,6 +149,8 @@ int main(int argc, char **argv) allow_invalid_path = 1; } else if (!strcmp(argv[i], "--allow-empty")) { allow_empty = 1; + } else if (!strcmp(argv[i], "--entry-id-only")) { + entry_id_only = 1; } else if (!strcmp(argv[i], "--volume")) { if (++i >= argc || parse_u32(argv[i], &volume_number) || volume_number > 255) { fprintf(stderr, "invalid --volume value\n"); @@ -250,7 +254,8 @@ int main(int argc, char **argv) long_name, short_name, be16_to_cpu(reply_buf + 2 + 112), - reply.fragSize); + reply.fragSize, + entry_id_only ? " entry-id-only" : ""); ncp_close(conn); return 0; @@ -326,7 +331,8 @@ int main(int argc, char **argv) long_name, short_name, be16_to_cpu(reply_buf + 2 + 112), - reply.fragSize); + reply.fragSize, + entry_id_only ? " entry-id-only" : ""); ncp_close(conn); return 0; diff --git a/tests/afp/afp_smoke_suite.sh b/tests/afp/afp_smoke_suite.sh index c60d3ae..c86775e 100755 --- a/tests/afp/afp_smoke_suite.sh +++ b/tests/afp/afp_smoke_suite.sh @@ -476,6 +476,11 @@ run_cmd \ "./afp_scan_info_smoke $COMMON_PRINT '$DIR_PATH'" \ "$SCRIPT_DIR/afp_scan_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$DIR_PATH" +run_cmd \ + "AFP 2.0 Scan File Information by Entry ID" \ + "./afp_scan_info_smoke --entry-id-only $COMMON_PRINT '$DIR_PATH'" \ + "$SCRIPT_DIR/afp_scan_info_smoke" --entry-id-only -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$DIR_PATH" + run_cmd \ "AFP Alloc Temporary Directory Handle" \ "./afp_temp_dir_handle_smoke $COMMON_PRINT '$DIR_PATH'" \