diff --git a/Makefile b/Makefile index a25ecfe..a2b152b 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,85 @@ MBT_ROOT := mbt include $(MBT_ROOT)/mk/mbt.mk + +# -- Webroot disk (issue #252) -------------------------------------- +# static/ ships as a formatted UFS370 image, because a UFS disk cannot +# travel any of the ways the rest of the package does: SMP has no element +# type for a DSORG=PS/RECFM=U image and must never touch site content, and +# TSO RECEIVE allocates its own target and refuses to merge into an +# existing dataset -- a first-install-only transport for something that +# gets updated. The operator uploads the image with IND$FILE instead; +# docs/installation.md carries the procedure. +# +# There is deliberately no codepage handling here. http_send_file() +# translates UFS files with the hard-coded IBM-1047 table, independent of +# the CODEPAGE= setting (src/httpfile.c), and IBM-1047 is what +# `ufsd-utils cp` writes -- measured byte for byte over the whole file, +# UTF-8 sequences included. Only 0x85 and 0xF7 fail to round-trip, so a +# text file must avoid characters whose UTF-8 encoding contains them. +# +# ufsd-utils is pinned rather than taken from PATH: `create` stamps an +# owner and a timestamp into the image, so the version that builds a +# release artifact is part of what is shipped. Release CI runs the shared +# mbt workflow and cannot install anything of its own, hence the fetch into +# .mbt/tools/. Pass UFSD_UTILS= to build against a different one. +# 1M is 256 blocks of 4096, which the inode list caps at 62 files -- the same +# 62 any disk up to 512 blocks gets, so a larger image buys space, not files. +# Owner and group are metadata: UFSD enforces the MOUNT's OWNER(), not the +# inode owner. They are named anyway, so a release artifact does not carry +# whatever userid happened to build it. +# +# It is built under $(BUILDDIR) and NOT under $(DISTDIR), which is not a +# detail: the release workflow publishes `dist/*` as GitHub Release assets, so +# an image sitting there would appear beside the versioned artifacts under a +# name carrying no version at all -- and as a second copy of what the archive +# already holds. The archive is where it belongs, next to the README and the +# jobs an operator needs with it. +WEBROOT_IMG := $(BUILDDIR)/webroot/httpd-webroot.img +WEBROOT_SRC := static +WEBROOT_SIZE := 1M +WEBROOT_OWNER := IBMUSER +WEBROOT_GROUP := SYSPROG + +UFSD_UTILS_VER := 1.0.1 +UFSD_UTILS_BIN := .mbt/tools/ufsd-utils-$(UFSD_UTILS_VER) +UFSD_UTILS ?= $(UFSD_UTILS_BIN) + +# Only the pinned binary is a prerequisite -- an overridden UFSD_UTILS names +# a program on PATH, which make would try (and fail) to build as a file. +WEBROOT_TOOL_DEP := $(if $(filter $(UFSD_UTILS_BIN),$(UFSD_UTILS)),$(UFSD_UTILS_BIN)) + +$(UFSD_UTILS_BIN): + $(E) "[webroot] fetching ufsd-utils $(UFSD_UTILS_VER)" + @mkdir -p $(dir $@) + @os=`uname -s | tr '[:upper:]' '[:lower:]'`; \ + arch=`uname -m`; \ + case "$$arch" in \ + x86_64|amd64) arch=amd64 ;; \ + arm64|aarch64) arch=arm64 ;; \ + *) echo "[webroot] no ufsd-utils release for $$arch" >&2; exit 1 ;; \ + esac; \ + name=ufsd-utils-$$os-$$arch; \ + curl -sSfL "https://github.com/mvslovers/ufsd-utils/releases/download/v$(UFSD_UTILS_VER)/$$name.tar.gz" \ + | tar xzOf - $$name > $@ || { rm -f $@; exit 1; } + @chmod +x $@ + +webroot: $(WEBROOT_IMG) + +# The directories are prerequisites next to the files: a *new* file is picked +# up either way, because make expands the wildcard when it parses this and the +# file is then newer than the image -- but a *deleted* one leaves an image that +# is newer than everything still there, and it would go on shipping the file. +# A directory's mtime moves when an entry is added or removed, which covers it. +$(WEBROOT_IMG): $(shell find $(WEBROOT_SRC) -type f -o -type d) $(WEBROOT_TOOL_DEP) + $(E) "[webroot] $(notdir $@) ($(WEBROOT_SIZE), from $(WEBROOT_SRC)/)" + @mkdir -p $(dir $@) + @rm -f $@ + $(Q)$(UFSD_UTILS) create $@ --size $(WEBROOT_SIZE) --blksize 4096 \ + --owner $(WEBROOT_OWNER) --group $(WEBROOT_GROUP) > /dev/null + $(Q)$(UFSD_UTILS) cp -r $(WEBROOT_SRC)/ $@:/ + +# Both read it: 'package' ships it in the archive ([distribution] extra), +# and 'dist' re-renders that archive on its own. +package dist: webroot + +.PHONY: webroot diff --git a/TODO.md b/TODO.md index de659e6..4a3c21b 100644 --- a/TODO.md +++ b/TODO.md @@ -11,9 +11,9 @@ stops. `CLAUDE.md` forbids a task list in itself because a copy of a tracker is wrong the first time someone closes something, and the only defence that works is to hold nothing worth going stale. -*Last reconciled against the tracker: 2026-08-23, three issues open (#250 filed -that day; #237 closed by PR #249, and before it #245 by PR #248, #233 by -PR #244, #242 by PR #246 and #243 by PR #247).* +*Last reconciled against the tracker: 2026-08-24, three issues open (#252 filed +and closed the same day by PR #253; before it #250, #237 by PR #249, #245 by +PR #248, #233 by PR #244, #242 by PR #246 and #243 by PR #247).* --- @@ -39,6 +39,14 @@ exit that could end a refused start `CC 0000`; nothing in that thread is open, and `docs/messages.md` §*A refused start* is where the resulting contract is written down. +**Nothing MVS-side blocks the 4.0.0 tag any more.** The release work was never +in the tracker — it is in [`smp-todo.md`](smp-todo.md) — and as of 2026-08-24 it +is done: `THTP400` is free in the CDS *and* the ACDS on both stands (O1), the +full install was rehearsed under the throwaway FMID `TTST400` and cleaned back +off with UCLIN, every step CC 0000 (O2), and the document root ships as a UFS +image (O3, #252). What is left there is a checksum for the release assets and +O4, a convenience sample nobody is blocked on. + --- ### 1 · #250 — does a LINKed module survive in the Job Pack Area? diff --git a/VERSION b/VERSION index fcdb2e1..d9b058f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.0.0 +4.0.0-dev diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..0aa971a --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,1006 @@ +# HTTPD Installation Guide + +This guide installs HTTPD on an MVS 3.8j system (TK4-, TK5, MVS/CE, or a custom +Hercules build) from the release distribution archive. + +The installation is managed by **SMP Release 4** — the SMP that ships with +MVS 3.8j, not SMP/E. That means the system keeps a record of what was +installed, and there is a supported way back out (see +[Removing HTTPD](#12-removing-httpd)). + +## Getting help + +**Report problems as a GitHub issue:** + + +That is the only place bug reports are tracked. Please include the two build +stamps HTTPD writes at startup (`HTTPD000I` and `HTTPD005I`, see +[step 10](#10-start-and-verify)) — they identify the exact build — plus the +console messages and, for an install problem, the job output. + +**Questions and general support** are on Discord: + + +--- + +Two placeholders are used throughout: + +| | | +|---|---| +| `` | the release, e.g. `4.0.0` — it appears in every shipped file name | +| `` | the same release as MVS dataset qualifier, e.g. `V4R0M0` | + +Both are already filled in inside the shipped jobs; you only need them to +recognise which file is which. + +--- + +## 1. What is in the archive + +| File | What it is | +|------|------------| +| `README.md` | this guide | +| `httpd--load.xmit` | the five load modules, as a TSO RECEIVE-ready XMIT | +| `httpd--samplib.xmit` | the sample library: STC procedure and configuration pattern | +| `httpd--alloc.jcl` | allocates the datasets SMP installs into — **run once** | +| `httpd--inst.jcl` | receives everything and installs it — repeatable | +| `httpd-webroot.img` | the document root as a UFS370 disk image — outside SMP, see [step 9](#9-the-webroot-disk) | + +The load library holds five modules: + +| Module | What it is | +|--------|------------| +| `HTTPD` | the server itself — the only one a started task names | +| `HTTPDSRV` | display module: server control blocks (`/.dsrv`) | +| `HTTPDM` | display module: arbitrary storage (`/.dm`) | +| `HTTPDMTT` | display module: Master Trace Table, i.e. the console log (`/.dmtt`) | +| `ABEND0C1` | a deliberate-abend diagnostic used to exercise storage reclaim | + +**Only `HTTPD` runs unless you ask for the others.** Since 4.0.0 no module is +reachable unless a `MOD=` line in the configuration member names it, so the four +above are inert on a fresh install. That is deliberate: `/.dm` and `/.dmtt` hand +out arbitrary storage and the console log, and `ABEND0C1` abends on purpose. +See [step 7](#7-install-the-procedure-and-the-configuration). + +> **The four are diagnostic, debugging and development tools, and they are on +> their way out.** They ship in 4.0.x so that a problem on a live system can be +> looked at without a private build — reading a control block beats guessing at +> one, and `/.dmtt` is often the fastest way to the console log. They are not +> production endpoints, and **the next release will not deliver them**: a server +> whose job is hosting an API has no business shipping an arbitrary-storage +> reader alongside it. Build them from source when you need them. +> +> Nothing depends on them. `HTTPD` is the only module the started task names, +> and removing the other four changes no behaviour of a server that never +> routed to them. + +The sample library holds three members: `HTTPD` (the started task procedure), +`HTTPPRM0` (the configuration pattern) and `HTTPWEBR` (a job that allocates the +dataset the webroot image is uploaded to). + +Where everything ends up: + +``` +HTTPD..LINKLIB the load modules -- point STEPLIB here +HTTPD..SAMPLIB the patterns you copy from in step 7 +HTTPD..AHTTPLOD SMP's distribution library, the base a RESTORE returns to +HTTPD..WEBROOT.UFS the document root, if you upload the shipped one (step 9) +``` + +The last one is not SMP's: it is a filesystem image, and it holds site content +that an `APPLY` must never touch. Step 9 is where it comes from. + +--- + +## 2. Prerequisites + +- **MVS 3.8j** on Hercules, up and IPLed, with SMP 4 usable (the `SMPREC` and + `SMPAPP` procedures and the `SYS1.SMP*` datasets — present on TK4-/TK5 and + MVS/CE as shipped). +- **RAKF** — see [Authorisation](#authorisation) below. HTTPD does not start + without either RAKF or an APF list entry, and no configuration option turns + that off. +- **UFSD**, only if you want to serve static files — see below. The archive + ships a document root for it as a disk image (step 9). +- **mvsMF**, only if you want the REST API the shipped configuration routes to + — see below. +- A userid authorised to submit jobs, to update a PROCLIB in the started-task + concatenation, and to update a PARMLIB. +- A way to upload a **binary** file to the host — see step 3. +- Roughly 15 cylinders of DASD: 10 for the two libraries the allocation job + creates, the rest for the two TSO RECEIVE targets. One of those, the staging + library, is scratched again by the last step of the install job. + +### UFSD is optional, and what it costs you is static files + +HTTPD serves static content from the **UFSD filesystem and nowhere else** — the +DD-based document root of 3.3.x is gone (4.0.0). `DOCROOT` names a UFS path, so +without the UFSD started task there is nothing to serve statically. + +Nothing about the *install* needs it: the SYSMOD declares no prerequisite, +`libufs` is linked statically into the load modules, and a UFS that does not +initialise is a warning, not a failure: + +``` +HTTPD044W UNABLE TO INITIALIZE FILE SYSTEM +``` + +The server starts, listens, and every `MOD=` route — mvsMF, the display modules +— works normally. Only static file requests fail. A CGI-only HTTPD is an +ordinary deployment. + +Note that `HTTPD001I` still reads `READY - SERVING /www` afterwards: it names +the **configured** document root, not a working filesystem. `HTTPD044W` is the +line that tells you the filesystem is not there, and it is written earlier in +the start. On a deployment that will never have UFSD, `UFS=0` in the +configuration member skips the initialisation altogether and the warning with +it. + +With UFSD there, the archive has a document root ready for it — +`httpd-webroot.img`, a formatted filesystem holding the welcome page, which +[step 9](#9-the-webroot-disk) uploads and mounts. + +Install HTTPD now and add UFSD later if you want it: +, with its own guide at +[ufsd/docs/installation.md](https://github.com/mvslovers/ufsd/blob/main/docs/installation.md). + +Note that UFSD has to be **running**, not just installed — it is a started task +HTTPD talks to across address spaces. + +### mvsMF is what the shipped configuration expects + +`HTTPPRM0` ships with three active routes, all of them to **mvsMF** — the +z/OSMF-compatible REST API, which is a separate product: + +``` +MOD=MVSMF /zosmf/info AUTH=NONE +MOD=MVSMF /zosmf/services/authenticate AUTH=NONE +MOD=MVSMF /zosmf/* AUTH=TOKEN +``` + +The first two are public on purpose — the anonymous reachability probe and the +token login endpoint do their own auth, so the gate must not challenge them. +The catch-all behind them is where the datasets, jobs and files live, and it is +gated. First match wins, so the order matters. + +If mvsMF is not installed, HTTPD starts and those routes register normally — the +program is not looked for until a request first matches. The failure then shows +up per request as + +``` +HTTPD908E EXTERNAL PROGRAM MVSMF could not be loaded (not found in STEPLIB?) +``` + +Comment the three lines out, or install mvsMF: +. Its load library has to be in the +STC's STEPLIB concatenation — HTTPD dispatches a module through the MVS LINK +SVC, which searches the task's own libraries. + +### RAKF is required for logins + +HTTPD has no user database of its own. Every login — the HTML form, HTTP Basic, +and the token endpoint — is verified by RAKF (RACINIT, SVC 244), and a `RES=` +route option is checked against a RAKF profile under the logged-in user's +identity. **On a system without RAKF nobody can log in.** + +A server whose routes are all `AUTH=NONE` never authenticates anybody and does +not need RAKF for that — but it still needs it for authorisation, below. + +The setup — the `HTTPD` user and its group — is step 8. + +### Authorisation + +HTTPD is link-edited `AC(1)` and needs to be APF-authorised: without it the LINK +SVC that dispatches every module and the RACF services behind every login are +both unavailable. + +It obtains authorisation itself at startup, which goes through **SVC 244 — and +that comes from RAKF**. The clean alternative is to add `HTTPD..LINKLIB` to +the APF list in `SYS1.PARMLIB(IEAAPF00)`. On MVS 3.8j the APF list is only read +at IPL, and the library name carries the version — so this is one IPL per +release, not one IPL ever. + +**With neither, HTTPD does not start.** Unlike FTPD, which warns and keeps +running, HTTPD treats this as fatal: + +``` +HTTPD012E HTTPD UNABLE TO DYNAMICALLY OBTAIN APF AUTHORIZATION +``` + +and `main()` returns the failing setup's return code without starting a +listener. Resolve this before step 5 — an install that completes cleanly will +still not start. + +The two routes are not equivalent in one respect that matters if you ever have +to debug HTTPD. An **APF entry** authorises the job step *before* program fetch, +so MVS loads HTTPD into subpool 252 **key 0** — authorised code must not be +patchable by problem-key code. **SVC 244** sets `JSCBAUTH` *after* the fetch and +cannot relabel storage that is already allocated, so the module stays key 8. +HTTPD runs problem state key 8 either way, which means that under an APF entry +it must never store into its own module storage. It does not, as of 4.0.0: the +server block is `main()`'s automatic storage, and the state that used to be +file-scope — the codepage pair, the saved STC ACEE, the Basic realm — lives +there with it (issue #197). + +You do not have to work out which route you got: HTTPD says so at startup, on +the line after the version banner. + +``` +HTTPD002I AUTHORIZED BY LIBRARY (MODULE KEY 0) APF list entry +HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8) SVC 244, from RAKF +``` + +The key is inferred from the route rather than measured — an authorised job step +has its module fetched key 0, an unauthorised one key 8, and SVC 244 arrives too +late to change either. Neither line is a warning: both routes end in an +authorised started task, and which one you want is a site decision. Quote the +line in a bug report — it decides the storage key HTTPD's own module runs in. + +--- + +## 3. Upload the two XMIT files + +Both `.xmit` files are EBCDIC NETDATA streams. Upload them **in binary** — no +ASCII/EBCDIC translation, no CRLF conversion — into sequential datasets with +`RECFM=FB LRECL=80 BLKSIZE=3120`. + +**The dataset names are yours to choose.** The install job names them on its +own `RECEIVE` commands, so nothing depends on what you call them; you will +enter them once in step 6. This guide uses `IBMUSER.HTTPD.LOAD.XMIT` and +`IBMUSER.HTTPD.SAMP.XMIT`. + +Whether you have to allocate them first depends on the upload path: **FTPD** +and **IND$FILE** create the dataset from the attributes you supply, mvsMF and +most other FTP servers need it to exist. Where a method says *pre-allocate*, +allocate both as `DSORG=PS, RECFM=FB, LRECL=80, BLKSIZE=3120` — 50 tracks +primary for the load XMIT (roughly 650 KB), 5 for the sample library, secondary +about 10% of each. Method b) below shows this with zowe; TSO or ISPF 3.2 does +the same job. + +The third file that has to reach the host, `httpd-webroot.img`, is **not** one of +these: it is a filesystem image with its own record format, it is not received +by anything, and it is only needed once the server runs. It has its own step — +[step 9](#9-the-webroot-disk) — and nothing here applies to it. + +Pick **one** method: + +### a) FTP — mvslovers/ftpd (no pre-allocation) + +FTPD creates the datasets from the attributes it is given (default port 2121): + +``` +ftp -P 2121 your-mvs-host +> binary +> put httpd--load.xmit 'IBMUSER.HTTPD.LOAD.XMIT' +> put httpd--samplib.xmit 'IBMUSER.HTTPD.SAMP.XMIT' +> quit +``` + +This is also the upload path to prefer when you are **upgrading**: the HTTPD you +are replacing is the one serving mvsMF, so uploading through mvsMF means +uploading through the thing you are about to stop. + +### b) mvsMF (z/OSMF-compatible REST API), via zowe + +Only when a *different* server is up, or you are not replacing this one. It +requires the datasets to **exist first**, so allocate them, here with zowe +itself: + +``` +zowe files create ps "IBMUSER.HTTPD.LOAD.XMIT" \ + --recfm FB --lrecl 80 --blksize 3120 --size 50TRK +zowe files create ps "IBMUSER.HTTPD.SAMP.XMIT" \ + --recfm FB --lrecl 80 --blksize 3120 --size 5TRK +``` + +then upload: + +``` +zowe zos-files upload file-to-data-set httpd--load.xmit \ + "IBMUSER.HTTPD.LOAD.XMIT" --binary +zowe zos-files upload file-to-data-set httpd--samplib.xmit \ + "IBMUSER.HTTPD.SAMP.XMIT" --binary +``` + +`--size` sets the primary allocation and gives you a secondary of about 10% of +it. The values above are for a 3390 and leave room to spare — the load XMIT is +roughly 650 KB, the sample library well under 20 KB. On a smaller device, or if +you hit an `SB37`, raise them. + +### c) Another FTP server (pre-allocate) + +*Pre-allocate* both datasets, then transfer **binary** into them — a plain +`binary` + `put`. Any `SITE` keywords for dataset attributes vary between +MVS 3.8j TCP/IP stacks (they are **not** the z/OS syntax); pre-allocating makes +them unnecessary. + +### d) IND$FILE (3270 emulator) + +No pre-allocation needed. Use your emulator's file transfer in **binary** mode +(no ASCII/CRLF translation) with `RECFM=FB LRECL=80 BLKSIZE=3120`. The exact +option syntax is client-dependent — consult its file-transfer documentation. +[Step 9](#9-the-webroot-disk) walks an IND$FILE upload through in full, for the +webroot image; the same emulator handling applies here. + +--- + +## 4. Allocate the product datasets + +Submit `httpd--alloc.jcl` unchanged, unless you want a specific unit or +volume — the `UNIT=SYSDA` and the space on each DD are the only things worth +editing. + +It creates `HTTPD..LINKLIB` and `HTTPD..AHTTPLOD` and nothing else. +The libraries the next step receives into are deliberately **not** allocated +here: TSO RECEIVE creates its own target and refuses to merge into an existing +dataset. + +Expect `COND CODE 0000`. + +> **Run this once.** There is no DELETE step in it, on purpose. After the +> install, `HTTPD..AHTTPLOD` holds SMP's accepted copy of the modules; a +> re-run that scratched it would leave the SMP inventory reporting an install +> that is no longer on the system, and nothing would say so. To start over, +> reject the SYSMOD first — see [Removing HTTPD](#12-removing-httpd). + +--- + +## 5. Stop a running HTTPD + +Only relevant when you are upgrading. The APPLY writes into +`HTTPD..LINKLIB`, and each release has its own — so a running *older* HTTPD +does not block the install. It does, however, keep running the old modules until +you restart it (step 10) against the procedure you copy in step 7. + +``` +/P HTTPD +``` + +Two things worth knowing before you do: + +- **HTTPD refuses to start a second instance on a port already served** + (`HTTPD037E`), so a forgotten `/P` becomes a refused start rather than a + confusing half-working one. That check is keyed by port, so a deliberate + second server on another port still starts — which is a good way to try a new + build without touching the running one. +- If mvsMF is your upload or console channel, stopping HTTPD takes it with it. + Have another way in (FTPD, TSO, the Hercules console) before you stop the + server. + +--- + +## 6. Install + +Open `httpd--inst.jcl` and replace the two placeholder dataset names +with what you uploaded in step 3: + +``` + RECEIVE INDSN('CHANGE.ME.HTTPLOAD') - <- IBMUSER.HTTPD.LOAD.XMIT + RECEIVE INDSN('CHANGE.ME.SAMPLIB') - <- IBMUSER.HTTPD.SAMP.XMIT +``` + +Those are the only lines you have to change. Submit it. + +The job runs eight steps, each conditional on the one before, so it stops at +the first failure rather than building on it: + +| Step | What it does | +|------|--------------| +| `DELOLD` | scratches the RECEIVE targets, so the job can be re-run | +| `RECV1` | load XMIT → `HTTPD..HTTPLOAD` (a staging library) | +| `RECV2` | samplib XMIT → `HTTPD..SAMPLIB` | +| `RECV` | receives the SYSMOD into the SMP inventory | +| `APPLYCHK` | dry run — `APPLY` only proceeds if this ends RC 0 | +| `APPLY` | copies the five load modules into `HTTPD..LINKLIB` | +| `ACCEPT` | makes this level the base a later `RESTORE` returns to | +| `CLEANUP` | scratches the staging library, which is now spent | + +The SYSMOD travels inline in the job — there is no third file to upload. + +**What a good run looks like.** Every step `COND CODE 0000`, and in the SMP +output: + +``` +HMA3930 SYSMOD THTP400 SUCCESSFULLY RECEIVED +HMA2380 COPY SUCCESSFUL - MOD=HTTPD - LMOD=HTTPD - LIBRARY=LINKLIB + - RETURN CODE=00 +HMA2050 APPLY PROCESSING COMPLETED - HIGHEST RETURN CODE IS 00 +``` + +There is one `HMA2380` line **per module** — five of them. A run that copies +fewer has lost one, and the `APPLY` still ends RC 00, so count them. + +Then check `HTTPD..LINKLIB` really holds all five (ISPF 3.4). Do look: SMP +reports the library by **ddname**, and a ddname says nothing about which dataset +was behind it. + +SMP **copies** these modules rather than re-binding them, which is why the +`AC(1)` authorisation code on `HTTPD` and every module's custom entry point are +exactly what the build produced. + +--- + +## 7. Install the procedure and the configuration + +SMP does not touch your PROCLIB or PARMLIB, and that is deliberate: **the +product owns the patterns, your system owns the copies.** If SMP owned the +running procedure, every change you made to it would be silently replaced by +the next update. So this step is yours, and it is the one place where you have +to read what you are copying. + +Copy from `HTTPD..SAMPLIB`: + +| Member | Copy to | Adjust | +|--------|---------|--------| +| `HTTPD` | a PROCLIB in the started-task concatenation | usually not — `STEPLIB` already names this release's LINKLIB. Add mvsMF's library to it if you use mvsMF | +| `HTTPPRM0` | a PARMLIB | **yes — see below** | + +`SYS2.PROCLIB` is the usual home for the procedure. + +The third member, `HTTPWEBR`, is not copied anywhere: it is a job you submit +straight from the sample library when you get to [step 9](#9-the-webroot-disk). + +### Which PARMLIB + +The shipped procedure defaults to `D='SYS2.PARMLIB'`. **On TK5 that dataset +does not exist** — put the member in `SYS1.PARMLIB` and either edit `D=` in +your copy of the procedure, or override it when starting: + +``` +/S HTTPD,D='SYS1.PARMLIB' +``` + +On MVS/CE, `SYS2.PARMLIB` exists and the default is fine. `M=` selects the +member (default `HTTPPRM0`), so a second configuration can live alongside the +first. + +### The STC identity — `STCUSER` and `STCGRP` + +The shipped procedure logs the server on to `HTTPD/USER` at startup: + +``` +// STCUSER=HTTPD, +// STCGRP=USER +//HTTPD EXEC PGM=HTTPD,REGION=8M,TIME=1440, +// PARM='STCUSER=&STCUSER STCGROUP=&STCGRP' +``` + +RAKF has no started-procedures table: it decides only *that* a caller is an STC, +never *which* one, and hands every started task the same `STC/STCGROUP` account +— which on a stock profile set holds **ALTER on every dataset on the system**. +So HTTPD replaces it with a dedicated identity of its own (issue #177). Define +that userid in step 8. + +Two traps if you write your own procedure: + +- The START symbolic is `STCGRP` while the keyword the program parses is + `STCGROUP=` — a JCL symbolic parameter name is at most seven characters. +- `__start()` splits the PARM into `argv` on **blanks, not commas**. Joining the + two keywords with a comma hands the whole string to `STCUSER=`, and the group + is then silently wrong. + +If the logon fails the server **continues** on the inherited identity and says +so with `HTTPD004W`, so a profile typo is not an outage — but it is also not +what you asked for. Watch for that message where you expect `HTTPD004I`. + +### The DD statements — do not add SYSPRINT, SYSTERM or SYSIN + +HTTPD writes its own STDOUT/STDERR and reads STDIN through private DDs +(`HTTPDOUT`, `HTTPDERR`, `HTTPDIN`) so that a module cannot scribble into the +JES datasets an operator reads. The three conventional names are **refused**, +before `main()` runs: + +``` +HTTPD014E SYSPRINT DD NOT ALLOWED, HTTPD USES HTTPDOUT FOR STDOUT +HTTPD015E SYSTERM DD NOT ALLOWED, HTTPD USES HTTPDERR FOR STDERR +HTTPD016E SYSIN DD NOT ALLOWED, HTTPD USES HTTPDIN FOR STDIN +``` + +The shipped procedure already has the right set. Leave it alone unless you know +why you are changing it. + +### Editing HTTPPRM0 + +Every keyword in the shipped member is commented out, so a member copied +unchanged runs on the built-in defaults — **port 8080, document root `/www`** — +plus the three active `MOD=MVSMF` route lines. Four things are worth a look +before the first start: + +``` +PORT=8080 +DOCROOT=/www +REALM=MVS Development System +MOD=MVSMF /zosmf/* AUTH=TOKEN +``` + +- `PORT` is 8080 by default. Nothing on a stock MVS 3.8j contends for it. +- `DOCROOT` is a **UFS path**, and it must exist in a mounted UFSD filesystem. + Without UFSD, leave it and expect `HTTPD044W` (step 2). +- `REALM` defaults to the system's SMF ID — unambiguous, but meaningless to a + user. It is what a browser shows in its Basic credential dialog. +- The `MOD=MVSMF` lines are the ones to review first. Comment them out if you + have no mvsMF. + +**Read the auth model before you publish anything.** Authentication is declared +per route and nowhere else — there is no server-wide login keyword, and the +`LOGIN` keyword of earlier versions is retired: + +``` +MOD=MVSMF /zosmf/* AUTH=TOKEN program route, gated +LOC=/admin/* AUTH=BASIC static prefix, gated +LOC=/* AUTH=NONE static prefix, public on purpose +``` + +- **A route without `AUTH=` is public**, and so is any path no route claims at + all. There is no global default left to fall back on. +- The modes are `NONE`, `FORM` (HTML login form), `BASIC` (401 + + `WWW-Authenticate`) and `TOKEN` (a bare 401 for API clients that handle it + themselves). A value that is none of these refuses the route and the server + **does not start** — `HTTPD411E` then `HTTPD420E`. That is on purpose: a + policy silently weakened is worse than a server that will not start. +- `AUTH=` does not select which credentials are accepted. Every route accepts + all of them, because they are resolved before the route is matched. It selects + *whether* a login is needed and *how* a missing one is challenged. +- A member still carrying `LOGIN=ALL` / `CGI` / `GET` / `HEAD` / `POST` is a + **fatal** configuration error (`HTTPD048E`), because ignoring it would publish + every route without an `AUTH=` of its own. `LOGIN=NONE` was the default and is + merely warned about (`HTTPD048W`). Convert to `AUTH=` per route, then delete + the line. + +The display modules deserve their own warning. `MOD=HTTPDM /.dm` with no `AUTH=` +hands anyone who can reach the port arbitrary storage reads, and `/.dmtt` hands +them the console log. They are debugging tools that will not be shipped after +4.0.x (step 1) — enable them while you are diagnosing something, and gate them +whenever you do: + +``` +MOD=HTTPDSRV /.dsrv AUTH=FORM +MOD=HTTPDM /.dm AUTH=BASIC +MOD=HTTPDMTT /.dmtt AUTH=BASIC +``` + +A `#` or `*` in the first column comments a line out. The full keyword reference +is [configuration.md](https://github.com/mvslovers/httpd/blob/main/docs/configuration.md). + +--- + +## 8. Set up RAKF + +Without this, HTTPD cannot authorise itself and does not start (step 2), and no +client can log in. + +1. Add the `HTTPD` user to `SYS1.SECURE.CNTL(USERS)`, default group `USER`: + + ``` + HTTPD USER DEFAULT-GROUP(USER) + ``` + + This is the identity from `STCUSER`/`STCGRP` in step 7. It needs **READ** on + whatever HTTPD itself opens before a client identity exists: the PARMLIB + member, the document root, the log DDs. + + It does **not** need `FACILITY SVC244`. HTTPD acquires APF authorisation at + start and releases it at `/P HTTPD`, and it restores the STC account it + started under immediately before the release — so both SVC 244 calls are made + by the same account, rather than requiring the profile on every system. + +2. Optionally define profiles for any route that carries `RES=class:resource`. + A `RES=` naming a resource **no profile covers** is not an error and does not + deny: SAF calls an unprotected resource allowed, so the authorisation stage + does nothing and only the `AUTH=` stage is left. HTTPD says so at startup: + + ``` + HTTPD425W NO PROFILE FOR FACILITY:HTTPD.ADMIN -- /admin/* NOT GATED + ``` + +3. Reload: `/F RAKF,RELOAD` + +--- + +## 9. The webroot disk + +`DOCROOT` names a path in the UFSD filesystem, so the pages HTTPD serves live on +a UFS disk and not in a library. The archive carries one ready to use: +`httpd-webroot.img`, a 1 MB UFS370 filesystem holding the welcome page. Three +steps put it on the system — allocate, upload, mount — and none of them belongs +to SMP. + +**Why it travels on its own.** SMP has no element type for a `DSORG=PS`, +`RECFM=U` image, and site content is precisely what an `APPLY` must never +touch. TSO RECEIVE is no better: it allocates its own target and refuses to +merge into an existing dataset, which makes it a first-install-only transport +for something you will replace. So the disk ships as a plain file and goes up +over **IND$FILE**, which needs nothing on the target beyond a 3270 session. + +This step needs **UFSD** (step 2). Without it there is nothing to mount and +nothing static to serve; every `MOD=` route works regardless. + +**Already have a webroot?** Then read this for the mechanics and keep your own +disk. Nothing here can overwrite it — the dataset is named for this release. + +### a) Allocate the dataset + +`HTTPD..SAMPLIB(HTTPWEBR)` does exactly this and nothing else: + +```jcl +//ALLOC EXEC PGM=IEFBR14 +//WEBROOT DD DSN=HTTPD..WEBROOT.UFS,DISP=(NEW,CATLG), +// UNIT=SYSDA,SPACE=(4096,256), +// DCB=(DSORG=PS,RECFM=U,BLKSIZE=4096) +``` + +**Run it before the upload, not after.** IND$FILE allocates an *existing* +dataset `DISP=SHR` and takes the DCB from its label; only when the dataset is +missing does it invent one of its own. Allocating it here is what pins +`BLKSIZE=4096` — the block size the image was formatted with, and the one thing +about the transfer that has to be right. + +`SPACE` is counted in blocks of 4096, so 256 blocks is the megabyte the image +occupies. **Primary extent only**: the filesystem is addressed by block number +within the primary extent, so anything in a secondary would never be reached. + +### b) Upload it with IND$FILE + +The image must arrive byte for byte: **binary**, no ASCII translation, no CRLF +conversion. With the dataset allocated, the command carries no options at all — +which also makes it independent of which IND$FILE build your system has: + +``` +IND$FILE PUT HTTPD.V4R0M0.WEBROOT.UFS +``` + +Binary is IND$FILE's default; `ASCII` and `CRLF` are what you would have to add +to break it. + +Most emulators issue that command for you. x3270, c3270 and wc3270 take it as +one action, and their file-transfer dialog asks for the same fields: + +``` +Transfer(Direction=send, + LocalFile=httpd-webroot.img, + HostFile=HTTPD.V4R0M0.WEBROOT.UFS, + Host=tso, Mode=binary, Exist=replace) +``` + +Leave `Recfm`, `Lrecl`, `Blksize` and the space fields **unset**: the dataset +exists, so its label decides, and an emulator's allocation defaults are not +worth auditing. + +Either way the session has to sit on an input field that can accept the command +before the transfer starts — a cleared TSO **READY** screen, or ISPF option 6. +The emulator types `IND$FILE` into whatever field the cursor is on; it does not +find one for you. + +If you would rather let IND$FILE create the dataset, it needs to be told +everything the allocation job otherwise says: + +``` +IND$FILE PUT HTTPD.V4R0M0.WEBROOT.UFS (RECFM(U) BLKSIZE(4096) TRACKS SPACE(70) +``` + +70 tracks holds a megabyte even on a 3350, where a 4096-byte block packs four to +a track; a 3380 or 3390 needs half that. There is no secondary quantity on +purpose (see above). + +A megabyte over a 3270 session takes minutes rather than seconds. That is the +transfer, not a hang. + +### c) Mount it + +The mount belongs to **UFSD**, not to HTTPD. In its Parmlib member: + +``` +MOUNT DSN(HTTPD.V4R0M0.WEBROOT.UFS) PATH(/www) MODE(RO) +``` + +or, without a restart — note that the operator command spells its operands with +`=` and commas where the Parmlib statement uses parentheses: + +``` +/F UFSD,MOUNT DSN=HTTPD.V4R0M0.WEBROOT.UFS,PATH=/www,MODE=RO +``` + +`/www` is HTTPD's default `DOCROOT`; if you changed it in `HTTPPRM0`, mount it +there instead. `MODE(RO)` is the default and the right setting here — the +shipped disk is product content and nothing in the server writes to it. + +**UFSD first, then HTTPD.** The filesystem has to be up and mounted before the +server that serves from it — a runtime dependency SMP cannot express, which is +why the SYSMOD declares no prerequisite on UFSD at all. + +### d) Verify + +``` +/F UFSD,MOUNT LIST +curl -v http://your-mvs-host:8080/ +``` + +`/` serves `/www/index.html`: HTTPD appends `index.html` to a request for a +directory. + +| What you see | What it means | +|---|---| +| `UFSD062E SUPERBLOCK VALIDATION FAILED FOR …` | the dataset's `BLKSIZE` is not the image's 4096 — or the transfer was not binary | +| `HTTPD044W UNABLE TO INITIALIZE FILE SYSTEM` | UFSD is not running (step 2) | +| `404` on `/` | mounted somewhere other than `DOCROOT` | +| a page of mojibake | the transfer translated the bytes; upload again in binary | + +### Replacing the content later + +**Unmount before you re-upload.** IND$FILE allocates `DISP=SHR`, so it will +happily rewrite a dataset UFSD has open — under the server's own buffers: + +``` +/F UFSD,UNMOUNT PATH=/www + ... upload ... +/F UFSD,MOUNT DSN=HTTPD.V4R0M0.WEBROOT.UFS,PATH=/www,MODE=RO +``` + +For pages of your own, build the image on your workstation with +[ufsd-utils](https://github.com/mvslovers/ufsd-utils) and upload it the same way: + +``` +ufsd-utils create mysite.img --size 1M --blksize 4096 --owner IBMUSER +ufsd-utils cp -r ./mysite/ mysite.img:/ +``` + +Text files are stored in **IBM-1047**, which is what `ufsd-utils` writes by +default and what HTTPD's static file path translates back with — a UFS file is +converted with that table regardless of the `CODEPAGE=` setting, which governs +MVS datasets and the server's own output. So there is nothing to configure here, +and nothing to match up. + +With mvsMF installed there is a second route for single files — +`PUT /zosmf/restfiles/fs/www/index.html` — but only into a filesystem mounted +`MODE(RW)`, which the shipped disk is not. + +One thing the image is not: reproducible. It carries its own creation +timestamp, so two builds of the same commit differ byte for byte. Compare it +against the archive you downloaded, never against one you rebuilt. + +--- + +## 10. Start and verify + +``` +/S HTTPD default member (HTTPPRM0) +/S HTTPD,M=HTTPPRM1 alternate config member +/S HTTPD,D='SYS1.PARMLIB' alternate PARMLIB -- TK5, see step 7 +``` + +A healthy start ends in `HTTPD001I … READY`: + +``` +HTTPD000I HTTPD (A69A370) STARTING +HTTPD005I LIBC370 1.0.3 (58767B3) +HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8) +HTTPD004I STC IDENTITY SET TO HTTPD/USER VIA RACINIT +HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/info +HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/* +HTTPD054I LISTENING ON ANY PORT 8080 +HTTPD061I STARTING SOCKET THREAD TCB(9CD9D0) TASK(10CFC8) STACKSIZE(32768) +HTTPD061I STARTING WORKER(11ED48) TCB(9CD420) TASK(120FC8) STACKSIZE(65536) +HTTPD001I HTTPD READY - SERVING /www +``` + +That transcript is a system with UFSD running. Without it, `HTTPD044W` appears +between `HTTPD004I` and `HTTPD054I` — and the last line still reads +`SERVING /www`, because it names the configured document root rather than a +working filesystem (step 2). + +The two build stamps identify exactly what is running: `HTTPD000I` gives the +version and the commit it was built from, `HTTPD005I` the libc370 it was linked +against — quote both in a bug report. A build made from a modified working tree +marks its hash `-DIRTY` and adds `HTTPD006W`; a released build never does. + +**The configuration is not echoed at startup** — not the member, not the +codepage, not the task limits. `/F HTTPD,D CONFIG` reports all of it on demand, +and that one look after the first start is worth taking: it prints what HTTPD +*parsed*, not what the member says, so a typo that fell back to a default is +visible there and nowhere else. + +**A refused start is recognised by the step return code, never by a console +string.** A start refused by initialisation ends `CC 0008`; a clean `/P HTTPD` +ends `CC 0000`. The first console line is the cause (`HTTPD037E`, `HTTPD028E`, +`HTTPD030E`, `HTTPD031E`, `HTTPD420E`, `HTTPD033E` or `HTTPD090E`); everything +under it is the ordinary stop sequence and says nothing about what went wrong. +The full contract is in +[messages.md](https://github.com/mvslovers/httpd/blob/main/docs/messages.md). + +Then reach it from a client: + +``` +curl -v http://your-mvs-host:8080/zosmf/info mvsMF installed +curl -v http://your-mvs-host:8080/ static, needs UFSD +``` + +Operator commands: + +``` +/F HTTPD,DISPLAY Config the configuration actually in effect (D C) +/F HTTPD,DISPLAY Stats request/error/byte counters, SMF level (D S) +/F HTTPD,DISPLAY Threads the worker pool, block by block (D T) +/F HTTPD,DISPLAY Ports the listening port (D P) +/F HTTPD,DISPLAY Login who is logged in (D L) +/F HTTPD,DISPLAY Version build stamps again (D V) +/F HTTPD,DISPLAY TIme server time and offset (D TI) +/F HTTPD,DISPLAY Memory xxxxxx[,nnn] (D M) +/F HTTPD,SET MIntask n resize the worker pool +/F HTTPD,SET MAxtask n +/F HTTPD,SET Stats NONE|ERROR|AUTH|ALL [RESET] +/F HTTPD,HELP every MODIFY command +/P HTTPD orderly stop (HTTPD099I SHUTDOWN COMPLETE) +``` + +Commands abbreviate to the capitalised prefix, which is why `DISPLAY Threads` +and `DISPLAY TIme` need one and two letters respectively. + +--- + +## 11. What HTTPD serves + +Two kinds of route, both declared in the configuration member, both carrying the +same per-route auth policy: + +- **`MOD=` — a program.** An MVS load module dispatched on the worker thread's + own TCB through the LINK SVC, once per request. mvsMF is the one that matters + in practice; the three display modules ship with the server. +- **`LOC=` — a static prefix.** No program: the request falls through to the + static file handler on `DOCROOT`, but the prefix still carries `AUTH=`/`RES=`. + This is how a static subtree gets a login without a CGI. + +Routes are tested **in order** and matched exactly unless they carry a `*`, so +list specific prefixes before a catch-all — a `LOC=/*` placed above +`MOD=MVSMF /zosmf/*` shadows it. + +HTTP/1.1 with persistent connections and chunked transfer encoding is the +default for 1.1 clients; 1.0 clients always get `Connection: close`. + +The full reference is +[configuration.md](https://github.com/mvslovers/httpd/blob/main/docs/configuration.md); +writing your own module is +[development.md](https://github.com/mvslovers/httpd/blob/main/docs/development.md). + +--- + +## 12. Removing HTTPD + +Because the installation is SMP-managed, there is a defined way back — but it is +**not** the `RESTORE` followed by `REJECT` that SMP documentation leads you to +expect. The install job accepts the FMID in the same run as the APPLY, and an +accepted function SYSMOD refuses both: `RESTORE` because it was accepted, +`REJECT` because accepting removed from `SYS1.SMPPTS` the control statements it +works from. The route that does work is a `UCLIN` job. + +What this release put on the system: + +| | | +|---|---| +| FMID | `THTP400` | +| Load modules | `HTTPD`, `HTTPDSRV`, `HTTPDM`, `HTTPDMTT`, `ABEND0C1` | +| Target library | `HTTPD..LINKLIB` | +| Distribution library | `HTTPD..AHTTPLOD` | +| Sample library | `HTTPD..SAMPLIB` | + +**1. Stop the server:** `/P HTTPD` + +**2. Cut the FMID out of the SMP inventory.** Submit this — it edits the CDS and +the ACDS and touches no library: + +``` +//HTTPDUCL JOB (SYS),'HTTPD UNINSTALL', +// CLASS=A,MSGCLASS=H,MSGLEVEL=(1,1), +// REGION=4096K +//UCLIN EXEC SMPAPP +//SMPCNTL DD * + UCLIN CDS . + DEL SYSMOD(THTP400) MOD(HTTPD) . + DEL SYSMOD(THTP400) MOD(HTTPDSRV) . + DEL SYSMOD(THTP400) MOD(HTTPDM) . + DEL SYSMOD(THTP400) MOD(HTTPDMTT) . + DEL SYSMOD(THTP400) MOD(ABEND0C1) . + DEL MOD(HTTPD) . + DEL MOD(HTTPDSRV) . + DEL MOD(HTTPDM) . + DEL MOD(HTTPDMTT) . + DEL MOD(ABEND0C1) . + DEL LMOD(HTTPD) . + DEL LMOD(HTTPDSRV) . + DEL LMOD(HTTPDM) . + DEL LMOD(HTTPDMTT) . + DEL LMOD(ABEND0C1) . + DEL SYSMOD(THTP400) . + ENDUCL . + UCLIN ACDS . + DEL SYSMOD(THTP400) MOD(HTTPD) . + DEL SYSMOD(THTP400) MOD(HTTPDSRV) . + DEL SYSMOD(THTP400) MOD(HTTPDM) . + DEL SYSMOD(THTP400) MOD(HTTPDMTT) . + DEL SYSMOD(THTP400) MOD(ABEND0C1) . + DEL MOD(HTTPD) . + DEL MOD(HTTPDSRV) . + DEL MOD(HTTPDM) . + DEL MOD(HTTPDMTT) . + DEL MOD(ABEND0C1) . + DEL SYSMOD(THTP400) . + ENDUCL . +/* +//LIST EXEC SMPAPP +//SMPCNTL DD * + RESETRC . + LIST CDS SYSMOD(THTP400) . + LIST ACDS SYSMOD(THTP400) . +/* +// +``` + +Every `DEL` reports `HMA2550 UPDATE COMPLETE`, and each `UCLIN` block ends +`RC 00`. + +**3. Read the LIST — this is the actual result.** Both zones must answer: + +``` +THE FOLLOWING SELECTED ENTRIES WERE NOT FOUND OR WERE NOT ELIGIBLE +FOR PROCESSING + TYPE NAME + SYSMOD THTP400 +``` + +with `HIGHEST RETURN CODE IS 04`. **RC 04 and an empty list means the FMID is +free.** Both zones matter: the CDS records what is applied, the ACDS what is +accepted, and they are separate inventories — an id gone from one and present in +the other is not free. + +**4. Scratch the libraries.** `UCLIN` edits the inventory only; both datasets are +still there, and a re-install's allocation job would fail on them: + +``` + DELETE HTTPD..LINKLIB NONVSAM SCRATCH PURGE + DELETE HTTPD..AHTTPLOD NONVSAM SCRATCH PURGE +``` + +Leave `HTTPD..SAMPLIB` alone if you like — the install job's `DELOLD` step +scratches it on its own. + +**5. What is not removed, because SMP never owned it:** the procedure and the +configuration member you copied in step 7, your RAKF definitions, and the +webroot disk `HTTPD..WEBROOT.UFS` with the `MOUNT` statement that names it. +Those are yours to delete — unmount the disk before you scratch it. + +> The `RESTORE`/`REJECT` behaviour above was measured on 2026-08-14 against an +> accepted FMID from a package built by this same generator, on an MVS/CE system +> running SMP 4 level 04.48. It is a property of SMP 4 and of accepting a +> function SYSMOD, not of any one product — but the `UCLIN` job above has not +> itself been run against `THTP400`. Read the `LIST` output rather than trusting +> the `DEL` cards. + +--- + +## Troubleshooting + +| Symptom | Likely cause | +|---------|--------------| +| Allocation job fails, dataset already exists | It was already run. Do not force it — see the warning in step 4 | +| `RECV1`/`RECV2` fails, target exists | Something else allocated it. RECEIVE refuses to merge; scratch it and re-run | +| `APPLYCHK` ends non-zero, `APPLY` skipped | Read the SMP output — the check exists to stop before anything is written. A missing DD is the usual cause | +| `APPLY` RC 00 but fewer than five `HMA2380` lines | A module was lost. Count them, then look in the library itself | +| SMP reports success, but a module is not where you expected | A ddname says nothing about the dataset behind it. Check the JCL, then look at the library itself | +| `S806` (module not found) at `/S HTTPD` | `STEPLIB` in the procedure does not name the LINKLIB the APPLY wrote to | +| `/S HTTPD` rejected — procedure not found | Procedure not copied into a PROCLIB in the started-task concatenation | +| `HTTPD012E … UNABLE TO DYNAMICALLY OBTAIN APF AUTHORIZATION`, no listener | No RAKF (so no SVC 244) and no APF entry — step 2. This one is fatal, not a warning | +| `HTTPD014E`/`015E`/`016E` and the step ends before any banner | A `SYSPRINT`, `SYSTERM` or `SYSIN` DD in the procedure — step 7 | +| `HTTPD037E HTTPD IS ALREADY ACTIVE ON PORT n` | An older instance still holds the port. `/P HTTPD` first — step 5 | +| `HTTPD004W RACINIT ENVIR=CREATE FAILED` | The `STCUSER`/`STCGRP` userid is not defined to RAKF — step 8. Not fatal; the server runs on the inherited STC identity | +| `HTTPD044W UNABLE TO INITIALIZE FILE SYSTEM` | UFSD is not running. Module routes are unaffected; only static files fail — step 2. `HTTPD001I` still names the configured `DOCROOT`, so it is not the line to read here | +| `HTTPD908E EXTERNAL PROGRAM MVSMF …` on every `/zosmf/` request | mvsMF is not in the STC's STEPLIB — step 2. The parenthetical "(not found in STEPLIB?)" is a guess; the real abend code is in the `IEA703I` line beside it, and `106-0F` means storage, not a missing member | +| `HTTPD420E ROUTE AUTHORIZATION POLICY INCOMPLETE`, `CC 0008` | A route's `AUTH=` value is not `NONE`/`FORM`/`BASIC`/`TOKEN`, or a `LOGIN=` line survives in the member — step 7 | +| `HTTPD425W NO PROFILE FOR …` | A `RES=` names a resource no RAKF profile covers, so that route is gated by `AUTH=` alone — step 8 | +| A route answers `200` to anyone | It carries no `AUTH=`, or no route claims the path at all. Both are public by design — step 7. `/.dsrv?target=MOD` prints the policy each route actually got | +| `401` on a route showing `AUTH=NONE` | Not HTTPD's gate. mvsMF runs its own auth track on `/zosmf/*` — establish which layer answered before debugging HTTPD's | +| `S106` at start on a freshly installed library | The XMIT was uploaded in text mode. Re-upload in **binary** and re-run the install job | +| `UFSD062E SUPERBLOCK VALIDATION FAILED` at `MOUNT` | The webroot dataset was not allocated `RECFM=U BLKSIZE=4096`, or the image was uploaded in text mode — step 9 | +| `404` on `/` with UFSD running | The webroot is mounted somewhere other than `DOCROOT`, or nothing is mounted there at all — step 9 | +| The step ends `CC 0000` but nothing was ever served | A clean `/P HTTPD` and a refused start are `CC 0000` and `CC 0008`. If it is 0000, the server ran — look for what stopped it | + +For the complete message reference, see +[messages.md](https://github.com/mvslovers/httpd/blob/main/docs/messages.md). diff --git a/mbt.lock b/mbt.lock index becc950..9f6ea16 100644 --- a/mbt.lock +++ b/mbt.lock @@ -1,6 +1,6 @@ { "mvslovers/ufsd": { - "sha256": "a64fb1510760565890c714074cd7bcb5bdb2283c3e78c4b771cc7070d3cc0078", - "version": "1.2.0" + "sha256": "3efc6ed09d63cb06afd8b6adf4e411d85efd48511cca266f0ada7b5560ebd5b0", + "version": "1.2.1" } } diff --git a/project.toml b/project.toml index 3f272a9..8201ecb 100644 --- a/project.toml +++ b/project.toml @@ -13,7 +13,7 @@ type = "application" # first 1.0.0 attempt shipped reporting an unreleased runtime nobody could # reproduce from a tag. HTTPD005I names the runtime it linked against, so an # unpinned release build makes that line unreproducible in exactly the same way. -libc370 = "1.0.2" +libc370 = "1.0.3" [build] # The build stamp behind HTTPD000I/001I/006W is not injected here: mbt @@ -49,7 +49,7 @@ cflags = [ cflags = ["-Wno-error"] [dependencies] -"mvslovers/ufsd" = ">=1.2.0" +"mvslovers/ufsd" = ">=1.2.1" # -- Internal autocall archive ------------------------------------ # All of httpd's objects, archived into build/httpdint.a. Every module @@ -282,5 +282,120 @@ headers = [ "include/types.h", ] +# -- Distribution (SMP4 install package) -------------------------- +# Turns `make package` into an installable product, on the two transports ufsd +# and ftpd ship with, plus a third one they have no need for: +# +# load modules -> XMIT -> staging HTTPLOAD -> SMP ++MOD -> HTTPD..LINKLIB +# samplib/ -> XMIT -> TSO RECEIVE -> HTTPD..SAMPLIB +# static/ -> UFS370 image -> IND$FILE -> HTTPD..WEBROOT.UFS +# +# The webroot disk (issue #252) rides neither of the first two, and not because +# of a gap in the tooling: SMP has no element type for a DSORG=PS/RECFM=U image +# and must never touch site content, and TSO RECEIVE allocates its own target +# and refuses to merge into an existing one -- a first-install-only transport +# for something an operator updates. So it travels as a plain file in the +# archive (`extra` below) and goes up over IND$FILE, which needs nothing on the +# target beyond a 3270 session. `make webroot` builds it; the Makefile carries +# the why, docs/installation.md the operator's side. +# +# SMP *copies* the host-bound modules rather than re-binding them (a COPY-style +# ++JCLIN is what says so), which is what keeps HTTPD's ac = 1 and every custom +# entry point exactly as ld370 set them. The sample library stays outside SMP: +# an MCS card ends at column 72 and HTTPPRM0 has comment lines reaching column +# 80, so routing the configuration pattern through the SYSMOD would make it +# answer to a delivery mechanism. The datasets are versioned, so every release +# brings a fresh SAMPLIB anyway -- there is no in-place update for SMP to +# manage. +# +# samplib/httpd carries @LINKLIB@ in its STEPLIB, which mbtdist replaces with +# the versioned target while staging -- the same placeholder ufsd's and ftpd's +# procs use, and the reason the shipped proc points at the library SMP actually +# installed into rather than at a name fixed when the file was written. Do not +# mention a placeholder in a nearby comment: the substitution runs over the +# whole member, and a 21-character dataset name landing in a comment line is how +# a proc that fit an FB-80 card stops fitting one. +# +# Every value is a dataset name; the ddname SMP addresses it by is always the +# last qualifier, so LINKLIB, SAMPLIB, HTTPLOAD and AHTTPLOD fall out of the +# names. The webroot is the one name with a fourth qualifier +# (HTTPD.@VRM@.WEBROOT.UFS) and it is deliberate: no ddname is derived from it +# because nothing here allocates or receives it, and .UFS says at a glance that +# the dataset is a filesystem image rather than a library. Do not normalise it +# into the three-qualifier pattern -- it is not part of that scheme. @VRM@ becomes V4R0M0 at package time (V4R0M0D while the version still +# carries -dev). ufsd/ftpd spell these LOAD / ALOD, which for +# a five-letter product would be nine characters; HTTPLOAD/AHTTPLOD keeps the +# suffix and drops the letter, rather than keeping HTTPD and truncating the +# suffix (HTTPDLOD/AHTTPDLD). Both names go into the FMID's JCLIN, so this is a +# one-shot choice -- changing it later is a new functional level. +# +# All five [[module]] entries ship, ABEND0C1 included: mbtdist packages every +# module and offers no exclude. Deliberate rather than overlooked -- since +# 4.0.0 a module is inert unless a Parmlib route names it, and the shipped +# HTTPPRM0 leaves its /.abend line commented out. It is the acceptance vehicle +# for the storage reclaim (#154/#174), and a diagnostic that is present but +# unreachable is worth more than one an operator would have to rebuild to use. +[distribution] +# Shipped into the archive as README.md, which is the name the generated install +# job points at ("See the README for which member goes where"). Without it the +# package tells the operator to read a file it does not contain. +readme = "docs/installation.md" + +# The webroot disk, put in the archive as `httpd-webroot.img` -- `extra` takes +# the basename, so the path here is only where `make webroot` writes it and the +# name an operator sees is the file's own. Listed rather than generated by +# mbtdist: the image is not a dataset the install job creates, and nothing in +# the SYSMOD or the RECEIVE plan knows about it. `make package` and `make dist` +# both depend on the target that builds it, because a missing `extra` file is a +# hard error here -- deliberately: an archive quietly missing the webroot would +# look complete and serve nothing on /. +# +# Under build/ rather than dist/ on purpose. The release workflow publishes +# `dist/*` as Release assets, so an image there would show up beside the +# versioned artifacts under a version-less name, duplicating what the archive +# already carries. +extra = ["build/webroot/httpd-webroot.img"] + +[distribution.smp] +# One FMID per minor release, spent exactly once: it names a functional level, +# and a changed one is a new level. Patches on top of it ship as PTFs. 4.0.x +# is THTP400; a later 4.1.0 becomes THTP410 and is installed as a clean cut +# (RESTORE + REJECT the old, then receive the new). The T stays clear of the +# E??nnnn namespace MVS 3.8j's own function SYSMODs occupy (EBB1102 is 3.8j +# itself, alongside EAS1102, EBT1102, EDE1102, ...). +# +# Verified free the way TFTP100 and TUFS120 were, on both stands, 2026-08-24: +# LIST CDS SYSMOD(THTP400) and LIST ACDS SYSMOD(THTP400) each answered RC 04 +# with "THE FOLLOWING SELECTED ENTRIES WERE NOT FOUND" -- mvsdev (MVS/CE, +# JOB01948) and drnmig3a (TK5, JOB00028), both HMASMP LVL 04.48. That is the +# check that counts: the SMPPTS scan done earlier shows only what was +# *received*, while the CDS and ACDS store hashed member names, so a LIST +# against both zones is the only way to see what is applied or accepted. +# A test install must still use a throwaway id -- a half-applied FMID would +# occupy this one, and smp-todo.md reserves TTST400 for exactly that. +fmid = "THTP400" +system = "Z038" + +# Empty despite the [dependencies] entry on ufsd, which is a *build* time one: +# libufs is linked statically, so nothing of ufsd has to be installed for the +# load modules to run. At run time it is soft even though 4.0.0 serves static +# files from UFS and nowhere else: ufs_sys_init() failing is HTTPD044W and the +# server keeps running, so every MOD= route -- mvsMF, the display modules -- +# works on a system with no UFSD at all. REQ(TUFS120) would therefore refuse +# the install wherever UFSD is absent, for a facility that already degrades on +# its own. The installation guide documents UFSD as an optional runtime +# prerequisite instead, which is also how ufsd itself treats RAKF. +prereq = [] + +accept_fmid = true # accept the FMID once so a PTF can be RESTOREd +lklib = "HTTPD.@VRM@.HTTPLOAD" # staging: what the load XMIT is received into +target = "HTTPD.@VRM@.LINKLIB" # where the load modules end up +distlib = "HTTPD.@VRM@.AHTTPLOD" + +[[distribution.library]] +dir = "samplib" +target = "HTTPD.@VRM@.SAMPLIB" + +# -- Release ------------------------------------------------------ [release] version_files = ["VERSION"] diff --git a/samplib/httpd b/samplib/httpd index 9af317c..a61d55e 100644 --- a/samplib/httpd +++ b/samplib/httpd @@ -36,7 +36,7 @@ //* PARM is space-separated: __start() splits argv on blanks, not commas. //HTTPD EXEC PGM=HTTPD,REGION=8M,TIME=1440, // PARM='STCUSER=&STCUSER STCGROUP=&STCGRP' -//STEPLIB DD DISP=SHR,DSN=HTTPD.LINKLIB +//STEPLIB DD DISP=SHR,DSN=@LINKLIB@ //SYSUDUMP DD SYSOUT=* //HTTPDERR DD SYSOUT=* //HTTPDOUT DD SYSOUT=* diff --git a/samplib/httpprm0 b/samplib/httpprm0 index 6c0b8ef..6c1cee7 100644 --- a/samplib/httpprm0 +++ b/samplib/httpprm0 @@ -84,13 +84,23 @@ # match wins): the anonymous reachability probe (/zosmf/info) and the token # login/logout endpoint (/zosmf/services/authenticate) do their own auth, so # the gate must not challenge them. +# TOKEN on the catch-all, not a blank: a route without AUTH= is PUBLIC, and +# leaving the whole API ungated would hand every dataset and job endpoint to +# anyone who can reach the port. TOKEN answers a bare 401 without a +# WWW-Authenticate, which is what an API client (Zowe, a SPA, curl scripts) +# wants -- see the AUTH= notes above. MOD=MVSMF /zosmf/info AUTH=NONE MOD=MVSMF /zosmf/services/authenticate AUTH=NONE -MOD=MVSMF /zosmf/* -# MOD=MVSMF /zosmf/* AUTH=TOKEN RES=FACILITY:MVSMF.ACCESS (protected API) -# MOD=HTTPDSRV /.dsrv AUTH=FORM (browser login) -# MOD=HTTPDM /.dm -# MOD=HTTPDMTT /.dmtt +MOD=MVSMF /zosmf/* AUTH=TOKEN +# Add a RACF/RAKF profile check on top of the login: +# MOD=MVSMF /zosmf/* AUTH=TOKEN RES=FACILITY:MVSMF.ACCESS +# +# The display modules read live storage and the console log, so none of them +# may be uncommented without an AUTH= of its own. They are development and +# diagnostic tools, not production endpoints. +# MOD=HTTPDSRV /.dsrv AUTH=FORM (browser login) +# MOD=HTTPDM /.dm AUTH=BASIC +# MOD=HTTPDMTT /.dmtt AUTH=BASIC # # ABEND0C1 allocates (?kb=n, default 128) and then abends S0C1 without freeing # -- the acceptance probe for the storage reclaim. Drive it a few hundred diff --git a/samplib/httpwebr b/samplib/httpwebr new file mode 100644 index 0000000..5a32130 --- /dev/null +++ b/samplib/httpwebr @@ -0,0 +1,35 @@ +//HTTPWEBR JOB (ACCT),'ALLOC HTTPD WEBROOT',CLASS=A,MSGCLASS=H, +// MSGLEVEL=(1,1),NOTIFY=&SYSUID +//* +//* HTTPWEBR - allocate the dataset the webroot disk is uploaded to +//* +//* The release archive carries httpd-webroot.img: a formatted UFS370 +//* filesystem holding the pages HTTPD serves from DOCROOT. You upload +//* it with IND$FILE and mount it with UFSD. The installation guide +//* has the whole procedure -- this job is only its first step. +//* +//* RUN THIS BEFORE THE UPLOAD. IND$FILE allocates an existing dataset +//* DISP=SHR and takes the DCB from its label; it invents one only when +//* the dataset is not there. Allocating it here is what pins +//* BLKSIZE=4096, the block size the image was formatted with. UFSD +//* compares the two at MOUNT and refuses a mismatch with: +//* +//* UFSD062E SUPERBLOCK VALIDATION FAILED FOR +//* +//* Primary extent only, on purpose. The filesystem is addressed by +//* block number within the primary extent, so blocks in a secondary +//* would never be reached. SPACE is counted in blocks of 4096 here, +//* which makes 256 blocks the 1 MB the shipped image occupies. MVS +//* rounds the request up to a track boundary; the few blocks that adds +//* sit past the end of the filesystem, because the superblock -- not +//* the dataset -- is what says how large the filesystem is. +//* +//* Change the dataset name, UNIT and VOLUME to suit. The name below +//* is the one the installation guide uses. An image larger than the +//* shipped one needs a matching SPACE: count it in 4096-byte blocks. +//* +//ALLOC EXEC PGM=IEFBR14 +//WEBROOT DD DSN=HTTPD.@VRM@.WEBROOT.UFS,DISP=(NEW,CATLG), +// UNIT=SYSDA,SPACE=(4096,256), +// DCB=(DSORG=PS,RECFM=U,BLKSIZE=4096) +// diff --git a/smp-todo.md b/smp-todo.md index ce78e5a..b1e1abf 100644 --- a/smp-todo.md +++ b/smp-todo.md @@ -1,277 +1,161 @@ -# SMP-Installation httpd — offene Punkte und TODOs - -> **Erprobtes Rezept — hier zuerst nachsehen:** [`../SMP-COOKBOOK.md`](../SMP-COOKBOOK.md) -> **Konzept, Belege und Handbuchzitate:** [`../SMP-INSTALLATION.md`](../SMP-INSTALLATION.md) -> -> Dieses Dokument beantwortet nicht *wie* SMP funktioniert — das steht dort. -> Hier steht, **was für httpd noch fehlt** und in welcher Reihenfolge es -> abgearbeitet wird. - -Ziel: eine **Testinstallation von httpd über SMP4** auf `mvsdev` — gebaut aus dem -aktuellen `4.0.0-dev`-Stand, inklusive SAMPLIB und UFS-Disk für das WWW-Root. -Sie ist die Generalprobe für das spätere Release der finalen `4.0.0`. - -Ablauf: **§1 Fragerunde → §3 Spike → §4–§6 Ausbau → §8 Abnahme.** -Die Fragen in §1 sind vor dem ersten Schreibzugriff zu klären; §2 listet, was -bereits verifiziert ist und **nicht neu hergeleitet werden muss**. - -> **Test ≠ Release.** `project.toml` steht auf `4.0.0-dev` — das ist der laufende -> Entwicklungsstand. Über SMP ausgeliefert wird später die finale, getaggte -> `4.0.0`. Die Testinstallation baut also aus einem Baum, der nicht das Release -> ist, und **muss deshalb ein Wegwerf-FMID benutzen** (`TTST400`). -> -> Warum das kein Detail ist: SMP führt sein Inventar im CDS. Ein Testlauf unter -> `THTP400`, der abbricht, halb angewendet wird oder aus einem `-dev`-Stand -> stammt, hinterlässt genau diese ID als belegt — und das echte 4.0.0 müsste -> dann gegen die eigenen Reste installiert werden. `THTP400` wird erst vom -> getaggten Release vergeben, und zwar genau einmal. +# SMP-Installation httpd — was noch offen ist ---- - -## 1. Fragerunde — vor dem ersten Schritt zu klären - -| # | Frage | Vorschlag | blockiert | -|:--:|---|---|---| -| **F1** | **Wohin installiert SMP die Loadmodule?** Im Repo stehen drei Ziele nebeneinander: `make deploy` landet mangels `[deploy]`-Abschnitt auf `IBMUSER.HTTPD.V4R0M0.LINKLIB`, `samplib/httpd` hat `STEPLIB DD DSN=HTTPD.LINKLIB`, das Konzept schlägt `SYS2.LINKLIB` vor. | **`SYS2.LINKLIB`** — gemessen: APF-autorisiert *und* im LNKLST (§2). Die Proc bräuchte dann keinen STEPLIB mehr, und httpds Selbstautorisierung würde entfallen — **im Experiment beobachten**, s. T6 | JCLIN, DD-Override, Proc-Muster | -| **F2** | **FMID?** Muss 7 Zeichen haben und darf nicht kollidieren. `THTP400` ist im **SMPPTS frei** (1544 SYSMODs geprüft, §2) und passt zur lokalen Konvention. Restrisiko: das **CDS** ist nicht abfragbar, ein längst akzeptiertes SYSMOD könnte dort stehen, ohne im PTS zu sein. | `THTP400` übernehmen; Gewissheit nur über einen SMP-`LIST SYSMODS`-Job | alles | -| **F3** | **HLQ und Volume** für die Produkt-Datasets (DLIB, SAMPLIB, LKLIB)? | `SYS2.HTTPD.*`, Volume wie `MBT_MVS_DEPS_VOLUME` | Allokationsjob | -| ~~F4~~ | ~~ACCEPT mitfahren?~~ **Entschieden: nein, nie.** ACCEPT nimmt `RESTORE` weg und blockiert das erneute Empfangen desselben FMID mit geändertem Inhalt. Begründung und Konsequenzen: `../SMP-COOKBOOK.md` §5.1 | — | — | -| **F5** | **Schreibfreigabe für `mvsdev`** — und ist ein Backup von `SYS1.SMPCDS`/`SMPPTS` vorhanden? | Backup vor dem ersten RECEIVE; Wegwerf-FMID hinterher `REJECT`en | Spike | -| **F6** | **UFS: eigene Disk für `/www`** oder in ein bestehendes Dateisystem mounten? Welcher `UFSDPRMx`-Member? | eigene Disk `HTTPD.WEBROOT`, `MOUNT … PATH(/www) MODE(RO)` | Setup-Job | -| **F7** | **Port für den Test?** Default ist 8080. | 8080, sofern frei | Konfigmuster | -| **F8** | **SAMPLIB: eigenes Produkt-Dataset** (`SYS2.HTTPD.SAMPLIB`) oder in `SYS1.SAMPLIB`? | eigenes — sonst DD-Override-Konflikt mit dem Systemdataset | MCS, Setup-Job | -| **F9** | **Wegwerf-FMID für den Test?** `4.0.0-dev` ist der laufende Entwicklungsstand; über SMP ausgeliefert wird die finale `4.0.0`. Der Test läuft also gegen einen Baum, der **nicht** das Release ist. | **Ja** — Test auf `TTST400`, `THTP400` bleibt für das getaggte 4.0.0 reserviert. Begründung unten. | Spike, Aufräumen | - ---- +**Das Paket wird generiert, nicht mehr von Hand gebaut.** `make package` erzeugt +aus dem `[distribution]`-Block in `project.toml` den SYSMOD, den Allokations- +und den Install-Job. Wie SMP4 dabei arbeitet und warum, steht in +`mbt/scripts/mbt/distribution.py` — dort sind die Handbuchstellen zitiert und +die Messungen vermerkt. Was ein Betreiber tun muss, steht in +[`docs/installation.md`](docs/installation.md). -## 2. Was bereits feststeht +Dieses Dokument hält nur noch das, was **für httpd offen** ist. -Nicht neu herleiten, nicht neu messen. Marker wie im Konzeptdokument -(✅ Repo · 🔬 gemessen · 📘 SMP4-Handbuch). +> *Stand: 2026-08-24, gegen `[distribution]` in `project.toml` abgeglichen.* -**Verfahren** +> **Die frühere Fassung verwies auf `../SMP-COOKBOOK.md` und +> `../SMP-INSTALLATION.md`. Beide Dateien gibt es nicht mehr.** Der Spike liegt +> noch in [`../smptest`](../smptest) — er hat 2026-08-08 gezeigt, dass SMP das +> LKLIB-Modul *kopiert* statt es neu zu binden, und genau darauf baut das +> generierte Paket auf. -- `++MOD(x) LKLIB(ddname)` liefert ein **fertig gebundenes** Loadmodul; ist das - LMOD im JCLIN über einen **IEBCOPY-Step** definiert, kopiert SMP es statt zu - binden. 📘 -- DDNAMEs = letzter Qualifier des Datasetnamens. 📘 -- Copy-Input im JCLIN muss **inline** hinter `//SYSIN DD *` stehen. 📘 -- `SELECT MEMBER=(…)` verwenden — sonst gilt die DLIB als *total* kopiert - (Grenze: zwei Ziel-Bibliotheken). 📘 -- SMP4 kennt **keinen** Datentyp: nur `++MOD/MAC/SRC/MACUPD/SRCUPD/ZAP`. 📘 - -**Zielsystem `mvsdev`** - -- `SMPAPP` und `SMPREC` liegen in **`SYS2.PROCLIB`**, nicht `SYS1.PROCLIB`. 🔬 -- Beide Procs haben genau einen Step (`HMASMP`) 🔬 — **DD-Overrides müssen - trotzdem qualifiziert werden**: `//HMASMP.LINKLIB DD …`, nicht `//LINKLIB DD …`. - Und die Override-Karten müssen **vor** allen hinzugefügten DDs stehen. Siehe - Falle T9; im smptest-Spike gemessen. 🔬 -- Der Proc bringt u. a. `LINKLIB`, `LPALIB`, `PROCLIB`, `PARMLIB`, `SAMPLIB`, - `ASAMPLIB`, `MACLIB`, `CMDLIB` mit — alle auf `SYS1.*`. Wir überschreiben, - was nach `SYS2.*` soll. 🔬 -- `SYS2.LINKLIB` (20 Member) und `SYS2.PARMLIB` (9 Member) existieren. 🔬 - -**Artefaktseite** - -- 6 Loadmodule gebaut: `HTTPD` (AC=1), `HTTPJES2`, `HTTPDM`, `HTTPDMTT`, - `HTTPDSL`, `HTTPDSRV`. ✅ -- `ld370 --pack … -xmit` → `upload_binary` → `RECEIVE` läuft produktiv in - `mbt/scripts/mbtdeploy.py`. ✅ -- `ufsd-utils` ist installiert (`~/go/bin/ufsd-utils`). 🔬 - -**Zielbibliothek `SYS2.LINKLIB`** 🔬 - -Aus `SYS1.PARMLIB` gelesen — beides spricht für `SYS2.LINKLIB` als Ziel: +--- -``` -IEAAPF00: SYS2.LINKLIB MVS000, USER BATCH LINKLIB <- APF-autorisiert -LNKLST00: SYS2.LINKLIB, USER LOAD MODULE LIBRARY <- im Linklist -IEABLD00: nur SYS1.LINKLIB-Module (HEWL, IFOXxx, LOGON …) <- kein BLDL-Eintrag -``` +## 1. Stand -- **APF:** `SYS2.LINKLIB` ist autorisiert. httpd bringt sich heute selbst in den - autorisierten Zustand (T6); von dort aus wäre es das von Anfang an. ✔ -- **LNKLST:** die Module werden ohne STEPLIB gefunden. ✔ -- **Kein IPL nötig**, damit ein *neues Member* sichtbar wird — in der residenten - BLDL-Liste (`IEABLD00`) steht nichts aus `SYS2.LINKLIB`. Vorsicht dagegen beim - **Erweitern oder Komprimieren** eines LNKLST-Datasets im laufenden Betrieb; - ausreichend Platz vorher einplanen. - -**SYSMOD-Inventar des Zielsystems** 🔬 - -`SYS1.SMPPTS` ist über die REST-API auslesbar — die Membernamen der MCS-Entries -sind **nicht** encodiert (dokumentierte Ausnahme 📘: *„With the exception of the -MCS entry in the SMPPTS dataset, these member names are encoded and cannot be -easily accessed by utilities other than SMP."*). - -- **1544 SYSMODs** liegen im PTS. Darunter die Funktions-SYSMODs des Sysgens: - `TIST801`, `TJES801`, `TMVS804`, `TMVS816`, `TMVS817`, `TNIP800`, `TTSO801` — - also dieselbe Form `T` + 3 Buchstaben + 3 Ziffern, die wir vorschlagen. -- Ebenfalls da: `RAK0001` (RAKFs Usermod). **`TRKF200` dagegen nicht** — RAKF 2.0 - als Funktion ist auf diesem System nicht empfangen. -- **Frei im PTS:** `THTP400`, `TTST400`, `TUFS100`, `TFTP100`, `TLBC100`. -- ⚠ **`TMVS100` für mvsmf nicht verwenden** — `TMVS8xx` sind die - MVS-Funktions-SYSMODs des Sysgens. Vorschlag stattdessen: `TZMF100`. - -**Das CDS ist so *nicht* prüfbar.** Seine Membernamen sind echte Hashes -(Stichprobe: `Ak\x7F\x1C]\x0D\xB6\x00`, `Jj7\x96\xFF\xDB\x3A\x00`) — ein Namensvergleich dort -liefert nur Scheinergebnisse. Für Gewissheit braucht es einen SMP-`LIST`-Job. -Ebenso: `list_members()` scheitert an beiden Datasets, weil mvsMF die -Steuerzeichen als ungültiges JSON ausliefert; der Roh-Request unten umgeht das. - -```python -# PTS-Inventar auslesen (read-only) -import re, base64, urllib.request -url = "http://:/zosmf/restfiles/ds/SYS1.SMPPTS/member" -req = urllib.request.Request(url) -req.add_header("Authorization", "Basic " + base64.b64encode(b"user:pass").decode()) -req.add_header("Accept", "application/json") -txt = urllib.request.urlopen(req, timeout=90).read().decode("utf-8", "replace") -ids = sorted({n.strip() for n in re.findall(r'"member"\s*:\s*"(.*?)"', txt) - if "\\u" not in n}) # MCS-Entries = lesbare SYSMOD-IDs -``` +**Vor dem 4.0.0-Tag ist nichts mehr offen, was MVS braucht.** O1 (FMID frei) und +O2 (Generalprobe) sind am 2026-08-24 gefahren, O3 (Webroot) ist mit Issue #252 +umgesetzt. Was bleibt, ist O4 — eine Bequemlichkeitsfrage, keine Blockade — und +die Checksummen aus O3. Die erledigten Punkte bleiben mitsamt Belegen stehen: +sie sind das Protokoll des Release-Laufs, und die nächste Minor-Version fährt +dieselbe Strecke. ---- +### ~~O1 — `THTP400` im CDS und ACDS prüfen~~ · **erledigt 2026-08-24** -## 3. TODO — Spike (P0, zuerst) - -> **Erledigt.** Der Spike wurde am 2026-08-08 im Projekt -> [`../smptest`](../smptest) gefahren, nicht mit `HTTPDM` — ein Modul, keine -> Abhängigkeiten, eigener FMID `TSMP100`. Ergebnis: SMP **kopiert** das -> LKLIB-Modul, `SYS2.CMDLIB(SMPTEST)` ist byteidentisch mit `build/SMPTEST`, -> ACCEPT trägt es in die DLIB, RESTORE nimmt es zurück. Belege und -> wiederverwendbare JCL in `../smptest/jcl/` und `../smptest/scripts/`. -> -> Für httpd bleibt davon: der Weg ist bestätigt, die Schritte unten sind die -> Vorlage für den ersten httpd-Lauf. - -Die ursprüngliche Fragestellung: **kopiert SMP das LKLIB-Modul wirklich -unverändert?** Braucht **kein** neues mbt-Feature — alles von Hand, mit den -vorhandenen Upload-Wegen. - -- [ ] **S1** `make modules` — Stand sicherstellen -- [ ] **S2** LKLIB auf MVS bringen — **Weg A (XMIT)**, der heute implementierte - Pfad (§10.4 im Konzeptdokument): - - ```sh - ld370 --pack build/HTTPDM.iebcopy -o build/httpd.deploy \ - -xmit --dsn HTTPD.V4R0M0.LKLIB - # -> upload_binary (FB80) -> TSO RECEIVE -> HTTPD.V4R0M0.LKLIB - ``` - - `ld370 --pack` erhält dabei die Attribute **jedes** Members (AC, RENT, - REUS) aus dem Bindelauf 📖 — deshalb überlebt `HTTPD`s `AC=1` den Transport. - `--blocksize` muss beim Binden und Packen identisch sein (Default 15040). - RECEIVE mergt nicht: Zielbibliothek vorher löschen, wie `mbtdeploy` es tut. - *Alternative für später:* `-iebcopy` + Batch-`IEBCOPY` statt TSO — Vorteile - und offene Fragen in §10.4. -- [ ] **S3** DLIB + SAMPLIB allokieren (IEFBR14): `SYS2.HTTPD.AHTTPLOD` als - Loadlibrary (DCB wie `SYS2.LINKLIB`) -- [ ] **S4** MCS-Datei von Hand schreiben (~12 Zeilen) und als FB80 hochladen: +Beide Stände melden die ID frei. Gefahren wurde auf jedem: ``` -++FUNCTION(TTST400) . -++VER(Z038) /* httpd 4.0.0-dev - SPIKE, Wegwerf-FMID */ . -++JCLIN . -//TTST400 JOB 1,'HTTPD SPIKE',MSGLEVEL=1,CLASS=A -//COPYLOAD EXEC PGM=IEBCOPY -//AHTTPLOD DD DISP=SHR,DSN=SYS2.HTTPD.AHTTPLOD -//LINKLIB DD DISP=SHR,DSN=SYS2.LINKLIB -//SYSIN DD * - COPY INDD=AHTTPLOD,OUTDD=LINKLIB - SELECT MEMBER=(HTTPDM) +//LIST EXEC SMPAPP +//SMPCNTL DD * + LIST CDS SYSMOD(THTP400) . + LIST ACDS SYSMOD(THTP400) . /* -++MOD(HTTPDM) LKLIB(HTTPLOAD) DISTLIB(AHTTPLOD) . ``` -- [ ] **S5** Install-Job von Hand: `RECEIVE SELECT(TTST400)` → - `APPLY S(TTST400) CHECK` → `APPLY S(TTST400) DIS(WRITE)`, mit - `//SMPPTFIN DD DSN=…`, `//HTTPLOAD DD …LKLIB`, `//AHTTPLOD DD …`, - `//LINKLIB DD DISP=SHR,DSN=SYS2.LINKLIB` -- [ ] **S6** **Bitvergleich** `SYS2.LINKLIB(HTTPDM)` gegen `build/HTTPDM` — - *das ist der eigentliche Test.* Gleich ⇒ SMP hat kopiert, nicht gebunden, - und `norent`/`ac`/Entry stammen unverändert aus ld370. -- [ ] **S7** `ACCEPT S(TTST400) DIS(WRITE)` → landet das Modul in `AHTTPLOD`? -- [ ] **S8** `++PTF` mit demselben Modul, danach `RESTORE` — kommt der Vorstand - sauber zurück? -- [ ] **S9** Aufräumen: `RESTORE` → `REJECT SELECT(TTST400)`, Test-Datasets - löschen. **Kontrollieren, dass `THTP400` im CDS unberührt ist** — es wird - erst vom getaggten 4.0.0 vergeben. - -**Abbruchkriterium:** Wenn S6 zeigt, dass SMP doch bindet, stoppen und auf das -RAKF-Muster wechseln (Anhang C.7 im Konzeptdokument) — nicht auf Objektdecks. - ---- - -## 4. TODO — SMP-Paket für httpd - -Erst wenn §3 grün ist. - -- [ ] **P1** FMID final vergeben (F2) und in `knowledge/` dokumentieren — - `THTP400` **erst mit dem getaggten 4.0.0**, nicht aus dem `-dev`-Baum -- [ ] **P2** `[smp]`-Abschnitt in `project.toml`: - -```toml -[smp] -fmid = "THTP400" -system = "Z038" -mode = "lklib" -lklib = "HTTPLOAD" # DDNAME der hochgeladenen Loadlibrary -distlib = "AHTTPLOD" # DDNAME der Loadmodul-DLIB -samplib = "AHTTPSAM" # DDNAME der SAMPLIB-DLIB -target = "SYS2.LINKLIB" # aus F1 -prereq = ["TUFS100"] # sonst aus [dependencies] abgeleitet -``` - -- [ ] **P3** MCS-Generator: `++FUNCTION`/`++VER`/`++JCLIN` + je `[[module]]` ein - `++MOD … LKLIB` + je SAMPLIB-Member ein `++MAC` -- [ ] **P4** COPY-JCLIN-Generator: `SELECT MEMBER=(…)` aus den - `[[module]]`-Namen -- [ ] **P5** Allokationsjob generieren (DLIB, SAMPLIB, ASAMPLIB, LKLIB) -- [ ] **P6** `make smppkg` → `dist/httpd-4.0.0.smpmcs` + `…lklib.xmit` + - `…-inst.jcl` + `…-setup.jcl` -- [ ] **P7** `make smpinst` — Upload + Job absetzen + RC prüfen (**schreibt auf MVS**) -- [ ] **P8** Alle 6 Module statt nur `HTTPDM`; `HTTPD` trägt **AC=1** — prüfen, - dass die Autorisierung den Kopiervorgang übersteht -- [ ] **P9** `++PTF`-Pfad (`make smpptf`) — **ganzes** LMOD via LKLIB -- [ ] **P10** **Modul entfällt / kommt hinzu:** Wenn sich die Modulliste ändert, - braucht der SYSMOD ein neues `++JCLIN` mit angepasster - `SELECT MEMBER=(…)`-Liste; ein entfallenes Modul zusätzlich per - `++MOD(x) DELETE`. Generator muss das aus dem Diff der `[[module]]`-Namen - gegen den letzten Release ableiten. -- [ ] **P11** **Aliase:** httpd hat heute keine. Falls je welche entstehen, - müssen sie mit `TALIAS(…)` deklariert werden **und** in der LKLIB - vorhanden sein — sonst kopiert SMP sie nicht 📘. - ---- - -## 5. TODO — SAMPLIB - -Siehe §14.5 im Konzeptdokument. Kernregel: - -> **Produkt besitzt die Muster, Site besitzt die Kopien.** -> `SYS2.HTTPD.SAMPLIB(HTTPD)` gehört SMP — `SYS2.PROCLIB(HTTPD)` nicht. - -Grund: `samplib/httpd` enthält `STEPLIB`, `VOL=SER=MVS000`, REGION, Port — alles -Werte, die der Betreiber ändert. Besäße SMP die aktive Proc, wäre jede Anpassung -beim nächsten APPLY weg. - -- [ ] **L1** `SYS2.HTTPD.SAMPLIB` + `.ASAMPLIB` allokieren (FB80) -- [ ] **L2** Bestehende Muster als `++MAC` aufnehmen: - `samplib/httpd` → Member `HTTPD`, `samplib/httpprm0` → Member `HTTPPRM0` -- [ ] **L3** **Generierte** Member ergänzen — sie tragen FMID, Version und - Datasetnamen und müssen zum Release passen: - - `HTTPINST` — der SMP-Install-Job - - `HTTPSETU` — der Setup-Job (Disk, Mount, Kopien) - - `HTTPWEB` — JCL-Muster für den Disk-Upload -- [ ] **L4** DD-Override im APPLY/ACCEPT-Step: - `//SAMPLIB DD DISP=SHR,DSN=SYS2.HTTPD.SAMPLIB` (letzter Qualifier passt) -- [ ] **L5** Kopierschritt **im Setup-Job**, nicht im Install-Job: +| Stand | Job | CDS | ACDS | +|---|---|---|---| +| `mvsdev` (MVS/CE, HMASMP LVL 04.48) | `JOB01948` | RC 04, `NOT FOUND` | RC 04, `NOT FOUND` | +| `drnmig3a` (TK5, HMASMP LVL 04.48) | `JOB00028` | RC 04, `NOT FOUND` | RC 04, `NOT FOUND` | + +Damit ist `THTP400` so belegt wie `TUFS120` und `TFTP100` vor ihren Tags — +der SMPPTS-Scan aus §3 zeigte nur *Empfangenes*, CDS und ACDS speichern +gehashte Membernamen, und nur der `LIST`-Job sieht, was wirklich angewendet +oder akzeptiert ist. Der Zonenoperand bleibt Pflicht (bares `LIST SYSMODS .` +ist SMP/E-Syntax → `HMA2033`), und qualifizieren auch: `LIST CDS .` gibt auf +MVS/CE 116 000 Zeilen aus. + +**Zwei Dinge, die dabei gemessen wurden und beim nächsten Mal Zeit sparen:** + +- Der `SMPAPP`-Procstep heißt auf beiden Ständen `HMASMP` und bringt **kein** + `SMPCNTL` mit. Beide Formen funktionieren — `//HMASMP.SMPCNTL DD *` wie + `//SMPCNTL DD *` unqualifiziert, weil die Prozedur nur einen Step hat. +- **Das Kriterium ist der Report, nicht der Returncode.** RC 04 steht am Ende + jeder LIST-Ausführung; die Aussage steckt in `LIST SELECT SUMMARY REPORT` + plus `THE FOLLOWING SELECTED ENTRIES WERE NOT FOUND` im **SMPOUT**. Wer nur + den Job-RC abfragt, kann eine leere von einer treffenden Liste nicht + unterscheiden. + +### ~~O2 — Testinstallation mit Wegwerf-FMID `TTST400`~~ · **erledigt 2026-08-24** + +Auf `mvsdev` gefahren, aus dem entpackten Archiv (`httpd-4.0.0-dev-dist.zip`), +also genau dem, was ein Betreiber bekommt. `fmid` stand dafür auf `TTST400` — +die Änderung ist **nicht** committet, `project.toml` sagt wieder `THTP400`. + +| Schritt | Ergebnis | +|---|---| +| XMITs binär hochgeladen (`IBMUSER.HTTPD.LOAD.XMIT`, `…SAMP.XMIT`) | ok | +| `httpd-4.0.0-dev-alloc.jcl` (`JOB01972`) | CC 0000, `LINKLIB` + `AHTTPLOD` angelegt | +| `httpd-4.0.0-dev-inst.jcl` (`JOB01973`), nur die zwei `CHANGE.ME.*` ersetzt | **alle acht Schritte CC 0000**: DELOLD, RECV1, RECV2, RECV, APPLYCHK, APPLY, ACCEPT, CLEANUP | +| `LIST CDS/ACDS SYSMOD(TTST400)` (`JOB01974`) | RC 00, `JCLIN=YES`, REC/APP/ACC gestempelt, `MOD = HTTPD HTTPDM HTTPDMTT HTTPDSRV ABEND0C1` in **beiden** Zonen | +| `HTTPD.V4R0M0D.LINKLIB` / `.AHTTPLOD` | je fünf Module | +| `HTTPD.V4R0M0D.SAMPLIB` | `HTTPD`, `HTTPPRM0`, `HTTPWEBR` | +| `HTTPD.V4R0M0D.HTTPLOAD` | von CLEANUP verschrottet, wie vorgesehen | +| UCLIN + IDCAMS-Scratch (`JOB01975`) | CC 0000 | +| `LIST` danach (`JOB01976`, `JOB01977`) | `TTST400` in beiden Zonen weg, `THTP400` weiterhin frei | + +Nach dem Lauf stehen auf `mvsdev` wieder nur `HTTPD.LINKLIB` und +`HTTPD.WWWROOT` — beides Site-Inhalt, unberührt. + +**Was der Lauf über das Paket beweist**, über die Returncodes hinaus: + +- Die `@VRM@`- und `@LINKLIB@`-Ersetzung kommt richtig auf dem Zielsystem an: + der installierte `HTTPWEBR` sagt `DSN=HTTPD.V4R0M0D.WEBROOT.UFS`, die Proc + `STEPLIB DD DSN=HTTPD.V4R0M0D.LINKLIB`. +- SMP kopiert, statt neu zu binden — die fünf Module stehen nach APPLY im + Ziel und nach ACCEPT in der DLIB. +- Der UCLIN-Weg aus `docs/installation.md` §12 funktioniert wie beschrieben; + er war bis dahin nur hergeleitet, nicht gefahren. +- Die Versionierung trägt: `V4R0M0D` (dev) kollidiert nicht mit dem späteren + `V4R0M0`, und die Site-Platte `HTTPD.WWWROOT` liegt neben allem. + +**Eine Falle, die dabei sichtbar wurde:** Datasets im Aufräumjob per IDCAMS +löschen, nicht über die REST-API — ein `DELETE /zosmf/restfiles/ds/…` lässt das +ENQ stehen, und der nächste `DISP=SHR`-Job hängt. + +### O3 — UFS-Webroot: entschieden und umgesetzt (Issue #252) + +Die Disk fährt **nicht** über SMP und **nicht** über XMIT, sondern als Datei im +Archiv und per **IND$FILE** aufs Ziel. Beides aus einem strukturellen Grund, +nicht aus Werkzeugmangel: + +- SMP4 kennt für ein `DSORG=PS`/`RECFM=U`-Image keinen Elementtyp + (`++MOD/MAC/SRC/MACUPD/SRCUPD/ZAP` ist die ganze Liste), und es ist + **Site-Inhalt** — ein APPLY dürfte es nie anfassen. +- TSO RECEIVE legt sein Ziel selbst an und weigert sich, in ein bestehendes + Dataset zu mischen: ein Transport nur für die Erstinstallation, für etwas, das + der Betreiber ersetzt. (`xmit370` könnte das Format ohnehin nicht — Verzeichnis + → PDS, `--recfm fb|f`, LRECL 80.) + +Umgesetzt: + +- **`make webroot`** baut `build/webroot/httpd-webroot.img` aus `static/` mit einem + gepinnten `ufsd-utils` (1 MB = 256 Blöcke, `--owner`/`--group` gesetzt, damit + im Artefakt keine Build-Maschinen-Userid steht). `package` und `dist` hängen + davon ab, `[distribution] extra` legt es ins Archiv. +- **`samplib/httpwebr`** allokiert `HTTPD.@VRM@.WEBROOT.UFS` + (`SPACE=(4096,256)`, `RECFM=U BLKSIZE=4096`, nur Primärextent). +- **`docs/installation.md` §9** ist der Betreiberweg: allokieren, IND$FILE, + mounten, prüfen, ersetzen. + +Was dabei gemessen wurde und in der Doku steht: + +| Frage | Befund | +|---|---| +| Blockung | RECFM=U puffert über `__fputc` bis BLKSIZE (libc370 `@@fputc.c:24`) → `BLKSIZE(4096)` schreibt exakte 4096er-Blöcke; 1 MB = 256 ganze Blöcke | +| Optionen | Binär ist Default, RECFM dann `U` (ind_file370 `indparse.c:249`) | +| Vorher allokieren | IND$FILE nimmt ein existierendes Dataset DISP=SHR mitsamt DCB (`indmain.c:190`, `:273`) → Upload ohne jede Option, unabhängig vom IND$FILE-Build | +| Falle | dasselbe DISP=SHR schreibt auch in ein *gemountetes* Dataset → `UNMOUNT PATH=/www` ist ein nummerierter Schritt | +| Fehlerbild | BLKSIZE-Mismatch fällt beim MOUNT auf: `UFSD062E` (ufsd `ufsd#sbl.c:69`) | +| Codepage | keine Entscheidung nötig: `http_send_file()` übersetzt UFS-Dateien fest mit IBM-1047 (`src/httpfile.c:70`), unabhängig von `CODEPAGE=` — genau was `ufsd-utils cp` schreibt. Round-Trip byteweise gemessen, inkl. UTF-8; nur `0x85` und `0xF7` überleben ihn nicht | + +Damit ist auch **U6** (Datenverlust beim Update) erledigt, ohne +`.SAMPLE`-Konstruktion: das Dataset trägt die Version im Namen, kann also die +Platte des Betreibers gar nicht treffen. + +Offen geblieben: + +- [ ] **U5-Rest** SHA256 der Release-Assets — mbt veröffentlicht heute keine + Prüfsummen. Das Image trägt einen Erstellungszeitstempel, ist also nicht + reproduzierbar; die Doku sagt das auch so, statt es zu versprechen. + +### O4 — Setup-Job: ausliefern oder Handarbeit lassen? + +`docs/installation.md` §7 lässt den Betreiber Proc und Konfigmember von Hand aus +der SAMPLIB kopieren. Das ist Absicht — *Produkt besitzt die Muster, Site +besitzt die Kopien* — aber ein **Muster** für den Kopierschritt wäre trotzdem +freundlicher als eine Prosaanweisung: ```jcl //COPYPROC EXEC PGM=IEBCOPY -//IN DD DISP=SHR,DSN=SYS2.HTTPD.SAMPLIB +//IN DD DISP=SHR,DSN=HTTPD.@VRM@.SAMPLIB //OUTP DD DISP=SHR,DSN=SYS2.PROCLIB //OUTM DD DISP=SHR,DSN=SYS2.PARMLIB //SYSIN DD * @@ -282,145 +166,157 @@ beim nächsten APPLY weg. /* ``` -- [ ] **L6** Für die Testinstallation eine **eigene Minimalkonfiguration** - erzeugen — siehe Falle T4 +Als weiteres SAMPLIB-Member (`HTTPSETU`) wäre das ein Zweizeiler in +`samplib/` — mit dem Haken, dass `@VRM@` dort ersetzt wird, `SYS2.PROCLIB` und +`SYS2.PARMLIB` aber geraten sind und auf TK5 falsch. Entscheidung offen. +Weder ufsd noch ftpd liefern so etwas aus. --- -## 6. TODO — UFS-Disk / WWW-Root +## 2. Erledigt — was mbt heute übernimmt + +Nicht neu planen. Die frühere §3 (Spike), §4 (SMP-Paket), §5 (SAMPLIB) sind +abgearbeitet oder ersetzt: + +| Früher offen | Heute | +|---|---| +| Spike: kopiert SMP wirklich? | ✅ 2026-08-08 in `../smptest` bewiesen (`TSMP100`, Bitvergleich) | +| MCS-/JCLIN-/Allokationsjob-Generator | ✅ `mbt/scripts/mbt/distribution.py` + `mbtdist.py` | +| `[smp]`-Block in `project.toml` | ✅ als `[distribution]` / `[distribution.smp]`, mit **Datasetnamen** statt DDNAMEs — der DDNAME ist immer der letzte Qualifier | +| SAMPLIB als `++MAC` in den SYSMOD | ❌ verworfen: eine MCS-Karte endet in Spalte 72, `HTTPPRM0` hat Kommentarzeilen bis 80. Die SAMPLIB fährt als eigenes XMIT per TSO RECEIVE, außerhalb von SMP | +| Install-Job als SAMPLIB-Member | ❌ liegt im Archiv (`httpd--inst.jcl`), nicht in der SAMPLIB | + +**Umgekehrt entschieden gegenüber der alten Fassung — zwei Punkte:** + +- **Ziel ist `HTTPD.@VRM@.LINKLIB`, nicht `SYS2.LINKLIB`** (alte F1). Versionierte + Produktdatasets heißen: kein IPL für einen APF-Eintrag, keine Kollision mit + Systembibliotheken, jede Release-Stufe nebeneinander installierbar. Die Proc + behält ihren STEPLIB und zieht ihn über `@LINKLIB@` aus dem Paket. + Konsequenz für die alte Falle T6: der Selbstautorisierungsweg über SVC 244 + bleibt der Normalfall, `HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8)` ist die + erwartete Zeile. Der APF-Zweig ist trotzdem beschrieben — ein Betreiber, der + die LINKLIB in `IEAAPF00` einträgt, bekommt `AUTHORIZED BY LIBRARY (MODULE + KEY 0)`, und `docs/installation.md` §2 sagt, was das für die Modulspeicherung + bedeutet. +- **ACCEPT fährt mit** (alte F4 sagte „nein, nie"). Der ACCEPT füllt die DLIB + und ist damit die Basis, auf die ein späteres `RESTORE` eines **PTF** + zurückgeht — ohne ihn würde RESTORE das Modul löschen statt es + zurückzunehmen. Der Preis ist, dass die FMID selbst permanent wird: `RESTORE` + scheitert, weil akzeptiert, und `REJECT`, weil der ACCEPT die MCS aus dem + SMPPTS entfernt. Der Weg zurück ist `UCLIN`, beschrieben in + `docs/installation.md` §12. **Service (PTFs) wird weiterhin nie akzeptiert.** -Siehe §14.1–§14.4 im Konzeptdokument. SMP4 kann das **nicht** ausliefern: eine -UFS-Disk ist ein `DSORG=PS`, `RECFM=U`, `BLKSIZE=4096`-Binärimage, und es gibt -keinen passenden Elementtyp. Sie ist außerdem **Site-Inhalt** — ein erneutes -APPLY dürfte sie nie anfassen. +--- -- [ ] **U1** Image bauen: +## 3. Verifiziertes Inventar -```sh -ufsd-utils create webroot.img --size 10M -ufsd-utils cp -r static/ webroot.img:/ -ufsd-utils ls -l webroot.img:/ # Kontrolle, s. Falle T3 -``` +Nicht neu messen (🔬 gemessen · 📘 SMP4-Handbuch · ✅ Repo). + +**Verfahren** 📘 -- [ ] **U2** Hochladen: `ufsd-utils upload webroot.img --dsn HTTPD.WEBROOT` - (legt das Dataset mit an) -- [ ] **U3** Mount eintragen — **`UFSDPRMx` gehört ufsd, nicht httpd** (F6): - `MOUNT DSN(HTTPD.WEBROOT) PATH(/www) MODE(RO)` - oder dynamisch `/F UFSD,MOUNT DSN=HTTPD.WEBROOT,PATH=/www,MODE=RO`. - **Kein `++MACUPD` in ufsds Parmlib** — Muster ausliefern, Schritt - dokumentieren. -- [ ] **U4** Laufzeitreihenfolge sicherstellen: **ufsd zuerst starten**, - Dateisystem gemountet, `/www` existiert — *dann* httpd. `DOCROOT` ist eine - Laufzeitabhängigkeit, die SMP nicht abbilden kann. -- [ ] **U5** `make webroot` + CI-Asset `httpd-4.0.0-webroot.img` (§20 im Konzeptdokument), mit SHA256-Summe statt Reproduzierbarkeitsversprechen -- [ ] **U6** **Update-Verhalten festlegen** (offen): `.SAMPLE`-Name oder - Existenzprüfung im Setup-Job. `ufsd-utils upload --replace` in einem - Update-Job wäre ein Datenverlust beim Betreiber. +- `++MOD(x) LKLIB(ddname)` liefert ein **fertig gebundenes** Loadmodul; ist das + LMOD im JCLIN über einen IEBCOPY-Step definiert, kopiert SMP es statt zu binden. +- DDNAME = letzter Qualifier des Datasetnamens. +- Copy-Input im JCLIN muss **inline** hinter `//SYSIN DD *` stehen. +- `SELECT MEMBER=(…)` verwenden — sonst gilt die DLIB als *total* kopiert. +- SMP4 kennt keinen Datentyp: nur `++MOD/MAC/SRC/MACUPD/SRCUPD/ZAP`. + +**Zielsystem `mvsdev`** 🔬 + +- `SMPAPP` und `SMPREC` liegen in `SYS2.PROCLIB`, nicht `SYS1.PROCLIB`. +- Beide Procs haben genau einen Step (`HMASMP`); DD-Overrides müssen ihn + trotzdem als Qualifier tragen. Siehe Falle T9. +- Der Proc bringt `LINKLIB`, `LPALIB`, `PROCLIB`, `PARMLIB`, `SAMPLIB`, + `ASAMPLIB`, `MACLIB`, `CMDLIB` mit — alle auf `SYS1.*`. + +**SYSMOD-Inventar** 🔬 — `SYS1.SMPPTS` ist auslesbar, weil die MCS-Entries als +einzige *nicht* encodiert sind (dokumentierte Ausnahme 📘). 1544 SYSMODs im PTS, +darunter die Funktions-SYSMODs des Sysgens. **Frei im PTS:** `THTP400`, +`TTST400`. Das **CDS ist so nicht prüfbar** — Stichprobe der Membernamen: +`Ak\x7F\x1C]\x0D\xB6\x00`, `Jj7\x96\xFF\xDB\x3A\x00`. Deshalb O1. + +> ⚠ Die alte Fassung nannte hier `TMVS100` als Vorschlag für mvsmf und riet +> davon ab. Der ökosystemweite Beschluss steht inzwischen im Root-`CLAUDE.md`: +> **nie `TMVS…`** (MVS/CE führt USERMODs `TMVS804/816/817`), mvsmf bekommt +> `TZMF010`. + +**Artefaktseite** ✅ — **5** Loadmodule: `HTTPD` (AC=1), `HTTPDSRV`, `HTTPDM`, +`HTTPDMTT`, `ABEND0C1`. Die alte Fassung nannte sechs mit `HTTPJES2` und +`HTTPDSL`; beide sind seit 4.0.0 nicht mehr gebaut (mvsMFs jobs- und +dataset-API ersetzen sie, Quellen liegen in `tbd/`). --- -## 7. Fallen +## 4. Fallen -Jede einzelne kostet sonst einen Arbeitstag. +Die, die noch gelten. -- **T1 — Die eiserne Regel für COPY-LMODs.** Ein Update liefert **immer das - ganze Loadmodul** über LKLIB nach. Ein einzelnes Objekt-`++MOD` gegen ein - COPY-LMOD bindet SMP *allein* und zerstört das Modul (📘 „no INCLUDE for the - current version"). Nie ein Objektdeck in ein `++PTF` für httpd. +- **T1 — Die eiserne Regel für COPY-LMODs.** Ein Update liefert **immer das ganze + Loadmodul** über LKLIB nach. Ein einzelnes Objekt-`++MOD` gegen ein COPY-LMOD + bindet SMP allein und zerstört das Modul (📘 „no INCLUDE for the current + version"). Nie ein Objektdeck in ein `++PTF` für httpd. -- **T2 — `norent` / `ac` bleiben bei ld370.** Der Grund, warum wir auf dem Host - binden: ld370s Template markiert alles RENT+REUS, C-Module mit mutable statics - nehmen dann bei TSO-Load einen S0C4 (dokumentiert in RAKFs - `APPLICATIONS/project.toml`). Wenn ein JCLIN-Bindeschritt entsteht, wo keiner - sein sollte, ist der Fehler *hier*. +- **T2 — `norent` / `ac` bleiben bei ld370.** Der Grund, warum auf dem Host + gebunden wird. Entsteht irgendwo ein JCLIN-Bindeschritt, wo keiner sein + sollte, ist der Fehler hier. - **T3 — ASCII→EBCDIC hängt an der Dateiendung.** `ufsd-utils cp` konvertiert - nur bei bekannter Endung (`.html`, `.css`, `.js`, …). Eine Datei ohne Endung - oder mit `.tmpl` landet **verbatim als ASCII** in der Disk und ist auf MVS - unlesbar — ohne Fehlermeldung. `-t` erzwingt Konvertierung, `-b` erzwingt - binär. Ausgabe von `ufsd-utils ls -l` gegenprüfen. - -- **T4 — Das ausgelieferte `httpprm0` aktiviert nur MVSMF-Routen.** Die einzigen - unkommentierten Zeilen im Muster sind `MOD=MVSMF /zosmf/…`. Für eine - httpd-Testinstallation **ohne** mvsmf ist das die falsche Konfiguration — - eigene Minimalkonfiguration mit `DOCROOT=/www` und ggf. `MOD=HTTPDSRV /.dsrv` - verwenden. - -- **T5 — mvsMF wirft `S80A ABEND` nach wenigen Requests.** Ein HTTP 503 heißt - dann **nicht** „Dataset fehlt". Außerdem liefern `dataset_exists()` und - `list_datasets()` gegen diesen Server durchgehend `False`/`[]` — auch für - nachweislich existierende Datasets. Verlässlich ist nur `list_members()`. - Existenzprüfungen im Setup-Job besser per JCL (`IEFBR14`/`IDCAMS LISTCAT`). - -- **T9 — DD-Overrides brauchen den Proc-Step UND die richtige Reihenfolge.** - Im smptest-Spike zweimal reingelaufen 🔬. `//CMDLIB DD DSN=SYS2.CMDLIB` nach - `EXEC SMPAPP` ist **kein** Override, sondern eine zweite DD gleichen Namens: - beide Datasets werden allokiert, SMP nimmt die des Procs, das Modul landet in - `SYS1.CMDLIB` — und jede SMP-Meldung sagt weiterhin `LIBRARY=CMDLIB`, der - Fehler ist also aus dem Log **nicht** ablesbar. Qualifizieren allein reicht - auch nicht: stehen hinzugefügte DDs (`SMPTLOAD`, `ASMPTEST`) vor der - Override-Karte, wird sie ebenfalls ignoriert. Richtig ist: - **erst alle Overrides, dann alle Ergänzungen**, alle mit `HMASMP.`-Präfix. - Kontrolle: nachsehen, wo das Member wirklich liegt — nicht dem RC glauben. - -- **T6 — Die Autorisierung ändert ihren Weg, und das will beobachtet werden.** - httpd hängt heute *nicht* davon ab, dass die STEPLIB autorisiert ist — es - **autorisiert sich selbst**. `httpd.c` verzweigt beim Start ✅: - - ```c - if (crt->crtopts & CRTOPTS_AUTH) rc = auth_setup(argv[0]); /* schon autorisiert */ - else rc = unauth_setup(argv[0]); /* holt sich die Autorisierung */ - ``` - - `unauth_setup()` ruft `__autask()` (SVC 244 → JSCBAUTH) und danach - `__austep()`, um die STEPLIB nachträglich APF-autorisiert zu machen — - Meldungen `HTTPD011I … via SVC 244` und `HTTPD013I STEPLIB is now APF - authorized`. Deshalb funktioniert die heutige Proc mit - `STEPLIB DD DSN=HTTPD.LINKLIB` auch ohne APF-Eintrag. - - Nach einer Installation in die autorisierte LNKLST-Verkettung sollte der - `auth_setup()`-Zweig greifen (`HTTPD010I … is APF authorized`) und die ganze - Selbstautorisierung entfallen. **Das ist während der Installationsexperimente - empirisch zu prüfen**, nicht vorher zu behaupten: - - - Welcher Zweig wird tatsächlich genommen? (Meldung im Log: `HTTPD010I` vs. - `HTTPD011I`/`HTTPD013I`) - - Ist `__austep()` dann überflüssig, harmlos oder störend? - - Braucht es eine zusätzliche Prüfung im Code — oder reicht `CRTOPTS_AUTH` - bereits? Der Verdacht ist, dass es schon reicht. - - Nebenbefund: SVC 244 kommt von RAKFs Usermod `RAK0001` — der liegt auf - `mvsdev` im PTS 🔬. Der heutige Selbstautorisierungsweg hängt also an RAKF. - **Kommt nach dem Spike, blockiert ihn nicht.** - -- **T7 — DDNAME = letzter Qualifier.** `SYS2.HTTPD.LINKLIB` hätte ebenfalls den - DDNAME `LINKLIB` — die Kollision mit dem Proc-DD verschwindet nicht durch - Umbenennen. Override ist der Weg. - -- **T8 — `ACCEPT` ohne `COND`.** RAKF macht das und akzeptiert damit auch nach - einem gescheiterten APPLY. Getrennte Jobs (F4). + nur bei bekannter Endung. Eine Datei ohne Endung oder mit `.tmpl` landet + **verbatim als ASCII** in der Disk und ist auf MVS unlesbar — ohne + Fehlermeldung. `-t` erzwingt Konvertierung, `-b` binär. Betrifft O3. + +- **T4 — Das ausgelieferte `httpprm0` aktiviert nur MVSMF-Routen.** Für eine + Testinstallation *ohne* mvsmf ist das die falsche Konfiguration. + `docs/installation.md` §7 sagt das jetzt auch dem Betreiber; für den Test + reicht eine eigene Minimalkonfiguration mit `DOCROOT=/www`. + +- **T7 — DDNAME = letzter Qualifier.** `HTTPD.@VRM@.LINKLIB` hat den DDNAME + `LINKLIB` und kollidiert damit zwangsläufig mit dem Proc-DD. Umbenennen hilft + nicht, Override ist der Weg — mbt kennt die Proc-DDs (`SMPAPP_PROC_DDS`) und + schreibt die Overrides selbst. + +- **T9 — DD-Overrides brauchen den Proc-Step UND die richtige Reihenfolge.** Im + Spike zweimal reingelaufen 🔬: `//LINKLIB DD …` nach `EXEC SMPAPP` ist **kein** + Override, sondern eine zweite DD gleichen Namens — beide Datasets werden + allokiert, SMP nimmt die des Procs, und jede Meldung sagt weiterhin + `LIBRARY=LINKLIB`. Aus dem Log ist der Fehler nicht ablesbar. Richtig: erst + alle Overrides, dann alle Ergänzungen, alle mit `HMASMP.`-Präfix. Der + generierte Job macht das; der Kommentar im Job sagt auch, dass man ihn nicht + umsortieren soll. + +**Erledigt, nur zur Erinnerung warum:** + +- ~~**T8 — `ACCEPT` ohne `COND`.**~~ Der generierte Job hängt jeden SMP-Step an + den vorigen: `ACCEPT EXEC SMPAPP,COND=(0,NE,APPLY.HMASMP)`. +- ~~**T5 — mvsMF `S80A` nach wenigen Requests.**~~ Aus der Zeit vor dem + Storage-Reclaim (#154/#174). Nicht als aktueller Befund weiterreichen; wenn + ein Setup-Job Existenzprüfungen braucht, sind `IEFBR14`/`IDCAMS LISTCAT` + trotzdem der robustere Weg. --- -## 8. Definition of Done — Testinstallation - -- [ ] `RECEIVE` / `APPLY` laufen mit RC ≤ 4, `APPLY CHECK` vorher sauber -- [ ] Alle 6 Module liegen in der Zielbibliothek und sind **bitgleich** mit - `build/` -- [ ] `SYS2.HTTPD.SAMPLIB` enthält `HTTPD`, `HTTPPRM0`, `HTTPINST`, `HTTPSETU`, - `HTTPWEB` -- [ ] Setup-Job hat Proc und Konfig nach `SYS2.PROCLIB`/`SYS2.PARMLIB` kopiert -- [ ] `HTTPD.WEBROOT` ist angelegt, unter `/www` gemountet, `index.html` darin - lesbar (EBCDIC!) -- [ ] `/S HTTPD` startet, `curl http://mvsdev:8080/` liefert `index.html` -- [ ] `RESTORE` bringt einen sauberen Vorstand zurück -- [ ] Wegwerf-FMID `TTST400` ist `REJECT`ed, Testdatasets sind gelöscht -- [ ] `THTP400` ist im CDS **nicht** belegt — reserviert für das getaggte 4.0.0 +## 5. Definition of Done — Testinstallation + +- [ ] `RECEIVE` / `APPLY` mit RC ≤ 4, `APPLY CHECK` vorher sauber +- [ ] **Fünf** `HMA2380 COPY SUCCESSFUL`-Zeilen — eine je Modul. Weniger fällt + nicht auf: der APPLY endet trotzdem RC 00 +- [ ] Alle fünf Module liegen in `HTTPD.V4R0M0D.LINKLIB` und sind **bitgleich** + mit `build/` +- [ ] `HTTPD.V4R0M0D.SAMPLIB` enthält `HTTPD` und `HTTPPRM0`, und der `STEPLIB` + im Member zeigt auf `HTTPD.V4R0M0D.LINKLIB` (die `@LINKLIB@`-Ersetzung) +- [ ] `/S HTTPD` startet bis `HTTPD001I … READY`, `HTTPD002I` sagt, über welchen + Weg autorisiert wurde +- [ ] `HTTPD.WEBROOT` angelegt, unter `/www` gemountet, `index.html` in EBCDIC + lesbar — **nur wenn O3 bis dahin steht**; sonst gegen eine `MOD=`-Route + prüfen und `HTTPD044W` als erwartet abhaken +- [ ] `curl http://mvsdev:8080/…` antwortet +- [ ] Aufräumen: UCLIN auf `TTST400`, Testdatasets gelöscht, `LIST CDS/ACDS + SYSMOD(THTP400)` antwortet RC 04 mit leerer Liste --- -## 9. Nicht Teil der Testinstallation +## 6. Nicht Teil davon -- Ausrollen auf ufsd, ftpd, mvsmf (dieselbe Mechanik, §21 Roadmap im Konzeptdokument) -- CI-Integration der Artefaktmatrix (§20 im Konzeptdokument) -- Objektdeck-Auslieferung — **verworfen**, Begründung in Anhang C +- Ausrollen auf ufsd, ftpd, mvsmf — ufsd und ftpd sind bereits umgestellt und + sind die Referenz, an der httpds `[distribution]` sich orientiert. +- Objektdeck-Auslieferung — verworfen, siehe T1/T2. diff --git a/static/index.html b/static/index.html index e700072..4ae1aff 100644 --- a/static/index.html +++ b/static/index.html @@ -1,93 +1,179 @@ - - - HTTPD 4.0.0 + + + + Welcome to HTTPD + + -
-

HTTPD

-
Version 4.0.0 — MVS 3.8j
-
✓ Server is running
-

- HTTP/1.1 server for IBM MVS 3.8j on Hercules.
- Part of the mvslovers open-source ecosystem. -

-