Files
mars-dosutils/nwtests.c
Mario Fetka 0df41482c7 dosutils: add creator and supporting NCP helpers
Add the CREATOR utility and wire it into the DOS tools build and NET
command dispatch.

Extend the client-side NCP helpers with the calls needed by the new and
updated tools, including DOS namespace information and effective-rights
queries. Also update MAP handling for automatic drive mappings used by
the login script.

This commit contains the tool/source support only. The DOS baseline test
suite is kept separate and will be added in a follow-up commit.
2026-05-27 13:31:02 +02:00

2786 lines
75 KiB
C

/* nwtests.c - small DOS utility tests */
#include "net.h"
#include "c32ncp.h"
#define TEST_RIGHT_S 0x01
#define TEST_RIGHT_R 0x02
#define TEST_RIGHT_W 0x04
#define TEST_RIGHT_C 0x08
#define TEST_RIGHT_E 0x10
#define TEST_RIGHT_M 0x20
#define TEST_RIGHT_F 0x40
#define TEST_RIGHT_A 0x80
#define TEST_NCP_RIGHT_READ 0x0001
#define TEST_NCP_RIGHT_WRITE 0x0002
#define TEST_NCP_RIGHT_CREATE 0x0008
#define TEST_NCP_RIGHT_DELETE 0x0010
#define TEST_NCP_RIGHT_OWNER 0x0020
#define TEST_NCP_RIGHT_SEARCH 0x0040
#define TEST_NCP_RIGHT_MODIFY 0x0080
#define TEST_NCP_RIGHT_SUPER 0x0100
static int tests_same_arg(char *a, char *b)
{
while (*a || *b) {
int ca = *a++;
int cb = *b++;
if (ca >= 'a' && ca <= 'z') ca -= 32;
if (cb >= 'a' && cb <= 'z') cb -= 32;
if (ca != cb) return(0);
}
return(1);
}
static void tests_usage(void)
{
fprintf(stdout, "Usage: TESTS [NCP87C32ATTR|NCP87C32AUTO|EFFRIGHT path|NCP22S4 path [mask]|NCP23MAP path|NCP22REN old new|RENDIR parent]\n");
fprintf(stdout, " TESTS NCP2225MISS [missing-file]\n");
fprintf(stdout, " TESTS NCP2225ATTR file [attr] (current-dir 8.3 file; attr hex)\n");
fprintf(stdout, " TESTS NCP2225TIME file [date time] (DOS hex words; modifies mtime)\n");
fprintf(stdout, " TESTS NCP2225ADATE file [date] (DOS hex word; modifies access date)\n");
fprintf(stdout, " TESTS NCP2225ARCH file [date time id] (archive xattr metadata)\n");
fprintf(stdout, " TESTS NCP2225CREATE file [date time id] (create xattr metadata)\n");
fprintf(stdout, " TESTS NCP2225MODID file [id] (modifier-id xattr metadata)\n");
fprintf(stdout, " TESTS NCP2225MAXSPACE dir [blocks] (directory maximum-space/quota)\n");
fprintf(stdout, " TESTS NCP221EINFO dir (dump NCP22/1E directory info layout)\n");
fprintf(stdout, " TESTS NCP22S4 path RF|42|0x42|ALL|NONE\n");
fprintf(stdout, " TESTS NCP22REN oldpath newpath (NCP22/2E Rename Or Move old)\n");
fprintf(stdout, " TESTS RENDIR parent (rename parent\\TREN <-> parent\\TREN2)\n");
fprintf(stdout, " TESTS NCP23MAP path (probe NCP23/F4 then F3 layouts; see server debug log)\n");
}
static int tests_get_current_drive(void)
{
REGS regs;
regs.h.ah = 0x19;
int86(0x21, &regs, &regs);
return((int)regs.h.al);
}
static int tests_current_dhandle(uint8 *dhandle)
{
uint8 connid = 0;
uint8 flags = 0;
int drive;
drive = tests_get_current_drive();
if (get_drive_info((uint8)drive, &connid, dhandle, &flags))
return(-1);
if (!connid || (flags & 0x80))
return(-1);
return(0);
}
static uint8 tests_map_ncp_mask(uint16 ncp_rights)
{
uint8 mask = 0;
if (ncp_rights & TEST_NCP_RIGHT_SUPER) mask |= TEST_RIGHT_S;
if (ncp_rights & TEST_NCP_RIGHT_READ) mask |= TEST_RIGHT_R;
if (ncp_rights & TEST_NCP_RIGHT_WRITE) mask |= TEST_RIGHT_W;
if (ncp_rights & TEST_NCP_RIGHT_CREATE) mask |= TEST_RIGHT_C;
if (ncp_rights & TEST_NCP_RIGHT_DELETE) mask |= TEST_RIGHT_E;
if (ncp_rights & TEST_NCP_RIGHT_MODIFY) mask |= TEST_RIGHT_M;
if (ncp_rights & TEST_NCP_RIGHT_SEARCH) mask |= TEST_RIGHT_F;
if (ncp_rights & TEST_NCP_RIGHT_OWNER) mask |= TEST_RIGHT_A;
return(mask);
}
static void tests_mask_string(uint8 mask, char *out)
{
out[0] = (mask & TEST_RIGHT_S) ? 'S' : '-';
out[1] = (mask & TEST_RIGHT_R) ? 'R' : '-';
out[2] = (mask & TEST_RIGHT_W) ? 'W' : '-';
out[3] = (mask & TEST_RIGHT_C) ? 'C' : '-';
out[4] = (mask & TEST_RIGHT_E) ? 'E' : '-';
out[5] = (mask & TEST_RIGHT_M) ? 'M' : '-';
out[6] = (mask & TEST_RIGHT_F) ? 'F' : '-';
out[7] = (mask & TEST_RIGHT_A) ? 'A' : '-';
out[8] = '\0';
}
static void tests_parent_path(char *dst, char *src, int max)
{
char tmp[260];
char *p;
tool_upcopy(tmp, src, sizeof(tmp));
p = strrchr(tmp, '\\');
if (!p) p = strrchr(tmp, ':');
if (!p) {
dst[0] = '\0';
return;
}
if (*p == ':') {
p++;
*p = '\0';
} else {
*p = '\0';
}
strmaxcpy(dst, tmp, max - 1);
}
static void tests_print_mask(char *label, int rc, uint8 mask)
{
char s[10];
if (rc) {
fprintf(stdout, "%-18.18s rc=%d\n", label, rc);
return;
}
tests_mask_string(mask, s);
fprintf(stdout, "%-18.18s [%s] %02X\n", label, s, mask);
}
static void tests_print_eff_header(char *title)
{
if (title && *title)
fprintf(stdout, "\n%s\n", title);
fprintf(stdout, "%-18s %5s %-10s %s\n", "call", "rc", "mask", "raw");
fprintf(stdout, "%-18s %5s %-10s %s\n", "------------------", "-----", "----------", "----");
}
static void tests_print_eff_row(char *label, int rc, uint8 mask,
uint16 raw, int has_raw)
{
char s[10];
if (rc) {
fprintf(stdout, "%-18.18s %5d %-10s %s\n", label, rc, "-", "-");
return;
}
tests_mask_string(mask, s);
if (has_raw)
fprintf(stdout, "%-18.18s %5d [%s] %04X\n", label, 0, s, raw);
else
fprintf(stdout, "%-18.18s %5d [%s] %s\n", label, 0, s, "-");
}
static int tests_hex_value(int ch)
{
if (ch >= '0' && ch <= '9') return(ch - '0');
if (ch >= 'a' && ch <= 'f') return(ch - 'a' + 10);
if (ch >= 'A' && ch <= 'F') return(ch - 'A' + 10);
return(-1);
}
static int tests_parse_irm_mask(char *s, uint8 *mask_out)
{
uint16 mask = 0;
int i;
int hex = 0;
if (!s || !*s || !mask_out)
return(-1);
if (tests_same_arg(s, "ALL")) {
*mask_out = 0xff;
return(0);
}
if (tests_same_arg(s, "NONE") || tests_same_arg(s, "N")) {
*mask_out = 0x00;
return(0);
}
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
if (!s[2])
return(-1);
for (i = 2; s[i]; i++) {
int v = tests_hex_value(s[i]);
if (v < 0)
return(-1);
mask = (uint16)((mask << 4) | v);
if (mask > 0xff)
return(-1);
}
*mask_out = (uint8)mask;
return(0);
}
/*
* Treat a plain one/two digit value as hexadecimal. That makes the DOS
* command natural for NetWare rights masks: "42" means R+F, not decimal 42.
*/
if (strlen(s) <= 2) {
hex = 1;
for (i = 0; s[i]; i++) {
if (s[i] < '0' || s[i] > '9') {
hex = 0;
break;
}
}
if (hex) {
for (i = 0; s[i]; i++)
mask = (uint16)((mask << 4) | tests_hex_value(s[i]));
*mask_out = (uint8)mask;
return(0);
}
}
for (i = 0; s[i]; i++) {
int ch = s[i];
if (ch >= 'a' && ch <= 'z') ch -= 32;
if (ch == ' ' || ch == ',' || ch == '+' || ch == '-' || ch == '/')
continue;
switch(ch) {
case 'S': mask |= TEST_RIGHT_S; break;
case 'R': mask |= TEST_RIGHT_R; break;
case 'W': mask |= TEST_RIGHT_W; break;
case 'C': mask |= TEST_RIGHT_C; break;
case 'E':
case 'D': mask |= TEST_RIGHT_E; break;
case 'M': mask |= TEST_RIGHT_M; break;
case 'F': mask |= TEST_RIGHT_F; break;
case 'A': mask |= TEST_RIGHT_A; break;
default: return(-1);
}
}
*mask_out = (uint8)mask;
return(0);
}
static int tests_parse_byte_arg(char *s, uint8 *value_out)
{
uint16 value = 0;
int i;
if (!s || !*s || !value_out)
return(-1);
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
if (!s[2])
return(-1);
for (i = 2; s[i]; i++) {
int v = tests_hex_value(s[i]);
if (v < 0)
return(-1);
value = (uint16)((value << 4) | v);
if (value > 0xff)
return(-1);
}
*value_out = (uint8)value;
return(0);
}
/* Plain values are hexadecimal, matching the existing rights-mask parser. */
for (i = 0; s[i]; i++) {
int v = tests_hex_value(s[i]);
if (v < 0)
return(-1);
value = (uint16)((value << 4) | v);
if (value > 0xff)
return(-1);
}
*value_out = (uint8)value;
return(0);
}
static int tests_parse_word_arg(char *s, uint16 *value_out)
{
uint32 value = 0;
int i;
if (!s || !*s || !value_out)
return(-1);
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
if (!s[2])
return(-1);
s += 2;
}
for (i = 0; s[i]; i++) {
int v = tests_hex_value(s[i]);
if (v < 0)
return(-1);
value = (value << 4) | (uint32)v;
if (value > 0xffffUL)
return(-1);
}
*value_out = (uint16)value;
return(0);
}
#define TEST_DM_ATTRIBUTES 0x00000002UL
#define TEST_DM_CREATE_DATE 0x00000004UL
#define TEST_DM_CREATE_TIME 0x00000008UL
#define TEST_DM_CREATOR_ID 0x00000010UL
#define TEST_DM_ARCHIVE_DATE 0x00000020UL
#define TEST_DM_ARCHIVE_TIME 0x00000040UL
#define TEST_DM_ARCHIVER_ID 0x00000080UL
#define TEST_DM_MODIFY_DATE 0x00000100UL
#define TEST_DM_MODIFY_TIME 0x00000200UL
#define TEST_DM_MODIFIER_ID 0x00000400UL
#define TEST_DM_LAST_ACCESS_DATE 0x00000800UL
#define TEST_DM_MAXIMUM_SPACE 0x00002000UL
static int tests_parse_dword_arg(char *s, uint32 *value_out)
{
uint32 value = 0;
int i;
if (!s || !*s || !value_out)
return(-1);
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
if (!s[2])
return(-1);
s += 2;
}
for (i = 0; s[i]; i++) {
int v = tests_hex_value(s[i]);
if (v < 0)
return(-1);
value = (value << 4) | (uint32)v;
}
*value_out = value;
return(0);
}
static int tests_copy_ncp22_name(uint8 *dst, char *src, uint8 *len_out)
{
char tmp[260];
char *p;
int len;
if (!dst || !len_out)
return(-1);
if (!src)
src = "";
tool_upcopy(tmp, src, sizeof(tmp));
if (strchr(tmp, '\\') || strchr(tmp, '/') || strchr(tmp, ':'))
return(-1);
src = tmp;
len = strlen(src);
if (len < 1 || len > 12)
return(-1);
memcpy(dst, src, len);
*len_out = (uint8)len;
return(0);
}
static int tests_read_attr_by_ncp87(uint8 dhandle, char *path, uint8 *attr_out)
{
uint32 attr = 0;
int rc;
if (attr_out)
*attr_out = 0;
rc = c32_ncp87_obtain_rim_attributes(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&attr,
NULL, NULL, NULL);
if (rc)
return(rc);
if (attr_out)
*attr_out = (uint8)(attr & 0xffUL);
return(0);
}
static int tests_ncp22_25_set_attr(uint8 dhandle, char *path, uint8 attr)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 change_bits[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 rest[104];
} req;
struct {
uint16 len;
} repl = { 0 };
memset(&req, 0, sizeof(req));
req.func = 0x25;
req.dirhandle = dhandle;
req.search_attributes = 0x06;
U32_TO_BE32(0xffffffffUL, req.searchsequence);
U32_TO_32(TEST_DM_ATTRIBUTES, req.change_bits);
U32_TO_32((uint32)attr, req.attributes);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
static int tests_ncp2225miss(char *path)
{
uint8 dhandle = 0;
int rc;
if (!path || !*path)
path = "NO22_25.$$$";
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP2225MISS failed: current drive is not a network drive\n");
return(1);
}
rc = tests_ncp22_25_set_attr(dhandle, path, 0x20);
fprintf(stdout, "NCP2225MISS %s neterrno=0x%02X\n", path, (unsigned)(-rc & 0xff));
if (rc != -0xff) {
fprintf(stdout, "NCP2225MISS failed: expected completion 0xFF for missing entry\n");
return(1);
}
return(0);
}
static int tests_ncp2225attr(char *path, char *attr_arg)
{
uint8 dhandle = 0;
uint8 before = 0;
uint8 target = 0;
uint8 after = 0;
uint8 restored = 0;
int rc;
int restore = 0;
if (!path || !*path) {
fprintf(stdout, "Usage: TESTS NCP2225ATTR file [attr] (file must be current-dir 8.3 name)\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP2225ATTR failed: current drive is not a network drive\n");
return(1);
}
rc = tests_read_attr_by_ncp87(dhandle, path, &before);
if (rc) {
fprintf(stdout, "NCP2225ATTR %s read-before rc=%d\n", path, rc);
return(rc);
}
if (attr_arg) {
if (tests_parse_byte_arg(attr_arg, &target)) {
fprintf(stdout, "Bad attr '%s'. Use e.g. 20, 21, 0x21.\n", attr_arg);
return(1);
}
} else {
target = (uint8)(before ^ 0x01); /* default: toggle read-only bit */
restore = 1;
}
fprintf(stdout, "NCP2225ATTR %s before=0x%02X target=0x%02X\n",
path, (unsigned)before, (unsigned)target);
rc = tests_ncp22_25_set_attr(dhandle, path, target);
fprintf(stdout, "ncp22/25 set attr rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = tests_read_attr_by_ncp87(dhandle, path, &after);
if (rc) {
fprintf(stdout, "NCP2225ATTR %s read-after rc=%d\n", path, rc);
return(rc);
}
fprintf(stdout, "NCP2225ATTR %s after=0x%02X\n", path, (unsigned)after);
if (after != target) {
fprintf(stdout, "NCP2225ATTR failed: expected attr=0x%02X\n", (unsigned)target);
return(1);
}
if (restore) {
rc = tests_ncp22_25_set_attr(dhandle, path, before);
fprintf(stdout, "ncp22/25 restore attr rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = tests_read_attr_by_ncp87(dhandle, path, &restored);
if (rc) {
fprintf(stdout, "NCP2225ATTR %s read-restore rc=%d\n", path, rc);
return(rc);
}
fprintf(stdout, "NCP2225ATTR %s restored=0x%02X\n", path, (unsigned)restored);
if (restored != before) {
fprintf(stdout, "NCP2225ATTR failed: expected restored attr=0x%02X\n",
(unsigned)before);
return(1);
}
}
return(0);
}
static int tests_ncp22_25_set_mtime(uint8 dhandle, char *path, uint16 dos_date, uint16 dos_time)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 change_bits[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 created_time[2];
uint8 created_date[2];
uint8 created_id[4];
uint8 archived_time[2];
uint8 archived_date[2];
uint8 archived_id[4];
uint8 updated_time[2];
uint8 updated_date[2];
uint8 updated_id[4];
uint8 rest[80];
} req;
struct {
uint16 len;
} repl = { 0 };
memset(&req, 0, sizeof(req));
req.func = 0x25;
req.dirhandle = dhandle;
req.search_attributes = 0x06;
U32_TO_BE32(0xffffffffUL, req.searchsequence);
U32_TO_32(TEST_DM_MODIFY_DATE | TEST_DM_MODIFY_TIME, req.change_bits);
U16_TO_16(dos_time, req.updated_time);
U16_TO_16(dos_date, req.updated_date);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
static int tests_ncp22_25_set_archive(uint8 dhandle, char *path,
uint16 dos_date, uint16 dos_time,
uint32 archiver_id)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 change_bits[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 created_time[2];
uint8 created_date[2];
uint8 created_id[4];
uint8 archived_time[2];
uint8 archived_date[2];
uint8 archived_id[4];
uint8 rest[88];
} req;
struct {
uint16 len;
} repl = { 0 };
memset(&req, 0, sizeof(req));
req.func = 0x25;
req.dirhandle = dhandle;
req.search_attributes = 0x06;
U32_TO_BE32(0xffffffffUL, req.searchsequence);
U32_TO_32(TEST_DM_ARCHIVE_DATE | TEST_DM_ARCHIVE_TIME | TEST_DM_ARCHIVER_ID,
req.change_bits);
U16_TO_16(dos_time, req.archived_time);
U16_TO_16(dos_date, req.archived_date);
U32_TO_BE32(archiver_id, req.archived_id);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
static int tests_ncp22_25_set_modifier(uint8 dhandle, char *path, uint32 modifier_id)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 change_bits[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 created_time[2];
uint8 created_date[2];
uint8 created_id[4];
uint8 archived_time[2];
uint8 archived_date[2];
uint8 archived_id[4];
uint8 updated_time[2];
uint8 updated_date[2];
uint8 updated_id[4];
uint8 rest[80];
} req;
struct {
uint16 len;
} repl = { 0 };
memset(&req, 0, sizeof(req));
req.func = 0x25;
req.dirhandle = dhandle;
req.search_attributes = 0x06;
U32_TO_BE32(0xffffffffUL, req.searchsequence);
U32_TO_32(TEST_DM_MODIFIER_ID, req.change_bits);
U32_TO_BE32(modifier_id, req.updated_id);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
static int tests_ncp22_25_set_create(uint8 dhandle, char *path,
uint16 dos_date, uint16 dos_time,
uint32 creator_id)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 change_bits[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 created_time[2];
uint8 created_date[2];
uint8 created_id[4];
uint8 rest[96];
} req;
struct {
uint16 len;
} repl = { 0 };
memset(&req, 0, sizeof(req));
req.func = 0x25;
req.dirhandle = dhandle;
req.search_attributes = 0x06;
U32_TO_BE32(0xffffffffUL, req.searchsequence);
U32_TO_32(TEST_DM_CREATE_DATE | TEST_DM_CREATE_TIME | TEST_DM_CREATOR_ID,
req.change_bits);
U16_TO_16(dos_time, req.created_time);
U16_TO_16(dos_date, req.created_date);
U32_TO_BE32(creator_id, req.created_id);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
static int tests_ncp22_25_set_adate(uint8 dhandle, char *path, uint16 dos_date)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 change_bits[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 created_time[2];
uint8 created_date[2];
uint8 created_id[4];
uint8 archived_time[2];
uint8 archived_date[2];
uint8 archived_id[4];
uint8 updated_time[2];
uint8 updated_date[2];
uint8 updated_id[4];
uint8 size[4];
uint8 reserved_1[44];
uint8 inherited_rights_mask[2];
uint8 last_access_date[2];
uint8 rest[28];
} req;
struct {
uint16 len;
} repl = { 0 };
memset(&req, 0, sizeof(req));
req.func = 0x25;
req.dirhandle = dhandle;
req.search_attributes = 0x06;
U32_TO_BE32(0xffffffffUL, req.searchsequence);
U32_TO_32(TEST_DM_LAST_ACCESS_DATE, req.change_bits);
U16_TO_16(dos_date, req.last_access_date);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
static int tests_ncp22_25_set_maxspace(uint8 dhandle, char *path, uint32 max_space)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 change_bits[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 created_time[2];
uint8 created_date[2];
uint8 created_id[4];
uint8 archived_time[2];
uint8 archived_date[2];
uint8 archived_id[4];
uint8 modify_time[2];
uint8 modify_date[2];
uint8 next_trustee[4];
uint8 reserved_1[48];
uint8 max_space[4];
uint8 inherited_rights_mask[2];
uint8 rest[26];
} req;
struct {
uint16 len;
} repl = { 0 };
memset(&req, 0, sizeof(req));
req.func = 0x25;
req.dirhandle = dhandle;
req.search_attributes = 0x10; /* directory */
U32_TO_BE32(0xffffffffUL, req.searchsequence);
U32_TO_32(TEST_DM_MAXIMUM_SPACE, req.change_bits);
U32_TO_32(0x10UL, req.attributes);
U32_TO_BE32(max_space, req.max_space);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
static void tests_print_dos_datetime(char *label, uint16 date, uint16 timev);
static uint16 tests_get_be16(uint8 *p)
{
return((uint16)(((uint16)p[0] << 8) | p[1]));
}
static uint16 tests_get_le16(uint8 *p)
{
return((uint16)(((uint16)p[1] << 8) | p[0]));
}
static uint32 tests_get_le32(uint8 *p)
{
return(((uint32)p[3] << 24) | ((uint32)p[2] << 16) |
((uint32)p[1] << 8) | (uint32)p[0]);
}
static int tests_ncp22_1e_read_dir_maxspace(uint8 dhandle, char *path, uint32 *max_space_out)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 namlen;
uint8 name[12];
} req;
struct {
uint16 len;
uint8 searchsequence[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 created_time[2];
uint8 created_date[2];
uint8 created_id[4];
uint8 archived_time[2];
uint8 archived_date[2];
uint8 archived_id[4];
uint8 modify_time[2];
uint8 modify_date[2];
uint8 next_trustee[4];
uint8 reserved_1[48];
uint8 max_space[4];
uint8 inherited_rights_mask[2];
uint8 rest[26];
} repl;
if (max_space_out)
*max_space_out = 0;
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
/*
* Net_Call expects repl.len to contain the available reply payload size.
* Leaving it zero makes DOS report rc=-51 even when mars_nwe returns a
* valid NCP22/1E directory entry.
*/
repl.len = sizeof(repl) - sizeof(repl.len);
req.func = 0x1e;
req.dirhandle = dhandle;
req.search_attributes = 0x10; /* directory */
U32_TO_BE32(0xffffffffUL, req.searchsequence);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
if (max_space_out)
*max_space_out = GET_BE32(repl.max_space);
return(0);
}
static int tests_ncp221einfo(char *path)
{
uint8 dhandle = 0;
char outname[14];
uint16 ctime_be, cdate_be;
uint16 atime_be, adate_be;
uint16 mtime_be, mdate_be;
uint16 ctime_le, cdate_le;
uint16 atime_le, adate_le;
uint16 mtime_le, mdate_le;
uint16 irm_be, irm_le;
uint32 attr_be, attr_le;
uint32 max_be, max_le;
uint32 cid_be, aid_be;
uint32 subdir_be;
int rc;
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 namlen;
uint8 name[12];
} req;
struct {
uint16 len;
uint8 searchsequence[4];
uint8 subdir[4];
uint8 attributes[4];
uint8 uniqueid;
uint8 flags;
uint8 namespace;
uint8 namlen;
uint8 name[12];
uint8 created_time[2];
uint8 created_date[2];
uint8 created_id[4];
uint8 archived_time[2];
uint8 archived_date[2];
uint8 archived_id[4];
uint8 modify_time[2];
uint8 modify_date[2];
uint8 next_trustee[4];
uint8 reserved_1[48];
uint8 max_space[4];
uint8 inherited_rights_mask[2];
uint8 rest[26];
} repl;
if (!path || !*path) {
fprintf(stdout, "Usage: TESTS NCP221EINFO dir\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP221EINFO failed: current drive is not a network drive\n");
return(1);
}
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
repl.len = sizeof(repl) - sizeof(repl.len);
req.func = 0x1e;
req.dirhandle = dhandle;
req.search_attributes = 0x10; /* directory */
U32_TO_BE32(0xffffffffUL, req.searchsequence);
if (tests_copy_ncp22_name(req.name, path, &req.namlen))
return(-1);
req.len = sizeof(req) - sizeof(req.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno) {
rc = -neterrno;
fprintf(stdout, "NCP221EINFO %s rc=%d neterrno=0x%02X\n",
path, rc, (unsigned)neterrno);
return(rc);
}
memset(outname, 0, sizeof(outname));
if (repl.namlen > 12)
repl.namlen = 12;
memcpy(outname, repl.name, repl.namlen);
attr_be = GET_BE32(repl.attributes);
attr_le = tests_get_le32(repl.attributes);
subdir_be = GET_BE32(repl.subdir);
max_be = GET_BE32(repl.max_space);
max_le = tests_get_le32(repl.max_space);
irm_be = tests_get_be16(repl.inherited_rights_mask);
irm_le = tests_get_le16(repl.inherited_rights_mask);
ctime_be = tests_get_be16(repl.created_time);
cdate_be = tests_get_be16(repl.created_date);
atime_be = tests_get_be16(repl.archived_time);
adate_be = tests_get_be16(repl.archived_date);
mtime_be = tests_get_be16(repl.modify_time);
mdate_be = tests_get_be16(repl.modify_date);
ctime_le = tests_get_le16(repl.created_time);
cdate_le = tests_get_le16(repl.created_date);
atime_le = tests_get_le16(repl.archived_time);
adate_le = tests_get_le16(repl.archived_date);
mtime_le = tests_get_le16(repl.modify_time);
mdate_le = tests_get_le16(repl.modify_date);
cid_be = GET_BE32(repl.created_id);
aid_be = GET_BE32(repl.archived_id);
fprintf(stdout, "NCP221EINFO %s OK name=%s len=%u repl_len=%u\n",
path, outname, (unsigned)repl.namlen, (unsigned)repl.len);
fprintf(stdout, "NCP221EINFO seq=0x%08lX subdir=0x%08lX unique=0x%02X flags=0x%02X ns=0x%02X\n",
(unsigned long)GET_BE32(repl.searchsequence),
(unsigned long)subdir_be,
(unsigned)repl.uniqueid, (unsigned)repl.flags, (unsigned)repl.namespace);
fprintf(stdout, "NCP221EINFO attr=0x%08lX (raw be=0x%08lX) max=0x%08lX (raw le=0x%08lX) irm=0x%04X (raw be=0x%04X)\n",
(unsigned long)attr_le, (unsigned long)attr_be,
(unsigned long)max_be, (unsigned long)max_le,
(unsigned)irm_le, (unsigned)irm_be);
fprintf(stdout, "NCP221EINFO ids create=0x%08lX archive=0x%08lX nextTrustee=0x%08lX\n",
(unsigned long)cid_be, (unsigned long)aid_be,
(unsigned long)GET_BE32(repl.next_trustee));
/*
* NCP22/1E uses mixed byte order in this old DOS layout:
* attributes, DOS date/time and IRM are little-endian;
* max_space and object IDs are big-endian in the mars_nwe reply.
*/
tests_print_dos_datetime("NCP221EINFO create", cdate_le, ctime_le);
tests_print_dos_datetime("NCP221EINFO archive", adate_le, atime_le);
tests_print_dos_datetime("NCP221EINFO modify ", mdate_le, mtime_le);
return(0);
}
static void tests_print_dos_datetime(char *label, uint16 date, uint16 timev)
{
unsigned year = ((date >> 9) & 0x7f) + 1980;
unsigned month = (date >> 5) & 0x0f;
unsigned day = date & 0x1f;
unsigned hour = (timev >> 11) & 0x1f;
unsigned minute = (timev >> 5) & 0x3f;
unsigned second = (timev & 0x1f) * 2;
fprintf(stdout, "%s %04u-%02u-%02u %02u:%02u:%02u date=0x%04X time=0x%04X\n",
label, year, month, day, hour, minute, second,
(unsigned)date, (unsigned)timev);
}
static void tests_print_dos_date(char *label, uint16 date)
{
unsigned year = ((date >> 9) & 0x7f) + 1980;
unsigned month = (date >> 5) & 0x0f;
unsigned day = date & 0x1f;
fprintf(stdout, "%s %04u-%02u-%02u date=0x%04X\n",
label, year, month, day, (unsigned)date);
}
static int tests_ncp2225time(char *path, char *date_arg, char *time_arg)
{
uint8 dhandle = 0;
C32_NDIR_INFO before;
C32_NDIR_INFO after;
C32_NDIR_INFO restored;
uint16 target_date = 0x5c99; /* 2026-05-25 */
uint16 target_time = 0xa320; /* 20:25:00 */
int rc;
int restore = 1;
if (!path || !*path) {
fprintf(stdout, "Usage: TESTS NCP2225TIME file [datehex timehex]\n");
return(1);
}
if ((date_arg && !time_arg) || (!date_arg && time_arg)) {
fprintf(stdout, "Usage: TESTS NCP2225TIME file [datehex timehex]\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP2225TIME failed: current drive is not a network drive\n");
return(1);
}
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&before,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225TIME %s read-before rc=%d\n", path, rc);
return(rc);
}
if (date_arg) {
if (tests_parse_word_arg(date_arg, &target_date) ||
tests_parse_word_arg(time_arg, &target_time)) {
fprintf(stdout, "Bad date/time. Use DOS hex words, e.g. 5C99 A320.\n");
return(1);
}
} else if (before.modify_date == target_date && before.modify_time == target_time) {
target_date = 0x5c9a; /* 2026-05-26 */
target_time = 0x6c00; /* 13:32:00 */
}
tests_print_dos_datetime("NCP2225TIME before", before.modify_date, before.modify_time);
tests_print_dos_datetime("NCP2225TIME target", target_date, target_time);
rc = tests_ncp22_25_set_mtime(dhandle, path, target_date, target_time);
fprintf(stdout, "ncp22/25 set time rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&after,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225TIME %s read-after rc=%d\n", path, rc);
return(rc);
}
tests_print_dos_datetime("NCP2225TIME after ", after.modify_date, after.modify_time);
if (after.modify_date != target_date || after.modify_time != target_time) {
fprintf(stdout, "NCP2225TIME failed: expected date=0x%04X time=0x%04X\n",
(unsigned)target_date, (unsigned)target_time);
return(1);
}
if (restore) {
rc = tests_ncp22_25_set_mtime(dhandle, path, before.modify_date, before.modify_time);
fprintf(stdout, "ncp22/25 restore time rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&restored,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225TIME %s read-restore rc=%d\n", path, rc);
return(rc);
}
tests_print_dos_datetime("NCP2225TIME restored", restored.modify_date, restored.modify_time);
if (restored.modify_date != before.modify_date || restored.modify_time != before.modify_time) {
fprintf(stdout, "NCP2225TIME failed: restore mismatch\n");
return(1);
}
}
return(0);
}
static int tests_ncp2225adate(char *path, char *date_arg)
{
uint8 dhandle = 0;
C32_NDIR_INFO before;
C32_NDIR_INFO after;
C32_NDIR_INFO restored;
uint16 target_date = 0x5c99; /* 2026-04-25 */
int rc;
int restore = 1;
if (!path || !*path) {
fprintf(stdout, "Usage: TESTS NCP2225ADATE file [datehex]\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP2225ADATE failed: current drive is not a network drive\n");
return(1);
}
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&before,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225ADATE %s read-before rc=%d\n", path, rc);
return(rc);
}
if (date_arg) {
if (tests_parse_word_arg(date_arg, &target_date)) {
fprintf(stdout, "Bad date. Use DOS hex word, e.g. 5C99.\n");
return(1);
}
} else if (before.last_access_date == target_date) {
target_date = 0x5c9a; /* 2026-04-26 */
}
tests_print_dos_date("NCP2225ADATE before", before.last_access_date);
tests_print_dos_date("NCP2225ADATE target", target_date);
tests_print_dos_datetime("NCP2225ADATE mtime-before", before.modify_date, before.modify_time);
rc = tests_ncp22_25_set_adate(dhandle, path, target_date);
fprintf(stdout, "ncp22/25 set adate rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&after,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225ADATE %s read-after rc=%d\n", path, rc);
return(rc);
}
tests_print_dos_date("NCP2225ADATE after ", after.last_access_date);
tests_print_dos_datetime("NCP2225ADATE mtime-after ", after.modify_date, after.modify_time);
if (after.last_access_date != target_date) {
fprintf(stdout, "NCP2225ADATE failed: expected date=0x%04X\n",
(unsigned)target_date);
return(1);
}
if (after.modify_date != before.modify_date || after.modify_time != before.modify_time) {
fprintf(stdout, "NCP2225ADATE failed: modify time changed unexpectedly\n");
return(1);
}
if (restore) {
rc = tests_ncp22_25_set_adate(dhandle, path, before.last_access_date);
fprintf(stdout, "ncp22/25 restore adate rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&restored,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225ADATE %s read-restore rc=%d\n", path, rc);
return(rc);
}
tests_print_dos_date("NCP2225ADATE restored", restored.last_access_date);
if (restored.last_access_date != before.last_access_date) {
fprintf(stdout, "NCP2225ADATE failed: restore mismatch\n");
return(1);
}
}
return(0);
}
static int tests_ncp2225maxspace(char *path, char *space_arg)
{
uint8 dhandle = 0;
uint32 before = 0;
uint32 target = 0x00100000UL; /* NetWare 4K blocks: 4 GiB */
int rc;
if (!path || !*path) {
fprintf(stdout, "Usage: TESTS NCP2225MAXSPACE dir [blockshex] (dir must be current-dir 8.3 name)\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP2225MAXSPACE failed: current drive is not a network drive\n");
return(1);
}
rc = tests_ncp22_1e_read_dir_maxspace(dhandle, path, &before);
if (rc) {
before = 0x40000000UL;
fprintf(stdout, "NCP2225MAXSPACE %s read-before rc=%d, using unlimited and continuing\n",
path, rc);
}
if (space_arg) {
if (tests_parse_dword_arg(space_arg, &target)) {
fprintf(stdout, "Bad maximum-space '%s'. Use hex blocks, e.g. 100000 or 0x100000.\n", space_arg);
return(1);
}
} else if (before == target) {
target = 0x00200000UL; /* 8 GiB */
}
fprintf(stdout, "NCP2225MAXSPACE %s before=0x%08lX target=0x%08lX\n",
path, (unsigned long)before, (unsigned long)target);
rc = tests_ncp22_25_set_maxspace(dhandle, path, target);
fprintf(stdout, "ncp22/25 set maxspace rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
/*
* The old NCP22/1E maximum-space readback layout is not reliable with all
* clients/servers; in particular it can return -51 although the NCP22/25
* set call reached the server and completed successfully. Keep this test
* focused on the write path. Verify the quota value through the server log
* or host tools such as repquota/setquota when needed.
*/
fprintf(stdout, "NCP2225MAXSPACE set-path OK; readback skipped\n");
return(0);
}
static int tests_ncp2225modid(char *path, char *id_arg)
{
uint8 dhandle = 0;
C32_NDIR_INFO before;
C32_NDIR_INFO after;
C32_NDIR_INFO restored;
uint32 target_id = 0x00010003UL;
int rc;
int restore = 1;
if (!path || !*path) {
fprintf(stdout, "Usage: TESTS NCP2225MODID file [idhex]\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP2225MODID failed: current drive is not a network drive\n");
return(1);
}
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&before,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225MODID %s read-before rc=%d\n", path, rc);
return(rc);
}
if (id_arg) {
target_id = strtoul(id_arg, NULL, 16);
} else if (before.modifier_id == target_id) {
target_id = 0x00000002UL;
}
fprintf(stdout, "NCP2225MODID before-id 0x%08lX\n", (unsigned long)before.modifier_id);
fprintf(stdout, "NCP2225MODID target-id 0x%08lX\n", (unsigned long)target_id);
rc = tests_ncp22_25_set_modifier(dhandle, path, target_id);
fprintf(stdout, "ncp22/25 set modid rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&after,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225MODID %s read-after rc=%d\n", path, rc);
return(rc);
}
fprintf(stdout, "NCP2225MODID after-id 0x%08lX\n", (unsigned long)after.modifier_id);
if (after.modifier_id != target_id) {
fprintf(stdout, "NCP2225MODID failed: modifier id mismatch\n");
return(1);
}
if (restore) {
rc = tests_ncp22_25_set_modifier(dhandle, path, before.modifier_id);
fprintf(stdout, "ncp22/25 restore modid rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&restored,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225MODID %s read-restore rc=%d\n", path, rc);
return(rc);
}
fprintf(stdout, "NCP2225MODID restored-id 0x%08lX\n", (unsigned long)restored.modifier_id);
if (restored.modifier_id != before.modifier_id) {
fprintf(stdout, "NCP2225MODID failed: restore mismatch\n");
return(1);
}
}
return(0);
}
static int tests_ncp2225create(char *path, char *date_arg, char *time_arg, char *id_arg)
{
uint8 dhandle = 0;
C32_NDIR_INFO before;
C32_NDIR_INFO after;
C32_NDIR_INFO restored;
uint16 target_date = 0x5c99; /* 2026-04-25 */
uint16 target_time = 0xa320; /* 20:25:00 */
uint32 target_id = 1;
int rc;
int restore = 1;
if (!path || !*path) {
fprintf(stdout, "Usage: TESTS NCP2225CREATE file [datehex timehex idhex]\n");
return(1);
}
if ((date_arg && (!time_arg || !id_arg)) || (!date_arg && (time_arg || id_arg))) {
fprintf(stdout, "Usage: TESTS NCP2225CREATE file [datehex timehex idhex]\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP2225CREATE failed: current drive is not a network drive\n");
return(1);
}
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&before,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225CREATE %s read-before rc=%d\n", path, rc);
return(rc);
}
if (date_arg) {
if (tests_parse_word_arg(date_arg, &target_date) ||
tests_parse_word_arg(time_arg, &target_time)) {
fprintf(stdout, "Bad create date/time. Use DOS hex words, e.g. 5C99 A320 00000001.\n");
return(1);
}
target_id = strtoul(id_arg, NULL, 16);
} else if (before.creation_date == target_date && before.creation_time == target_time &&
before.creator_id == target_id) {
target_date = 0x5c9a;
target_time = 0x6c00;
target_id = 2;
}
tests_print_dos_datetime("NCP2225CREATE before", before.creation_date, before.creation_time);
fprintf(stdout, "NCP2225CREATE before-id 0x%08lX\n", (unsigned long)before.creator_id);
tests_print_dos_datetime("NCP2225CREATE target", target_date, target_time);
fprintf(stdout, "NCP2225CREATE target-id 0x%08lX\n", (unsigned long)target_id);
rc = tests_ncp22_25_set_create(dhandle, path, target_date, target_time, target_id);
fprintf(stdout, "ncp22/25 set create rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&after,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225CREATE %s read-after rc=%d\n", path, rc);
return(rc);
}
tests_print_dos_datetime("NCP2225CREATE after ", after.creation_date, after.creation_time);
fprintf(stdout, "NCP2225CREATE after-id 0x%08lX\n", (unsigned long)after.creator_id);
if (after.creation_date != target_date || after.creation_time != target_time ||
after.creator_id != target_id) {
fprintf(stdout, "NCP2225CREATE failed: create metadata mismatch\n");
return(1);
}
if (restore) {
rc = tests_ncp22_25_set_create(dhandle, path, before.creation_date,
before.creation_time, before.creator_id);
fprintf(stdout, "ncp22/25 restore create rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&restored,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225CREATE %s read-restore rc=%d\n", path, rc);
return(rc);
}
tests_print_dos_datetime("NCP2225CREATE restored", restored.creation_date, restored.creation_time);
fprintf(stdout, "NCP2225CREATE restored-id 0x%08lX\n", (unsigned long)restored.creator_id);
if (restored.creation_date != before.creation_date ||
restored.creation_time != before.creation_time ||
restored.creator_id != before.creator_id) {
fprintf(stdout, "NCP2225CREATE failed: restore mismatch\n");
return(1);
}
}
return(0);
}
static int tests_ncp2225arch(char *path, char *date_arg, char *time_arg, char *id_arg)
{
uint8 dhandle = 0;
C32_NDIR_INFO before;
C32_NDIR_INFO after;
C32_NDIR_INFO restored;
uint16 target_date = 0x5c99; /* 2026-04-25 */
uint16 target_time = 0xa320; /* 20:25:00 */
uint32 target_id = 1;
int rc;
int restore = 1;
if (!path || !*path) {
fprintf(stdout, "Usage: TESTS NCP2225ARCH file [datehex timehex idhex]\n");
return(1);
}
if ((date_arg && (!time_arg || !id_arg)) || (!date_arg && (time_arg || id_arg))) {
fprintf(stdout, "Usage: TESTS NCP2225ARCH file [datehex timehex idhex]\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP2225ARCH failed: current drive is not a network drive\n");
return(1);
}
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&before,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225ARCH %s read-before rc=%d\n", path, rc);
return(rc);
}
if (date_arg) {
if (tests_parse_word_arg(date_arg, &target_date) ||
tests_parse_word_arg(time_arg, &target_time)) {
fprintf(stdout, "Bad archive date/time. Use DOS hex words, e.g. 5C99 A320 00000001.\n");
return(1);
}
target_id = strtoul(id_arg, NULL, 16);
} else if (before.archive_date == target_date && before.archive_time == target_time &&
before.archiver_id == target_id) {
target_date = 0x5c9a;
target_time = 0x6c00;
target_id = 2;
}
tests_print_dos_datetime("NCP2225ARCH before", before.archive_date, before.archive_time);
fprintf(stdout, "NCP2225ARCH before-id 0x%08lX\n", (unsigned long)before.archiver_id);
tests_print_dos_datetime("NCP2225ARCH target", target_date, target_time);
fprintf(stdout, "NCP2225ARCH target-id 0x%08lX\n", (unsigned long)target_id);
rc = tests_ncp22_25_set_archive(dhandle, path, target_date, target_time, target_id);
fprintf(stdout, "ncp22/25 set archive rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&after,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225ARCH %s read-after rc=%d\n", path, rc);
return(rc);
}
tests_print_dos_datetime("NCP2225ARCH after ", after.archive_date, after.archive_time);
fprintf(stdout, "NCP2225ARCH after-id 0x%08lX\n", (unsigned long)after.archiver_id);
if (after.archive_date != target_date || after.archive_time != target_time ||
after.archiver_id != target_id) {
fprintf(stdout, "NCP2225ARCH failed: archive metadata mismatch\n");
return(1);
}
if (restore) {
rc = tests_ncp22_25_set_archive(dhandle, path, before.archive_date,
before.archive_time, before.archiver_id);
fprintf(stdout, "ncp22/25 restore archive rc=%d neterrno=0x%02X\n",
rc, (unsigned)(rc < 0 ? -rc : 0));
if (rc)
return(rc);
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&restored,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP2225ARCH %s read-restore rc=%d\n", path, rc);
return(rc);
}
tests_print_dos_datetime("NCP2225ARCH restored", restored.archive_date, restored.archive_time);
fprintf(stdout, "NCP2225ARCH restored-id 0x%08lX\n", (unsigned long)restored.archiver_id);
}
return(0);
}
static int tests_ncp22_4_modify_irm(uint8 dhandle, char *path, uint8 mask)
{
uint8 plen;
if (!path)
path = "";
plen = (uint8)strlen(path);
/*
* NCP 22 / subfunction 4:
* byte subfunction = 0x04
* byte dir handle
* byte grant rights mask
* byte revoke rights mask
* byte path length
* path bytes
*
* The operation is modify-style, not absolute-set-style:
* new_mask = (old_mask | grant_mask) & ~revoke_mask
* To make this TESTS command set an exact target mask, grant the bits
* in the target and revoke all bits outside the target.
* No reply payload is expected.
*/
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 grant;
uint8 revoke;
uint8 pathlen;
uint8 path[256];
} req;
struct {
uint16 len;
} repl = { 0 };
req.func = 0x04;
req.dirhandle = dhandle;
req.grant = mask;
req.revoke = (uint8)(~mask & 0xff);
req.pathlen = plen;
req.len = 5 + plen;
strmaxcpy(req.path, path, plen);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
}
static int tests_put_ncp22_component_path(uint8 *dst, int max, char *path,
uint8 *count_out)
{
int pos = 0;
int count = 0;
char *p;
if (!dst || max < 1 || !count_out)
return(-1);
if (!path)
path = "";
p = strchr(path, ':');
if (p)
path = p + 1;
while (*path == '\\' || *path == '/')
path++;
while (*path) {
char *start = path;
int len;
while (*path && *path != '\\' && *path != '/')
path++;
len = (int)(path - start);
if (len > 0) {
if (len > 255 || pos + 1 + len > max || count >= 255)
return(-1);
dst[pos++] = (uint8)len;
memcpy(dst + pos, start, len);
pos += len;
count++;
}
while (*path == '\\' || *path == '/')
path++;
}
*count_out = (uint8)count;
return(pos);
}
static int tests_ncp22_2e_rename(uint8 dhandle, char *src, char *dst)
{
uint8 srcbuf[260];
uint8 dstbuf[260];
uint8 srccount = 0;
uint8 dstcount = 0;
int srclen;
int dstlen;
srclen = tests_put_ncp22_component_path(srcbuf, sizeof(srcbuf), src, &srccount);
dstlen = tests_put_ncp22_component_path(dstbuf, sizeof(dstbuf), dst, &dstcount);
if (srclen < 0 || dstlen < 0)
return(-1);
{
struct {
uint16 len;
uint8 func;
uint8 source_dhandle;
uint8 search_attributes;
uint8 source_component_count;
uint8 data[520];
} req;
struct {
uint16 len;
} repl = { 0 };
int pos = 0;
memset(&req, 0, sizeof(req));
req.func = 0x2e;
req.source_dhandle = dhandle;
req.search_attributes = 0x06;
req.source_component_count = srccount;
memcpy(req.data + pos, srcbuf, srclen);
pos += srclen;
req.data[pos++] = dhandle;
req.data[pos++] = dstcount;
memcpy(req.data + pos, dstbuf, dstlen);
pos += dstlen;
req.len = (uint16)(4 + pos);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
}
static int tests_ncp22ren(char *src, char *dst)
{
uint8 dhandle = 0;
int rc;
if (!src || !dst) {
fprintf(stdout, "Usage: TESTS NCP22REN oldpath newpath\n");
return(1);
}
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP22REN failed: current drive is not a network drive\n");
return(1);
}
fprintf(stdout, "NCP22/2E Rename Or Move old: %s -> %s\n", src, dst);
fprintf(stdout, "Current dir handle: %u\n", (unsigned)dhandle);
rc = tests_ncp22_2e_rename(dhandle, src, dst);
fprintf(stdout, "ncp22/2e rc=%d\n", rc);
return(rc ? rc : 0);
}
static void tests_join_child(char *out, int outlen, char *parent, char *child)
{
int len;
if (!parent || !*parent || tests_same_arg(parent, ".")) {
strmaxcpy(out, child, outlen - 1);
return;
}
strmaxcpy(out, parent, outlen - 1);
len = strlen(out);
if (len > 0 && out[len - 1] != '\\' && out[len - 1] != ':' && len < outlen - 1) {
out[len++] = '\\';
out[len] = '\0';
}
strmaxcpy(out + len, child, outlen - len - 1);
}
static int tests_ncp22rendir(char *parent)
{
uint8 dhandle = 0;
char src[260];
char dst[260];
int rc;
if (!parent || !*parent)
parent = ".";
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP22RENDIR failed: current drive is not a network drive\n");
return(1);
}
tests_join_child(src, sizeof(src), parent, "TREN");
tests_join_child(dst, sizeof(dst), parent, "TREN2");
fprintf(stdout, "NCP22/2E Directory Rename test: %s -> %s\n", src, dst);
fprintf(stdout, "Current dir handle: %u\n", (unsigned)dhandle);
fprintf(stdout, "Create the source directory first, e.g. MD %s\n", src);
rc = tests_ncp22_2e_rename(dhandle, src, dst);
fprintf(stdout, "ncp22/2e dir rc=%d\n", rc);
/* return(rc ? rc : 0); */
return(0);
}
static int tests_read_irm_by_ncp87(uint8 dhandle, char *path, uint8 *mask_out)
{
C32_NDIR_INFO info;
int rc;
if (mask_out)
*mask_out = 0;
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&info,
NULL, NULL, NULL);
if (rc)
return(rc);
if (mask_out)
*mask_out = (uint8)info.inherited_rights;
return(0);
}
static int tests_ncp22s4(char *path, char *mask_arg)
{
uint8 dhandle = 0;
uint8 before = 0;
uint8 after = 0;
uint8 mask = 0;
int rc;
if (!path)
path = ".";
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP22S4 failed: current drive is not a network drive\n");
return(1);
}
fprintf(stdout, "NCP22/4 Modify Maximum/Inherit Rights Mask for %s\n", path);
fprintf(stdout, "Current dir handle: %u\n", (unsigned)dhandle);
rc = tests_read_irm_by_ncp87(dhandle, path, &before);
tests_print_mask("before ncp87 irm", rc, before);
if (!mask_arg)
return(rc ? rc : 0);
if (tests_parse_irm_mask(mask_arg, &mask)) {
fprintf(stdout, "Bad mask '%s'. Use e.g. RF, 42, 0x42, ALL, NONE, SRWCEMFA.\\n",
mask_arg);
return(1);
}
tests_print_mask("target mask", 0, mask);
rc = tests_ncp22_4_modify_irm(dhandle, path, mask);
if (rc) {
fprintf(stdout, "ncp22/4 rc=%d\\n", rc);
return(rc);
}
fprintf(stdout, "ncp22/4 rc=0\\n");
rc = tests_read_irm_by_ncp87(dhandle, path, &after);
tests_print_mask("after ncp87 irm", rc, after);
return(rc ? rc : 0);
}
static int tests_ncp87c32attr(void)
{
uint8 dhandle = 0;
uint32 attr = 0;
uint16 actual = 0;
uint16 handle_lo = 0;
uint16 handle_hi = 0;
int rc;
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP87C32ATTR failed: current drive is not a network drive\n");
return(1);
}
rc = c32_ncp87_obtain_rim_attributes("LOGIN.EXE",
(uint16)dhandle,
&attr,
&actual,
&handle_lo,
&handle_hi);
if (rc) {
fprintf(stdout, "NCP87C32ATTR failed rc=%d\n", rc);
return(rc);
}
fprintf(stdout, "NCP87C32ATTR LOGIN.EXE attr=%02lX handle=%04X:%04X actual=%04X\n",
attr & 0xffUL, handle_hi, handle_lo, actual);
return(0);
}
static int tests_ncp87c32auto(void)
{
/*
* Kept as a compatibility alias for the former verbose test command.
* The production helper path is exercised by NCP87C32ATTR.
*/
return tests_ncp87c32attr();
}
static void tests_print_reply_bytes(uint8 *data, int len)
{
int i;
int n = len;
if (n < 0) n = 0;
if (n > 16) n = 16;
for (i = 0; i < n; i++)
fprintf(stdout, "%02X", (unsigned)data[i]);
}
static void tests_print_ncp23_probe_row(char *label, int rc,
uint16 repl_len, uint8 *repl_data)
{
fprintf(stdout, "%-16.16s %5d %5u ", label, rc, (unsigned)repl_len);
if (!rc)
tests_print_reply_bytes(repl_data, repl_len);
else
fprintf(stdout, "-");
fprintf(stdout, "\n");
}
static void tests_print_ncp23_path(uint8 *data, int len)
{
int pos = 0;
int first = 1;
while (pos < len) {
int l = data[pos++];
int i;
if (l < 0 || pos + l > len) {
fprintf(stdout, " <bad-component>");
return;
}
if (!first) fprintf(stdout, "\\");
for (i = 0; i < l; i++)
fputc(data[pos+i], stdout);
pos += l;
first = 0;
}
}
static uint16 tests_ncp23_path_reply_len(uint8 *data, uint16 maxlen)
{
uint16 pos = 0;
while (pos < maxlen) {
uint8 l = data[pos];
/*
* The DOS Net_Call reply buffer length is the buffer capacity here, not
* the actual NCP payload length. mars_nwe pads the unused part with
* zeroes, while a valid F3 path component cannot have length zero.
*/
if (!l)
break;
pos++;
if ((uint16)(pos + l) > maxlen)
return(maxlen);
pos = (uint16)(pos + l);
}
return(pos);
}
static int tests_ncp23_f4(uint8 dhandle, char *path, uint16 *repl_len,
uint8 *repl_data, int repl_max)
{
uint8 plen;
struct {
uint16 len;
uint8 func;
uint8 dhandle;
uint8 pathlen;
uint8 path[255];
} req;
struct {
uint16 len;
uint8 data[300];
} repl;
if (!path)
path = "";
plen = (uint8)strlen(path);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
repl.len = sizeof(repl.data);
req.func = 0xf4;
req.dhandle = dhandle;
req.pathlen = plen;
memcpy(req.path, path, plen);
req.len = 3 + plen;
neterrno = Net_Call(0xE300, &req, &repl);
if (repl_len)
*repl_len = repl.len;
if (repl_data && repl_max > 0) {
int copy = repl.len;
if (copy > repl_max) copy = repl_max;
memcpy(repl_data, repl.data, copy);
}
if (neterrno)
return(-neterrno);
return(0);
}
static int tests_ncp23_f3(uint8 volume, uint32 dirnum, uint8 namespace,
uint16 *repl_len, uint8 *repl_data, int repl_max)
{
struct {
uint16 len;
uint8 func;
uint8 volume;
uint8 dirnum[4];
uint8 namespace;
} req;
struct {
uint16 len;
uint8 data[300];
} repl;
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
repl.len = sizeof(repl.data);
req.func = 0xf3;
req.volume = volume;
U32_TO_32(dirnum, req.dirnum);
req.namespace = namespace;
req.len = 7;
neterrno = Net_Call(0xE300, &req, &repl);
if (repl_len)
*repl_len = repl.len;
if (repl_data && repl_max > 0) {
int copy = repl.len;
if (copy > repl_max) copy = repl_max;
memcpy(repl_data, repl.data, copy);
}
if (neterrno)
return(-neterrno);
return(0);
}
static int tests_ncp22_1a(uint8 volume, uint16 dirnum,
uint16 *repl_len, uint8 *repl_data, int repl_max)
{
struct {
uint16 len;
uint8 func;
uint8 volume;
uint8 dirnum[2];
} req;
struct {
uint16 len;
uint8 data[260];
} repl;
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
repl.len = sizeof(repl.data);
req.func = 0x1a;
req.volume = volume;
U16_TO_BE16(dirnum, req.dirnum);
req.len = 4;
neterrno = Net_Call(0xE200, &req, &repl);
if (repl_len)
*repl_len = repl.len;
if (repl_data && repl_max > 0) {
int copy = repl.len;
if (copy > repl_max) copy = repl_max;
memcpy(repl_data, repl.data, copy);
}
if (neterrno)
return(-neterrno);
return(0);
}
static uint16 tests_ncp22_1a_reply_len(uint8 *data, uint16 maxlen)
{
if (!data || maxlen < 1)
return(0);
if ((uint16)(data[0] + 1) > maxlen)
return(maxlen);
return((uint16)(data[0] + 1));
}
static void tests_print_ncp22_1a_path(uint8 *data, uint16 len)
{
int i;
int plen;
if (!data || len < 1)
return;
plen = data[0];
if (plen + 1 > len)
plen = len - 1;
for (i = 0; i < plen; i++)
fputc(data[1 + i], stdout);
}
static int tests_ncp23map(char *path)
{
uint8 dhandle = 0;
C32_NDIR_INFO info;
int rc;
uint16 repl_len;
uint8 repl_data[300];
int i;
int num_count;
uint32 f4_dirnum = 0;
uint8 f4_volume = 0;
int f4_ok = 0;
uint32 nums[3];
char *names[3];
if (!path)
path = ".";
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "NCP23MAP failed: current drive is not a network drive\n");
return(1);
}
memset(&info, 0, sizeof(info));
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&info,
NULL, NULL, NULL);
fprintf(stdout, "NCP23/F4+F3 layout probes for %s\n", path);
fprintf(stdout, "Current dir handle: %u\n", (unsigned)dhandle);
if (rc) {
fprintf(stdout, "NCP87 info rc=%d; F3 probes will use zero dir numbers\n", rc);
info.vol_number = 0;
info.dir_ent_num = 0;
info.dos_dir_num = 0;
} else {
fprintf(stdout, "NCP87 info vol=%lu dirEnt=%lu dosDir=%lu\n",
(unsigned long)info.vol_number,
(unsigned long)info.dir_ent_num,
(unsigned long)info.dos_dir_num);
}
fprintf(stdout, "\nNCP23 map calls\n");
fprintf(stdout, "%-12s %5s %5s %-12s %s\n", "call", "rc", "len", "reply-hex", "decoded");
fprintf(stdout, "%-12s %5s %5s %-12s %s\n", "------------", "-----", "-----", "------------", "----------------");
memset(repl_data, 0, sizeof(repl_data));
repl_len = 0;
rc = tests_ncp23_f4(dhandle, path, &repl_len, repl_data, sizeof(repl_data));
fprintf(stdout, "%-12s %5d %5u ", "f4 path", rc,
(!rc && repl_len >= 5) ? 5U : (unsigned)repl_len);
if (!rc && repl_len >= 5) {
f4_ok = 1;
f4_volume = repl_data[0];
f4_dirnum = GET_32(repl_data + 1);
tests_print_reply_bytes(repl_data, 5);
fprintf(stdout, " vol=%u dir=%lu", (unsigned)f4_volume, (unsigned long)f4_dirnum);
} else {
fprintf(stdout, "- ");
}
fprintf(stdout, "\n");
if (f4_ok && f4_dirnum <= 0xffffUL) {
memset(repl_data, 0, sizeof(repl_data));
repl_len = 0;
rc = tests_ncp22_1a(f4_volume, (uint16)f4_dirnum,
&repl_len, repl_data, sizeof(repl_data));
if (!rc)
repl_len = tests_ncp22_1a_reply_len(repl_data, repl_len);
fprintf(stdout, "%-12s %5d %5u ", "22/1A f4", rc, (unsigned)repl_len);
if (!rc) {
tests_print_reply_bytes(repl_data, repl_len);
fprintf(stdout, " ");
tests_print_ncp22_1a_path(repl_data, repl_len);
} else {
fprintf(stdout, "- ");
}
fprintf(stdout, "\n");
}
num_count = 0;
if (f4_ok) {
nums[num_count] = f4_dirnum;
names[num_count++] = "f3 f4dir";
}
nums[num_count] = info.dir_ent_num;
names[num_count++] = "f3 dirEnt";
nums[num_count] = info.dos_dir_num;
names[num_count++] = "f3 dosDir";
for (i = (f4_ok ? 1 : 0); i < num_count; i++) {
if (nums[i] > 0xffffUL)
continue;
memset(repl_data, 0, sizeof(repl_data));
repl_len = 0;
rc = tests_ncp22_1a((uint8)info.vol_number, (uint16)nums[i],
&repl_len, repl_data, sizeof(repl_data));
if (!rc)
repl_len = tests_ncp22_1a_reply_len(repl_data, repl_len);
fprintf(stdout, "%-12s %5d %5u ",
(i == (f4_ok ? 1 : 0)) ? "22/1A dir" : "22/1A dos",
rc, (unsigned)repl_len);
if (!rc) {
tests_print_reply_bytes(repl_data, repl_len);
fprintf(stdout, " ");
tests_print_ncp22_1a_path(repl_data, repl_len);
} else {
fprintf(stdout, "- ");
}
fprintf(stdout, "\n");
}
for (i = 0; i < num_count; i++) {
memset(repl_data, 0, sizeof(repl_data));
repl_len = 0;
rc = tests_ncp23_f3((uint8)info.vol_number, nums[i], 0,
&repl_len, repl_data, sizeof(repl_data));
if (!rc)
repl_len = tests_ncp23_path_reply_len(repl_data, repl_len);
fprintf(stdout, "%-12s %5d %5u ", names[i], rc, (unsigned)repl_len);
if (!rc) {
tests_print_reply_bytes(repl_data, repl_len);
fprintf(stdout, " ");
tests_print_ncp23_path(repl_data, repl_len);
} else {
fprintf(stdout, "- ");
}
fprintf(stdout, "\n");
}
fprintf(stdout, "\nExpected F4 reply is: volume byte + directory number dword (LO-HI).\n");
fprintf(stdout, "Expected F3 reply is: length-prefixed path components without volume.\n");
fprintf(stdout, "Expected 22/1A reply is: length byte + DOS path string.\n");
return(0);
}
static int tests_ncp22_eff_variant(int subfn, int variant,
uint8 dhandle, char *path,
uint8 *rights_out)
{
uint8 plen;
int subdir = 1;
if (!path)
path = "";
plen = (uint8)strlen(path);
if (rights_out)
*rights_out = 0;
/*
* Exploratory old NCP22 directory service calls.
*
* subfn 3 = Get Effective Directory Rights
* subfn 42 = Get Effective Rights (SDK symbol NWNCP22S42...)
*
* The exact DOS requester wrapper layout is not documented in our tree, so
* TESTS tries a few known 2.x/3.x style request shapes and reports which
* one succeeds. We do not use these in production until the matching
* Novell RIGHTS shape is identified.
*/
if (variant == 0) {
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 pathlen;
uint8 path[256];
} req;
struct {
uint16 len;
uint8 rights;
} repl = { sizeof(repl) - sizeof(uint16) };
req.func = (uint8)subfn;
req.dirhandle = dhandle;
req.pathlen = plen;
req.len = 3 + plen;
strmaxcpy(req.path, path, plen);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
if (rights_out)
*rights_out = repl.rights;
return(0);
}
if (variant == 1) {
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 sub_dir[2];
uint8 pathlen;
uint8 path[256];
} req;
struct {
uint16 len;
uint8 rights;
} repl = { sizeof(repl) - sizeof(uint16) };
req.func = (uint8)subfn;
req.dirhandle = dhandle;
U16_TO_BE16(subdir, req.sub_dir);
req.pathlen = plen;
req.len = 5 + plen;
strmaxcpy(req.path, path, plen);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
if (rights_out)
*rights_out = repl.rights;
return(0);
}
if (variant == 2) {
struct {
uint16 len;
uint8 func;
uint8 pathlen;
uint8 path[256];
} req;
struct {
uint16 len;
uint8 rights;
} repl = { sizeof(repl) - sizeof(uint16) };
req.func = (uint8)subfn;
req.pathlen = plen;
req.len = 2 + plen;
strmaxcpy(req.path, path, plen);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
if (rights_out)
*rights_out = repl.rights;
return(0);
}
return(-999);
}
static int tests_ncp22_50_obj_eff(uint32 object_id, uint8 dhandle,
char *path, uint16 *rights_out)
{
uint8 plen;
if (!path)
path = "";
plen = (uint8)strlen(path);
if (rights_out)
*rights_out = 0;
/*
* Client32/NCPWIN32 exports this as:
* NWNCP22s50GetObjEffectRights
*
* Disassembly of NCPWIN32.DLL shows request shape:
* word len = 7 + pathlen
* byte subfunction = 0x32
* dword object id, big endian
* byte dir handle
* byte path length
* path bytes
*
* Reply is a little-endian word effective rights mask.
*/
{
struct {
uint16 len;
uint8 func;
uint8 object_id[4];
uint8 dirhandle;
uint8 pathlen;
uint8 path[256];
} req;
struct {
uint16 len;
uint8 rights[2];
} repl = { sizeof(repl) - sizeof(uint16) };
req.func = 0x32;
U32_TO_BE32(object_id, req.object_id);
req.dirhandle = dhandle;
req.pathlen = plen;
req.len = 7 + plen;
strmaxcpy(req.path, path, plen);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
if (rights_out)
*rights_out = (uint16)(repl.rights[0] | ((uint16)repl.rights[1] << 8));
return(0);
}
}
static void tests_print_ncp22_obj50_row(char *label, uint32 object_id,
uint8 dhandle, char *path)
{
uint16 rights = 0;
uint8 mask = 0;
int rc;
rc = tests_ncp22_50_obj_eff(object_id, dhandle, path, &rights);
if (!rc)
mask = tests_map_ncp_mask(rights);
tests_print_eff_row(label, rc, mask, rights, !rc);
}
static void tests_print_ncp22_row(char *label, int subfn, int variant,
uint8 dhandle, char *path)
{
uint8 rights = 0;
int rc;
rc = tests_ncp22_eff_variant(subfn, variant, dhandle, path, &rights);
tests_print_eff_row(label, rc, rights, rights, !rc);
}
static int tests_effright(char *path)
{
uint8 dhandle = 0;
uint8 eff_old = 0;
uint8 mask = 0;
char usepath[260];
int newhandle;
uint16 ncp_rights = 0;
C32_NDIR_INFO info;
uint32 my_obj_id = 0;
uint16 my_obj_type = 0;
uint8 my_obj_name[48];
int rc;
if (!path)
path = ".";
if (tests_current_dhandle(&dhandle)) {
fprintf(stdout, "EFFRIGHT failed: current drive is not a network drive\n");
return(1);
}
fprintf(stdout, "EFFRIGHT diagnostics for %s\n", path);
fprintf(stdout, "Current dir handle: %u\n", (unsigned)dhandle);
{
int access_level;
access_level = ncp_14_46(&my_obj_id);
my_obj_name[0] = '\0';
if (access_level >= 0 && my_obj_id) {
ncp_17_36(my_obj_id, my_obj_name, &my_obj_type);
fprintf(stdout, "Current object: %08lX type=%04X access=%02X name=%s\n\n",
(unsigned long)my_obj_id, my_obj_type,
(unsigned)access_level, my_obj_name);
} else {
fprintf(stdout, "Current object: unknown rc=%d\n\n", access_level);
}
}
rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&info,
NULL, NULL, NULL);
if (rc) {
fprintf(stdout, "NCP87 info rc=%d\n\n", rc);
} else {
tests_mask_string((uint8)info.inherited_rights, usepath);
fprintf(stdout, "NCP87 info vol=%lu dirEnt=%lu dosDir=%lu inh=[%s] %04X\n\n",
(unsigned long)info.vol_number,
(unsigned long)info.dir_ent_num,
(unsigned long)info.dos_dir_num,
usepath,
info.inherited_rights);
}
tests_print_eff_header("Effective rights matrix:");
rc = c32_ncp87_get_effective_rights(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&ncp_rights,
NULL, NULL, NULL);
if (!rc)
mask = tests_map_ncp_mask(ncp_rights);
tests_print_eff_row("ncp87 path", rc, mask, ncp_rights, !rc);
if (!c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&info,
NULL, NULL, NULL)) {
rc = c32_ncp87_get_effective_rights_by_dirent((uint8)info.vol_number,
info.dos_dir_num,
&ncp_rights,
NULL, NULL, NULL);
if (!rc)
mask = tests_map_ncp_mask(ncp_rights);
tests_print_eff_row("ncp87 dosDir", rc, mask, ncp_rights, !rc);
rc = c32_ncp87_get_effective_rights_by_dirent((uint8)info.vol_number,
info.dir_ent_num,
&ncp_rights,
NULL, NULL, NULL);
if (!rc)
mask = tests_map_ncp_mask(ncp_rights);
tests_print_eff_row("ncp87 dirEnt", rc, mask, ncp_rights, !rc);
}
/* Old handle path used by our earlier RIGHTS fallback. */
tool_upcopy(usepath, path, sizeof(usepath));
newhandle = alloc_temp_dir_handle(dhandle, usepath, 0, &eff_old);
if (newhandle >= 0) {
dealloc_dir_handle(newhandle);
tests_print_eff_row("old handle path", 0, eff_old, eff_old, 1);
} else {
tests_print_eff_row("old handle path", newhandle, 0, 0, 0);
}
tests_parent_path(usepath, path, sizeof(usepath));
newhandle = alloc_temp_dir_handle(dhandle, usepath, 0, &eff_old);
if (newhandle >= 0) {
dealloc_dir_handle(newhandle);
tests_print_eff_row("old handle parent", 0, eff_old, eff_old, 1);
} else {
tests_print_eff_row("old handle parent", newhandle, 0, 0, 0);
}
if (usepath[0]) {
int subdir = 1;
int r = ncp_16_02(dhandle, (uint8 *)usepath, &subdir,
NULL, NULL, NULL);
if (r >= 0)
tests_print_eff_row("ncp16_02 parent", 0, (uint8)r, (uint16)r, 1);
else
tests_print_eff_row("ncp16_02 parent", r, 0, 0, 0);
}
tests_print_ncp22_row("ncp22/3 v0", 3, 0, dhandle, path);
tests_print_ncp22_row("ncp22/3 v1", 3, 1, dhandle, path);
tests_print_ncp22_row("ncp22/42 v0", 42, 0, dhandle, path);
tests_print_ncp22_row("ncp22/42 v1", 42, 1, dhandle, path);
if (my_obj_id)
tests_print_ncp22_obj50_row("obj50 path", my_obj_id, dhandle, path);
if (usepath[0]) {
tests_print_ncp22_row("p ncp22/3 v0", 3, 0, dhandle, usepath);
tests_print_ncp22_row("p ncp22/3 v1", 3, 1, dhandle, usepath);
tests_print_ncp22_row("p ncp22/42 v0", 42, 0, dhandle, usepath);
tests_print_ncp22_row("p ncp22/42 v1", 42, 1, dhandle, usepath);
if (my_obj_id)
tests_print_ncp22_obj50_row("p obj50 path", my_obj_id, dhandle, usepath);
}
fprintf(stdout, "\nCompare with Novell: NPUBLIC\\RIGHTS %s\n", path);
return(0);
}
int func_tests(int argc, char *argv[], int mode)
{
(void)mode;
if (argc < 2) {
tests_usage();
return(1);
}
if (tests_same_arg(argv[1], "NCP87C32ATTR"))
return tests_ncp87c32attr();
if (tests_same_arg(argv[1], "NCP87C32AUTO"))
return tests_ncp87c32auto();
if (tests_same_arg(argv[1], "EFFRIGHT")) {
if (argc < 3)
return tests_effright(".");
return tests_effright(argv[2]);
}
if (tests_same_arg(argv[1], "NCP23MAP") ||
tests_same_arg(argv[1], "NCP23F")) {
if (argc < 3)
return tests_ncp23map(".");
return tests_ncp23map(argv[2]);
}
if (tests_same_arg(argv[1], "NCP22REN") ||
tests_same_arg(argv[1], "NCP22S2E")) {
if (argc < 3)
return tests_ncp22ren(NULL, NULL);
if (argc < 4)
return tests_ncp22rendir(argv[2]);
return tests_ncp22ren(argv[2], argv[3]);
}
if (tests_same_arg(argv[1], "RENDIR") ||
tests_same_arg(argv[1], "DIRREN") ||
tests_same_arg(argv[1], "NCP22RENDIR") ||
tests_same_arg(argv[1], "NCP22DIRREN")) {
if (argc < 3)
return tests_ncp22rendir(".");
return tests_ncp22rendir(argv[2]);
}
if (tests_same_arg(argv[1], "NCP2225MISS")) {
if (argc < 3)
return tests_ncp2225miss(NULL);
return tests_ncp2225miss(argv[2]);
}
if (tests_same_arg(argv[1], "NCP2225ATTR")) {
if (argc < 3)
return tests_ncp2225attr(NULL, NULL);
if (argc < 4)
return tests_ncp2225attr(argv[2], NULL);
return tests_ncp2225attr(argv[2], argv[3]);
}
if (tests_same_arg(argv[1], "NCP2225TIME")) {
if (argc < 3)
return tests_ncp2225time(NULL, NULL, NULL);
if (argc < 5)
return tests_ncp2225time(argv[2], NULL, NULL);
return tests_ncp2225time(argv[2], argv[3], argv[4]);
}
if (tests_same_arg(argv[1], "NCP2225ADATE")) {
if (argc < 3)
return tests_ncp2225adate(NULL, NULL);
if (argc < 4)
return tests_ncp2225adate(argv[2], NULL);
return tests_ncp2225adate(argv[2], argv[3]);
}
if (tests_same_arg(argv[1], "NCP2225MAXSPACE")) {
if (argc < 3)
return tests_ncp2225maxspace(NULL, NULL);
if (argc < 4)
return tests_ncp2225maxspace(argv[2], NULL);
return tests_ncp2225maxspace(argv[2], argv[3]);
}
if (tests_same_arg(argv[1], "NCP221EINFO")) {
if (argc < 3)
return tests_ncp221einfo(".");
return tests_ncp221einfo(argv[2]);
}
if (tests_same_arg(argv[1], "NCP2225MODID")) {
if (argc < 3)
return tests_ncp2225modid(NULL, NULL);
if (argc < 4)
return tests_ncp2225modid(argv[2], NULL);
return tests_ncp2225modid(argv[2], argv[3]);
}
if (tests_same_arg(argv[1], "NCP2225CREATE")) {
if (argc < 3)
return tests_ncp2225create(NULL, NULL, NULL, NULL);
if (argc < 6)
return tests_ncp2225create(argv[2], NULL, NULL, NULL);
return tests_ncp2225create(argv[2], argv[3], argv[4], argv[5]);
}
if (tests_same_arg(argv[1], "NCP2225ARCH")) {
if (argc < 3)
return tests_ncp2225arch(NULL, NULL, NULL, NULL);
if (argc < 6)
return tests_ncp2225arch(argv[2], NULL, NULL, NULL);
return tests_ncp2225arch(argv[2], argv[3], argv[4], argv[5]);
}
if (tests_same_arg(argv[1], "NCP22S4") ||
tests_same_arg(argv[1], "MAXRIGHT") ||
tests_same_arg(argv[1], "IRM")) {
if (argc < 3)
return tests_ncp22s4(".", NULL);
if (argc < 4)
return tests_ncp22s4(argv[2], NULL);
return tests_ncp22s4(argv[2], argv[3]);
}
tests_usage();
return(1);
}