Files
mars-nwe/tests/linux/afp_open_file_fork_smoke.c
Mario Fetka db9283a410
All checks were successful
Source release / source-package (push) Successful in 46s
nwconn: implement AFP open file fork smoke path
Implement the WebSDK/nwafp.h NCP 0x2222/35 AFP subfunction 0x08, Open File Fork, for the same conservative path-backed subset that the current AFP smoke endpoints use.

The request is decoded as volume number, AFP Entry ID, fork selector, access mode, path length, and AFP path.  Until persistent CNID/base-ID lookup exists, empty path / Entry-ID-only opens continue to fail with Invalid Path rather than pretending that the temporary stat-derived AFP Entry IDs are a durable namespace.  The handler also keeps resource forks and write access negative for now, because those require AppleDouble/resource-fork and write-safe Finder metadata semantics that are not implemented yet.

For the supported data-fork read-only case, delegate to the existing NetWare open-file path via nw_creat_open_file().  The reply returns the normal six-byte NetWare file handle shape used by the AFP handle APIs followed by the current data-fork length.  That lets follow-up smoke tests verify handle interoperability with AFP Get Entry ID From NetWare Handle and with the ordinary NetWare close path while keeping write/resource semantics conservative.

Add afp_open_file_fork_smoke so Linux ncpfs/libncp tests can exercise the endpoint through the same requester path as the other AFP probes.  The helper closes the returned handle in the same connection and documents the expected data-fork-only coverage in tests/linux/README.md and TODO.md.

Tests: git diff --check

Tests: cmake --build build-off --target nwconn with ENABLE_NETATALK_LIBATALK=OFF

Tests: cmake --build build-on --target nwconn with ENABLE_NETATALK_LIBATALK=ON requested; local environment lacks libatalk headers/library so CMake reports the metadata backend disabled, but the target still builds and the new handler has no direct libatalk symbol references

Tests: gcc -fsyntax-only tests/linux/afp_open_file_fork_smoke.c with local ncpfs header stubs

TODO: add persistent CNID/base-ID lookup, AppleDouble/resource-fork open support, and write-safe AFP fork semantics before accepting resource-fork or write-mode opens.
2026-05-30 10:04:26 +02:00

251 lines
7.3 KiB
C

/*
* Linux smoke test for NetWare AFP Open File Fork.
*
* This uses ncpfs/libncp so the request travels through the same NCP path as a
* normal Linux requester. The first mars_nwe implementation deliberately
* exercises the conservative path-backed data-fork subset and returns a normal
* NetWare file handle, which this helper closes before exiting.
*/
#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_OPEN_FILE_FORK 0x08
#define NWE_INVALID_NAMESPACE 0xbf
#define NWE_INVALID_PATH 0x9c
#define AFP_DATA_FORK 0x00
#define AFP_ACCESS_READ 0x01
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [--allow-invalid-namespace] [--allow-invalid-path] "
"[--volume N] [--entry-id ID] [--fork N] [--access N] "
"[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/pmdflts.ini\n"
" %s --allow-invalid-namespace -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/pmdflts.ini\n"
" %s --allow-invalid-path -S MARS -U SUPERVISOR -P secret SYS:NO_SUCH_FILE\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 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];
}
static uint32_t le32_to_cpu(const uint8_t p[4])
{
return ((uint32_t)p[0]) |
((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24);
}
static void cpu_to_le32(uint32_t v, uint8_t p[4])
{
p[0] = (uint8_t)v;
p[1] = (uint8_t)(v >> 8);
p[2] = (uint8_t)(v >> 16);
p[3] = (uint8_t)(v >> 24);
}
static void close_file_handle(NWCONN_HANDLE conn, uint32_t fhandle)
{
uint8_t rq[7];
memset(rq, 0, sizeof(rq));
cpu_to_le32(fhandle, rq + 3);
(void)NWRequestSimple(conn, 0x42, 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;
uint32_t fork_indicator = AFP_DATA_FORK;
uint32_t access_mode = AFP_ACCESS_READ;
int i;
size_t path_len;
uint8_t request[1 + 4 + 1 + 1 + 1 + 255];
uint8_t reply_buf[10];
uint32_t fhandle;
uint32_t fork_len;
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], "--fork")) {
if (++i >= argc || parse_u32(argv[i], &fork_indicator) ||
fork_indicator > 255) {
fprintf(stderr, "invalid --fork value\n");
ncp_close(conn);
return 2;
}
} else if (!strcmp(argv[i], "--access")) {
if (++i >= argc || parse_u32(argv[i], &access_mode) ||
access_mode > 255) {
fprintf(stderr, "invalid --access 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 Open File Fork: %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)fork_indicator;
request[6] = (uint8_t)access_mode;
request[7] = (uint8_t)path_len;
memcpy(request + 8, 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_OPEN_FILE_FORK),
request,
8 + path_len,
&reply);
if (((unsigned int)err & 0xff) == NWE_INVALID_NAMESPACE &&
allow_invalid_namespace) {
printf("AFP Open File Fork 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 Open File Fork returned invalid path as expected: path=%s\n", path);
ncp_close(conn);
return 0;
}
if (err) {
fprintf(stderr,
"AFP Open File Fork failed: completion=0x%02x (%u) path=%s\n",
(unsigned int)err & 0xff, (unsigned int)err, path);
ncp_close(conn);
return 1;
}
if (reply.fragSize < 10) {
fprintf(stderr, "short AFP reply: %zu bytes\n", reply.fragSize);
ncp_close(conn);
return 1;
}
fhandle = le32_to_cpu(reply_buf + 2);
fork_len = be32_to_cpu(reply_buf + 6);
printf("AFP Open File Fork path=%s handle=%u fork=%u access=0x%02x fork_len=%u\n",
path, fhandle, (unsigned int)fork_indicator,
(unsigned int)access_mode, fork_len);
close_file_handle(conn, fhandle);
ncp_close(conn);
return 0;
}