Implement FSE semaphore monitor records
All checks were successful
Source release / source-package (push) Successful in 1m44s

This commit is contained in:
Mario Fetka
2026-06-23 05:03:21 +02:00
parent ea31b8b613
commit 65bafeee5e
6 changed files with 434 additions and 10 deletions

View File

@@ -154,7 +154,7 @@ MARS routes many NCP 23 selectors through `src/nwbind.c`; some FSE monitor/admin
| Scan File Information / Set File Information | `23/0F`, `23/10` | partial | `src/nwconn.c`; metadata field layouts need endpoint comments where missing. |
| Queue calls | `23/64`, `23/68`, `23/69`, `23/6C`, `23/79`, `23/7A`, `23/7C`, `23/7F`, `23/83`, `23/84` | partial | `src/nwconn.c`/`src/nwbind.c`; queue path case and operator model remain TODO. |
| Map Directory Number to Path / Convert Path to Dir Entry | `23/F3`, `23/F4` | partial | Active compatibility helpers; verify against old admin/DOS utilities. |
| File Server Environment monitor selectors | many CAL/FSE APIs in `include/*calfse*` | partial/stub | `src/nwconn.c`/`src/nwbind.c`; old decimal `23/219` == wire/code `0x17/0xDB` and new decimal `23/235` == wire/code `0x17/0xEB` Get Connection's Open Files query the target `nwconn` handle table; `ncp23.fse.smoke` covers empty active connections and invalid stations. |
| File Server Environment monitor selectors | many CAL/FSE APIs in `include/*calfse*` | partial/stub | `src/nwconn.c`/`src/nwbind.c`; old decimal `23/219` == wire/code `0x17/0xDB` and new decimal `23/235` == wire/code `0x17/0xEB` Get Connection's Open Files query the target `nwconn` handle table; decimal `23/225` == wire/code `0x17/0xE1`, decimal `23/226` == wire/code `0x17/0xE2`, decimal `23/241` == wire/code `0x17/0xF1` and decimal `23/242` == wire/code `0x17/0xF2` report the existing semaphore table; `ncp23.fse.smoke` covers active and invalid-station paths. |
### Endpoint reference matrix
@@ -165,6 +165,10 @@ Use this matrix when implementing or auditing an endpoint. The PDF and old WebS
| `List Relations Of an Object` decimal `23/76` == wire/code `0x17/0x4C` | `/home/mario/Iso/ncp__enu.pdf`, Bindery, page 155 | `/home/mario/Iso/netwaresdk/.../redleg14/a_apiref/0003R020.htm`; membership examples in `WebSDK/SAMPLE/TIDS/3/XFER/NWCALLS.PAS` | none found in `/home/mario/mars/nss` for this bindery NCP | `src/nwbind.c`; `ncp23.bindery.smoke` |
| `Get Connection's Open Files (old)` decimal `23/219` == wire/code `0x17/0xDB` | `/home/mario/Iso/ncp__enu.pdf`, FSE, pages 865-866 | `/home/mario/Iso/netwaresdk/.../redleg14/a_apiref/0009R009.htm` | `/home/mario/mars/nss/public_core/comn/common/fileHandle.c` and related file-handle structures | `src/nwbind.c` forwards to target `nwconn`; `src/nwfile.c` serializes old open-file records; `ncp23.fse.smoke` |
| `Get Connection's Open Files` decimal `23/235` == wire/code `0x17/0xEB` | `/home/mario/Iso/ncp__enu.pdf`, FSE, pages 862-864 | `/home/mario/Iso/netwaresdk/.../redleg14/a_apiref/0009R008.htm` | `/home/mario/mars/nss/public_core/comn/common/fileHandle.c` and related file-handle structures | `src/nwbind.c` forwards to target `nwconn`; `src/nwfile.c` serializes new open-file records; `ncp23.fse.smoke` |
| `Get Connection's Semaphores (old)` decimal `23/225` == wire/code `0x17/0xE1` | `/home/mario/Iso/ncp__enu.pdf`, FSE, page 870 | `/home/mario/Iso/netwaresdk/.../redleg14/a_apiref/0009R011.htm` | none found in `/home/mario/mars/nss`; MARS semaphore emulator is in `src/sema.c` | `src/nwbind.c` serializes `CONNECTION.semas` through `src/sema.c`; `ncp23.fse.smoke` |
| `Get Semaphore Information (old)` decimal `23/226` == wire/code `0x17/0xE2` | `/home/mario/Iso/ncp__enu.pdf`, FSE, page 940 | `/home/mario/Iso/netwaresdk/.../redleg14/a_apiref/0016R013.htm` | none found in `/home/mario/mars/nss`; MARS semaphore emulator is in `src/sema.c` | `src/nwbind.c` serializes semaphore users from active connections; `ncp23.fse.smoke` |
| `Get Connection's Semaphores` decimal `23/241` == wire/code `0x17/0xF1` | `/home/mario/Iso/ncp__enu.pdf`, FSE, page 868 | `/home/mario/Iso/netwaresdk/.../redleg14/a_apiref/0009R010.htm` | none found in `/home/mario/mars/nss`; MARS semaphore emulator is in `src/sema.c` | `src/nwbind.c` serializes `CONNECTION.semas` through `src/sema.c`; `ncp23.fse.smoke` |
| `Get Semaphore Information` decimal `23/242` == wire/code `0x17/0xF2` | `/home/mario/Iso/ncp__enu.pdf`, FSE, page 938 | `/home/mario/Iso/netwaresdk/.../redleg14/a_apiref/0016R012.htm` | none found in `/home/mario/mars/nss`; MARS semaphore emulator is in `src/sema.c` | `src/nwbind.c` serializes semaphore users from active connections; `ncp23.fse.smoke` |
### Direct file and lock NCPs

