Cover pre-22 NetWare compatibility calls
All checks were successful
Source release / source-package (push) Successful in 1m31s

This commit is contained in:
Mario Fetka
2026-06-22 12:39:56 +02:00
parent 058c016fcc
commit ac553b8e1c
5 changed files with 323 additions and 1 deletions

View File

@@ -152,12 +152,31 @@ MARS routes many NCP 23 selectors through `src/nwbind.c`; some FSE monitor/admin
### Direct file and lock NCPs
The WebSDK `NCP Calls` index for NetWare 1.x/2.x/3.x selectors before the
Directory Services group (`22`) currently yields these direct or nested groups:
- `0x2222/03`-`0x2222/0E`: old file and logical-record log/lock/release/clear.
- `0x2222/17/06` and `0x2222/17/10`: direct print/printer status queue helpers.
- `0x2222/18`: Get Volume Info With Number.
- `0x2222/20`: Get File Server Date And Time.
- `0x2222/21/00`, `/01`, `/02`, `/03`, `/09`, `/10`, `/11`: old/new broadcast
and broadcast-mode/message calls.
`tests/ncp/ncp_pre22_smoke.c` directly covers the stable old compatibility
surface for `18`, `19`, `20`, and `21/00`-`21/03`. The lock and direct-print
families need separate semantic tests because they depend on file-lock state or
spool/printer backend behaviour.
| PDF decimal / code | Name | MARS status | Handler / notes |
| --- | --- | --- | --- |
| `0x2222/03` | Log File | implemented | `src/nwconn.c`; old lock/log path. |
| `0x2222/04`, `0x2222/6A` | Lock File Set old/new | partial | Lock semantics are compatibility-level. |
| `0x2222/05`, `0x2222/06`, `0x2222/07`, `0x2222/08` | Release/Clear File and File Set | partial | Check old-client locking behaviour. |
| `0x2222/09`-`0x2222/0E`, `0x2222/6B`, `0x2222/6C` | Logical record log/lock/release/clear | partial | Active compatibility paths; tests needed. |
| `0x2222/11` / `17/06`, `17/10` | Direct Print / Printer Status Queue helpers | open | `src/nwconn.c` has only a disabled documentation stub; queue printing exists elsewhere, but these direct old spool NCPs still need implementation/tests. |
| `0x2222/12` / `18` | Get Volume Info With Number | implemented | Direct old call covered by `ncp.pre22.smoke`. |
| `0x2222/14` / `20` | Get File Server Date And Time | implemented | Direct old call covered by `ncp.pre22.smoke`. |
| `0x2222/15` / `21/00`-`21/03`, `21/09`-`21/11` | Message / Broadcast group | implemented | Forwarded to `src/nwbind.c`; old send/get and enable/disable covered by `ncp.pre22.smoke`. |
| `0x2222/1A`-`0x2222/1F`, `0x2222/6D`, `0x2222/6E` | Physical record log/lock/release/clear | partial | Active compatibility paths; tests needed. |
| `0x2222/41`-`0x2222/4D`, `0x2222/54` | Old direct file open/create/close/read/write/rename/erase | implemented/partial | Active in `src/nwconn.c`; file write now covered indirectly by quota smokes. |
| `0x2222/4F` | Set File Extended Attributes | stub/no-op | Active compatibility stub; document request layout before changing. |

View File

@@ -3501,7 +3501,7 @@ static int handle_ncp_serv(void)
U16_TO_BE16(fsp.fsu_files, xdata->total_dirs);
U16_TO_BE16(fsp.fsu_ffree, xdata->avail_dirs);
if ( get_volume_options(volume) & VOL_OPTION_REMOUNT) {
U16_TO_BE16(1, xdata->removable);
U16_TO_BE16(0xffff, xdata->removable);
} else {
U16_TO_BE16(0, xdata->removable);
}

View File

