diff --git a/ENDPOINTS.md b/ENDPOINTS.md index f48cdf7..e0bdd16 100644 --- a/ENDPOINTS.md +++ b/ENDPOINTS.md @@ -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 diff --git a/TODO.md b/TODO.md index 27f41af..7f535f8 100644 --- a/TODO.md +++ b/TODO.md @@ -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. | diff --git a/include/sema.h b/include/sema.h index f2c2345..f872a01 100644 --- a/include/sema.h +++ b/include/sema.h @@ -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 diff --git a/src/nwbind.c b/src/nwbind.c index 0ca7a3b..fc812ee 100644 --- a/src/nwbind.c +++ b/src/nwbind.c @@ -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 */ /* diff --git a/src/sema.c b/src/sema.c index b97685e..9428fda 100644 --- a/src/sema.c +++ b/src/sema.c @@ -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); } - diff --git a/tests/ncp/ncp23_fse_smoke.c b/tests/ncp/ncp23_fse_smoke.c index d04b494..42e22aa 100644 --- a/tests/ncp/ncp23_fse_smoke.c +++ b/tests/ncp/ncp23_fse_smoke.c @@ -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; }