Default the Linux AFP Entry ID smoke test to sending the supplied path directly with directory handle 0. The verified smoke cases use SYS:-style paths against mars_nwe, and those requests reach NCP 0x23/0x0c and return Entry IDs. The intermediate automatic temporary directory-handle allocation path is not required for that endpoint coverage and can fail before the AFP request is sent, which makes the smoke test diagnose the wrong subsystem. Keep the handle-allocation path available behind --alloc-handle for follow-up debugging, keep --raw-path as an explicit no-op for compatibility, and keep --dir-handle N for testing relative paths against a handle that is valid in the current connection. This changes only the Linux smoke test; server AFP protocol behavior is unchanged.
326 lines
10 KiB
C
326 lines
10 KiB
C
/*
|
|
* Linux smoke test for NetWare AFP Entry ID path lookup.
|
|
*
|
|
* This intentionally uses ncpfs/libncp instead of a private socket path so the
|
|
* request is sent through the same client stack that is commonly available on
|
|
* Linux mars_nwe test hosts.
|
|
*/
|
|
|
|
#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_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] [--allow-invalid-path] "
|
|
"[--dir-handle N] [--alloc-handle] [--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:PUBLIC\n"
|
|
" %s --dir-handle 2 -S MARS -U SUPERVISOR -P secret PUBLIC\n"
|
|
" %s --alloc-handle -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);
|
|
}
|
|
|
|
static int parse_u8(const char *text, unsigned int *value)
|
|
{
|
|
char *end = NULL;
|
|
unsigned long v;
|
|
|
|
errno = 0;
|
|
v = strtoul(text, &end, 0);
|
|
if (errno || !end || *end || v > 255)
|
|
return -1;
|
|
*value = (unsigned int)v;
|
|
return 0;
|
|
}
|
|
|
|
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) |
|
|
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)
|
|
{
|
|
char root_path[64];
|
|
size_t volume_len = strlen(volume);
|
|
uint8_t request[3 + sizeof(root_path)];
|
|
uint8_t reply_buf[2];
|
|
NW_FRAGMENT reply;
|
|
NWCCODE err;
|
|
|
|
if (!volume_len || volume_len + 1 >= sizeof(root_path))
|
|
return NWE_INVALID_PATH;
|
|
|
|
memcpy(root_path, volume, volume_len);
|
|
root_path[volume_len] = ':';
|
|
root_path[volume_len + 1] = '\0';
|
|
|
|
/*
|
|
* mars_nwe implements the old NCP 0x16/0x13 Allocate Temporary Directory
|
|
* Handle endpoint directly. Use that server-native call for the smoke
|
|
* test instead of NCP 87 namespace allocation: the namespace helper is
|
|
* useful for ncpfs against NetWare, but mars_nwe currently rejects the
|
|
* DIRBASE volume-root form before the AFP endpoint is reached.
|
|
*
|
|
* Request payload after the subfunction byte:
|
|
* source directory handle
|
|
* drive letter / target slot hint
|
|
* path length
|
|
* path bytes, here "VOL:"
|
|
*/
|
|
request[0] = 0; /* source dir handle */
|
|
request[1] = 0; /* no client drive-letter mapping needed */
|
|
request[2] = (uint8_t)(volume_len + 1);
|
|
memcpy(request + 3, root_path, volume_len + 1);
|
|
|
|
memset(reply_buf, 0, sizeof(reply_buf));
|
|
reply.fragAddr.rw = reply_buf;
|
|
reply.fragSize = sizeof(reply_buf);
|
|
|
|
err = NWRequestSimple(conn,
|
|
NCPC_SFN(0x16, 0x13),
|
|
request,
|
|
3 + volume_len + 1,
|
|
&reply);
|
|
if (err)
|
|
return err;
|
|
|
|
if (reply.fragSize < 1)
|
|
return NWE_INVALID_NCP_PACKET_LENGTH;
|
|
|
|
*dir_handle = reply_buf[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)
|
|
{
|
|
NWCONN_HANDLE conn;
|
|
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 = 1;
|
|
int alloc_handle = 0;
|
|
int i;
|
|
size_t path_len;
|
|
char volume[32];
|
|
uint8_t request[1 + 1 + 255];
|
|
uint8_t reply_buf[4];
|
|
NWCCODE err;
|
|
|
|
if (NWCallsInit(NULL, NULL)) {
|
|
fprintf(stderr, "NWCallsInit failed\n");
|
|
return 2;
|
|
}
|
|
|
|
/*
|
|
* The smoke test needs normal file-service path access before it can send
|
|
* AFP 0x0c: it has to allocate/use a directory handle for the supplied
|
|
* VOL:PATH. ncp_initialize(..., 0, ...) intentionally clears the user
|
|
* name even if -U/-P were supplied, so mars_nwe treats the connection as
|
|
* unauthenticated and rejects non-login paths before the AFP request is
|
|
* reached. Require a login here; no-password users can still use -n.
|
|
*/
|
|
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], "--raw-path")) {
|
|
raw_path = 1;
|
|
alloc_handle = 0;
|
|
} else if (!strcmp(argv[i], "--alloc-handle")) {
|
|
raw_path = 0;
|
|
alloc_handle = 1;
|
|
} else if (!strcmp(argv[i], "--dir-handle")) {
|
|
if (++i >= argc || parse_u8(argv[i], &dir_handle)) {
|
|
fprintf(stderr, "invalid --dir-handle 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;
|
|
}
|
|
|
|
request_path = path;
|
|
/*
|
|
* Default to raw-path mode: send the supplied path directly with
|
|
* directory handle 0. mars_nwe accepts SYS: style paths for the AFP
|
|
* probe, and this keeps the smoke test focused on NCP 0x23/0x0c instead
|
|
* of depending on a separate directory-handle allocation path. Use
|
|
* --alloc-handle to explicitly exercise the VOL:PATH -> temporary handle
|
|
* helper, or --dir-handle N to pass a caller-provided handle with a
|
|
* relative path.
|
|
*/
|
|
if (alloc_handle && !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, request_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_GET_ENTRY_ID_FROM_PATH_NAME),
|
|
request,
|
|
2 + path_len,
|
|
&reply);
|
|
|
|
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 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 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 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;
|
|
}
|