diff --git a/CLAUDE.md b/CLAUDE.md
index 9b1f35c..4a199a7 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -115,13 +115,13 @@ httpd.c (main / initialize)
### Data Structures
-**HTTPD (288 bytes, 0x120):** Server-wide singleton. Listener socket, worker pool manager, CGI table, config values, UFS handle, Lua state, FTPD handle, MQTT telemetry handle, stats arrays. (Will shrink as confirmed removals are implemented.)
+**HTTPD (320 bytes, 0x140):** Server-wide singleton. Listener socket, worker pool manager, route table, config values, UFS handle, docroot, codepage, keep-alive settings, credential key/array, stats counters. Slots freed by the 4.0.0 removals are kept as `unused_nn` placeholders so no offset moves.
**HTTPC (4,096 bytes):** Per-client session. Allocated on accept(), freed on close. Fixed layout with 4,008-byte inline buffer (CBUFSIZE). Contains state machine position, socket, environment variables, file handles, credential.
**HTTPX (~270 bytes):** Function vector table. CGI modules call all server functions through this vector — they never link directly to HTTPD code. **Never change existing offsets** — only append new function pointers at the end.
-**HTTPCGI (20 bytes):** CGI path-to-program mapping. URL pattern → load module name.
+**HTTPCGI (32 bytes):** One route. URL pattern → load module name (`MOD=`) or NULL for a program-less static prefix (`LOC=`), plus the per-route auth policy (`auth`, `resattr`, `resclass`, `resname`).
### Request Processing Pipeline
@@ -287,7 +287,7 @@ each one is shown as hex plus a named field table:
| `HTTPD` | the server singleton (320 bytes, `0x140`) | no |
| `MGR` | `CTHDMGR`, the worker pool manager | no |
| `FS` | `UFSSYS` handle (8 bytes with the libufs stub) | no |
-| `CGI` | one `HTTPCGI` route, or the route array | **yes** |
+| `MOD` | the route array — every `MOD=` / `LOC=` entry | **yes** |
| `TASK` | a `CTHDTASK` | **yes** |
| `FILE` | a `FILE` handle | **yes** |
@@ -316,8 +316,8 @@ Note for anyone reading `jesst.c`: its output goes through `printf`, and in a
module `stdout` reaches the HTTP client — so those `printf`s *are* the response
body, not log output.
-**Known display bug:** the `HTTPCGI` field table in `httpdsrv.c` predates the
-per-route auth policy. It shows `login` (legacy) and omits `auth` / `resattr` /
-`resclass` / `resname`, so on an authorization question it can say the opposite
-of the truth — `/zosmf/info` reports `login 0` while answering 401. Read the hex
-at `+14`. Tracked as issue #146.
+**Reading the route table:** `?target=MOD` decodes the whole 32-byte `HTTPCGI`,
+so `auth` (`+14`) is named and spelled out — that is the field the request is
+gated on, not `login` (`+09`), which is the legacy byte and is labelled as such.
+`AUTH=DEFAULT` means the route carried no `AUTH=` keyword and falls back to the
+global `LOGIN` policy, which is not the same as "no authentication".
diff --git a/src/httpdsrv.c b/src/httpdsrv.c
index 5fb019c..a93af7a 100644
--- a/src/httpdsrv.c
+++ b/src/httpdsrv.c
@@ -9,7 +9,7 @@ static int getself(char *jobname, char *jobid);
#endif
static int display_httpd(HTTPD *httpd, HTTPC *httpc);
-static int display_cgi(HTTPD *httpd, HTTPC *httpc);
+static int display_route(HTTPD *httpd, HTTPC *httpc);
static int display_file(HTTPD *httpd, HTTPC *httpc);
static int display_fs(HTTPD *httpd, HTTPC *httpc);
static int display_mgr(HTTPD *httpd, HTTPC *httpc);
@@ -19,7 +19,7 @@ static int display_workers(HTTPD *httpd, HTTPC *httpc);
static int display_ufs(HTTPD *httpd, HTTPC *httpc, UFS *ufs);
static int display_ufssys(HTTPD *httpd, HTTPC *httpc, UFSSYS *sys);
-static int display_cgi_row(HTTPD *httpd, HTTPC *httpc, HTTPCGI *cgi, unsigned n);
+static int display_route_row(HTTPD *httpd, HTTPC *httpc, HTTPCGI *route, unsigned n);
static int display_worker_row(HTTPD *httpd, HTTPC *httpc, CTHDWORK *worker, unsigned n);
static int display_queue_data(HTTPD *httpd, HTTPC *httpc, CTHDQUE *q);
#if 0 /* ufs370 internal types -- not available with libufs stub */
@@ -58,11 +58,6 @@ int main(int argc, char **argv)
if (!target) target = "HTTPD";
len = strlen(target);
- if (http_cmpn(target, "CGI", len)==0) {
- display_cgi(httpd, httpc);
- goto quit;
- }
-
if (http_cmpn(target, "FILE", len)==0) {
display_file(httpd, httpc);
goto quit;
@@ -78,6 +73,15 @@ int main(int argc, char **argv)
goto quit;
}
+ /* The route array (MOD= programs and LOC= static prefixes). Was
+ "target=CGI" before the Parmlib keywords replaced that vocabulary.
+ Tested after MGR on purpose: target matches as a prefix, so a bare
+ "?target=M" has always resolved to MGR and still does. */
+ if (http_cmpn(target, "MOD", len)==0) {
+ display_route(httpd, httpc);
+ goto quit;
+ }
+
if (http_cmpn(target, "HTTPD", len)==0) {
display_httpd(httpd, httpc);
goto quit;
@@ -222,6 +226,13 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
"
%p | \n",
8, httpx, sizeof(HTTPX), httpx);
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->httpc | "
+ "HTTP Client Array (%u) | "
+ "%p |
\n",
+ O(httpc), array_count(&httpd->httpc), httpd->httpc);
+
u = (UCHAR*)&httpd->addr;
http_printf(httpc,
"| +%04X | "
@@ -245,15 +256,29 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
O(listen), httpd->listen);
/* stats file handle removed in 4.0.0 — SMF recording */
+ http_printf(httpc,
+ "
| +%04X | "
+ "httpd->unused_1c | "
+ "(reserved) | "
+ "%p |
\n",
+ O(unused_1c), httpd->unused_1c);
if (httpd->dbg) {
- http_printf(httpc,
+ http_printf(httpc,
"| +%04X | "
"httpd->dbg | "
"HTTP Debug File Handle | "
- "%p |
\n",
+ "%p | \n",
O(dbg), httpd->dbg, httpd->dbg);
}
+ else {
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->dbg | "
+ "HTTP Debug File Handle | "
+ "%p |
\n",
+ O(dbg), httpd->dbg);
+ }
http_printf(httpc,
"| +%04X | "
@@ -327,14 +352,29 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
}
http_printf(httpc, "
\n");
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->unused | "
+ "(reserved) | "
+ "%02X |
\n",
+ O(unused), httpd->unused);
+
if (httpd->socket_thread) {
- http_printf(httpc,
+ http_printf(httpc,
"| +%04X | "
"httpd->socket_thread | "
"Socket Thread Handle | "
- "%p |
\n",
+ "%p | \n",
O(socket_thread), httpd->socket_thread, httpd->socket_thread);
}
+ else {
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->socket_thread | "
+ "Socket Thread Handle | "
+ "%p |
\n",
+ O(socket_thread), httpd->socket_thread);
+ }
http_printf(httpc,
"| +%04X | "
@@ -352,8 +392,8 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
http_printf(httpc,
"
| +%04X | "
- "httpd->httpcgi | "
- "Common Gateway Interface Array | "
+ "httpd->httpcgi | "
+ "Route Array (MOD= programs, LOC= static prefixes) | "
"%p |
\n",
O(httpcgi), httpd->httpcgi, httpd->httpcgi);
@@ -364,6 +404,13 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
"%-24.24s | \n",
O(uptime), ctime64(&httpd->uptime));
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->unused_50 | "
+ "(reserved) | "
+ "%p |
\n",
+ O(unused_50), httpd->unused_50);
+
http_printf(httpc,
"| +%04X | "
"httpd->ufssys | "
@@ -412,16 +459,69 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
"Config Client Timeout Seconds | "
"%u |
\n",
O(cfg_client_timeout), httpd->cfg_client_timeout);
-
+
/* cfg_st_*_max removed in 4.0.0 — replaced by SMF + counters */
-
- http_printf(httpc,
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->smf_level | "
+ "SMF Recording Level | "
+ "%u %s |
\n",
+ O(smf_level), httpd->smf_level,
+ httpd->smf_level == SMF_LEVEL_NONE ? "NONE" :
+ httpd->smf_level == SMF_LEVEL_ERROR ? "ERROR" :
+ httpd->smf_level == SMF_LEVEL_AUTH ? "AUTH" :
+ httpd->smf_level == SMF_LEVEL_ALL ? "ALL" : "(unknown)");
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->smf_type | "
+ "SMF Record Type | "
+ "%u |
\n",
+ O(smf_type), httpd->smf_type);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->unused_69 | "
+ "(reserved) | "
+ "%02X %02X |
\n",
+ O(unused_69), httpd->unused_69[0], httpd->unused_69[1]);
+
+ http_printf(httpc,
"| +%04X | "
"httpd->cfg_cgictx | "
"Config CGI Context Pointers | "
- "%u |
\n",
+ "%u | \n",
O(cfg_cgictx), httpd->cfg_cgictx);
-
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->ufs_enabled | "
+ "UFS Filesystem Enabled | "
+ "%u |
\n",
+ O(ufs_enabled), httpd->ufs_enabled);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->dbg_enabled | "
+ "Debug Output Enabled | "
+ "%u |
\n",
+ O(dbg_enabled), httpd->dbg_enabled);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->bind_tries | "
+ "Socket Bind Retry Count | "
+ "%u |
\n",
+ O(bind_tries), httpd->bind_tries);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->bind_sleep | "
+ "Socket Bind Retry Delay (seconds) | "
+ "%u |
\n",
+ O(bind_sleep), httpd->bind_sleep);
+
http_printf(httpc,
"| +%04X | "
"httpd->total_requests | "
@@ -449,7 +549,14 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
"Active Connections | "
"%u |
\n",
O(active_connections), httpd->active_connections);
-
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->unused_80 | "
+ "(reserved) | "
+ "%p |
\n",
+ O(unused_80), httpd->unused_80);
+
http_printf(httpc,
"| +%04X | "
"httpd->unused_84 | "
@@ -477,7 +584,14 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
"Unix \"like\" File System Handle | "
"%p |
\n",
O(ufs), httpd->ufs);
-
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->unused_94 | "
+ "(reserved) | "
+ "%p |
\n",
+ O(unused_94), httpd->unused_94);
+
http_printf(httpc,
"| +%04X | "
"httpd->self | "
@@ -492,6 +606,84 @@ display_httpd(HTTPD *httpd, HTTPC *httpc)
"%p |
\n",
O(cgictx), httpd->cgictx, (HTTPD_CGICTX_MAX+1)*4, httpd->cgictx);
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->docroot | "
+ "UFS Document Root Prefix | "
+ "\"%.128s\" |
\n",
+ O(docroot), httpd->docroot);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->listen_queue | "
+ "Listen Backlog | "
+ "%u |
\n",
+ O(listen_queue), httpd->listen_queue);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->unused_121 | "
+ "(reserved, alignment padding) | "
+ "%02X %02X %02X |
\n",
+ O(unused_121), httpd->unused_121[0], httpd->unused_121[1],
+ httpd->unused_121[2]);
+
+ /* An empty codepage is the default, not "none": set_defaults() leaves it
+ ** empty and http_xlate_init() reads that as CP037. Say so -- a bare ""
+ ** in the table would not tell a reader which tables are actually loaded. */
+ if (httpd->codepage[0]) {
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->codepage | "
+ "Codepage Name (CODEPAGE=) | "
+ "\"%.16s\" |
\n",
+ O(codepage), httpd->codepage);
+ }
+ else {
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->codepage | "
+ "Codepage Name (CODEPAGE=) | "
+ "\"\" CP037 (default, no CODEPAGE keyword) |
\n",
+ O(codepage));
+ }
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->cfg_keepalive_timeout | "
+ "Keep-Alive Idle Timeout (seconds) | "
+ "%u |
\n",
+ O(cfg_keepalive_timeout), httpd->cfg_keepalive_timeout);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->cfg_keepalive_max | "
+ "Max Requests per Connection | "
+ "%u |
\n",
+ O(cfg_keepalive_max), httpd->cfg_keepalive_max);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->cfg_session_timeout | "
+ "Credential Idle TTL (minutes, 0 = off) | "
+ "%u |
\n",
+ O(cfg_session_timeout), httpd->cfg_session_timeout);
+
+ /* credkey is the blowfish key: report the pointer, never a storage link */
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->credkey | "
+ "Credential Key Handle | "
+ "%p |
\n",
+ O(credkey), httpd->credkey);
+
+ http_printf(httpc,
+ "| +%04X | "
+ "httpd->credarr | "
+ "Credential Array Handle | "
+ "%p |
\n",
+ O(credarr), httpd->credarr);
+
http_printf(httpc, "\n");
send_last(httpd, httpc);
@@ -1403,13 +1595,13 @@ struct ufs {
#ifdef O
#undef O
#endif
-#define O(a) ((unsigned)&(cgi->a) - (unsigned)cgi)
+#define O(a) ((unsigned)&(route->a) - (unsigned)route)
static int
-display_cgi(HTTPD *httpd, HTTPC *httpc)
+display_route(HTTPD *httpd, HTTPC *httpc)
{
int rc = 0;
- HTTPCGI *cgi = NULL;
+ HTTPCGI *route = NULL;
HTTPCGI **array = NULL;
char *memory = NULL;
unsigned n, count;
@@ -1428,20 +1620,20 @@ display_cgi(HTTPD *httpd, HTTPC *httpc)
array = (HTTPCGI**) strtoul(memory, NULL, 16);
count = array_count(&array);
- http_printf(httpc, "CGI Array %p
", array);
+ http_printf(httpc, "Route Array %p
", array);
#if 0
- http_printf(httpc,
+ http_printf(httpc,
"