View File

@@ -94,7 +94,7 @@ client/test requires it.
| Directory quota scan decimal `22/40` (wire/code `0x28`) | 3.x | partial | Sequence byte order is handled; enrich scan reply semantics after related resource-fork/namespace fields are understood. |
| Salvage scan/recover/purge bridges, including old `22/27`, `22/28`, `22/29` style views | 3.x | partial | Align NCP replies, JSON sidecars and NSS-shaped deleted metadata from one snapshot builder. |
| File Server Environment group `0x2222/23` usage/volume/LAN/server-status selectors | 3.x | partial | Add selector-specific tests for monitor tools and ensure dummy/no-op replies are explicitly documented. |
| File Server Environment open-file, lock/semaphore monitor and console-control selectors | 3.x | partial | `23/DB` and `23/EB` open-file monitor selectors query the target `nwconn` handle table and are smoke-tested for empty active connections and `0xFD` invalid stations; add lock/semaphore/using-file monitor selectors before marking done. |
| File Server Environment open-file, lock/semaphore monitor and console-control selectors | 3.x | partial | `23/DB` and `23/EB` open-file monitor selectors query the target `nwconn` handle table; `23/E1`, `23/F1`, `23/E2` and `23/F2` expose the existing semaphore table; `ncp23.fse.smoke` covers active and invalid-station paths. Add lock/using-file monitor selectors before marking done. |
| Semaphore groups, including forwarded selector families and direct old-style calls | 2.x/3.x | partial | Old direct `0x2222/20` / `32/00`-`32/04` is implemented and smoke-covered; continue auditing forwarded monitor-selector semantics. |
| Extended Attribute group `0x2222/86` | 3.x | partial | Keep work tied to backup/metadata compatibility and existing xattr roundtrip tests. |
| Name Space groups `0x2222/87` and `0x2222/89`, DOS namespace selectors | 3.x | open | Make DOS namespace rules match old clients before broad LONG/OS2 expansion. |

View File

