tests: resolve AFP smoke volume paths
All checks were successful
Source release / source-package (push) Successful in 47s

Teach the Linux AFP Entry ID smoke test to treat VOL:PATH arguments like normal NetWare paths instead of sending the full string as the AFP path component.

The WebSDK documents AFP Get Entry ID From Path Name as taking a NetWare directory handle plus a path string.  A user-supplied path such as SYS:PUBLIC therefore needs a directory handle for the SYS volume root and a relative AFP path of PUBLIC; sending SYS:PUBLIC as the AFP path with directory handle zero makes the server reject the request with Invalid Path before the actual AFP lookup is useful.

Use the existing ncpfs/libncp request path to allocate a temporary directory handle for the volume root when the test receives a VOL:PATH argument and no explicit --dir-handle was supplied.  Keep --raw-path for callers that want to send the path exactly as typed, and add --allow-invalid-path so negative path-resolution tests can distinguish Invalid Path from Invalid Namespace.

Also add failure diagnostics to the server-side AFP path lookup so unsupported-backend, boundary-check, path-resolution, and stat failures are visible in the mars_nwe log.

This changes only the Linux smoke test and debug logging; it does not change successful AFP protocol semantics.
This commit is contained in:
Mario Fetka
2026-05-30 00:41:24 +00:00
parent b7999fcb7d
commit 069bbba88c
2 changed files with 149 additions and 18 deletions

View File