@@ -47,6 +47,12 @@ builds list them, but they print `SKIP/WARN` and exit with CTest skip code 77
unless `MARS_NWE_RUN_NETWORKED_TESTS=1` is set. This gives the test report a
warning-like skipped entry instead of making normal offline builds fail.
`ncp.pre22.smoke` is the direct NetWare 1.x/2.x/3.x compatibility smoke for
selected NCPs before the NCP 22 Directory Services group. It uses raw
`NWRequestSimple` calls for `18` Get Volume Info With Number, `19` Get Station
Number, `20` Get File Server Date And Time, and the old `21/00`-`21/03`
broadcast paths.
Run only offline tests with:
```sh

View File

@@ -17,6 +17,28 @@ add_executable(ncp22_directory_smoke ncp22_directory_smoke.c)
target_include_directories(ncp22_directory_smoke PRIVATE ${NCPFS_INCLUDE_DIR})
target_link_libraries(ncp22_directory_smoke ${NCPFS_LIBRARY})
add_executable(ncp_pre22_smoke ncp_pre22_smoke.c)
target_include_directories(ncp_pre22_smoke PRIVATE ${NCPFS_INCLUDE_DIR})
target_link_libraries(ncp_pre22_smoke ${NCPFS_LIBRARY})
add_test(NAME ncp.pre22.smoke
COMMAND sh ${MARS_NWE_NETWORKED_CTEST_GATE}
ncp.pre22.smoke
"a running mars-nwe server, NCPFS tools, credentials, and a readable NetWare volume path"
MARS_NWE_TEST_SERVER
MARS_NWE_TEST_ADMIN
MARS_NWE_TEST_PASSWORD
--
sh -c "exec \"\$1\" -S \"\$MARS_NWE_TEST_SERVER\" -U \"\$MARS_NWE_TEST_ADMIN\" -P \"\$MARS_NWE_TEST_PASSWORD\" \"\${MARS_NWE_TEST_NCP22_PATH:-SYS:PUBLIC}\""
sh
$<TARGET_FILE:ncp_pre22_smoke>
)
set_tests_properties(ncp.pre22.smoke PROPERTIES
LABELS "networked;integration;ncp;pre22"
SKIP_RETURN_CODE 77
TIMEOUT 120
)
add_test(NAME ncp22.directory.smoke
COMMAND sh ${MARS_NWE_NETWORKED_CTEST_GATE}
ncp22.directory.smoke

275
tests/ncp/ncp_pre22_smoke.c Normal file
View File

@@ -0,0 +1,275 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Direct network smoke test for NetWare 1.x/2.x/3.x-compatible NCPs before
* the NCP 22 Directory Services group.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ncp/nwcalls.h>
#include <ncp/ncplib.h>
#include <ncp/kernel/ncp.h>
#ifndef NCPC_SUBFUNCTION
#define NCPC_SUBFUNCTION 0x10000
#endif
#ifndef NCPC_SFN
#define NCPC_SFN(FN, SFN) ((FN) | ((SFN) << 8) | NCPC_SUBFUNCTION)
#endif
#define NWE_INVALID_PATH 0x9c
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [ncpfs options] VOL:PATH\n"
"\n"
"Exercises selected pre-22 NetWare 1.x/2.x/3.x-compatible NCPs:\n"
" 18 Get Volume Info With Number\n"
" 19 Get Station Number\n"
" 20 Get File Server Date And Time\n"
" 21/00 Send Broadcast Message (old)\n"
" 21/01 Get Broadcast Message (old)\n"
" 21/02 Disable Broadcasts, 21/03 Enable Broadcasts\n"
"\n"
"Example:\n"
" %s -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n",
prog, prog);
}
static uint16_t be16_to_cpu(const uint8_t p[2])
{
return ((uint16_t)p[0] << 8) | (uint16_t)p[1];
}
static long get_station_number(NWCONN_HANDLE conn, uint8_t *station)
{
uint8_t reply_buf[3];
NW_FRAGMENT reply;
long err;
memset(reply_buf, 0, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, 19, NULL, 0, &reply);
if (err)
return err;
if (reply.fragSize < 1 || reply_buf[0] == 0)
return NWE_INVALID_PATH;
*station = reply_buf[0];
printf("NCP 19 station=%u reply_len=%lu\n",
(unsigned int)*station, (unsigned long)reply.fragSize);
return 0;
}
static long check_volume_info(NWCONN_HANDLE conn, const char *path)
{
char volume_name[256];
uint8_t request[1];
uint8_t reply_buf[28];
NW_FRAGMENT reply;
char returned_name[17];
int volume_number = -1;
char *colon;
long err;
snprintf(volume_name, sizeof(volume_name), "%s", path);
colon = strchr(volume_name, ':');
if (colon)
*colon = '\0';
if (!volume_name[0])
return NWE_INVALID_PATH;
err = ncp_get_volume_number(conn, volume_name, &volume_number);
if (err)
return err;
if (volume_number < 0 || volume_number > 255)
return NWE_INVALID_PATH;
request[0] = (uint8_t)volume_number;
memset(reply_buf, 0, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, 18, request, sizeof(request), &reply);
if (err)
return err;
if (reply.fragSize < sizeof(reply_buf))
return NWE_INVALID_PATH;
memcpy(returned_name, reply_buf + 10, 16);
returned_name[16] = '\0';
printf("NCP 18 volume=%d name=%s sectors_per_cluster=%u total_clusters=%u avail_clusters=%u removable=%u\n",
volume_number, returned_name,
(unsigned int)be16_to_cpu(reply_buf + 0),
(unsigned int)be16_to_cpu(reply_buf + 2),
(unsigned int)be16_to_cpu(reply_buf + 4),
(unsigned int)be16_to_cpu(reply_buf + 26));
if (strncasecmp(returned_name, volume_name, strlen(volume_name)))
return NWE_INVALID_PATH;
if (be16_to_cpu(reply_buf + 26) != 0 &&
be16_to_cpu(reply_buf + 26) != 0xffff)
return NWE_INVALID_PATH;
return 0;
}
static long check_datetime(NWCONN_HANDLE conn)
{
uint8_t reply_buf[7];
NW_FRAGMENT reply;
long err;
memset(reply_buf, 0, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, 20, NULL, 0, &reply);
if (err)
return err;
if (reply.fragSize < sizeof(reply_buf))
return NWE_INVALID_PATH;
printf("NCP 20 date=%u-%02u-%02u time=%02u:%02u:%02u dow=%u\n",
1900U + (unsigned int)reply_buf[0],
(unsigned int)reply_buf[1], (unsigned int)reply_buf[2],
(unsigned int)reply_buf[3], (unsigned int)reply_buf[4],
(unsigned int)reply_buf[5], (unsigned int)reply_buf[6]);
if (reply_buf[1] < 1 || reply_buf[1] > 12 ||
reply_buf[2] < 1 || reply_buf[2] > 31 ||
reply_buf[3] > 23 || reply_buf[4] > 59 ||
reply_buf[5] > 59 || reply_buf[6] > 6)
return NWE_INVALID_PATH;
return 0;
}
static long check_broadcasts(NWCONN_HANDLE conn, uint8_t station)
{
static const char message[] = "mars_nwe pre22 broadcast";
uint8_t request[1 + 1 + sizeof(message)];
uint8_t reply_buf[64];
NW_FRAGMENT reply;
long err;
size_t message_len = strlen(message);
err = NWRequestSimple(conn, NCPC_SFN(21, 0x02), NULL, 0, NULL);
if (err)
return err;
err = NWRequestSimple(conn, NCPC_SFN(21, 0x03), NULL, 0, NULL);
if (err)
return err;
request[0] = 1;
request[1] = station;
request[2] = (uint8_t)message_len;
memcpy(request + 3, message, message_len);
memset(reply_buf, 0, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, NCPC_SFN(21, 0x00),
request, 3 + message_len, &reply);
if (err)
return err;
if (reply.fragSize < 2 || reply_buf[0] != 1 || reply_buf[1] != 0)
return NWE_INVALID_PATH;
memset(reply_buf, 0, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, NCPC_SFN(21, 0x01), NULL, 0, &reply);
if (err)
return err;
if (reply.fragSize < 1 + message_len ||
reply_buf[0] != message_len ||
memcmp(reply_buf + 1, message, message_len))
return NWE_INVALID_PATH;
printf("NCP 21/00+01 old broadcast roundtrip station=%u message=%s\n",
(unsigned int)station, message);
return 0;
}
int main(int argc, char **argv)
{
NWCONN_HANDLE conn;
long init_err = 0;
const char *path = NULL;
uint8_t station = 0;
long err;
int i;
if (NWCallsInit(NULL, NULL)) {
fprintf(stderr, "NWCallsInit failed\n");
return 2;
}
conn = ncp_initialize(&argc, argv, 1, &init_err);
if (!conn) {
fprintf(stderr, "ncp_initialize/login failed: %ld\n", init_err);
usage(argv[0]);
return 2;
}
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
usage(argv[0]);
ncp_close(conn);
return 0;
} else if (!path) {
path = argv[i];
} else {
fprintf(stderr, "unexpected argument: %s\n", argv[i]);
usage(argv[0]);
ncp_close(conn);
return 2;
}
}
if (!path || !strchr(path, ':')) {
usage(argv[0]);
ncp_close(conn);
return 2;
}
err = check_volume_info(conn, path);
if (err) {
fprintf(stderr, "NCP 18 failed: path=%s error=0x%04x\n",
path, (unsigned int)err);
ncp_close(conn);
return 1;
}
err = get_station_number(conn, &station);
if (err) {
fprintf(stderr, "NCP 19 failed: error=0x%04x\n", (unsigned int)err);
ncp_close(conn);
return 1;
}
err = check_datetime(conn);
if (err) {
fprintf(stderr, "NCP 20 failed: error=0x%04x\n", (unsigned int)err);
ncp_close(conn);
return 1;
}
err = check_broadcasts(conn, station);
if (err) {
fprintf(stderr, "NCP 21 broadcast check failed: error=0x%04x\n",
(unsigned int)err);
ncp_close(conn);
return 1;
}
ncp_close(conn);
return 0;
}