@@ -19,7 +19,17 @@
#ifndef _SEMA_H_
#define _SEMA_H_
typedef struct {
int handle;
int namlen;
const uint8 *name;
int opencount;
int value;
} SEMA_INFO;
extern void clear_conn_semas(CONNECTION *c);
extern int handle_func_0x20(CONNECTION *c, uint8 *p, int ufunc, uint8 *responsedata);
extern int get_conn_sema_info(CONNECTION *c, int index, SEMA_INFO *info);
extern int find_sema_info(int namlen, const uint8 *name, SEMA_INFO *info);
#endif

View File

@@ -60,6 +60,8 @@ static int rcv_flags = 0;
static ipxAddr_t from_addr; /* actual calling address */
static NCPREQUEST *ncprequest = (NCPREQUEST*)&ipx_in_data;
static int server_goes_down=0;
static int max_connections=MAX_CONNECTIONS;
static CONNECTION *connections=NULL;
static int send_request_to_nwconn(int connection, int function,
@@ -70,6 +72,105 @@ static void nwconn_open_files_callback(int connection,
char *data, int data_len,
int completition);
static int build_conn_semas_response(CONNECTION *c, int last_seen,
uint8 *data, int max_len)
{
uint8 *p = data + 4;
int count = 0;
int next = 0;
int k;
if (last_seen < 0)
last_seen = 0;
for (k = last_seen; k < c->count_semas; k++) {
SEMA_INFO info;
int record_len;
if (get_conn_sema_info(c, k, &info) < 0)
continue;
record_len = 7 + info.namlen;
if ((p - data) + record_len > max_len) {
next = k;
break;
}
U16_TO_16(info.opencount, p); p += 2;
U16_TO_16((uint16)info.value, p); p += 2;
U16_TO_16(0, p); p += 2; /* task tracking is not stored by sema.c */
*p++ = (uint8)info.namlen;
memcpy(p, info.name, info.namlen);
p += info.namlen;
count++;
}
U16_TO_16(next, data);
U16_TO_16(count, data + 2);
return((int)(p - data));
}
static int build_sema_info_response(int namlen, const uint8 *name,
int last_seen, int old_format,
uint8 *data, int max_len)
{
SEMA_INFO info;
uint8 *p;
int count = 0;
int next = 0;
int k;
if (last_seen < 0)
last_seen = 0;
if (find_sema_info(namlen, name, &info) < 0) {
memset(data, 0, old_format ? 6 : 8);
return(old_format ? 6 : 8);
}
p = data + (old_format ? 6 : 8);
for (k = last_seen; k < max_connections; k++) {
CONNECTION *c = &connections[k];
int s;
if (!c->active)
continue;
for (s = 0; s < c->count_semas; s++) {
if (c->semas[s].handle == info.handle) {
int record_len = old_format ? 3 : 4;
if ((p - data) + record_len > max_len) {
next = k;
goto done;
}
if (old_format) {
U16_TO_BE16(k + 1, p); p += 2;
*p++ = 0; /* task tracking is not stored by sema.c */
} else {
U16_TO_16(k + 1, p); p += 2;
U16_TO_16(0, p); p += 2; /* task tracking is not stored by sema.c */
}
count++;
break;
}
}
}
done:
if (old_format) {
U16_TO_BE16(next, data);
U16_TO_BE16(info.opencount, data + 2);
data[4] = (uint8)info.value;
data[5] = (uint8)count;
} else {
U16_TO_16(next, data);
U16_TO_16(info.opencount, data + 2);
U16_TO_16((uint16)info.value, data + 4);
U16_TO_16(count, data + 6);
}
return((int)(p - data));
}
static void write_to_nwserv(int what, int connection, int mode,
char *data, int size)
@@ -107,8 +208,6 @@ static void write_to_nwserv(int what, int connection, int mode,
write_to_nwserv(0xffff, 0, 0, NULL, 0)
static int max_nw_vols=MAX_NW_VOLS;
static int max_connections=MAX_CONNECTIONS;
static CONNECTION *connections=NULL;
static CONNECTION *act_c=(CONNECTION*)NULL;
static int internal_act=0;
@@ -3305,6 +3404,49 @@ static void handle_fxx(int gelen, int func)
data_len = 0;
}
} break;
case 0xe1 : /* SDK 23/225 / wire 0xe1 Get Connection's Semaphores (old) */
case 0xf1 : { /* SDK 23/241 / wire 0xf1 Get Connection's Semaphores */
/*
* Request: SubFuncStrucLen=5, SubFunctionCode=225/241,
* ConnectionNumber, LastRecordSeen.
* Response: NextRequestRecord, NumberOfSemaphores, then
* OpenCount, SemaphoreValue, TaskNumber,
* SemaphoreNameLen, and SemaphoreName records.
*
* The current semaphore emulator stores connection-open
* state and global semaphore value/open count. It does not
* preserve per-task ownership, so TaskNumber is reported as
* zero until the semaphore backend gains task state.
*/
int conn = GET_16(rdata);
if (conn <= 0 || conn > max_connections ||
!connections[conn-1].active) {
completition = 0xfd; /* Bad Station Number */
data_len = 0;
} else {
data_len = build_conn_semas_response(
&connections[conn-1], GET_16(rdata + 2),
(uint8*)responsedata, IPX_MAX_DATA);
}
} break;
case 0xe2 : /* SDK 23/226 / wire 0xe2 Get Semaphore Information (old) */
case 0xf2 : { /* SDK 23/242 / wire 0xf2 Get Semaphore Information */
/*
* Request: SubFuncStrucLen=4+SemaphoreNameLen,
* SubFunctionCode=226/242, LastRecordSeen,
* SemaphoreNameLen, SemaphoreName.
* Response: semaphore open/value metadata plus connection
* records for clients that have the semaphore open. The old
* variant uses the documented Hi-Lo reply fields and byte
* value/count fields; the new variant uses Lo-Hi words.
*/
int old_format = (ufunc == 0xe2);
int last_seen = old_format ? GET_BE16(rdata) : GET_16(rdata);
int namlen = *(rdata + 2);
data_len = build_sema_info_response(namlen, rdata + 3,
last_seen, old_format,
(uint8*)responsedata, IPX_MAX_DATA);
} break;
#if 0
case 0xec : /* SDK 23/236 / wire 0xec Get Connection Using A File */
/*

View File

@@ -112,6 +112,42 @@ static int examine_sema(int handle, int *value, int *opencount)
return(-0xff); /* wrong handle */
}
static int fill_sema_info(int handle, SEMA_INFO *info)
{
SEMA *se;
if (handle > 0 && --handle < count_semas && NULL != (se=semas[handle])){
info->handle=handle+1;
info->namlen=se->namlen;
info->name=se->name;
info->opencount=se->opencount;
info->value=se->value;
return(0);
}
return(-0xff); /* wrong handle */
}
int get_conn_sema_info(CONNECTION *c, int index, SEMA_INFO *info)
{
if (!c || !info || index < 0 || index >= c->count_semas)
return(-0xff);
if (c->semas[index].handle < 1)
return(-0xff);
return(fill_sema_info(c->semas[index].handle, info));
}
int find_sema_info(int namlen, const uint8 *name, SEMA_INFO *info)
{
int k=-1;
if (!name || !info || namlen < 1)
return(-0xff);
while (++k < count_semas) {
SEMA *se=semas[k];
if (se && se->namlen==namlen && !memcmp(se->name, name, namlen))
return(fill_sema_info(k+1, info));
}
return(-0xff);
}
static int open_conn_sema(CONNECTION *c, int handle)
{
int k=c->count_semas;
@@ -322,4 +358,3 @@ int handle_func_0x20(CONNECTION *c, uint8 *p, int ufunc, uint8 *responsedata)
}
return(result);
}