@@ -32,6 +32,7 @@
# define LOC_RW_BUFFERSIZE 512
#endif
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
#if !CALL_NWCONN_OVER_SOCKET
@@ -486,19 +487,39 @@ static int afp_get_entry_id_from_path_name(uint8 *afp_req, int afp_len,
uint32 entry_id = 0;
int result;
if (afp_len < 3) return(-0x7e); /* NCP Boundary Check Failed */
if (afp_len < 3) {
XDPRINTF((2,0, "AFP Get Entry ID From Path Name rejected: short request len=%d",
afp_len));
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 (path_len < 0 || afp_len < 3 + path_len) {
XDPRINTF((2,0, "AFP Get Entry ID From Path Name rejected: boundary check len=%d path_len=%d",
afp_len, path_len));
return(-0x7e);
}
if (!nwatalk_backend_available()) return(-0xbf); /* invalid namespace */
if (!nwatalk_backend_available()) {
XDPRINTF((3,0, "AFP Get Entry ID From Path Name rejected: libatalk backend unavailable"));
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 (volume < 0) {
XDPRINTF((2,0, "AFP Get Entry ID From Path Name path resolve failed: dh=%d path='%s' result=-0x%x",
(int)dir_handle, visable_data(afp_req + 3, path_len), -volume));
return(volume);
}
if (stat(unixname, &stbuff)) return(-0x9c); /* Invalid Path */
if (stat(unixname, &stbuff)) {
XDPRINTF((2,0, "AFP Get Entry ID From Path Name stat failed: dh=%d path='%s' unix='%s' errno=%d",
(int)dir_handle, visable_data(afp_req + 3, path_len),
unixname, errno));
return(-0x9c); /* Invalid Path */
}
result = nwatalk_get_entry_id(unixname, &entry_id);
if (result < 0 || !entry_id)

View File

@@ -24,20 +24,23 @@
#define AFP_GET_ENTRY_ID_FROM_PATH_NAME 0x0c
#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] [--dir-handle N] "
"[ncpfs options] PATH\n"
"Usage: %s [--allow-invalid-namespace] [--allow-invalid-path] "
"[--dir-handle N] [--raw-path] [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:LOGIN\n"
" %s --allow-invalid-namespace -S MARS SYS:LOGIN\n",
prog, prog, prog);
" %s --allow-invalid-namespace -S MARS SYS:LOGIN\n"
" %s --allow-invalid-path -S MARS SYS:NO_SUCH_PATH\n",
prog, prog, prog, prog);
}
static int parse_u8(const char *text, unsigned int *value)
@@ -58,7 +61,75 @@ static uint32_t be32_to_cpu(const uint8_t p[4])
return ((uint32_t)p[0] << 24) |
((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) |
((uint32_t)p[3]);
p[3];
}
static int split_volume_path(const char *path, char *volume, size_t volume_size,
const char **relpath)
{
const char *colon = strchr(path, ':');
size_t len;
if (!colon)
return -1;
len = (size_t)(colon - path);
if (!len || len >= volume_size)
return -1;
memcpy(volume, path, len);
volume[len] = '\0';
*relpath = colon + 1;
while (**relpath == '/' || **relpath == '\\')
(*relpath)++;
return 0;
}
static NWCCODE allocate_temp_dir_handle(NWCONN_HANDLE conn, const char *volume,
unsigned int *dir_handle)
{
uint8_t rq[1 + 1 + 1 + 32];
uint8_t rpbuf[2];
NW_FRAGMENT rp;
size_t volume_len = strlen(volume);
NWCCODE err;
if (volume_len + 1 > 32)
return NWE_INVALID_PATH;
rq[0] = 0; /* source directory handle */
rq[1] = 0; /* drive letter, unused by the test */
rq[2] = (uint8_t)(volume_len + 1);
memcpy(rq + 3, volume, volume_len);
rq[3 + volume_len] = ':';
memset(rpbuf, 0, sizeof(rpbuf));
rp.fragAddr.rw = rpbuf;
rp.fragSize = sizeof(rpbuf);
err = NWRequestSimple(conn, NCPC_SFN(0x16, 0x13), rq,
3 + volume_len + 1, &rp);
if (err)
return err;
if (rp.fragSize < 1)
return NWE_INVALID_NCP_PACKET_LENGTH;
*dir_handle = rpbuf[0];
return 0;
}
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)
@@ -67,10 +138,15 @@ int main(int argc, char **argv)
NW_FRAGMENT reply;
long init_err = 0;
const char *path = NULL;
const char *request_path = NULL;
unsigned int dir_handle = 0;
unsigned int allocated_dir_handle = AFP_TEMP_DH_NONE;
int allow_invalid_namespace = 0;
int allow_invalid_path = 0;
int raw_path = 0;
int i;
size_t path_len;
char volume[32];
uint8_t request[1 + 1 + 255];
uint8_t reply_buf[4];
NWCCODE err;
@@ -90,6 +166,10 @@ int main(int argc, char **argv)
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], "--raw-path")) {
raw_path = 1;
} else if (!strcmp(argv[i], "--dir-handle")) {
if (++i >= argc || parse_u8(argv[i], &dir_handle)) {
fprintf(stderr, "invalid --dir-handle value\n");
@@ -117,17 +197,32 @@ int main(int argc, char **argv)
return 2;
}
path_len = strlen(path);
request_path = path;
if (!raw_path && dir_handle == 0 &&
split_volume_path(path, volume, sizeof(volume), &request_path) == 0) {
err = allocate_temp_dir_handle(conn, volume, &dir_handle);
if (err) {
fprintf(stderr,
"Allocate Temp Dir Handle failed: completion=0x%02x (%u) volume=%s path=%s\n",
(unsigned int)err & 0xff, (unsigned int)err, volume, path);
ncp_close(conn);
return 1;
}
allocated_dir_handle = dir_handle;
}
path_len = strlen(request_path);
if (path_len > 255) {
fprintf(stderr, "PATH is too long for AFP Get Entry ID From Path Name: %zu\n",
path_len);
deallocate_dir_handle(conn, allocated_dir_handle);
ncp_close(conn);
return 2;
}
request[0] = (uint8_t)dir_handle;
request[1] = (uint8_t)path_len;
memcpy(request + 2, path, path_len);
memcpy(request + 2, request_path, path_len);
memset(reply_buf, 0, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
@@ -139,32 +234,47 @@ int main(int argc, char **argv)
2 + path_len,
&reply);
if (err == NWE_INVALID_NAMESPACE && allow_invalid_namespace) {
if (((unsigned int)err & 0xff) == NWE_INVALID_NAMESPACE && allow_invalid_namespace) {
printf("AFP Get Entry ID From Path Name returned invalid namespace "
"as expected: path=%s\n", path);
"as expected: path=%s request_path=%s dir_handle=%u\n",
path, request_path, dir_handle);
deallocate_dir_handle(conn, allocated_dir_handle);
ncp_close(conn);
return 0;
}
if (((unsigned int)err & 0xff) == NWE_INVALID_PATH && allow_invalid_path) {
printf("AFP Get Entry ID From Path Name returned invalid path "
"as expected: path=%s request_path=%s dir_handle=%u\n",
path, request_path, dir_handle);
deallocate_dir_handle(conn, allocated_dir_handle);
ncp_close(conn);
return 0;
}
if (err) {
fprintf(stderr,
"AFP Get Entry ID From Path Name failed: completion=0x%02x (%u) path=%s\n",
(unsigned int)err & 0xff, (unsigned int)err, path);
"AFP Get Entry ID From Path Name failed: completion=0x%02x (%u) path=%s request_path=%s dir_handle=%u\n",
(unsigned int)err & 0xff, (unsigned int)err, path,
request_path, dir_handle);
deallocate_dir_handle(conn, allocated_dir_handle);
ncp_close(conn);
return 1;
}
if (reply.fragSize < 4) {
fprintf(stderr, "short AFP reply: %zu bytes\n", reply.fragSize);
deallocate_dir_handle(conn, allocated_dir_handle);
ncp_close(conn);
return 1;
}
printf("AFP Entry ID path=%s dir_handle=%u entry_id=0x%08x (%u)\n",
path, dir_handle,
printf("AFP Entry ID path=%s request_path=%s dir_handle=%u entry_id=0x%08x (%u)\n",
path, request_path, dir_handle,
(unsigned int)be32_to_cpu(reply_buf),
(unsigned int)be32_to_cpu(reply_buf));
deallocate_dir_handle(conn, allocated_dir_handle);
ncp_close(conn);
return 0;
}