Files
mars-dosutils/nwtests.c
Mario Fetka 50524cf759 dosutils: add GPL-2 headers and file descriptions
Add GPL-2-or-later license headers to the DOS utility source files and
document the purpose and local dependencies of each C, header and assembler
file.

Preserve the original Martin Stover copyright attribution for the historic
MARS-NWE utility sources, including files that did not previously carry an
explicit header but are part of the original tool set. Add Mario Fetka as the
2026 copyright holder for the current maintenance work, and use Mario-only
headers for files without original Martin Stover ownership.

Also add a root-level COPYING file containing the GPL-2 license text.
2026-05-29 08:07:09 +02:00

2809 lines
76 KiB
C

/*
* mars-nwe-dosutils - NetWare/DOS utility tools.
*
* Copyright (C) 2026 Mario Fetka
* Copyright (C) 1993,1996 Martin Stover, Marburg, Germany
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* Purpose: NWTESTS diagnostic utility for exercising NetWare requester, NCP and namespace behavior.
* Depends on: net.h, c32ncp.h, netcall.c requester helpers, c32ncp.c namespace/NCP helpers, tools.c shared utility routines.
*/
#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);
}