View File

@@ -27,6 +27,11 @@ static uint16_t le16_to_cpu(const uint8_t p[2])
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
static uint16_t be16_to_cpu(const uint8_t p[2])
{
return ((uint16_t)p[0] << 8) | (uint16_t)p[1];
}
static void cpu_to_le16(uint8_t p[2], uint16_t v)
{
p[0] = (uint8_t)v;
@@ -41,6 +46,10 @@ static void usage(const char *prog)
"Exercises selected NetWare 2.x/3.x NCP 23 File Server Environment endpoints:\n"
" 23/DB Get Connection's Open Files (old)\n"
" 23/EB Get Connection's Open Files\n"
" 23/E1 Get Connection's Semaphores (old)\n"
" 23/F1 Get Connection's Semaphores\n"
" 23/E2 Get Semaphore Information (old)\n"
" 23/F2 Get Semaphore Information\n"
"\n"
"Example:\n"
" %s -S MARS -U SUPERVISOR -P secret\n",
@@ -141,12 +150,173 @@ static long check_bad_station(NWCONN_HANDLE conn, uint8_t subfunction)
return 0;
}
static long old_semaphore_open(NWCONN_HANDLE conn, const char *name,
uint8_t initial_value, uint8_t handle[4])
{
uint8_t request[3 + 127];
uint8_t reply_buf[5];
NW_FRAGMENT reply;
size_t name_len = strlen(name);
long err;
if (!name_len || name_len > 127)
return NWE_INVALID_PATH;
request[0] = 0x00;
request[1] = initial_value;
request[2] = (uint8_t)name_len;
memcpy(request + 3, name, name_len);
memset(reply_buf, 0, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, 32, request, 3 + name_len, &reply);
if (err)
return err;
if (reply.fragSize != sizeof(reply_buf) || reply_buf[4] == 0)
return NWE_INVALID_PATH;
memcpy(handle, reply_buf, 4);
return 0;
}
static long old_semaphore_close(NWCONN_HANDLE conn, const uint8_t handle[4])
{
uint8_t request[5];
request[0] = 0x04;
memcpy(request + 1, handle, 4);
return NWRequestSimple(conn, 32, request, sizeof(request), NULL);
}
static long check_connection_semaphores(NWCONN_HANDLE conn, uint8_t subfunction,
uint16_t connection, const char *name)
{
uint8_t request[4];
uint8_t reply_buf[512];
NW_FRAGMENT reply;
size_t name_len = strlen(name);
long err;
uint16_t count;
size_t off;
int found = 0;
cpu_to_le16(request, connection);
cpu_to_le16(request + 2, 0);
memset(reply_buf, 0xaa, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, NCPC_SFN(23, subfunction), request,
sizeof(request), &reply);
if (err)
return err;
if (reply.fragSize < 4 || le16_to_cpu(reply_buf) != 0)
return NWE_INVALID_PATH;
count = le16_to_cpu(reply_buf + 2);
off = 4;
while (count-- > 0) {
uint8_t sem_name_len;
if (off + 7 > reply.fragSize)
return NWE_INVALID_PATH;
if (le16_to_cpu(reply_buf + off) == 0)
return NWE_INVALID_PATH;
sem_name_len = reply_buf[off + 6];
off += 7;
if (off + sem_name_len > reply.fragSize)
return NWE_INVALID_PATH;
if (sem_name_len == name_len &&
!memcmp(reply_buf + off, name, name_len))
found = 1;
off += sem_name_len;
}
if (!found)
return NWE_INVALID_PATH;
printf("NCP 23/%02X connection-semaphores connection=%u name=%s\n",
(unsigned int)subfunction, (unsigned int)connection, name);
return 0;
}
static long check_semaphore_information(NWCONN_HANDLE conn, uint8_t subfunction,
uint16_t connection, const char *name)
{
uint8_t request[3 + 127];
uint8_t reply_buf[512];
NW_FRAGMENT reply;
size_t name_len = strlen(name);
long err;
int old_format = (subfunction == 0xe2);
uint16_t count;
size_t off;
int found = 0;
if (!name_len || name_len > 127)
return NWE_INVALID_PATH;
memset(request, 0, sizeof(request));
request[2] = (uint8_t)name_len;
memcpy(request + 3, name, name_len);
memset(reply_buf, 0xaa, sizeof(reply_buf));
reply.fragAddr.rw = reply_buf;
reply.fragSize = sizeof(reply_buf);
err = NWRequestSimple(conn, NCPC_SFN(23, subfunction), request,
3 + name_len, &reply);
if (err)
return err;
if (old_format) {
if (reply.fragSize < 6 || be16_to_cpu(reply_buf) != 0 ||
be16_to_cpu(reply_buf + 2) == 0)
return NWE_INVALID_PATH;
count = reply_buf[5];
off = 6;
while (count-- > 0) {
if (off + 3 > reply.fragSize)
return NWE_INVALID_PATH;
if (be16_to_cpu(reply_buf + off) == connection)
found = 1;
off += 3;
}
} else {
if (reply.fragSize < 8 || le16_to_cpu(reply_buf) != 0 ||
le16_to_cpu(reply_buf + 2) == 0)
return NWE_INVALID_PATH;
count = le16_to_cpu(reply_buf + 6);
off = 8;
while (count-- > 0) {
if (off + 4 > reply.fragSize)
return NWE_INVALID_PATH;
if (le16_to_cpu(reply_buf + off) == connection)
found = 1;
off += 4;
}
}
if (!found)
return NWE_INVALID_PATH;
printf("NCP 23/%02X semaphore-information connection=%u name=%s\n",
(unsigned int)subfunction, (unsigned int)connection, name);
return 0;
}
int main(int argc, char **argv)
{
NWCONN_HANDLE conn;
long init_err;
long err;
uint8_t station = 0;
uint8_t sem_handle[4];
char sem_name[32];
int sem_open = 0;
if (argc > 1 &&
(!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
@@ -188,22 +358,85 @@ int main(int argc, char **argv)
return 1;
}
snprintf(sem_name, sizeof(sem_name), "FSESEM%03u", (unsigned int)station);
err = old_semaphore_open(conn, sem_name, 3, sem_handle);
if (err) {
fprintf(stderr, "NCP 32/00 open semaphore failed: error=0x%04lx\n",
err);
ncp_close(conn);
return 1;
}
sem_open = 1;
err = check_connection_semaphores(conn, 0xe1, station, sem_name);
if (err) {
fprintf(stderr,
"NCP 23/E1 old connection-semaphores failed: error=0x%04lx\n",
err);
goto cleanup;
}
err = check_connection_semaphores(conn, 0xf1, station, sem_name);
if (err) {
fprintf(stderr, "NCP 23/F1 connection-semaphores failed: error=0x%04lx\n",
err);
goto cleanup;
}
err = check_semaphore_information(conn, 0xe2, station, sem_name);
if (err) {
fprintf(stderr,
"NCP 23/E2 old semaphore-information failed: error=0x%04lx\n",
err);
goto cleanup;
}
err = check_semaphore_information(conn, 0xf2, station, sem_name);
if (err) {
fprintf(stderr,
"NCP 23/F2 semaphore-information failed: error=0x%04lx\n",
err);
goto cleanup;
}
err = check_bad_station(conn, 0xdb);
if (err) {
fprintf(stderr, "NCP 23/DB invalid station failed: error=0x%04lx\n",
err);
ncp_close(conn);
return 1;
goto cleanup;
}
err = check_bad_station(conn, 0xeb);
if (err) {
fprintf(stderr, "NCP 23/EB invalid station failed: error=0x%04lx\n",
err);
ncp_close(conn);
return 1;
goto cleanup;
}
err = check_bad_station(conn, 0xe1);
if (err) {
fprintf(stderr, "NCP 23/E1 invalid station failed: error=0x%04lx\n",
err);
goto cleanup;
}
err = check_bad_station(conn, 0xf1);
if (err) {
fprintf(stderr, "NCP 23/F1 invalid station failed: error=0x%04lx\n",
err);
goto cleanup;
}
cleanup:
if (sem_open) {
long close_err = old_semaphore_close(conn, sem_handle);
if (!err && close_err) {
fprintf(stderr,
"NCP 32/04 close semaphore failed: error=0x%04lx\n",
close_err);
err = close_err;
}
}
ncp_close(conn);
return 0;
return err ? 1 : 0;
}