Repository navigation
v1.0.1
FTPD 1.0.1 is a patch release: six defects fixed since 1.0.0, two new
configuration keywords and one new startup message. Nothing about what FTPD
reaches has changed and the install procedure is the one 1.0.0 shipped — but
1.0.1 does not install over 1.0.0, see Upgrading from 1.0.0 below.
If FTPD.V1R0M0.LINKLIB is in your APF list, 1.0.0 cannot start at all, and
this release is the fix. If FTPD authorizes itself through SVC 244 — the stock
TK4-/TK5 case — you were never exposed to that one, and the rest of this release
is about a start that gives up honestly and a transfer that stops when the
client does.
The S0C4 at startup from an APF-authorized library (#101)
FTPD is link-edited AC(1). Fetched from an APF-authorized library the job
step is already authorized when program fetch runs, so MVS obtains the job pack
area in subpool 252, key 0 — authorized code must not be patchable by
problem-key code. The started task runs problem state key 8, so any store into
the module's own storage takes a protection exception.
FTPD's first such store was the first statement of main(), which is why the
entire symptom is one line:
12.34.30 STC 1124 IEF450I FTPD FTPD - ABEND S0C4 U0000 - TIME=12.34.30
No FTPD000I, no banner — the abend lands ahead of the first WTO. The
self-authorizing route hides this rather than avoiding it: SVC 244 sets
JSCBAUTH after the fetch and cannot relabel storage that is already
allocated, so the module stays key 8 and the very same store goes through
unnoticed. That is why a stock TK4-/TK5 never saw it.
The state moved out of the module. ftpd_server — written once, never read — is
deleted; the log level and the whole trace ring, which was the larger half, now
live in main()'s storage, hence key 8, published through the C runtime's
process anchor. The eleven-function logging API is unchanged, so all 84 call
sites are untouched. The translate tables are const now.
tools/check-module-data.py rejects mutable file-scope data and function-local
statics in the AC(1) module, as its own CI job, so this cannot come back
quietly.
Verified on the route that killed 1.0.0. Started from an APF-authorized
LINKLIB, 1.0.1 comes up and runs normally — the same start that gave 1.0.0 the
IEF450I above and nothing else. Alongside that: the fix checked at assembler
level (cc370 -S before and after), and tools/check-module-data.py clean on
the fixed tree while still reporting all 13 pre-fix sites on the commit before
it.
The original was found in UFSD by Rob Prins,
who did most of the diagnosis himself
(ufsd#64, fixed in UFSD 1.2.1).
FTPD's own sites came out of running ufsd's checker across the ecosystem
afterwards — FTPD was the worst affected of the projects checked, because its
first store happens before its first message. Thank you, Rob.
A start that reported READY without listening (#111, #109, #113)
Three defects, one story: what FTPD does when the bind does not succeed.
main() waited five seconds for the socket thread to publish the listener and
then issued FTPD001I READY whether or not it ever appeared. The loop was a
timeout, not a check. Measured on a server in that state, F FTPD,SESSIONS
answered FTPD015I ACTIVE SESSIONS: 0 / 10 — a dead address space reading
exactly like a quiet one.
The wait now ends on whichever of the two things actually happened — the
listener was published, or the socket thread gave up — and an FTPD that cannot
listen ends rather than sitting there. No console command retries a bind, so
/P and /S was the only way back either way; what changes is that the step
ends COND CODE 0008 instead of 0000, the one part of a start an automation
product can read without parsing the console.
Measured with a throwaway STC bound to an address the host does not have, and
with no BINDTRIES line in its config member, so these are the shipped
defaults:
04.17.13 FTPD051E BIND() FAILED ON 10.99.99.99 PORT 2122, ERRNO=49
04.17.13 FTPD051I RETRYING BIND IN 5S (1 OF 3)
04.17.18 FTPD051I RETRYING BIND IN 5S (2 OF 3)
04.17.23 FTPD051I RETRYING BIND IN 5S (3 OF 3)
04.17.28 FTPD051E BIND() STILL FAILING AFTER 3 TRIES, ERRNO=49
04.17.28 FTPD056E FTPD IS NOT LISTENING ON PORT 2122, THIS INSTANCE ENDS
IEF142I FTPDT FTPDT - STEP WAS EXECUTED - COND CODE 0008
Because giving up ends the address space, how long FTPD waits first became a
site decision: BINDTRIES and BINDWAIT, both 1–100, defaulting to 3 and 5
— fifteen seconds from /S to a verdict, and FTPD057I reports both in the
CONFIG dump. What gets retried changed too: EADDRNOTAVAIL is retried now as
well as EADDRINUSE. The first is the one that matters at IPL, where FTPD can
start before the stack has an address and where raising BINDTRIES is the
answer. Everything else is refused immediately.
The stale-port sweep — the recovery that closes sockets a dead FTPD left
holding the port — is fixed with it. It used to run in front of every bind,
where the only thing it could do was close a socket belonging to something
alive, and it stopped at the first match, although a dead FTPD leaves its
listener and every connection it had accepted, each one holding the port. It
now runs only when a bind has actually failed with EADDRINUSE, closes every
match, and rebinds after a single 2 second settle. FTPD051E moved behind it,
so a start that recovers this way shows the FTPD053I lines and FTPD054I with
nothing alarming in front of them.
Worth knowing if something other than FTPD may hold that port. The socket
table lives in Hercules and is global to the emulator — which is why a dead
STC's leftovers are reachable at all — and it exposes no owner. What bounds the
sweep is the FTPD.PORT.nnnnn ENQ, the EADDRINUSE, and the address comparison
(SRVBIND can name one interface, and a leftover socket on another cannot be
what made our bind fail). Past that it is indiscriminate: a live foreign
listener on FTPD's port does lose its socket.
Transfers, and noticing that the client is gone
RETR of an MVS data set read on to EOF after the client had gone (#106).
The three send loops ignored what the data connection told them, and the
completion path replied 250 Transfer completed successfully unconditionally —
so an aborted transfer looked like a good one to the client and in the log, and
the byte counters included records that never left the machine. Measured on a
2693-track (~51 MB) data set, client stops reading and drops the connection:
| before | after | |
|---|---|---|
| session active after client vanished | ~90 s | 0.7 s |
| control reply | 250 Transfer completed successfully. |
426 Transfer aborted: data connection lost after 228672 bytes. |
| remaining data set | read to EOF from DASD | not read |
The control connection survives an aborted transfer — QUIT still answers 221
— which is correct: a dead data connection is not a dead session. Normal
transfers still report 250. This covers MVS data sets; the UFS path stops
correctly on a send error but still replies 226, which is the same flaw and is
not fixed here.
A failed write on the control connection was discarded (#104). The four
places that actually write to the control socket now funnel through one sink,
which completes short writes, logs the failure once per session — a dead
control connection otherwise produces one message per queued reply — and moves
the session to closing. That flag is deliberately separate from the session
state, because the 220 greeting used to overwrite the state right after a failed
reply: the guarantee cannot rest on ~200 handlers behaving. The trace line moved
behind the success check, so a reply that never reached the client no longer
appears in the trace as if it had.
SITE SUBMIT read the jobid from freed storage (#102). The post-close read
fetched it from the VSFILE that had already been released; jesircl2() hands it
out legally between ENDREQ and close.
New: FTPD008I says which route authorized the started task
Neither route announced itself, and the route is exactly what decides whether
FTPD's own module storage is writable. Startup now reports it on the line after
the version banner:
FTPD008I AUTHORIZED BY LIBRARY (MODULE KEY 0) APF list entry
FTPD008I AUTHORIZED BY SVC (MODULE KEY 8) SVC 244, from RAKF
Neither line is a warning — both routes end in an authorized started task, and
which one you want is a site decision. The key is inferred from the route rather
than measured: an authorized job step has its module fetched key 0, an
unauthorized one key 8, and SVC 244 arrives too late to change either.
Installing
Download ftpd-1.0.1-dist.zip (or .tar.gz); the guide and every file you
need are inside, and the procedure is unchanged from 1.0.0:
README.md |
the installation guide |
ftpd-1.0.1-load.xmit |
the FTPD load module |
ftpd-1.0.1-samplib.xmit |
the STC procedure and the configuration pattern |
ftpd-1.0.1-alloc.jcl |
allocate the datasets — run once |
ftpd-1.0.1-inst.jcl |
receive, apply, accept |
Upload the two XMITs in binary, submit the allocation job once, then the
install job — the only thing to edit is the two dataset names you uploaded to.
The SYSMOD travels inline in the job, so there is no third file to transfer.
Everything lands under FTPD.V1R0M1.*, so it does not collide with an existing
V1R0M0 installation. FMID is TFTP100.
Two things in the shipped material are still not ready to run as they are:
- On TK5,
SYS2.PARMLIBdoes not exist — putFTPDPRM0inSYS1.PARMLIB
and start with/S FTPD,D='SYS1.PARMLIB'. - The two JES DDs in the procedure,
HASPCKPTandHASPACE1, carry the unit
and volume of the system FTPD was built on. Point them at your own JES2
checkpoint and spool, or delete both if you do not want the JES interface.
Both are required together: withHASPACE1missing, every JES command
answers451.
Upgrading from 1.0.0
An FMID names a functional level, one per minor release, so 1.0.1 ships as
TFTP100 again — the id 1.0.0 already applied and accepted. SMP will not
apply a function SYSMOD that is already applied, and the install job assumes a
clean inventory. Two ways round it:
| Keeping it SMP-managed | Free the FMID first with the UCLIN job in doc/uninstall.md, then run the 1.0.1 allocation and install jobs as for a fresh install |
| Without SMP | Receive ftpd-1.0.1-load.xmit into a library and point the procedure's STEPLIB at it |
That second route is a free choice of library, because FTPD authorizes itself
through SVC 244 and that comes from RAKF, which you need anyway: a library
outside IEAAPF00 simply gets you FTPD008I AUTHORIZED BY SVC (MODULE KEY 8),
the route #101 never affected. On the APF route, remember that the list is
read at IPL only — until FTPD.V1R0M1.LINKLIB is in IEAAPF00 and the system
has IPLed, FTPD falls back to SVC 244 rather than failing.
Either way, copy the 1.0.1 procedure from the new SAMPLIB into your PROCLIB
(step 7) — it is what points STEPLIB at the new LINKLIB. Your FTPDPRM0 and
your RAKF definitions are unaffected: BINDTRIES and BINDWAIT are optional.
If you are installing FTPD for the first time, none of this applies — the
install job is the whole story.
Also in this release
The correction announced with 1.0.0 is now part of the product. The removal
section of the guide shipped inside ftpd-1.0.1-dist.zip no longer sends you to
RESTORE followed by REJECT, which an accepted function SYSMOD refuses both
of; it points at the UCLIN job that works, with the messages to check. The
guide also gained an APF section on why the two authorization routes differ in
storage key, and a troubleshooting row for the bare S0C4.
Release artifacts are built against libc370 1.0.3 (1.0.0 shipped against
1.0.2), and three things in it reach FTPD directly. jesircl2() is what makes
the SITE SUBMIT fix above possible at all. send() now honours the X'75'
retry code on a bounded budget, which is the other half of the control-write
change: waiting out a temporarily full send buffer belongs in the C library, so
anything negative that reaches FTPD means the peer is gone. And one change is
operator-visible at /S FTPD — a startup stack shortage now abends U0801
by name instead of S80A from inside SVC 10, so a runbook keyed on S80A
should key on U0801 instead. FTPD005I stamps the library level the running
module was linked against.
One item completes the list, and it is why the startup waits above can be
trusted to wait at all: the five inline STIMER sites carry clobber lists now,
and the fields polled across TCBs are volatile — without either, the compiler
is free to keep a live value in a register across the SVC and to hoist the poll
out of its loop.
Still open
1.0.1 fixes defects; it does not touch the identity model, so the warning that
came with 1.0.0 stands unchanged. The STC's resting identity is still
STC/STCGROUP, which holds ALTER on every data set
(#97); JES access still has no
authorization model, JESOWNER being a client-set display filter rather than a
control (#90); and the ACEE
switch is still not fully serialized against concurrent sessions
(#64). Keep the FTPD identity
scoped to what the started task itself needs — never give it broad data set
authority. Closing those three is what 1.1.0 is for.
Two known limits worth knowing before you meet them: STOR allocates a
hardcoded 10/5-track SPACE, which caps an upload at about 85 tracks
(#100), and shutdown can take up
to ~5 seconds to notice idle sessions
(#66).
Full Changelog: v1.0.0...v1.0.1