Repository navigation
Releases: mvslovers/ftpd
Release list
FTPD 1.3.0
FTPD 1.3.0 is a minor release of the FTP server for MVS 3.8j (FTPD). It is a drop-in for an FTPD 1.2.0 that SMP installed: same data sets, same started task procedure, same APF entry, same FTPDPRM0. The FMID is TFTP130, and the install deletes TFTP120. Three things in it can stop an upgrade that is done without reading: the JES view now asks RAKF which jobs a user may see (without a profile, a user sees only the own jobs), the started task no longer switches to FTPD/USER at startup, and a text line longer than the record of the data set now fails the transfer instead of being cut. Besides that, it closes gaps in the SITE command set and repairs behaviour that was wrong, silent or hung.
FTPD 1.3.0 is built with LIBC/370 (libc370) 2.25.1 and CC/370 (cc370) 1.6.1 by the release workflow, which uses MBT (mbt) 3.0.0-rc1 as the build tool, and carries the UFSD client library (libufs) of UFSD 1.4.0, unchanged.
Read this first
Upgrading from 1.2.0 (installed by SMP). Install into the same data sets and skip the allocation job: the data sets exist, and the job would allocate them NEW. The APPLY ends COND CODE 0004 on HMA2461 SYSMOD TFTP120 NOT FOUND ON SMPSCDS LIBRARY. That is the expected result of a replacement, not a failure. MVS/CE is different: the FTPD 1.2.0 that comes with the MVS/CE image was not installed by SMP and has no FTPD.* data sets, so on MVS/CE the install is a first install as far as SMP and the data sets go. Run the allocation job, run the install job (its APPLY ends COND CODE 0000, no HMA2461), and replace the procedure of the image by the shipped one; the library of the image's procedure stays and is no longer used. From 1.1.0 or older, upgrade to 1.2.0 first (1.2.0 deletes TFTP110 only; on a system where TFTP100 still owns the load module the install copies nothing and ends RC 00).
Verify the install, not the return code. HMA2380 COPY SUCCESSFUL must be in the job log, and FTPD.LINKLIB must hold the member FTPD. After /S FTPD the startup messages must read:
FTPD000I FTPD 1.3.0 (6B6367D) STARTING
FTPD005I LIBC370 2.25.1 (FCDE4E8)
LIBC370 2.10.3 on the second line means the started task still runs 1.2.0.
What you must act on:
- Decide who may see the jobs of other users. The JES view asks the RAKF class
JESSPOOL. Before, every user could list, read and cancel the jobs of everybody. Now a user sees, reads and cancels only the own jobs, and the jobs of other users need a profile inJESSPOOL. The class exists from RAKF 2.0.0. On a RAKF without it (TK5 as shipped), FTPD 1.3.0 installs and runs, and each user sees only the own jobs. If an administrator or an operator relies on seeing all jobs, define the profiles on RAKF 2.0.0 or later before you upgrade.SITE JESOWNERwith another user ID or*needs the same right and answers501 JESOWNER is not permittedwithout it. - The started task keeps the identity RAKF gives it. FTPD 1.2.0 and earlier logged on as
FTPD/USERat startup, whatever RAKF assigned. FTPD 1.3.0 does not, and says which identity it runs under inFTPD004I(orFTPD004W). On a RAKF without a mapping by started-task name, such as TK5 before Update 6, the task now runs asSTC/STCGROUP. The group of theFTPDline inUSERSmust be allowed onFACILITY SVC244, or FTPD cannot obtain APF authorization (FTPD003W APF SETUP FAILED RC=4). The groupUSERof the 1.2.0 guide is not one the shipped profile allows. - A text line longer than the record of the data set fails.
STORandAPPEanswer451and remove a data set the command created. Before, FTPD cut the line without a word and answered250. A script that relied on the silent cut now fails.SITE TRUNCATEbrings the cutting back, and the250then says so. APPEappends. It adds to a sequential data set and creates what is missing; it refuses an existing member of a PDS. Before, it replaced the data set likeSTOR.SITE RDW, and aSITE SBSENDEOLother thanCRLF, answer501. Before, the answer was200and nothing changed.- Settings FTPD never used are now reported.
AUTHUSER,JESINTERFACELEVELand the lines of the formVOLSER,UNITinFTPDPRMare reported as ignored, in the log and with one console messageFTPD062W.CONFIGno longer writesFTPD047IandFTPD048I. - An FTPD that is not authorized answers
PASSwith530. Before,PASSgot no answer at all and the session was closed after the third one.FTPD003Wat startup still means the install is not finished. - The started task procedure no longer needs the
HASPCKPTandHASPACE1DD statements. FTPD finds the JES2 checkpoint and the spool itself. Keep a statement only when it is needed (anotherDSNPRFX, or a checkpoint that is neither cataloged nor on an online volume). A statement that names the wrong data set or volume now breaks every JES command with451.
Known issue: a comment card before the JOB statement takes the job's credentials (ftpd#277). When a job is stored to the JES view, FTPD takes the first card that has the word JOB between blanks for the JOB statement. A comment card such as //* RUN THE JOB NOW ahead of the real JOB statement matches, so NOTIFY, USER and PASSWORD are appended to the comment and the real JOB statement gets none. The job does not run under your identity. Workaround: put no card with the word JOB between blanks before the JOB statement, and write no comment with it. Planned for 1.4.0.
What changed
| before (1.2.0) | now (1.3.0) | |
|---|---|---|
| jobs in the JES view | every user lists, reads and cancels every job | own jobs only; others by JESSPOOL profile (#90) |
| identity of the started task | switched to FTPD/USER at startup (#179) |
keeps the identity RAKF gives it, named in FTPD004I |
MAXSESSIONS of 1, 2 or 3 |
no session ever starts: connections queue, no worker wakes (#168) | one worker per allowed session; the sessions run |
| connection refused by the worker pool | control connection stays open, session never freed (#177) | 421 and the connection is closed |
| burst of clients | one connection accepted per look, one look a second: ten clients greeted over about ten seconds (#209) | every waiting connection accepted at each look: greeted within about a second |
STOR/APPE into a data set under retention |
session waits for an operator reply to IEC507D (#198) |
550 with the expiration date |
same, expiration date 99000 |
the same wait (#204) | the same 550 |
STOR/APPE of a member into a PDS with a full directory |
451, and a retry fails the same way (#173) |
552 with the remedy |
after a STOR failed on a full PDS directory |
the old name stays allocated to the started task; MKD of it answers 550; P FTPD ends with abend SA0A (#194, #223) |
data set can be renamed, its old name created again, and P FTPD ends cleanly |
| listing that runs out of storage | empty list (#118) | 451 Insufficient storage to list ... |
STATS |
a transfer of a data set counted two to three times, running sessions included (#206) | each byte once, ended sessions only |
| download of more than 256 bytes per call | exposed to the X'75' restart defect on the send side (#137) | fixed by LIBC/370 2.24.0 and later |
text STOR into a VB/VBA data set |
record boundaries lost (#185) | one record per line |
text STOR into a RECFM=U data set |
all lines stored as one record (#245) | one block per line; RETR sends one line per block |
STOR that ends in E37 |
the DD numbers stay allocated, a batch job with DISP=OLD on the data set waits until FTPD ends, and P FTPD logs further IEC032I E37-04 (#265) |
DCB, allocation and ENQ are released and the session goes on |
| UFS create without free inodes | 452 |
552, the condition is permanent (#151) |
SITE keywords |
PRI=5 is an unknown word; keywords are case-sensitive |
shortest forms accepted; case does not matter (#130, #189) |
SITE JESSTATUS |
any value stored, every job listed | ALL, INPUT, ACTIVE or OUTPUT filter the list, RETR and DELE |
get or delete of a job ID such as JOB* |
550 Jobid ... not found ... from JES2 |
501 Invalid job ID before JES2 is asked (#267) |
Measured on MVS by the acceptance run of the release candidate, on TK5 and MVS/CE systems started for the test: a burst of clients was greeted within 0.97 seconds at most; STATS counted 80000 bytes for a transfer of 80000 bytes; a SITE RETPD above MAXRETPD was answered 501; after an E37 on STOR the DCB, the allocation and the ENQ were released and the session went on. The E37 row of 1.2.0 was measured on a throwaway system with 1.2.0 and with the same data set filled up. The rows for the connection refused by the worker pool (#177), the listing that runs out of storage (#118), the UFS inodes (#151), FTPD077E and the download of more than 256 bytes per call (the X'75' defect, #137, fixed in LIBC/370) are covered by the unit tests of the release; they were not exercised on a running system.
| 1.2.0 | 1.3.0 | |
|---|---|---|
| FMID | TFTP120 |
TFTP130, deletes TFTP120 |
| Libraries | FTPD.LINKLIB, FTPD.SAMPLIB, FTPD.AFTPDLOD |
unchanged |
| C library | LIBC/370 2.10.3 | LIBC/370 2.25.1, CC/370 1.6.1 |
| libufs (UFSD) | 1.4.0 | 1.4.0, unchanged |
| RAKF | 1.2.x or 2.0.0 | 1.2.x or 2.0.0; 2.0.0 or later to let users see the jobs of others (JESSPOOL) |
Install
The release has three assets (the files you install from):
| Asset | What it is |
|---|---|
ftpd-1.3.0-dist.zip, ftpd-1.3.0-dist.tar.gz |
the distribution archive, the same content in two formats; download this one |
ftpd-1.3.0-load.xmit |
the FTPD load module as a TSO RECEIVE-ready XMIT (also inside the archive) |
Unpack the archive and follow its README.md, which is the installation guide of this release. The archive holds:
| Fi...
v1.2.0
FTPD 1.2.0 moves FTPD to LIBC/370 (libc370) 2.10.3 and CC/370 (cc370) 1.6.1, and fixes an upload that reported success for a member that was never stored. The port to libc370 2.10.3 required that fix. With the earlier C library, a member upload that ran out of space in a PDS ended in an ABEND SD37: FTPD answered 552, and the old member survived. With 2.10.3 the same condition comes back as an error code at close, and without the fix the port alone would have answered 250 Transfer completed successfully for every such upload. For a system running 1.1.0 this is a drop-in: the same data sets, the same procedure, the same APF entry and the same FTPDPRM0. Stop the server, run the install job, start it again. Built against libc370 2.10.3 and libufs from UFSD 1.4.0. The FMID is TFTP120 and it deletes TFTP110.
Read this first
- Coming from 1.1.0:
/P FTPD, run the install job,/S FTPD. Skip the allocation job, because the data sets already exist. - Coming from 1.0.x: install 1.1.0 first (v1.1.0, section 2b of the guide), then 1.2.0.
TFTP120deletes onlyTFTP110. On a system whereTFTP100still owns the load module, the 1.2.0 install copies nothing and still endsRC 00. - On an upgrade from 1.1.0 the
APPLYendsCOND CODE 0004onHMA2461 SYSMOD TFTP110 NOT FOUND ON SMPSCDS LIBRARY. That is expected, and the install job'sACCEPTis gated to run through it. A fresh install endsRC 00. - Verify by the banner and the member list, not by the condition codes. After the restart
FTPD005Imust readLIBC370 2.10.3 (A790DC4); 1.1.0 showedLIBC370 V1.0.6.FTPD.LINKLIBmust holdFTPD. - A member upload into a full PDS now answers
552, and the member is not stored. If you replaced an existing member, its old content is still there. - LIST/NLST of a PDS under a storage shortage answers
451 Insufficient storageinstead of550 No data sets found. This path was not reproduced on MVS: a storage shortage in the middle of a directory read cannot be triggered on purpose.
Fixed
| before | now | |
|---|---|---|
| a member upload into a full PDS (#170) | with the C library change and without this fix: 250 Transfer completed successfully, member not stored |
552, member not in the directory |
| replacing a member that no longer fits (#170) | the same 250 |
552; the old member keeps its content |
| LIST/NLST of a PDS when storage runs out (#158) | 550 No data sets found, as if the PDS were empty |
451 Insufficient storage to list <dsn> |
| three log messages (#155) | each dash logged as three junk characters | a plain hyphen |
Measured on MVS with a small PDS (FB 80, BLKSIZE 19040, two tracks, five directory blocks), filled with 47-record members until one fails:
| build | C library | result |
|---|---|---|
| the previous development build | LIBC/370 2.6.3 | 4/4: ABEND SD37 in close, answered 552, old member kept |
| the port without the fix | LIBC/370 2.10.3 | 59 uploads after the 21st answered 250, none of them stored |
| 1.2.0 | LIBC/370 2.10.3 | 4/4, twice: 552, failed member not in the directory, old member kept with all 47 records |
The test is section 8 of test/test_ftpd.sh.
| 1.1.0 | 1.2.0 | |
|---|---|---|
| FMID | TFTP110 |
TFTP120, deletes TFTP110 |
| Libraries | FTPD.LINKLIB, … |
unchanged |
| C library | LIBC/370 1.0.6 | LIBC/370 2.10.3, CC/370 1.6.1 |
| libufs (UFSD) | 1.3.0 | 1.4.0 |
UFSD stays optional at runtime. UFSD 1.4.0 changed only its C library; its server and the libufs interface are the same as in 1.3.0.
Install
Download ftpd-1.2.0-dist.zip (or .tar.gz); the guide and every file you need are inside:
README.md |
the installation guide |
ftpd-1.2.0-load.xmit |
the FTPD load module |
ftpd-1.2.0-samplib.xmit |
the STC procedure and the configuration pattern |
ftpd-1.2.0-alloc.jcl |
allocate the data sets: first install only |
ftpd-1.2.0-inst.jcl |
receive, apply, accept |
Upload the two XMITs in binary. Then submit the install job, after the allocation job on a first install. The only thing to edit is the two data set names you uploaded to.
The documentation in the repository:
- Installation guide, the same file as the package's
README.md - Removing FTPD
- RAKF setup
Changes
v1.1.0
FTPD 1.1.0 is about uploads: how much space they get, what happens when they run out of it, and where the data set lands. Eleven issues closed — #100, #121, #122, #124, #127, #128, #129, #133, #135, #143 and #148 — three of them reported from outside, and one change that touches every installation. It is not a drop-in for 1.0.x: the data set names lose their version qualifier, so the started task's STEPLIB and the APF entry are re-pointed once. Your FTPDPRM0 needs no change — no keyword changed meaning, and the three new ones default to the old behaviour. Built against libc370 1.0.6 and libufs from ufsd 1.3.0 (the build requires ufsd >= 1.3.0; UFSD itself stays optional at runtime, see the guide's prerequisites). The FMID is TFTP110 and it deletes TFTP100.
This release was published once and withdrawn. If you installed the
earlier build, or tried to, read Upgrading from 1.0.x below — the procedure
has changed and is now shorter, not longer.
Read this first: what an operator or a client notices
- The data sets are
FTPD.LINKLIB,FTPD.SAMPLIBandFTPD.AFTPDLODnow (#121), notFTPD.V1R0Mx.*. After the install, scratch the old libraries, re-pointSTEPLIBand update the APF entry — the install job cannot do that for you, and untilSTEPLIBmoves you are still running 1.0.x with no error anywhere. - No migration step before the install (#143). The SYSMOD carries
++VER(Z038) DELETE(TFTP100); SMP transfers ownership of the module itself during theAPPLY. - On an upgrade the
APPLYstep endsCOND CODE 0004onHMA2461 SYSMOD TFTP100 NOT FOUND ON SMPSCDS LIBRARY. Expected; the install job gates itsACCEPTaccordingly. A fresh install endsRC 00. - An upload that runs out of space answers
552, not451(#129, #135).552is permanent: a conforming client no longer retries it. A genuine write error still answers451. - A failed upload no longer leaves its data set behind (#129). A data set FTPD allocated for the transfer is scratched (
FTPD074I); an upload into an existing data set is never scratched. - Unused space is released at close (#128) for data sets FTPD allocated itself.
DEFUNITandDEFVOLUMEare honoured now (#133), and so areSITE UNIT=andSITE VOLUME=. If your parmlib names a volume, new data sets land there from this release on; a volume that is not online is reported at startup (FTPD059W) and ignored.- STOR space is configurable (#100):
DEFPRIMARY,DEFSECONDARYandDEFSPACETYPEinFTPDPRM0,SITE PRIMARY=/SECONDARY=/TRACKS/CYLINDERS/BLOCKSper session. The defaults are the old hardcodedTRK(10,5). - A UFS upload that failed no longer answers
226(#148):552for out of space, the UFS error for any other write failure,426for a broken data connection. Expect the552at about 4.06 MB per file — a property of the UFS on-disk format, not a full disk. - An
IEAAPF00entry forFTPD.LINKLIBdictates its volume. If you already have one, putVOL=SER=on the allocation job before you run it (see Install).
Fixed, and measured on MVS
| before | now | |
|---|---|---|
| an upload past the allocation (#127, #129, #135) | SB37/SD37/SE37, answered 451 — retryable, and every retry met the same full data set |
552, permanent, with advice on how to allocate more |
| STOR space (#100) | SPACE=TRK(10,5), fixed — about 85 tracks, ~4 MB at FB/80/3120 |
DEFPRIMARY/DEFSECONDARY/DEFSPACETYPE, overridable by SITE |
| an unparseable space value | silently 10 in one code path, 100 in another |
refused, with a message naming the default it fell back to |
the session after an SD37 (#129) |
300 seconds to a client timeout; the DD left allocated, so the scratch got IDCAMS RC=8 |
ends promptly; the data set is gone; a retry from a fresh allocation works |
| the data set of a failed upload (#129) | left catalogued, holding the partial upload | scratched, FTPD074I |
| unused space after a small upload (#128) | kept | released at close |
| TYPE A upload, more than 256 bytes per receive (#122, #124) | lines missing, fragments spliced together | 5 rounds of 2 MB: byte-identical every time |
DEFUNIT, DEFVOLUME, SITE UNIT=, SITE VOLUME= (#133) |
parsed and ignored | honoured; configured values checked at startup |
SITE VOLUME= naming a volume that is not mounted (#133) |
session hung, IEF238D outstanding on the console |
a reply to the client |
| a failed UFS upload (#148) | 226 Transfer complete |
552, the UFS error, or 426 |
| data set names (#121) | FTPD.V1R0M2.LINKLIB, … — new names every patch |
FTPD.LINKLIB, … — one APF entry, one IPL, ever |
| replacing the previous FMID (#143) | the withdrawn first build: a manual step before the install, silent when skipped | DELETE(TFTP100) during the APPLY |
| 1.0.x | 1.1.0 | |
|---|---|---|
| FMID | TFTP100 |
TFTP110 |
| Libraries | FTPD.V1R0M2.LINKLIB, … |
FTPD.LINKLIB, … |
| libc370 | 1.0.4 | 1.0.6 |
| libufs (ufsd) | 1.2.2 | 1.3.0 |
Install
Download ftpd-1.1.0-dist.zip (or .tar.gz); the guide and every file you need are inside:
README.md |
the installation guide |
ftpd-1.1.0-load.xmit |
the FTPD load module |
ftpd-1.1.0-samplib.xmit |
the STC procedure and the configuration pattern |
ftpd-1.1.0-alloc.jcl |
allocate the data sets — run once |
ftpd-1.1.0-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 data set names you uploaded to. The SYSMOD travels inline in the job, so there is no third file to transfer. FMID is TFTP110.
The documentation in the repository:
- Installation guide — the same file as the package's
README.md; section 2a covers the upgrade from 1.0.x - Removing FTPD
- RAKF setup
One thing to check before you run the allocation job. On MVS 3.8j an IEAAPF00 entry is dsname volser, not a name alone. If you have already added FTPD.LINKLIB to the APF list, that entry does not merely permit a volume — it dictates one, and the allocation job says UNIT=SYSDA with no VOL=SER. Put the volume in:
//LINKLIB DD DSN=FTPD.LINKLIB,DISP=(NEW,CATLG,DELETE),
// UNIT=SYSDA,VOL=SER=your-apf-volume,
Get it wrong and you have a library that exists, holds the right module, passes every check — and is not authorised. Nothing reports it, because FTPD falls back to the SVC 244 route and starts anyway. The startup message is the only place the two are distinguishable: on a system where you added an APF entry, FTPD008I AUTHORIZED BY SVC (MODULE KEY 8) means the entry is not covering the library. Found the hard way during an install of UFSD 1.3.0 on a development system.
The two caveats from 1.0.x still apply to the shipped material:
- On TK5,
SYS2.PARMLIBdoes not exist — putFTPDPRM0inSYS1.PARMLIBand 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.
Not using SMP? Receive ftpd-1.1.0-load.xmit into a library and point the procedure's STEPLIB at it.
Upgrading from 1.0.x
Install it. The FMID takes care of itself — that is #143, described below.
What the install job cannot do for you is the data sets, and the reason is worth thirty seconds. SMP records the ddname an element was installed through, never the data set name. Both FTPD.V1R0M2.LINKLIB and FTPD.LINKLIB end in LINKLIB, so both installs use ddname LINKLIB — and when 1.1.0's APPLY deletes the old load module it resolves that ddname in its own job, which points at the new library. FTPD.V1R0M2.LINKLIB is never opened.
So after the install:
- Scratch the old libraries. Look them up rather than assuming the patch level — ISPF 3.4 on
FTPD.*, orLISTCAT LEVEL(FTPD). Expect three:FTPD.V1R0M?.LINKLIB,.AFTPDLOD,.SAMPLIB. They are invisible to SMP now, so nothing will ever mention them again. - Repoint the started task's
STEPLIBatFTPD.LINKLIB. This is the step that decides whether you are running 1.1.0 at all. If you skipped step 1 the old library still exists and still holds a working module: FTPD starts, serves, and logs no error — it is simply the old one. - Update the APF entry. The last time it will need changing, because the name no longer carries a version. One IPL ever, not one per release.
Stop FTPD before you begin: FTPD.LINKLIB is the library it loads from.
One return code that looks wrong and is not. On an upgrade the APPLY step ends COND CODE 0004, not 0000:
HMA2270 APPLY PROCESSING SUCCESSFULLY COMPLETED FOR SYSMOD TFTP110
HMA2461 SYSMOD TFTP100 NOT FOUND ON SMPSCDS LIBRARY
HMA2050 APPLY PROCESSING COMPLETED - HIGHEST RETURN CODE IS 04
HMA2461 is SMP noting that the deleted level has no backup entry to keep — it never will, because it was deleted rather than superseded. HMA2270 is the line that says the APPLY worked. The install job expects this and gates its ACCEPT accordingly. A fresh install has nothing to delete and ends RC 00.
Your FTPDPRM0 and your RAKF definitions are unaffected. No keyword changed meaning; the three new ones default to the old behaviour.
What is running
FTPD005I stamps the library the module was actually linked against, so a deploy that did not take cannot hide:
FTPD000I FTPD 1.1.0 (23011CD) STARTING
FTPD005I LIBC370 1.0.6
...
v1.0.2
FTPD 1.0.2 is a relink: the release artifacts are built against libc370
1.0.4 instead of 1.0.3. One issue closed, no new feature, no breaking change —
and one behaviour change that is not labelled breaking but should be read.
If you saw an occasional ABEND S001 under parallel transfers, this is the
release that fixes it. If you also run UFSD, take ufsd 1.2.2 with it: the
same library fixes reach it, and it is the same kind of relink.
The headline: #117, an S001 that was never in the transfer path
Rob Prins reported an ABEND S001 that hit
occasionally during uploads to UFS, mostly under parallel transfers, and sent
the dump. The dump was all it took — and the answer was somewhere else
entirely.
The abend is not in the UFS path, or the upload path, or FTPD at all. It is a
locking defect in libc370, the C runtime statically linked into every one of
our servers. vvprintf()'s %s and %eEgGfF branches called the public
fputs(), and every __examin() conversion went through the public fputc().
Those wrappers lock and unconditionally unlock the same FILE, and ENQ/DEQ
RET=HAVE carries no nesting count — so the inner unlock released the caller's
hold at the first conversion, and everything after it ran unserialized,
including the \n-triggered QSAM PUT.
Two session threads writing a log line at the same moment therefore merged and
truncated records, and the broken PUT reached the JES2 spool as an I/O error —
which MVS, with no SYNAD exit on the DCB, turned into S001 and took the whole
address space with it. Rob's dump shows the collision itself: the last log
record before the abend is two log lines merged into one truncated record. That
is why it was occasional, and why parallel transfers made it likely.
Fixed as libc370#145 and #147,
and measured there on demand: a writer wedged after 8 of 400 lines against the
unfixed library, and 800 of 800 records intact against the fixed one. libc370
ships through the cc370 sysroot rather than as a pinned dependency, so this
relink is the entire delivery mechanism — the fix reaches a running FTPD no
other way. Thank you, Rob.
fprintf() was never affected: it formats into a private buffer and issues one
fwrite().
Three neighbours came with it: puts() locked twice and could be split at its
newline by a concurrent printf; fclose() flushed, closed the DCB and freed
both buffer and FILE with no hold on the lock; and __enqdeq()'s DEQ branch
overwrote pl.opt instead of OR-ing into it, so every DEQ went out
SCOPE=STEP and could never release a SCOPE=SYSTEM ENQ.
Not labelled breaking, but read it (#119)
libc370 1.0.4 now plants a SYNAD on the DCBs its stdio layer opens, so an
uncorrectable I/O error — bad track, wrong-length record, device error — becomes
ferror() + errno EIO instead of ABEND S001. For a server that is plainly the
right trade; it is what turns #117's corrupted PUT from a dead address space
into a recoverable condition.
The other side of it is that feof() is deliberately not set, so a loud
failure becomes a silent one wherever a read loop treats the resulting
NULL/0 as end of data. FTPD had three such places. None of them is a new
defect — each used to end in S001, and would now have ended in a wrong answer:
| Before | Now | |
|---|---|---|
Read error on DD:FTPDPRM |
S001 |
FTPD009E, start abandoned |
Read error during RETR |
S001 |
451, with the byte count actually sent |
Write error during STOR |
S001 |
451, and the transfer stops at the failing record |
Automation or a runbook that reads a 250 as "the transfer is complete" now
has a second outcome to know about, and it is the one that matters: before
this release a bad track mid-RETR ended the send loop exactly like the end of
the data set and fell through to 250 Transfer completed successfully. The
client stored a fragment and was told it had the whole data set, with nothing on
the wire to tell the difference. STOR was the same in the other direction, and
worse in one respect: after the first failed write it kept issuing PUTs against
a DCB that had already failed, then reported 250 over an incomplete data set
on DASD.
The parmlib case is quieter and reaches further. A read error stopped the
fgets() loop indistinguishably from a clean end of member, kept whatever had
been parsed, left the rest at its default, and still reported a successful load
— so FTPD came up looking healthy with DASD volumes missing from its list and
SITE/JES settings silently back at their defaults. It now abandons the start:
FTPD009E PARMLIB (DD:FTPDPRM) READ FAILED -- CONFIGURATION INCOMPLETE,
SHUTTING DOWN
Measured on MVS, with the error vector libc370's own probe established — the
same data set behind two DDs that disagree about BLKSIZE, so the first READ
meets a 3120-byte block with an 80-byte buffer. TSTPRM runs it on both the
batch and the TSO leg:
guard removed round 2: rc=0 errno=5 port=2121 maxsess=10
shipped round 2: rc=4 errno=5 port=2121 maxsess=10
The first line is the defect in one row. Both rounds read the same member;
SRVPORT is its first keyword and comes back parsed, MAXSESSIONS is its
second and comes back at the default 10 rather than the 17 that is in the file.
A truncated parse, reported as a successful load. The shipped build refuses it.
The RETR and STOR guards are not measured. The lying DD cannot be
injected through the fopen() those paths use, and building a DSCB that lies
about its own blocks would be guesswork. They are a reviewed ferror() check,
and this is the honest description of them.
Deliberately not fixed here
Both recorded in #119:
- FTPD's log goes to STDOUT through
printf(). With the SYNAD in place, an I/O
error on SYSOUT now leaves the started task running with logging silently
dead rather than abending. Fixing that properly needs the fail-fast /
clearerr()decision that is still open as libc370#149. - In
STOR, a negative return from the data connection also still ends in
250. Pre-existing, unrelated to this relink, and a different fix.
What is running
FTPD005I stamps the library the module was actually linked against, so a
deploy that did not take cannot hide:
FTPD000I FTPD 1.0.2 (6CCB125) STARTING
FTPD005I LIBC370 1.0.4 (1CA9E04)
If that second line still says 1.0.3, the STC is running the old module —
the point of the relink is exactly that line.
Installing
Download ftpd-1.0.2-dist.zip (or .tar.gz); the guide and every file you
need are inside, and the procedure is unchanged from 1.0.1:
README.md |
the installation guide |
ftpd-1.0.2-load.xmit |
the FTPD load module |
ftpd-1.0.2-samplib.xmit |
the STC procedure and the configuration pattern |
ftpd-1.0.2-alloc.jcl |
allocate the datasets — run once |
ftpd-1.0.2-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.V1R0M2.*, so it does not collide with an existing
V1R0M0 or V1R0M1 installation. FMID is TFTP100.
The two caveats from 1.0.1 still apply to the shipped material:
- 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.1
The same shape as 1.0.0 → 1.0.1, for the same reason. An FMID names a
functional level, one per minor release, so 1.0.2 ships as TFTP100 again —
the id 1.0.1 already applied and accepted, and SMP will not apply a function
SYSMOD that is already applied.
| Keeping it SMP-managed | Free the FMID first with the UCLIN job in doc/uninstall.md, then run the 1.0.2 allocation and install jobs as for a fresh install |
| Without SMP | Receive ftpd-1.0.2-load.xmit into a library and point the procedure's STEPLIB at it |
Run only the UCLIN step of that page, not its DELETE step. The libraries
are versioned per patch — FTPD.V1R0M2.* here — so the new install never
touches 1.0.1's, and the DELETE names the old release's datasets. Scratching
them is a choice about the predecessor, not a step of this upgrade; after the
UCLIN they are simply orphans with no inventory record, and leaving them is
fine.
Check the inventory before and after with the zone operand — it is mandatory,
LIST SYSMODS . is SMP/E syntax and gets HMA2033:
LIST CDS SYSMOD(TFTP100) .
LIST ACDS SYSMOD(TFTP100) .
RC 04 with an empty list means the id is free.
Either way, copy the 1.0.2 procedure from the new SAMPLIB into your PROCLIB —
it is what points STEPLIB at the new LINKLIB. On the APF route, remember
the list is read at IPL only: until FTPD.V1R0M2.LINKLIB is in IEAAPF00 and
the system has IPLed, FTPD falls back to SVC 244 rather than failing. Your
FTPDPRM0 and your RAKF definitions are unaffected — no configuration keyword
changed in this release.
A correction shipped with it. doc/uninstall.md said <vrm> was V1R0M0
for all of 1.0.x. It is not: the qualifier carries the patch level, and that
page is exactly where a patch upgrade is sent. Following the old sentence
scratched the predecessor's libraries and left the live insta...
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 spoo...
v1.0.0
FTPD 1.0.0 is the first release of the standalone FTP server for MVS 3.8j. It
installs through SMP 4 — the SMP that ships with MVS 3.8j, not SMP/E — so
the system keeps a record of what is installed, and there is a defined way back
out again.
Installing
Download ftpd-1.0.0-dist.zip (or .tar.gz). It contains everything you
need and a step-by-step guide:
README.md |
the installation guide |
ftpd-1.0.0-load.xmit |
the FTPD load module |
ftpd-1.0.0-samplib.xmit |
the STC procedure and the configuration pattern |
ftpd-1.0.0-alloc.jcl |
allocate the datasets — run once |
ftpd-1.0.0-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.V1R0M0.*. Copying the procedure and the
configuration pattern into your PROCLIB and PARMLIB stays a manual step, by
design: the product owns the patterns, your system owns the copies, and an
update that silently replaced a running procedure would be worse than one that
leaves it alone. The guide says which member goes where.
Two things in the shipped material are 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.
FMID is TFTP100.
Correction — removing FTPD again
The README.md inside the archive is wrong about this, and it cannot be
fixed in a file already downloaded. It says to remove FTPD with RESTORE
followed by REJECT. Measured after this release went out: neither works.
The install job accepts the FMID in the same run as the APPLY, and an accepted
function SYSMOD refuses RESTORE because it was accepted, then refuses
REJECT because accepting removed the control statements REJECT works from.
A UCLIN job does work. The procedure, with the job and the messages to check,
is at
doc/uninstall.md.
Nothing about installing is affected — only taking it off again.
What FTPD reaches
Three namespaces, selected per session, on port 2121 by default:
- MVS data sets — sequential data sets and PDS members, with
z/OS-compatibleSITEkeywords forRECFM,LRECL,BLKSIZE,SPACE,
UNITandVOLUME. - JES —
quote site filetype=jes: submit a job, list your jobs, retrieve
and purge spool output. - UFS — the UFSD filesystem, reached by
cdto a UFS path.
UFSD is optional. libufs is linked statically and FTPD asks UFSD for a
session only when a client actually uses a UFS path, so on a system without the
UFSD started task those commands answer 550 UFS service not available and
everything else works normally. The SYSMOD declares no prerequisite — install
FTPD now and add UFSD later if
you want it.
RAKF is not optional
FTPD has no user database of its own. Every USER/PASS is verified by RAKF
(RACINIT, SVC 244), and every data set access is checked against the RAKF
DATASET class under the logged-in user's identity, not the server's. On a
system without RAKF nobody can log in, and no configuration option turns that
off. There is no INSECURE mode; the one that existed during development was
removed on purpose.
The setup is one user, one group and an optional FTPAUTH FACILITY profile —
step 8 of the guide, with the full reference in
FTPD_RAKF_SETUP.md.
Next: 1.1.0 is about authorization
1.0.0 authenticates every session and checks every data set access, but the
identity model around those checks still has known gaps, and that is what the
next release is for. The open work:
- The resting identity is too powerful (#97).
Between sessions the STC sits onSTC/STCGROUP, which holds ALTER on every
data set. The recovery path can transiently put a session on that identity,
which is fail-closed only while it is least-privilege — so FTPD needs to log
on to a low-privilege userid at startup instead. - JES access has no authorization model at all
(#90).JESOWNERis a
client-set display filter, not a control. - The ACEE switch is not fully serialized
(#64).ASXBSENVis
address-space-wide while sessions run as concurrent subtasks.
Until those are closed, keep the FTPD identity scoped to what the started task
itself needs — never give it broad data set authority. The reasoning is spelled
out as a security invariant in the RAKF guide.
Full Changelog: https://github.com/mvslovers/ftpd/commits/v1.0.0