nwconn: implement AFP temporary directory handles
All checks were successful
Source release / source-package (push) Successful in 49s
All checks were successful
Source release / source-package (push) Successful in 49s
Implement the WebSDK/nwafp.h NCP 0x2222/35 AFP subfunction 0x0b, Alloc Temporary Directory Handle, for the same conservative path-backed subset used by the current AFP Entry ID and File Information probes.\n\nThe documented request carries an AFP volume number, a base AFP Entry ID, and an optional AFP-style path. mars_nwe still has no persistent CNID/base-ID lookup, so this patch deliberately accepts only requests with a path component and rejects entry-id-only allocation as Invalid Path. For the supported smoke-test path, raw SYS:-style paths are resolved through the existing NetWare path machinery and the final handle is allocated with nw_alloc_dir_handle() as a temporary, task-scoped NetWare directory handle.\n\nThe implementation keeps the AFP namespace gate intact: without the optional Netatalk/libatalk backend, the endpoint continues to return Invalid Namespace rather than pretending that the Mac namespace exists. With the backend enabled, successful replies contain the allocated temporary directory handle and the effective-rights mask returned by the existing directory-handle table. Diagnostics include the AFP volume/base Entry ID input, path, returned handle, and rights so smoke-test output can be matched to server logs.\n\nAdd a Linux ncpfs/libncp smoke helper for the new endpoint. The helper sends the AFP 0x0b request through NWRequestSimple(), prints the returned handle and rights mask, and immediately deallocates the handle in the same connection via the normal NetWare Deallocate Directory Handle call. The README documents that these handles are connection/task-local and must not be copied into later tests or reused from server logs.\n\nTests:\n- git diff --check\n- gcc -fsyntax-only tests/linux/afp_temp_dir_handle_smoke.c with temporary local ncpfs header stubs\n\nNot run:\n- Full CMake build in this container: missing gdbm/ncpfs development headers/libraries.\n\nTODO:\n- Replace the path-backed subset with persistent CNID/base-ID lookup once the AFP metadata backend grows durable directory identity support.\n- Verify live Linux smoke cases against SYS:, SYS:PUBLIC, SYS:SYSTEM, and SYS:BURST on a mars_nwe host with ENABLE_NETATALK_LIBATALK=ON.
This commit is contained in:
6
TODO.md
6
TODO.md
@@ -217,6 +217,12 @@ Current status:
|
||||
coverage uses `tests/linux/afp_entry_id_smoke --from-handle` and has been
|
||||
verified against `SYS:PUBLIC/pmdflts.ini` and `SYS:PUBLIC/ohlogscr.bat`,
|
||||
returning volume 0, `fork=0`, and stat-derived fallback Entry IDs for now.
|
||||
- `AFP Alloc Temporary Directory Handle` is implemented for the same
|
||||
path-backed smoke subset. Linux smoke coverage exists in
|
||||
`tests/linux/afp_temp_dir_handle_smoke`; it returns a temporary NetWare
|
||||
directory handle plus effective-rights mask and immediately deallocates the
|
||||
handle in the same connection. Entry-ID-only allocation remains TODO until
|
||||
persistent CNID/base-ID lookup exists.
|
||||
- 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.
|
||||
|
||||
72
src/nwconn.c
72
src/nwconn.c
@@ -611,6 +611,68 @@ static int afp_get_entry_id_from_netware_handle(uint8 *afp_req, int afp_len,
|
||||
return(6);
|
||||
}
|
||||
|
||||
static int afp_alloc_temporary_dir_handle(uint8 *afp_req, int afp_len,
|
||||
uint8 *response)
|
||||
/*
|
||||
* WebSDK / nwafp.h call 0x0b allocates a temporary NetWare directory handle
|
||||
* from AFP namespace input: volume number, AFP Entry ID, and optional AFP-style
|
||||
* path. Until a persistent CNID/base-ID map exists, accept the same raw
|
||||
* SYS:-style path-backed subset used by the other implemented AFP probes and
|
||||
* delegate the final handle allocation to the existing NetWare directory-handle
|
||||
* table.
|
||||
*/
|
||||
{
|
||||
uint8 volume_number;
|
||||
uint32 request_entry_id;
|
||||
int path_len;
|
||||
int eff_rights = 0;
|
||||
int dirhandle;
|
||||
|
||||
if (afp_len < 7) {
|
||||
XDPRINTF((2,0, "AFP Alloc Temporary Dir Handle rejected: short request len=%d",
|
||||
afp_len));
|
||||
return(-0x7e); /* NCP Boundary Check Failed */
|
||||
}
|
||||
|
||||
volume_number = afp_req[1];
|
||||
request_entry_id = GET_BE32(afp_req + 2);
|
||||
path_len = (int)afp_req[6];
|
||||
if (path_len < 0 || afp_len < 7 + path_len) {
|
||||
XDPRINTF((2,0, "AFP Alloc Temporary Dir Handle rejected: boundary check len=%d path_len=%d",
|
||||
afp_len, path_len));
|
||||
return(-0x7e);
|
||||
}
|
||||
|
||||
if (!nwatalk_backend_available()) {
|
||||
XDPRINTF((3,0, "AFP Alloc Temporary Dir Handle rejected: libatalk backend unavailable"));
|
||||
return(-0xbf); /* invalid namespace */
|
||||
}
|
||||
|
||||
if (!path_len) {
|
||||
XDPRINTF((2,0, "AFP Alloc Temporary Dir Handle rejected: entry-id-only lookup unsupported vol=%d entry=0x%08x",
|
||||
(int)volume_number, request_entry_id));
|
||||
return(-0x9c); /* Invalid Path until persistent entry-id lookup exists */
|
||||
}
|
||||
|
||||
dirhandle = nw_alloc_dir_handle(0, afp_req + 7, path_len, 0, 1,
|
||||
(int)(ncprequest->task), &eff_rights);
|
||||
if (dirhandle < 0) {
|
||||
XDPRINTF((2,0, "AFP Alloc Temporary Dir Handle path resolve failed: vol=%d entry=0x%08x path='%s' result=-0x%x",
|
||||
(int)volume_number, request_entry_id,
|
||||
visable_data(afp_req + 7, path_len), -dirhandle));
|
||||
return(dirhandle);
|
||||
}
|
||||
|
||||
response[0] = (uint8)dirhandle;
|
||||
response[1] = (uint8)eff_rights;
|
||||
|
||||
XDPRINTF((3,0, "AFP Alloc Temporary Dir Handle: vol=%d entry=0x%08x path='%s' dir_handle=%d rights=0x%02x",
|
||||
(int)volume_number, request_entry_id,
|
||||
visable_data(afp_req + 7, path_len), dirhandle, eff_rights));
|
||||
return(2);
|
||||
}
|
||||
|
||||
|
||||
static int afp_get_entry_id_from_path_name(uint8 *afp_req, int afp_len,
|
||||
uint8 *response)
|
||||
{
|
||||
@@ -2969,7 +3031,10 @@ static int handle_ncp_serv(void)
|
||||
* lookup exists, support the same path-backed SYS:-style
|
||||
* 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
|
||||
* Unix path and returns the corresponding AFP ID. Alloc
|
||||
* Temporary Dir Handle uses the same path-backed subset and
|
||||
* returns a connection-local NetWare directory handle plus
|
||||
* effective rights. 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
|
||||
@@ -2989,6 +3054,11 @@ static int handle_ncp_serv(void)
|
||||
afp_len, responsedata);
|
||||
if (result > -1) data_len = result;
|
||||
else completition = (uint8)-result;
|
||||
} else if (ufunc == 0x0b) {
|
||||
int result = afp_alloc_temporary_dir_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);
|
||||
|
||||
@@ -29,3 +29,7 @@ target_link_libraries(afp_file_info_smoke ${NCPFS_LIBRARY})
|
||||
add_executable(afp_scan_info_smoke afp_scan_info_smoke.c)
|
||||
target_include_directories(afp_scan_info_smoke PRIVATE ${NCPFS_INCLUDE_DIR})
|
||||
target_link_libraries(afp_scan_info_smoke ${NCPFS_LIBRARY})
|
||||
|
||||
add_executable(afp_temp_dir_handle_smoke afp_temp_dir_handle_smoke.c)
|
||||
target_include_directories(afp_temp_dir_handle_smoke PRIVATE ${NCPFS_INCLUDE_DIR})
|
||||
target_link_libraries(afp_temp_dir_handle_smoke ${NCPFS_LIBRARY})
|
||||
|
||||
@@ -14,6 +14,7 @@ cmake -DMARS_NWE_BUILD_LINUX_TESTS=ON ...
|
||||
cmake --build . --target afp_entry_id_smoke
|
||||
cmake --build . --target afp_file_info_smoke
|
||||
cmake --build . --target afp_scan_info_smoke
|
||||
cmake --build . --target afp_temp_dir_handle_smoke
|
||||
```
|
||||
|
||||
## AFP Entry ID smoke test
|
||||
@@ -136,6 +137,47 @@ CNID/AppleDouble/libatalk-backed identity, parent Entry ID derivation, and AFP
|
||||
resource-fork handle semantics remain future Mac-namespace work; the current
|
||||
smoke coverage only verifies the conservative read-only data-fork mapping.
|
||||
|
||||
## AFP Alloc Temporary Directory Handle smoke test
|
||||
|
||||
`afp_temp_dir_handle_smoke` sends the WebSDK-documented NetWare AFP request:
|
||||
|
||||
```text
|
||||
NCP 0x2222/35/11 AFP Alloc Temporary Directory Handle
|
||||
```
|
||||
|
||||
The request layout is the AFP volume number, base AFP Entry ID, path length,
|
||||
and AFP-style path. The current mars_nwe implementation supports the same
|
||||
conservative path-backed subset as the Entry ID and File Information probes:
|
||||
pass a raw `SYS:`-style path and keep volume/base Entry ID at zero. Pure
|
||||
Entry-ID-relative allocation is still rejected with Invalid Path until
|
||||
persistent CNID/base-ID lookup exists.
|
||||
|
||||
Useful smoke cases for a standard MARS-NWE `SYS` volume are:
|
||||
|
||||
```sh
|
||||
./tests/linux/afp_temp_dir_handle_smoke -S MARS -U SUPERVISOR -P secret SYS:
|
||||
./tests/linux/afp_temp_dir_handle_smoke -S MARS -U SUPERVISOR -P secret SYS:PUBLIC
|
||||
./tests/linux/afp_temp_dir_handle_smoke -S MARS -U SUPERVISOR -P secret SYS:SYSTEM
|
||||
./tests/linux/afp_temp_dir_handle_smoke -S MARS -U SUPERVISOR -P secret SYS:BURST
|
||||
```
|
||||
|
||||
A successful reply prints the allocated temporary NetWare directory handle and
|
||||
the effective-rights mask returned by the server. The smoke helper immediately
|
||||
deallocates the handle with the normal NetWare Deallocate Directory Handle call
|
||||
before closing the connection, so the handle value is only useful inside that
|
||||
client connection and must not be copied into later tests or server logs.
|
||||
|
||||
Example output and server diagnostic shape:
|
||||
|
||||
```text
|
||||
AFP Alloc Temporary Dir Handle path=SYS:PUBLIC dir_handle=2 rights=0xff
|
||||
AFP Alloc Temporary Dir Handle: vol=0 entry=0x00000000 path='SYS:PUBLIC' dir_handle=2 rights=0xff
|
||||
```
|
||||
|
||||
If the server was built without the optional Netatalk/libatalk backend, use
|
||||
`--allow-invalid-namespace` for the expected negative test. Use
|
||||
`--allow-invalid-path` for path-resolution negative tests.
|
||||
|
||||
## AFP File Information smoke test
|
||||
|
||||
`afp_file_info_smoke` sends the WebSDK-documented NetWare AFP file
|
||||
|
||||
208
tests/linux/afp_temp_dir_handle_smoke.c
Normal file
208
tests/linux/afp_temp_dir_handle_smoke.c
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Linux smoke test for NetWare AFP Alloc Temporary Directory Handle.
|
||||
*
|
||||
* This uses ncpfs/libncp so the request travels through the same NCP path as a
|
||||
* normal Linux requester. The server-side AFP endpoint returns a temporary
|
||||
* NetWare directory handle and an effective-rights mask for a path-backed AFP
|
||||
* request.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <ncp/nwcalls.h>
|
||||
#include <ncp/ncplib.h>
|
||||
#include <ncp/kernel/ncp.h>
|
||||
|
||||
#ifndef NCPC_SUBFUNCTION
|
||||
#define NCPC_SUBFUNCTION 0x10000
|
||||
#endif
|
||||
#ifndef NCPC_SFN
|
||||
#define NCPC_SFN(FN, SFN) ((FN) | ((SFN) << 8) | NCPC_SUBFUNCTION)
|
||||
#endif
|
||||
|
||||
#define AFP_ALLOC_TEMPORARY_DIR_HANDLE 0x0b
|
||||
#define NWE_INVALID_NAMESPACE 0xbf
|
||||
#define NWE_INVALID_PATH 0x9c
|
||||
#define AFP_TEMP_DH_NONE 0xff
|
||||
|
||||
static void usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: %s [--allow-invalid-namespace] [--allow-invalid-path] "
|
||||
"[--volume N] [--entry-id ID] [ncpfs options] PATH\n"
|
||||
"\n"
|
||||
"ncpfs options are parsed by ncp_initialize(), for example:\n"
|
||||
" -S SERVER -U USER -P PASSWORD -n\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" %s -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n"
|
||||
" %s --allow-invalid-namespace -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n"
|
||||
" %s --allow-invalid-path -S MARS -U SUPERVISOR -P secret SYS:NO_SUCH_PATH\n",
|
||||
prog, prog, prog, prog);
|
||||
}
|
||||
|
||||
static int parse_u32(const char *text, uint32_t *value)
|
||||
{
|
||||
char *end = NULL;
|
||||
unsigned long v;
|
||||
|
||||
errno = 0;
|
||||
v = strtoul(text, &end, 0);
|
||||
if (errno || !end || *end || v > 0xffffffffUL)
|
||||
return -1;
|
||||
*value = (uint32_t)v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cpu_to_be32(uint32_t v, uint8_t p[4])
|
||||
{
|
||||
p[0] = (uint8_t)(v >> 24);
|
||||
p[1] = (uint8_t)(v >> 16);
|
||||
p[2] = (uint8_t)(v >> 8);
|
||||
p[3] = (uint8_t)v;
|
||||
}
|
||||
|
||||
static void deallocate_dir_handle(NWCONN_HANDLE conn, unsigned int dir_handle)
|
||||
{
|
||||
uint8_t rq[1];
|
||||
|
||||
if (dir_handle == AFP_TEMP_DH_NONE)
|
||||
return;
|
||||
|
||||
rq[0] = (uint8_t)dir_handle;
|
||||
(void)NWRequestSimple(conn, NCPC_SFN(0x16, 0x14), rq, sizeof(rq), NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
NWCONN_HANDLE conn;
|
||||
NW_FRAGMENT reply;
|
||||
long init_err = 0;
|
||||
const char *path = NULL;
|
||||
int allow_invalid_namespace = 0;
|
||||
int allow_invalid_path = 0;
|
||||
uint32_t volume_number = 0;
|
||||
uint32_t base_entry_id = 0;
|
||||
unsigned int allocated_dir_handle = AFP_TEMP_DH_NONE;
|
||||
int i;
|
||||
size_t path_len;
|
||||
uint8_t request[1 + 4 + 1 + 255];
|
||||
uint8_t reply_buf[2];
|
||||
NWCCODE err;
|
||||
|
||||
if (NWCallsInit(NULL, NULL)) {
|
||||
fprintf(stderr, "NWCallsInit failed\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
conn = ncp_initialize(&argc, argv, 1, &init_err);
|
||||
if (!conn) {
|
||||
fprintf(stderr, "ncp_initialize/login failed: %ld\n", init_err);
|
||||
usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--allow-invalid-namespace")) {
|
||||
allow_invalid_namespace = 1;
|
||||
} else if (!strcmp(argv[i], "--allow-invalid-path")) {
|
||||
allow_invalid_path = 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");
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
} else if (!strcmp(argv[i], "--entry-id")) {
|
||||
if (++i >= argc || parse_u32(argv[i], &base_entry_id)) {
|
||||
fprintf(stderr, "invalid --entry-id value\n");
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
|
||||
usage(argv[0]);
|
||||
ncp_close(conn);
|
||||
return 0;
|
||||
} else if (!path) {
|
||||
path = argv[i];
|
||||
} else {
|
||||
fprintf(stderr, "unexpected argument: %s\n", argv[i]);
|
||||
usage(argv[0]);
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
fprintf(stderr, "missing PATH\n");
|
||||
usage(argv[0]);
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
|
||||
path_len = strlen(path);
|
||||
if (path_len > 255) {
|
||||
fprintf(stderr,
|
||||
"PATH is too long for AFP Alloc Temporary Directory Handle: %zu\n",
|
||||
path_len);
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
|
||||
request[0] = (uint8_t)volume_number;
|
||||
cpu_to_be32(base_entry_id, request + 1);
|
||||
request[5] = (uint8_t)path_len;
|
||||
memcpy(request + 6, path, path_len);
|
||||
|
||||
memset(reply_buf, 0, sizeof(reply_buf));
|
||||
reply.fragAddr.rw = reply_buf;
|
||||
reply.fragSize = sizeof(reply_buf);
|
||||
|
||||
err = NWRequestSimple(conn,
|
||||
NCPC_SFN(0x23, AFP_ALLOC_TEMPORARY_DIR_HANDLE),
|
||||
request,
|
||||
6 + path_len,
|
||||
&reply);
|
||||
|
||||
if (((unsigned int)err & 0xff) == NWE_INVALID_NAMESPACE &&
|
||||
allow_invalid_namespace) {
|
||||
printf("AFP Alloc Temporary Dir Handle returned invalid namespace "
|
||||
"as expected: path=%s\n", path);
|
||||
ncp_close(conn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (((unsigned int)err & 0xff) == NWE_INVALID_PATH && allow_invalid_path) {
|
||||
printf("AFP Alloc Temporary Dir Handle returned invalid path "
|
||||
"as expected: path=%s\n", path);
|
||||
ncp_close(conn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
fprintf(stderr,
|
||||
"AFP Alloc Temporary Dir Handle failed: completion=0x%02x (%u) path=%s\n",
|
||||
(unsigned int)err & 0xff, (unsigned int)err, path);
|
||||
ncp_close(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (reply.fragSize < 2) {
|
||||
fprintf(stderr, "short AFP reply: %zu bytes\n", reply.fragSize);
|
||||
ncp_close(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
allocated_dir_handle = reply_buf[0];
|
||||
printf("AFP Alloc Temporary Dir Handle path=%s dir_handle=%u rights=0x%02x\n",
|
||||
path, allocated_dir_handle, (unsigned int)reply_buf[1]);
|
||||
|
||||
deallocate_dir_handle(conn, allocated_dir_handle);
|
||||
ncp_close(conn);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user