nwconn: implement AFP 2.0 Get File Information
All checks were successful
Source release / source-package (push) Successful in 46s
All checks were successful
Source release / source-package (push) Successful in 46s
Route NCP 0x23/0x0f AFP 2.0 Get File Information through the existing read-only AFP file-information helper. The WebSDK and nwafp.h list both AFP Get File Information and AFP 2.0 Get File Information as path/file-information queries that return the AFP file-information record used by Mac namespace clients. The already implemented 0x05 path handles SYS:-style path requests and fills the safe read-only fields from Unix stat data plus the optional libatalk Finder Info and resource-fork helpers. Use the same conservative path-backed reply for 0x0f for now. This gives Linux smoke-test coverage for the AFP 2.0 subfunction without adding entry-id-only lookup, persistent CNID mapping, write-side metadata updates, or fuller resource-fork semantics. Extend afp_file_info_smoke with --afp20 so the same SYS:, SYS:PUBLIC, SYS:SYSTEM, and SYS:BURST cases can exercise the AFP 2.0 subfunction. Update the Linux test README and TODO tracking to record that AFP 2.0 file information is covered by the same temporary fallback model. This implements only the read-only path-based subset; richer AFP 2.0 behavior remains future Mac-namespace work.
This commit is contained in:
10
TODO.md
10
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
|
||||
|
||||
|
||||
39
src/nwconn.c
39
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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user